Skip to content

Commit ce7346b

Browse files
dev: refactor cli
1 parent 931d30b commit ce7346b

7 files changed

Lines changed: 816 additions & 604 deletions

File tree

src/mkdocs_note/cli.py

Lines changed: 226 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@
88

99
import sys
1010
import click
11+
from pathlib import Path
1112
from importlib import metadata
1213

14+
from mkdocs_note.config import MkdocsNoteConfig
15+
from mkdocs_note.utils.cli.new import NoteCreator
16+
from mkdocs_note.utils.cli.remove import NoteRemover
17+
from mkdocs_note.utils.cli.move import NoteMover
18+
from mkdocs_note.utils.cli.clean import NoteCleaner
19+
1320

1421
def get_version():
1522
"""Get the version of mkdocs-note package.
@@ -99,27 +106,65 @@ def cli(ctx):
99106

100107

101108
@cli.command("new")
102-
@click.argument("file_path", required=False)
109+
@click.argument("file_path", required=True)
110+
@click.option(
111+
"--template",
112+
"-t",
113+
type=click.Path(exists=True, path_type=Path),
114+
help="Custom template file to use",
115+
)
103116
@click.pass_context
104-
def new_command(ctx, file_path):
117+
def new_command(ctx, file_path, template):
105118
"""Create a new note file with proper asset structure.
106119
107120
\b
108121
Examples:
109122
mkdocs-note new docs/notes/my-note.md
110123
mkdocs-note new docs/notes/python/intro.md
124+
mkdocs-note new docs/notes/custom.md --template templates/custom.md
111125
112126
FILE_PATH: Path where the new note file should be created
113127
"""
114-
click.echo("⚠️ 'new' command is not yet implemented")
115-
click.echo("💡 This feature is being refactored")
116-
sys.exit(1)
128+
try:
129+
# Load configuration
130+
config = MkdocsNoteConfig()
131+
creator = NoteCreator(config)
132+
133+
# Convert to Path
134+
note_path = Path(file_path)
135+
136+
# Create the note
137+
result = creator.create_new_note(note_path, template_path=template)
138+
139+
if result.success:
140+
click.echo(f"✅ {result.message}")
141+
click.echo(f"📝 Note: {result.data['note_path']}")
142+
click.echo(f"📁 Assets: {result.data['asset_dir']}")
143+
sys.exit(0)
144+
else:
145+
click.echo(f"❌ Error: {result.message}", err=True)
146+
sys.exit(1)
147+
148+
except Exception as e:
149+
click.echo(f"❌ Unexpected error: {e}", err=True)
150+
sys.exit(1)
117151

118152

119153
@cli.command("remove")
120-
@click.argument("file_path", required=False)
154+
@click.argument("file_path", required=True)
155+
@click.option(
156+
"--keep-assets",
157+
is_flag=True,
158+
help="Keep the asset directory when removing the note",
159+
)
160+
@click.option(
161+
"--yes",
162+
"-y",
163+
is_flag=True,
164+
help="Skip confirmation prompt",
165+
)
121166
@click.pass_context
122-
def remove_command(ctx, file_path):
167+
def remove_command(ctx, file_path, keep_assets, yes):
123168
"""Remove a note file and its corresponding asset directory.
124169
125170
\b
@@ -129,27 +174,74 @@ def remove_command(ctx, file_path):
129174
Examples:
130175
mkdocs-note remove docs/notes/test.md
131176
mkdocs-note rm docs/notes/test.md --yes
177+
mkdocs-note remove docs/notes/test.md --keep-assets
132178
133179
FILE_PATH: Path to the note file to remove
134180
"""
135-
click.echo("⚠️ 'remove' command is not yet implemented")
136-
click.echo("💡 This feature is being refactored")
137-
sys.exit(1)
181+
try:
182+
note_path = Path(file_path)
183+
184+
# Check if file exists
185+
if not note_path.exists():
186+
click.echo(f"❌ Error: File does not exist: {note_path}", err=True)
187+
sys.exit(1)
188+
189+
# Confirmation prompt (unless --yes)
190+
if not yes:
191+
asset_msg = "and its assets" if not keep_assets else "(keeping assets)"
192+
if not click.confirm(f"Remove {note_path} {asset_msg}?"):
193+
click.echo("⚠️ Cancelled")
194+
sys.exit(0)
195+
196+
# Load configuration
197+
config = MkdocsNoteConfig()
198+
remover = NoteRemover(config)
199+
200+
# Remove the note
201+
result = remover.remove_note(note_path, remove_assets=not keep_assets)
202+
203+
if result.success:
204+
click.echo(f"✅ {result.message}")
205+
if result.data["removed_assets"]:
206+
click.echo(f"📁 Removed assets: {result.data['asset_dir']}")
207+
else:
208+
click.echo(f"📁 Kept assets: {result.data['asset_dir']}")
209+
sys.exit(0)
210+
else:
211+
click.echo(f"❌ Error: {result.message}", err=True)
212+
sys.exit(1)
213+
214+
except Exception as e:
215+
click.echo(f"❌ Unexpected error: {e}", err=True)
216+
sys.exit(1)
138217

139218

140219
@cli.command("rm")
141-
@click.argument("file_path", required=False)
220+
@click.argument("file_path", required=True)
221+
@click.option("--keep-assets", is_flag=True, help="Keep the asset directory")
222+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
142223
@click.pass_context
143-
def rm_command(ctx, file_path):
224+
def rm_command(ctx, file_path, keep_assets, yes):
144225
"""Alias for 'remove' command - Remove a note file and its asset directory."""
145-
ctx.invoke(remove_command, file_path=file_path)
226+
ctx.invoke(remove_command, file_path=file_path, keep_assets=keep_assets, yes=yes)
146227

147228

148229
@cli.command("move")
149-
@click.argument("source", required=False)
150-
@click.argument("destination", required=False)
230+
@click.argument("source", required=True)
231+
@click.argument("destination", required=True)
232+
@click.option(
233+
"--keep-source-assets",
234+
is_flag=True,
235+
help="Keep the source asset directory (don't move it)",
236+
)
237+
@click.option(
238+
"--yes",
239+
"-y",
240+
is_flag=True,
241+
help="Skip confirmation prompt",
242+
)
151243
@click.pass_context
152-
def move_command(ctx, source, destination):
244+
def move_command(ctx, source, destination, keep_source_assets, yes):
153245
"""Move or rename a note file/directory and its asset directory.
154246
155247
\b
@@ -159,58 +251,146 @@ def move_command(ctx, source, destination):
159251
Examples:
160252
mkdocs-note move docs/notes/old.md docs/notes/new.md
161253
mkdocs-note mv docs/notes/test.md docs/notes/archive
254+
mkdocs-note move docs/notes/drafts docs/notes/published --yes
162255
163256
\b
164257
Arguments:
165258
SOURCE: Current path of the note file or directory
166259
DESTINATION: Destination path (or parent directory if exists)
167260
"""
168-
click.echo("⚠️ 'move' command is not yet implemented")
169-
click.echo("💡 This feature is being refactored")
170-
sys.exit(1)
261+
try:
262+
source_path = Path(source)
263+
dest_path = Path(destination)
264+
265+
# Check if source exists
266+
if not source_path.exists():
267+
click.echo(f"❌ Error: Source does not exist: {source_path}", err=True)
268+
sys.exit(1)
269+
270+
# Confirmation prompt (unless --yes)
271+
if not yes:
272+
asset_msg = "with assets" if not keep_source_assets else "(keeping assets)"
273+
if not click.confirm(f"Move {source_path}{dest_path} {asset_msg}?"):
274+
click.echo("⚠️ Cancelled")
275+
sys.exit(0)
276+
277+
# Load configuration
278+
config = MkdocsNoteConfig()
279+
mover = NoteMover(config)
280+
281+
# Move the note or directory
282+
result = mover.move_note_or_directory(
283+
source_path, dest_path, move_assets=not keep_source_assets
284+
)
285+
286+
if result.success:
287+
click.echo(f"✅ {result.message}")
288+
if "source" in result.data and "destination" in result.data:
289+
click.echo(f"📝 From: {result.data['source']}")
290+
click.echo(f"📝 To: {result.data['destination']}")
291+
if "asset_moved" in result.data:
292+
if result.data["asset_moved"]:
293+
click.echo("📁 Assets moved")
294+
else:
295+
click.echo("📁 Assets kept at source")
296+
sys.exit(0)
297+
else:
298+
click.echo(f"❌ Error: {result.message}", err=True)
299+
sys.exit(1)
300+
301+
except Exception as e:
302+
click.echo(f"❌ Unexpected error: {e}", err=True)
303+
sys.exit(1)
171304

172305

173306
@cli.command("mv")
174-
@click.argument("source", required=False)
175-
@click.argument("destination", required=False)
307+
@click.argument("source", required=True)
308+
@click.argument("destination", required=True)
309+
@click.option("--keep-source-assets", is_flag=True, help="Keep source assets")
310+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
176311
@click.pass_context
177-
def mv_command(ctx, source, destination):
312+
def mv_command(ctx, source, destination, keep_source_assets, yes):
178313
"""Alias for 'move' command - Move or rename a note file/directory and its assets."""
179-
ctx.invoke(move_command, source=source, destination=destination)
314+
ctx.invoke(
315+
move_command,
316+
source=source,
317+
destination=destination,
318+
keep_source_assets=keep_source_assets,
319+
yes=yes,
320+
)
180321

181322

182323
@cli.command("clean")
324+
@click.option(
325+
"--dry-run",
326+
is_flag=True,
327+
help="Show what would be removed without actually removing",
328+
)
329+
@click.option(
330+
"--yes",
331+
"-y",
332+
is_flag=True,
333+
help="Skip confirmation prompt",
334+
)
183335
@click.pass_context
184-
def clean_command(ctx):
336+
def clean_command(ctx, dry_run, yes):
185337
"""Clean up orphaned asset directories without corresponding notes.
186338
187339
\b
188340
Examples:
189341
mkdocs-note clean --dry-run
190342
mkdocs-note clean --yes
343+
mkdocs-note clean
191344
"""
192-
click.echo("⚠️ 'clean' command is not yet implemented")
193-
click.echo("💡 This feature is being refactored")
194-
sys.exit(1)
195-
196-
197-
@cli.command("template")
198-
@click.pass_context
199-
def template_command(ctx):
200-
"""Manage the note template file.
201-
202-
This command helps you check and create the template file
203-
configured in your mkdocs.yml.
204-
205-
\b
206-
Examples:
207-
mkdocs-note template
208-
mkdocs-note template --check
209-
mkdocs-note template --create
210-
"""
211-
click.echo("⚠️ 'template' command is not yet implemented")
212-
click.echo("💡 This feature is being refactored")
213-
sys.exit(1)
345+
try:
346+
# Load configuration
347+
config = MkdocsNoteConfig()
348+
cleaner = NoteCleaner(config)
349+
350+
# Find orphaned assets
351+
if dry_run:
352+
click.echo("🔍 Scanning for orphaned assets (dry run mode)...")
353+
else:
354+
click.echo("🔍 Scanning for orphaned assets...")
355+
356+
result = cleaner.clean_orphaned_assets(dry_run=True)
357+
358+
if result.data["removed_count"] == 0:
359+
click.echo("✅ No orphaned asset directories found")
360+
sys.exit(0)
361+
362+
# Show what will be removed
363+
click.echo(
364+
f"\n{'Would remove' if dry_run else 'Found'} {result.data['removed_count']} orphaned asset director{'y' if result.data['removed_count'] == 1 else 'ies'}:"
365+
)
366+
for orphaned_dir in result.data["orphaned_dirs"]:
367+
click.echo(f" 📁 {orphaned_dir}")
368+
369+
# If dry run, exit here
370+
if dry_run:
371+
click.echo("\n💡 Run without --dry-run to actually remove these directories")
372+
sys.exit(0)
373+
374+
# Confirmation prompt (unless --yes)
375+
if not yes:
376+
if not click.confirm(f"\nRemove these {result.data['removed_count']} directories?"):
377+
click.echo("⚠️ Cancelled")
378+
sys.exit(0)
379+
380+
# Actually clean
381+
click.echo("\n🗑️ Removing orphaned assets...")
382+
result = cleaner.clean_orphaned_assets(dry_run=False)
383+
384+
if result.success:
385+
click.echo(f"✅ {result.message}")
386+
sys.exit(0)
387+
else:
388+
click.echo(f"❌ Error: {result.message}", err=True)
389+
sys.exit(1)
390+
391+
except Exception as e:
392+
click.echo(f"❌ Unexpected error: {e}", err=True)
393+
sys.exit(1)
214394

215395

216396
if __name__ == "__main__":

src/mkdocs_note/config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,24 @@ class MkdocsNoteConfig(Config):
5757
This prevents broken image links when assets haven't been moved to the
5858
co-located asset directory structure yet.
5959
"""
60+
61+
# CLI-specific configuration
62+
supported_extensions = config_opt.Type(list, default=[".md"])
63+
"""List of supported note file extensions.
64+
Used by CLI commands to validate note file types.
65+
"""
66+
67+
exclude_patterns = config_opt.Type(list, default=["index.md", "README.md"])
68+
"""List of filename patterns to exclude from note management.
69+
Files matching these patterns will not be created, moved, or managed by CLI commands.
70+
"""
71+
72+
timestamp_zone = config_opt.Type(str, default="UTC+0")
73+
"""Timezone for timestamp generation in format 'UTC+X' or 'UTC-X'.
74+
Used when creating new notes to generate date/time in frontmatter.
75+
"""
76+
77+
output_date_format = config_opt.Type(str, default="%Y-%m-%d %H:%M:%S")
78+
"""Date format string for timestamp output.
79+
Uses Python strftime format codes.
80+
"""

0 commit comments

Comments
 (0)