Skip to content

Commit 75d9caa

Browse files
dev: refactor 'new' and 'remove' of cli underlying utils
1 parent ce7346b commit 75d9caa

7 files changed

Lines changed: 294 additions & 412 deletions

File tree

src/mkdocs_note/cli.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ def new_command(ctx, file_path, template):
121121
Examples:
122122
mkdocs-note new docs/notes/my-note.md
123123
mkdocs-note new docs/notes/python/intro.md
124-
mkdocs-note new docs/notes/custom.md --template templates/custom.md
125124
126125
FILE_PATH: Path where the new note file should be created
127126
"""
@@ -368,12 +367,16 @@ def clean_command(ctx, dry_run, yes):
368367

369368
# If dry run, exit here
370369
if dry_run:
371-
click.echo("\n💡 Run without --dry-run to actually remove these directories")
370+
click.echo(
371+
"\n💡 Run without --dry-run to actually remove these directories"
372+
)
372373
sys.exit(0)
373374

374375
# Confirmation prompt (unless --yes)
375376
if not yes:
376-
if not click.confirm(f"\nRemove these {result.data['removed_count']} directories?"):
377+
if not click.confirm(
378+
f"\nRemove these {result.data['removed_count']} directories?"
379+
):
377380
click.echo("⚠️ Cancelled")
378381
sys.exit(0)
379382

src/mkdocs_note/config.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,6 @@ class MkdocsNoteConfig(Config):
4747
Available options:
4848
- name: Node naming strategy ("title" or "file_name")
4949
- debug: Enable debug logging for graph generation
50-
"""
51-
52-
enable_asset_fallback = config_opt.Type(bool, default=True)
53-
"""Whether to fallback to original asset paths when assets with processed uri are not found.
54-
55-
When enabled (default: True), if a processed asset file doesn't exist,
56-
the original asset path will be preserved instead of being replaced.
57-
This prevents broken image links when assets haven't been moved to the
58-
co-located asset directory structure yet.
5950
"""
6051

