Coverage for lice2/core.py: 100%

1"""Main core of the application."""

2

3 from __future__ import annotations

4

5 import sys

6 from pathlib import Path

7 from types import SimpleNamespace

8 from typing import Any, Callable, Optional

9

10 import typer

11 from rich import print as rprint

12 from rich.markup import escape

13

14 from lice2 import __version__

15 from lice2.config import check_default_license, settings

16 from lice2.constants import LANGS, LICENSES

17 from lice2.helpers import (

18 copy_to_clipboard,

19 format_license,

20 generate_header,

21 generate_license,

22 get_context,

23 get_lang,

24 get_local_year,

25 get_metadata,

26 get_suffix,

27 guess_organization,

28 list_languages,

29 list_licenses,

30 list_vars,

31 load_file_template,

32 load_package_template,

33 validate_license,

34 validate_year,

35)

36

37 app = typer.Typer(rich_markup_mode="rich")

38

39

40@app.command(

41 help=(

42 "Generates a license template with context variables, and "

43 "optionally write this to a file."

44 ),

45 context_settings={"help_option_names": ["-h", "--help"]},

46)

47 def main( # noqa: PLR0913

48 license_name: str = typer.Argument(

49 default=check_default_license(),

50 help=f"The license to generate, one of: {', '.join(LICENSES)}",

51 callback=validate_license,

52 metavar="[license]",

53 ),

54 organization: str = typer.Option(

55 guess_organization(),

56 "--org",

57 "-o",

58 help='Organization, defaults to .gitconfig or os.environ["USER"]',

59 ),

60 project: str = typer.Option(

61 Path.cwd().name,

62 "--proj",

63 "-p",

64 help="Name of project, defaults to name of current directory",

65 ),

66 template_path: Optional[str] = typer.Option(

67 None,

68 "--template",

69 "-t",

70 help="Path to license template file",

71 ),

72 year: Optional[str] = typer.Option(

73 get_local_year(),

74 "--year",

75 "-y",

76 help="Copyright year",

77 callback=validate_year,

78 ),

79 language: Optional[str] = typer.Option(

80 None,

81 "--language",

82 "-l",

83 help=(

84 "Format output for language source file, one of: "

85 f"{', '.join(LANGS.keys())} "

86 f"[dim]{escape('[default: txt]')}[/dim]"

87 ),

88 show_default=False,

89 ),

90 ofile: Optional[str] = typer.Option(

91 "stdout",

92 "--file",

93 "-f",

94 help=(

95 "Name of the output source file (with -l, "

96 "extension can be omitted)"

97 ),

98 ),

99 *,

100 header: bool = typer.Option(

101 False,

102 "--header",

103 help="Generate source file header for specified license",

104 ),

105 clipboard: bool = typer.Option(

106 False,

107 "--clipboard",

108 "-c",

109 help="Copy the generated license to the clipboard",

110 ),

111 show_vars: Optional[bool] = typer.Option(

112 None,

113 "--vars",

114 help="List template variables for specified license",

115 ),

116 show_licenses: bool = typer.Option(

117 False,

118 "--licenses",

119 help="List available license templates and their parameters",

120 ),

121 show_languages: bool = typer.Option(

122 False,

123 "--languages",

124 help="List available source code formatting languages",

125 ),

126 legacy: bool = typer.Option(

127 False,

128 "--legacy",

129 help="Use legacy method to generate license",

130 ),

131 version: bool = typer.Option(

132 False,

133 "--version",

134 "-v",

135 is_eager=True,

136 help="Show version info",

137 ),

138 metadata: bool = typer.Option(

139 False,

140 "--metadata",

141 help=(

142 "Output a JSON string listing all available licenses and "

143 "languages This allows easy integration into other tools."

144 ),

145 ),

146) -> None:

147 """Generate a license file.

148

149 Can generate a license file, a source file header, or list available

150 licenses, template variables, and source code formatting.

151 """

152 # deal with the '--version' flag first

153 if version:

154 rprint(

155 "\n[green]Lice2 - Generate license files for your projects."

156 f"\n[/green]Version: {__version__} "

157 "\u00a9 2013-2024\n"

158 )

159 raise typer.Exit(0)

160

161 # get the args into a dict to avoid refactoring all the code...

162 args_base: dict[str, str | bool | None] = {

163 "license": license_name,

164 "header": header,

165 "organization": organization,

166 "project": project,

167 "template_path": template_path,

168 "year": year,

169 "language": language,

170 "ofile": ofile,

171 "clipboard": clipboard or settings.clipboard,

172 "legacy": legacy or settings.legacy,

173 "list_vars": show_vars,

174 "list_licenses": show_licenses,

175 "list_languages": show_languages,

176 }

177 # convert to SimpleNamespace, so we can use dot notation

178 args = SimpleNamespace(**args_base)

179

180 # get the language if set

181 lang = get_lang(args)

182

183 actions: list[tuple[bool, Callable[..., None], list[Any]]] = [

184 (metadata, get_metadata, [args]),

185 (args.list_licenses, list_licenses, []),

186 (args.list_languages, list_languages, []),

187 (header, generate_header, [args, lang]),

188 (args.list_vars, list_vars, [args, license_name]),

189 ]

190

191 # Iterate through the list and call the utility functions based on the

192 # conditions. All the utility functions exit the program after execution.

193 # This saves us from having to write a lot of if-else statements.

194 for condition, func, func_args in actions:

195 if condition:

196 func(*func_args)

197

198 # create context

199 if args.template_path:

200 template = load_file_template(args.template_path)

201 else:

202 template = load_package_template(license_name)

203

204 content = generate_license(template, get_context(args))

205

206 if args.ofile != "stdout":

207 ext = get_suffix(args.ofile)

208 if ext:

209 output = args.ofile

210 out = format_license(

211 content, ext, legacy=args.legacy

212 ) # format license by file suffix

213 else:

214 output = f"{args.ofile}.{lang}" if lang else args.ofile

215 out = format_license(content, lang, legacy=args.legacy)

216

217 out.seek(0)

218 with Path(output).open(mode="w") as f:

219 f.write(out.getvalue())

220 else:

221 out = format_license(content, lang, legacy=args.legacy)

222 out.seek(0)

223 if not args.clipboard:

224 sys.stdout.write(out.getvalue())

225 else:

226 copy_to_clipboard(out)

227

228 out.close()

229

230

231 if __name__ == "__main__":

232 app() # pragma: no cover