Coverage for src/lektor_ng/devcli.py: 63%

49 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-03 22:18 +0000

1# pylint: disable=import-outside-toplevel 

2import os 

3import sys 

4 

5import click 

6 

7from lektor_ng.cli_utils import AliasedGroup, extraflag, pass_context 

8 

9try: 

10 from IPython import embed 

11 from traitlets.config.loader import Config 

12except ImportError: 

13 pass # fallback to normal Python InteractiveConsole 

14 

15 

16@click.group(cls=AliasedGroup, short_help="Development commands.") 

17def cli(): 

18 """Development commands for Lektor. 

19 

20 This provides various development support commands for Lektor. This is 

21 primarily useful for Lektor plugin development but also if you want to 

22 extend Lektor itself. Additional functionality can be unlocked by 

23 exporting the `LEKTOR_DEV=1` environment variable. 

24 """ 

25 

26 

27@cli.command("shell", short_help="Starts a python shell.") 

28@extraflag 

29@pass_context 

30def shell_cmd(ctx, extra_flags): 

31 """Starts a Python shell in the context of a Lektor project. 

32 

33 This is particularly useful for debugging plugins and to explore the 

34 API. To quit the shell just use `quit()`. Within the shell various 

35 utilities are available right from the get-go for you. 

36 

37 \b 

38 - `project`: the loaded project as object. 

39 - `env`: an environment for the loaded project. 

40 - `pad`: a database pad initialized for the project and environment 

41 that is ready to use. 

42 """ 

43 ctx.load_plugins(extra_flags=extra_flags) 

44 import code 

45 

46 from lektor.builder import Builder 

47 

48 from lektor_ng.db import F, Tree 

49 

50 banner = f"Python {sys.version} on {sys.platform}\nLektor Project: {ctx.get_env().root_path}" 

51 ns = {} 

52 startup = os.environ.get("PYTHONSTARTUP") 

53 if startup and os.path.isfile(startup): 

54 with open(startup, encoding="utf-8") as f: 

55 eval(compile(f.read(), startup, "exec"), ns) # pylint: disable=eval-used 

56 pad = ctx.get_env().new_pad() 

57 ns.update( 

58 project=ctx.get_project(), 

59 env=ctx.get_env(), 

60 pad=pad, 

61 tree=Tree(pad), 

62 config=ctx.get_env().load_config(), 

63 make_builder=lambda: Builder(ctx.get_env().new_pad(), ctx.get_default_output_path()), 

64 F=F, 

65 ) 

66 try: 

67 c = Config() 

68 c.TerminalInteractiveShell.banner2 = banner 

69 embed(config=c, user_ns=ns) 

70 except NameError: # No IPython 

71 code.interact(banner=banner, local=ns) 

72 

73 

74@cli.command("new-plugin", short_help="Creates a new plugin") 

75@click.option("--path", type=click.Path(), help="The destination path") 

76@click.argument("plugin_name", required=False) 

77@pass_context 

78def new_plugin(ctx, **defaults): 

79 """This command creates a new plugin. 

80 

81 This will present you with a very short wizard that guides you through 

82 creation of a new plugin. At the end of it, it will create a plugin 

83 in the packages folder of the current project or the path you defined. 

84 

85 This is the fastest way to creating a new plugin. 

86 """ 

87 from .quickstart import plugin_quickstart 

88 

89 project = ctx.get_project(silent=True) 

90 plugin_quickstart(defaults, project=project) 

91 

92 

93@cli.command("new-theme", short_help="Creates a new theme") 

94@click.option("--path", type=click.Path(), help="The destination path") 

95@click.argument("theme_name", required=False) 

96@pass_context 

97def new_theme(ctx, **defaults): 

98 """This command creates a new theme. 

99 

100 This will present you with a very short wizard that guides you through 

101 creation of a new theme. At the end of it, it will create a theme 

102 in the packages folder of the current project or the path you defined. 

103 

104 This is the fastest way to creating a new theme. 

105 """ 

106 from .quickstart import theme_quickstart 

107 

108 project = ctx.get_project(silent=True) 

109 theme_quickstart(defaults, project=project)