6152
# CLI-specific configuration
@@ -69,12 +60,7 @@ class MkdocsNoteConfig(Config):
6960
Files matching these patterns will not be created, moved, or managed by CLI commands.
7061
"""
7162

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")
63+
timestamp_format = config_opt.Type(str, default="%Y-%m-%d %H:%M:%S")
7864
"""Date format string for timestamp output.
7965
Uses Python strftime format codes.
8066
"""
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
from pathlib import Path
2+
from datetime import datetime
3+
4+
from mkdocs.plugins import get_plugin_logger
5+
6+
from mkdocs_note.utils.cli import common
7+
8+
log = get_plugin_logger(__name__)
9+
10+
11+
class NewCommand:
12+
"""Command to create a new note."""
13+
14+
timestamp_format: str = "%Y-%m-%d %H:%M:%S"
15+
16+
def _generate_note_basic_meta(self, file_path: Path) -> str:
17+
"""Generate the note meta."""
18+
log.debug(f"Generating note meta for: {file_path}")
19+
20+
return f"""---
21+
date: {datetime.now().strftime(self.timestamp_format)}
22+
title: {file_path.stem.replace("-", " ").replace("_", " ").title()}
23+
permalink:
24+
publish: true
25+
---
26+
"""
27+
28+
def _validate_before_execution(self, file_path: Path) -> bool:
29+
"""Validate before executing the new command.
30+
31+
Args:
32+
file_path (Path): The path to the new note file
33+
34+
Returns:
35+
bool: True if the validation is successful, False otherwise
36+
"""
37+
try:
38+
# Check if file already exists
39+
if file_path.exists():
40+
log.error(f"File already exists: {file_path}")
41+
return False
42+
except Exception as e:
43+
log.error(f"Error validating before execution: {e}")
44+
return False
45+
46+
def execute(self, file_path: Path) -> None:
47+
"""Execute the new command.
48+
49+
Args:
50+
file_path (Path): The path to the new note file
51+
"""
52+
try:
53+
if self._validate_before_execution(file_path):
54+
# Ensure parent directory exists
55+
common.ensure_parent_directory(file_path)
56+
57+
# Generate note meta
58+
note_meta = self._generate_note_basic_meta(file_path)
59+
60+
# Create note file
61+
file_path.write_text(note_meta, encoding="utf-8")
62+
63+
# Create corresponding asset directory
64+
asset_dir = common.get_asset_directory(file_path)
65+
asset_dir.mkdir(parents=True, exist_ok=True)
66+
else:
67+
log.error(f"Validation failed for: {file_path}")
68+
return
69+
# Create corresponding asset directory
70+
asset_dir = common.get_asset_directory(file_path)
71+
asset_dir.mkdir(parents=True, exist_ok=True)
72+
73+
except Exception as e:
74+
log.error(f"Error executing new command: {e}")
75+
return
76+
77+
78+
class RemoveCommand:
79+
"""Command to remove a note(s) and its(their)
80+
corresponding asset directory(ies) like `rm -rf`.
81+
"""
82+
83+
def _validate_before_execution(self, path: Path) -> int:
84+
"""Validate before executing the remove command.
85+
86+
Args:
87+
path (Path): The path to the note file(s) to remove
88+
89+
Returns:
90+
int: The signal marking the result of the validation:
91+
0: Failed
92+
1: Single file remove request
93+
2: Multiple files that refer to a directory remove request
94+
"""
95+
try:
96+
# Check if path exist
97+
if not path.exists():
98+
log.error(f"Path does not exist: {path}")
99+
return 0
100+
# Check if path is a directory
101+
elif path.is_dir():
102+
return 2
103+
# Check if path is a file
104+
elif path.is_file():
105+
log.error(f"Path is a file: {path}")
106+
return 1
107+
except Exception as e:
108+
log.error(f"Error validating before execution: {e}")
109+
return 0
110+
111+
def _remove_single_document(
112+
self,
113+
path: Path,
114+
remove_assets: bool = True
115+
) -> None:
116+
"""Remove a single document.
117+
118+
Args:
119+
path (Path): The path to the note file to remove
120+
remove_assets (bool): Whether to remove the asset directory
121+
"""
122+
try:
123+
# Get the corresponding asset directory
124+
asset_dir = common.get_asset_directory(path)
125+
126+
# Remove the document
127+
path.unlink()
128+
log.info(f"Successfully removed document: {path}")
129+
130+
# Remove the asset directory if requested and exists
131+
if remove_assets and asset_dir.exists():
132+
import shutil
133+
shutil.rmtree(asset_dir)
134+
log.info(f"Successfully removed asset directory: {asset_dir}")
135+
else:
136+
log.warning(f"Asset directory does not exist: {asset_dir}, skipping removal")
137+
except Exception as e:
138+
log.error(f"Error removing single document: {e}")
139+
140+
def _remove_docs_directory(
141+
self,
142+
directory: Path,
143+
remove_assets: bool = True
144+
) -> None:
145+
"""Remove a directory of documents.
146+
147+
Args:
148+
directory (Path): The path to the directory of documents to remove
149+
remove_assets (bool): Whether to remove the asset directories
150+
"""
151+
try:
152+
# Get the list of documents in the directory
153+
documents = [p for p in directory.iterdir() if p.is_file() and (p.suffix == ".md" or p.suffix == ".ipynb")]
154+
155+
# Remove each document
156+
for document in documents:
157+
self._remove_single_document(document, remove_assets)
158+
except Exception as e:
159+
log.error(f"Error removing directory of documents: {e}")
160+
161+
def execute(
162+
self,
163+
path: Path,
164+
remove_assets: bool = True
165+
) -> None:
166+
"""Execute the remove command.
167+
168+
Args:
169+
path (Path): The path to the note file to remove
170+
"""
171+
try:
172+
# Validate before execution
173+
pre_check = self._validate_before_execution(path)
174+
if pre_check == 0:
175+
log.error(f"Validation failed for: {path}")
176+
elif pre_check == 1:
177+
self._remove_single_document(path, remove_assets)
178+
elif pre_check == 2:
179+
self._remove_docs_directory(path, remove_assets)
180+
except Exception as e:
181+
log.error(f"Error executing remove command: {e}")
182+
return
183+
184+
185+
class MoveCommand:
186+
"""Command to move a note(s) and its(their)
187+
corresponding asset directory(ies) like `mv`.
188+
"""
189+
190+
def _validate_before_execution(
191+
self,
192+
source: Path,
193+
destination: Path
194+
) -> int:
195+
"""Validate before executing the move command.
196+
197+
Args:
198+
source (Path): The path to the source note file(s) to move
199+
destination (Path): The path to the destination note file(s) to move
200+
201+
Returns:
202+
int: The signal marking the result of the validation:
203+
0: Failed
204+
1: Single file move request
205+
2: Multiple files that refer to a directory move request
206+
"""
207+
try:
208+
# Check if source exists
209+
if not source.exists():
210+
log.error(f"Source does not exist: {source}")
211+
return 0
212+
# Check if source is a directory
213+
elif source.is_dir():
214+
return 2
215+
# Check if source is a file
216+
elif source.is_file():
217+
return 1
218+
219+
# Check if destination exists
220+
if destination.exists():
221+
log.error(f"Destination already exists: {destination}")
222+
return 0
223+
except Exception as e:
224+
log.error(f"Error validating before execution: {e}")
225+
return 0
226+
227+
def _move_single_document(
228+
self,
229+
source: Path,
230+
destination: Path
231+
) -> None:
232+
"""Move a single document.
233+
234+
Args:
235+
source (Path): The path to the source note file to move
236+
destination (Path): The path to the destination note file to move
237+
"""
238+
try:
239+
pass
240+
except Exception as e:
241+
log.error(f"Error moving single document: {e}")
242+
243+
def _move_docs_directory(
244+
self,
245+
source: Path,
246+
destination: Path
247+
) -> None:
248+
"""Move a directory of documents.
249+
250+
Args:
251+
source (Path): The path to the source directory of documents to move
252+
destination (Path): The path to the destination directory of documents to move
253+
"""
254+
try:
255+
pass
256+
except Exception as e:
257+
log.error(f"Error moving directory of documents: {e}")
258+
259+
def execute(
260+
self,
261+
source: Path,
262+
destination: Path
263+
) -> None:
264+
"""Execute the move command.
265+
266+
Args:
267+
source (Path): The path to the source note file(s) to move
268+
destination (Path): The path to the destination note file(s) to move
269+
"""
270+
pass

src/mkdocs_note/utils/cli/common.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,8 @@ def get_logger(name: str) -> logging.Logger:
128128
logger = logging.getLogger(name)
129129
if not logger.handlers:
130130
handler = logging.StreamHandler()
131-
formatter = logging.Formatter(
132-
"%(levelname)s - %(name)s - %(message)s"
133-
)
131+
formatter = logging.Formatter("%(levelname)s - %(name)s - %(message)s")
134132
handler.setFormatter(formatter)
135133
logger.addHandler(handler)
136134
logger.setLevel(logging.INFO)
137135
return logger
138-

0 commit comments

Comments
 (0)