Coverage for src/prosemark/adapters/daily_repo_fs.py: 100%
32 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
1# Copyright (c) 2024 Prosemark Contributors
2# This software is licensed under the MIT License
4"""File system implementation of DailyRepo for freeform writing files."""
6from datetime import datetime
7from pathlib import Path
9from prosemark.adapters.frontmatter_codec import FrontmatterCodec
10from prosemark.exceptions import FileSystemError
11from prosemark.ports.clock import Clock
12from prosemark.ports.daily_repo import DailyRepo
13from prosemark.ports.id_generator import IdGenerator
16class DailyRepoFs(DailyRepo):
17 """File system implementation of DailyRepo for freeform writing files.
19 This adapter manages the creation of timestamped freeform writing files
20 outside the binder structure with:
21 - Filename format: YYYYMMDDTHHMM_<uuid7>.md
22 - Optional title in YAML frontmatter
23 - Standalone markdown files for quick writing
24 - Configurable daily directory for organization
26 Example file structure:
27 ```
28 daily/
29 ├── 20250920T0830_0192f0c1-2345-7123-8abc-def012345678.md
30 ├── 20250920T1430_0192f0c1-2345-7456-8abc-def012345678.md
31 └── ...
32 ```
34 File content format:
35 ```yaml
36 ---
37 title: "Morning Thoughts"
38 id: "0192f0c1-2345-7123-8abc-def012345678"
39 created: "2025-09-20T08:30:00Z"
40 ---
41 # Freeform Writing
43 Content goes here...
44 ```
46 The adapter ensures:
47 - Unique filenames with timestamp and UUID components
48 - Consistent frontmatter structure
49 - Directory creation as needed
50 - Proper error handling for file system operations
51 """
53 def __init__(
54 self,
55 daily_path: Path,
56 id_generator: IdGenerator,
57 clock: Clock,
58 ) -> None:
59 """Initialize repository with daily directory and dependencies.
61 Args:
62 daily_path: Directory for storing daily freeform files
63 id_generator: ID generator for creating unique identifiers
64 clock: Clock for timestamp generation
66 """
67 self.daily_path = daily_path
68 self.id_generator = id_generator
69 self.clock = clock
70 self.frontmatter_codec = FrontmatterCodec()
72 def write_freeform(self, title: str | None = None) -> str:
73 """Create a new timestamped freewrite file.
75 Args:
76 title: Optional title to include in the file's frontmatter
78 Returns:
79 The filename of the created freewrite file
81 Raises:
82 FileSystemError: If the file cannot be created
84 """
85 try:
86 # Generate unique ID and timestamp
87 unique_id = self.id_generator.new()
88 now_iso = self.clock.now_iso()
90 # Create timestamp prefix for filename
91 # Parse ISO format to extract timestamp components
92 timestamp = datetime.fromisoformat(now_iso)
93 timestamp_prefix = timestamp.strftime('%Y%m%dT%H%M')
95 # Build filename
96 filename = f'{timestamp_prefix}_{unique_id}.md'
97 file_path = self.daily_path / filename
99 # Ensure directory exists
100 self.daily_path.mkdir(parents=True, exist_ok=True)
102 # Prepare frontmatter
103 frontmatter = {
104 'id': str(unique_id),
105 'created': now_iso,
106 }
108 if title:
109 frontmatter['title'] = title
111 # Create content with frontmatter
112 content_body = '\n# Freeform Writing\n\n'
113 full_content = self.frontmatter_codec.generate(frontmatter, content_body)
115 # Write file
116 file_path.write_text(full_content, encoding='utf-8')
117 except OSError as exc:
118 msg = f'Cannot create freeform file: {exc}'
119 raise FileSystemError(msg) from exc
120 else:
121 return filename