Coverage for src / rendercv / cli / new_command / new_command.py: 100%
64 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
1import pathlib
2from collections.abc import Callable
3from typing import Annotated
5import rich.panel
6import typer
7from rich import print
9from rendercv.exception import RenderCVUserError
10from rendercv.schema.models.design.built_in_design import available_themes
11from rendercv.schema.models.locale.locale import available_locales
12from rendercv.schema.sample_generator import create_sample_yaml_input_file
14from ..app import app
15from ..copy_templates import copy_templates
16from ..error_handler import handle_user_errors
17from .print_welcome import print_welcome
20@app.command(
21 name="new",
22 help=(
23 "Generate a YAML input file to get started. Example: [yellow]rendercv new"
24 ' "John Doe"[/yellow]. Details: [cyan]rendercv new --help[/cyan]'
25 ),
26)
27@handle_user_errors
28def cli_command_new(
29 full_name: Annotated[str, typer.Argument(help="Your full name")],
30 theme: Annotated[
31 str,
32 typer.Option(
33 help=(
34 "The name of the theme (available themes are:"
35 f" {', '.join(available_themes)})"
36 )
37 ),
38 ] = "classic",
39 locale: Annotated[
40 str,
41 typer.Option(
42 help=(
43 "The name of the locale (available locales are:"
44 f" {', '.join(available_locales)}). You can also continue with"
45 " `English`, and then write your own `locale` field for your own"
46 " language."
47 )
48 ),
49 ] = "english",
50 create_typst_templates: Annotated[
51 bool,
52 typer.Option(
53 "--create-typst-templates",
54 help="Create Typst templates",
55 ),
56 ] = False,
57 create_markdown_templates: Annotated[
58 bool,
59 typer.Option(
60 "--create-markdown-templates",
61 help="Create Markdown templates",
62 ),
63 ] = False,
64):
65 if theme not in available_themes:
66 message = (
67 f"Theme {theme} is not available. Available themes are:"
68 f" {', '.join(available_themes)}"
69 )
70 raise RenderCVUserError(message)
72 if locale not in available_locales:
73 message = (
74 f"Locale {locale} is not available. Available locales are:"
75 f" {', '.join(available_locales)}"
76 )
77 raise RenderCVUserError(message)
79 print_welcome()
81 input_file_path = pathlib.Path(f"{full_name.replace(' ', '_')}_CV.yaml")
82 typst_templates_folder = pathlib.Path(theme)
83 markdown_folder = pathlib.Path("markdown")
85 # Define all items to create: (description, path, creator, skip)
86 items_to_create: list[tuple[str, pathlib.Path, Callable[[], object], bool]] = [
87 (
88 "Your YAML input file",
89 input_file_path,
90 lambda: create_sample_yaml_input_file(
91 file_path=input_file_path, name=full_name, theme=theme, locale=locale
92 ),
93 True, # never skip the input file
94 ),
95 (
96 "Typst templates",
97 typst_templates_folder,
98 lambda: copy_templates("typst", typst_templates_folder),
99 create_typst_templates,
100 ),
101 (
102 "Markdown templates",
103 markdown_folder,
104 lambda: copy_templates("markdown", markdown_folder),
105 create_markdown_templates,
106 ),
107 ]
109 # Process items
110 created_items: list[tuple[str, pathlib.Path]] = []
111 existing_items: list[tuple[str, pathlib.Path]] = []
112 for description, path, creator, create in items_to_create:
113 if not create:
114 continue
115 if path.exists():
116 existing_items.append((description, path))
117 else:
118 creator()
119 created_items.append((description, path))
121 print(build_creation_panel(input_file_path, created_items, existing_items))
124def build_creation_panel(
125 input_file_path: pathlib.Path,
126 created_items: list[tuple[str, pathlib.Path]],
127 existing_items: list[tuple[str, pathlib.Path]],
128) -> rich.panel.Panel:
129 """Build Rich panel summarizing created and existing files.
131 Why:
132 The ``new`` command output is a structured panel with file statuses,
133 next steps, and template info. Extracting this keeps the command
134 handler focused on orchestration while this function owns presentation.
136 Args:
137 input_file_path: Path to the generated YAML input file.
138 created_items: Items that were newly created (description, path).
139 existing_items: Items that already existed (description, path).
141 Returns:
142 Rich Panel ready for printing.
143 """
144 lines: list[str] = []
146 # Input file status (always first)
147 input_file_created = any(
148 desc == "Your YAML input file" for desc, _ in created_items
149 )
150 if input_file_created:
151 lines.append(
152 "[green]✓[/green] Created your YAML input file: "
153 f"[purple]./{input_file_path}[/purple]"
154 )
155 else:
156 lines.append(
157 f"Your YAML input file already exists: [purple]./{input_file_path}[/purple]"
158 )
160 # Next steps (always visible)
161 lines.append("")
162 lines.append("Next steps:")
163 lines.append(" 1. Edit the YAML input file with your information")
164 lines.append(f" 2. Run: [cyan]rendercv render {input_file_path}[/cyan]")
166 # Templates (exclude input file from these lists)
167 created_templates = [
168 (d, p) for d, p in created_items if d != "Your YAML input file"
169 ]
170 existing_templates = [
171 (d, p) for d, p in existing_items if d != "Your YAML input file"
172 ]
174 if created_templates:
175 lines.append("")
176 lines.append("Also created:")
177 for desc, path in created_templates:
178 lines.append(f" ○ {desc}: ./{path}")
180 if existing_templates:
181 lines.append("")
182 lines.append("Not modified (already exist):")
183 for desc, path in existing_templates:
184 lines.append(f" - {desc}: ./{path}")
186 if created_templates or existing_templates:
187 lines.append("")
188 lines.append(
189 "Templates are for advanced design customization. You can ignore or"
190 " delete them."
191 )
193 return rich.panel.Panel(
194 "\n".join(lines),
195 title="Get started",
196 title_align="left",
197 border_style="bright_black",
198 )