worklog-opsdevnz 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {worklog_opsdevnz-0.1.2/src/worklog_opsdevnz.egg-info → worklog_opsdevnz-0.2.0}/PKG-INFO +4 -1
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/README.md +2 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/pyproject.toml +2 -1
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz/config.py +3 -0
- worklog_opsdevnz-0.2.0/src/worklog_opsdevnz/template.py +94 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0/src/worklog_opsdevnz.egg-info}/PKG-INFO +4 -1
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/requires.txt +1 -0
- worklog_opsdevnz-0.2.0/tests/test_template.py +285 -0
- worklog_opsdevnz-0.1.2/src/worklog_opsdevnz/template.py +0 -46
- worklog_opsdevnz-0.1.2/tests/test_template.py +0 -45
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/LICENSE +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/setup.cfg +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz/__init__.py +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz/cli.py +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz/paths.py +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/SOURCES.txt +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/dependency_links.txt +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/entry_points.txt +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/top_level.txt +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/tests/test_cli.py +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/tests/test_config.py +0 -0
- {worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/tests/test_paths.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: worklog-opsdevnz
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Configurable worklog management CLI for dated development logs
|
|
5
5
|
Author-email: "OpsDev.nz Collective" <john@opsdev.nz>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
|
29
29
|
Requires-Dist: pytest-mock>=3.10; extra == "dev"
|
|
30
30
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
31
31
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: zensical>=0.0.44; extra == "dev"
|
|
32
33
|
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
# worklog-opsdevnz
|
|
@@ -60,6 +61,7 @@ worklog_dir = "docs/worklog"
|
|
|
60
61
|
structure = "year" # "flat", "year", or "year-month"
|
|
61
62
|
author = "Your Name"
|
|
62
63
|
editor = "nvim" # optional, overridden by -e / $VISUAL / $EDITOR
|
|
64
|
+
template = "my-template.md" # optional custom body template
|
|
63
65
|
default_tags = ["worklog", "log"]
|
|
64
66
|
|
|
65
67
|
[[sections]]
|
|
@@ -81,6 +83,7 @@ title = "Next"
|
|
|
81
83
|
- Three directory structure modes: `flat`, `year`, `year-month`
|
|
82
84
|
- YAML frontmatter: date, author, tags, draft status
|
|
83
85
|
- Configurable section headers per project
|
|
86
|
+
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
84
87
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
85
88
|
- `--version` flag for installed version
|
|
86
89
|
|
|
@@ -27,6 +27,7 @@ worklog_dir = "docs/worklog"
|
|
|
27
27
|
structure = "year" # "flat", "year", or "year-month"
|
|
28
28
|
author = "Your Name"
|
|
29
29
|
editor = "nvim" # optional, overridden by -e / $VISUAL / $EDITOR
|
|
30
|
+
template = "my-template.md" # optional custom body template
|
|
30
31
|
default_tags = ["worklog", "log"]
|
|
31
32
|
|
|
32
33
|
[[sections]]
|
|
@@ -48,6 +49,7 @@ title = "Next"
|
|
|
48
49
|
- Three directory structure modes: `flat`, `year`, `year-month`
|
|
49
50
|
- YAML frontmatter: date, author, tags, draft status
|
|
50
51
|
- Configurable section headers per project
|
|
52
|
+
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
51
53
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
52
54
|
- `--version` flag for installed version
|
|
53
55
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "worklog-opsdevnz"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Configurable worklog management CLI for dated development logs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -37,6 +37,7 @@ dev = [
|
|
|
37
37
|
"pytest-mock>=3.10",
|
|
38
38
|
"ruff>=0.4",
|
|
39
39
|
"mypy>=1.0",
|
|
40
|
+
"zensical>=0.0.44",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.urls]
|
|
@@ -67,5 +67,8 @@ def get_config() -> dict[str, Any]:
|
|
|
67
67
|
worklog_dir = merged["worklog_dir"]
|
|
68
68
|
if not Path(worklog_dir).is_absolute():
|
|
69
69
|
merged["worklog_dir"] = str(config_path.parent / worklog_dir)
|
|
70
|
+
template = merged.get("template")
|
|
71
|
+
if template and not Path(template).is_absolute():
|
|
72
|
+
merged["template"] = str(config_path.parent / template)
|
|
70
73
|
return merged
|
|
71
74
|
return dict(DEFAULT_CONFIG)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Frontmatter and body generation for worklog entries."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_frontmatter(
|
|
7
|
+
config: dict[str, Any],
|
|
8
|
+
iso_date: str,
|
|
9
|
+
) -> str:
|
|
10
|
+
"""Generate YAML frontmatter."""
|
|
11
|
+
title = f"Work Log - {iso_date}"
|
|
12
|
+
tags = "\n".join(f" - {t}" for t in config.get("default_tags", []))
|
|
13
|
+
author = config.get("author", "unknown")
|
|
14
|
+
|
|
15
|
+
return f"""---
|
|
16
|
+
title: "{title}"
|
|
17
|
+
date: {iso_date}
|
|
18
|
+
author: {author}
|
|
19
|
+
tags:
|
|
20
|
+
{tags}
|
|
21
|
+
draft: false
|
|
22
|
+
---
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def generate_body(config: dict[str, Any]) -> str:
|
|
27
|
+
"""Generate section headers from config."""
|
|
28
|
+
sections = config.get("sections", [])
|
|
29
|
+
lines = []
|
|
30
|
+
for section in sections:
|
|
31
|
+
title = section.get("title", "")
|
|
32
|
+
content = section.get("content", "")
|
|
33
|
+
lines.append(f"## {title}\n")
|
|
34
|
+
if content:
|
|
35
|
+
lines.append(f"{content}\n")
|
|
36
|
+
return "\n".join(lines)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _render_tags(tags: list[str]) -> str:
|
|
40
|
+
"""Render tags as a block-style YAML list substitution.
|
|
41
|
+
|
|
42
|
+
Empty list → '[]'. Populated list → leading newline followed by
|
|
43
|
+
each tag on its own indented line with '-' prefix.
|
|
44
|
+
"""
|
|
45
|
+
if not tags:
|
|
46
|
+
return "[]"
|
|
47
|
+
return "\n" + "\n".join(f" - {t}" for t in tags)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def render_template(
|
|
51
|
+
template_path: str,
|
|
52
|
+
iso_date: str,
|
|
53
|
+
config: dict[str, Any],
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Render a custom Markdown template with placeholder substitution.
|
|
56
|
+
|
|
57
|
+
Supports {{DATE}}, {{TITLE}}, {{AUTHOR}}, and {{TAGS}} placeholders.
|
|
58
|
+
All placeholders are case-sensitive.
|
|
59
|
+
"""
|
|
60
|
+
title = f"Work Log - {iso_date}"
|
|
61
|
+
tags = _render_tags(config.get("default_tags", []))
|
|
62
|
+
author = config.get("author", "unknown")
|
|
63
|
+
with open(template_path) as f:
|
|
64
|
+
content = f.read()
|
|
65
|
+
content = content.replace("{{DATE}}", iso_date)
|
|
66
|
+
content = content.replace("{{TITLE}}", title)
|
|
67
|
+
content = content.replace("{{AUTHOR}}", author)
|
|
68
|
+
content = content.replace("{{TAGS}}", tags)
|
|
69
|
+
# Clean trailing whitespace (avoids artifacts when placeholder
|
|
70
|
+
# substitution leaves space before a newline, e.g. 'tags: \n')
|
|
71
|
+
content = "\n".join(line.rstrip() for line in content.split("\n"))
|
|
72
|
+
return content
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def generate_content(
|
|
76
|
+
config: dict[str, Any],
|
|
77
|
+
iso_date: str,
|
|
78
|
+
) -> str:
|
|
79
|
+
"""Generate full worklog content.
|
|
80
|
+
|
|
81
|
+
If a 'template' field is set in config, the template defines the
|
|
82
|
+
complete entry (including YAML frontmatter). Placeholders are
|
|
83
|
+
substituted but nothing else is prepended.
|
|
84
|
+
|
|
85
|
+
Otherwise uses the built-in default: config-driven frontmatter and
|
|
86
|
+
sections-based body.
|
|
87
|
+
"""
|
|
88
|
+
template_path = config.get("template")
|
|
89
|
+
if template_path:
|
|
90
|
+
return render_template(template_path, iso_date, config)
|
|
91
|
+
|
|
92
|
+
frontmatter = generate_frontmatter(config, iso_date)
|
|
93
|
+
body = generate_body(config)
|
|
94
|
+
return f"{frontmatter}\n{body}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: worklog-opsdevnz
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Configurable worklog management CLI for dated development logs
|
|
5
5
|
Author-email: "OpsDev.nz Collective" <john@opsdev.nz>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
|
29
29
|
Requires-Dist: pytest-mock>=3.10; extra == "dev"
|
|
30
30
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
31
31
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: zensical>=0.0.44; extra == "dev"
|
|
32
33
|
Dynamic: license-file
|
|
33
34
|
|
|
34
35
|
# worklog-opsdevnz
|
|
@@ -60,6 +61,7 @@ worklog_dir = "docs/worklog"
|
|
|
60
61
|
structure = "year" # "flat", "year", or "year-month"
|
|
61
62
|
author = "Your Name"
|
|
62
63
|
editor = "nvim" # optional, overridden by -e / $VISUAL / $EDITOR
|
|
64
|
+
template = "my-template.md" # optional custom body template
|
|
63
65
|
default_tags = ["worklog", "log"]
|
|
64
66
|
|
|
65
67
|
[[sections]]
|
|
@@ -81,6 +83,7 @@ title = "Next"
|
|
|
81
83
|
- Three directory structure modes: `flat`, `year`, `year-month`
|
|
82
84
|
- YAML frontmatter: date, author, tags, draft status
|
|
83
85
|
- Configurable section headers per project
|
|
86
|
+
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
84
87
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
85
88
|
- `--version` flag for installed version
|
|
86
89
|
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Tests for frontmatter and body generation."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from worklog_opsdevnz.template import (
|
|
6
|
+
generate_frontmatter,
|
|
7
|
+
generate_body,
|
|
8
|
+
generate_content,
|
|
9
|
+
render_template,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_generate_frontmatter():
|
|
14
|
+
config = {
|
|
15
|
+
"author": "Test Author",
|
|
16
|
+
"default_tags": ["internal", "test"],
|
|
17
|
+
}
|
|
18
|
+
fm = generate_frontmatter(config, "2026-05-23")
|
|
19
|
+
assert "title: " in fm
|
|
20
|
+
assert "Work Log - 2026-05-23" in fm
|
|
21
|
+
assert "date: 2026-05-23" in fm
|
|
22
|
+
assert "author: Test Author" in fm
|
|
23
|
+
assert " - internal" in fm
|
|
24
|
+
assert " - test" in fm
|
|
25
|
+
assert "draft: false" in fm
|
|
26
|
+
assert fm.startswith("---")
|
|
27
|
+
assert fm.strip().endswith("---")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_generate_body_with_sections():
|
|
31
|
+
config = {
|
|
32
|
+
"sections": [
|
|
33
|
+
{"title": "Focus", "content": ""},
|
|
34
|
+
{"title": "Notes", "content": "some note"},
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
body = generate_body(config)
|
|
38
|
+
assert "## Focus" in body
|
|
39
|
+
assert "## Notes" in body
|
|
40
|
+
assert "some note" in body
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_generate_content_full():
|
|
44
|
+
config = {
|
|
45
|
+
"author": "opsdev",
|
|
46
|
+
"default_tags": ["log"],
|
|
47
|
+
"sections": [{"title": "Today", "content": ""}],
|
|
48
|
+
}
|
|
49
|
+
content = generate_content(config, "2026-05-23")
|
|
50
|
+
assert content.startswith("---")
|
|
51
|
+
assert "Work Log - 2026-05-23" in content
|
|
52
|
+
assert "## Today" in content
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_render_template_with_placeholders(tmp_path):
|
|
56
|
+
template = tmp_path / "my-template.md"
|
|
57
|
+
template.write_text(
|
|
58
|
+
"---\n"
|
|
59
|
+
'title: "{{TITLE}}"\n'
|
|
60
|
+
"date: {{DATE}}\n"
|
|
61
|
+
"author: {{AUTHOR}}\n"
|
|
62
|
+
"tags: {{TAGS}}\n"
|
|
63
|
+
"draft: false\n"
|
|
64
|
+
"---\n\n"
|
|
65
|
+
"# {{TITLE}}\n\n"
|
|
66
|
+
"Date: {{DATE}}\n\n"
|
|
67
|
+
"Free-form notes."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
config = {"author": "opsdev", "default_tags": ["dev", "log"]}
|
|
71
|
+
result = render_template(str(template), "2026-05-26", config)
|
|
72
|
+
|
|
73
|
+
# Placeholders replaced
|
|
74
|
+
assert "{{DATE}}" not in result
|
|
75
|
+
assert "{{TITLE}}" not in result
|
|
76
|
+
assert "{{AUTHOR}}" not in result
|
|
77
|
+
assert "{{TAGS}}" not in result
|
|
78
|
+
|
|
79
|
+
# Content verified
|
|
80
|
+
assert "# Work Log - 2026-05-26" in result
|
|
81
|
+
assert "Date: 2026-05-26" in result
|
|
82
|
+
assert "Free-form notes." in result
|
|
83
|
+
assert 'title: "Work Log - 2026-05-26"' in result
|
|
84
|
+
assert "date: 2026-05-26" in result
|
|
85
|
+
assert "author: opsdev" in result
|
|
86
|
+
assert "draft: false" in result
|
|
87
|
+
|
|
88
|
+
# Tags rendered as block-style YAML
|
|
89
|
+
assert " - dev" in result
|
|
90
|
+
assert " - log" in result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_render_template_file_not_found():
|
|
94
|
+
with pytest.raises(FileNotFoundError):
|
|
95
|
+
render_template("/nonexistent/template.md", "2026-05-26", {})
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_generate_content_with_template(tmp_path):
|
|
99
|
+
"""When template is set, it defines the complete entry.
|
|
100
|
+
|
|
101
|
+
No auto-generated frontmatter is prepended.
|
|
102
|
+
"""
|
|
103
|
+
template = tmp_path / "my-template.md"
|
|
104
|
+
template.write_text(
|
|
105
|
+
"---\n"
|
|
106
|
+
'title: "{{TITLE}}"\n'
|
|
107
|
+
"date: {{DATE}}\n"
|
|
108
|
+
"author: {{AUTHOR}}\n"
|
|
109
|
+
"tags: {{TAGS}}\n"
|
|
110
|
+
"draft: false\n"
|
|
111
|
+
"---\n\n"
|
|
112
|
+
"# {{TITLE}}\n\n"
|
|
113
|
+
"Date: {{DATE}}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
config = {
|
|
117
|
+
"author": "opsdev",
|
|
118
|
+
"default_tags": ["log"],
|
|
119
|
+
"template": str(template),
|
|
120
|
+
}
|
|
121
|
+
content = generate_content(config, "2026-05-26")
|
|
122
|
+
|
|
123
|
+
# Template defines the full entry — its frontmatter is present
|
|
124
|
+
assert content.startswith("---")
|
|
125
|
+
assert 'title: "Work Log - 2026-05-26"' in content
|
|
126
|
+
assert "date: 2026-05-26" in content
|
|
127
|
+
assert "author: opsdev" in content
|
|
128
|
+
assert " - log" in content
|
|
129
|
+
assert "draft: false" in content
|
|
130
|
+
|
|
131
|
+
# Body comes from template
|
|
132
|
+
assert "# Work Log - 2026-05-26" in content
|
|
133
|
+
assert "Date: 2026-05-26" in content
|
|
134
|
+
|
|
135
|
+
# No duplicate frontmatter — only one '---' block
|
|
136
|
+
assert content.count("---") == 2 # opening + closing
|
|
137
|
+
|
|
138
|
+
# Sections are NOT present (template controls everything)
|
|
139
|
+
assert "## Focus for Today" not in content
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_generate_content_without_template():
|
|
143
|
+
"""No template field → built-in sections body, unchanged behaviour."""
|
|
144
|
+
config = {
|
|
145
|
+
"author": "opsdev",
|
|
146
|
+
"default_tags": ["log"],
|
|
147
|
+
"sections": [{"title": "Today", "content": ""}],
|
|
148
|
+
}
|
|
149
|
+
content = generate_content(config, "2026-05-26")
|
|
150
|
+
assert "## Today" in content
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── {{TAGS}} rendering ────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_render_template_tags_empty(tmp_path):
|
|
157
|
+
"""Empty default_tags → {{TAGS}} substitutes as '[]'."""
|
|
158
|
+
template = tmp_path / "t.md"
|
|
159
|
+
template.write_text("tags: {{TAGS}}\n")
|
|
160
|
+
|
|
161
|
+
result = render_template(str(template), "2026-06-01", {"default_tags": []})
|
|
162
|
+
assert "tags: []" in result
|
|
163
|
+
assert "{{TAGS}}" not in result
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_render_template_tags_populated(tmp_path):
|
|
167
|
+
"""Populated default_tags → {{TAGS}} renders as block-style YAML list."""
|
|
168
|
+
template = tmp_path / "t.md"
|
|
169
|
+
template.write_text("tags: {{TAGS}}\n")
|
|
170
|
+
|
|
171
|
+
config = {"default_tags": ["dev", "log", "ops"]}
|
|
172
|
+
result = render_template(str(template), "2026-06-01", config)
|
|
173
|
+
|
|
174
|
+
# Block-style: leading newline + indented items, no trailing whitespace
|
|
175
|
+
assert "tags:\n - dev\n - log\n - ops" in result
|
|
176
|
+
assert "{{TAGS}}" not in result
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_render_template_tags_not_configured(tmp_path):
|
|
180
|
+
"""No default_tags in config → {{TAGS}} substitutes as '[]'."""
|
|
181
|
+
template = tmp_path / "t.md"
|
|
182
|
+
template.write_text("tags: {{TAGS}}\n")
|
|
183
|
+
|
|
184
|
+
result = render_template(str(template), "2026-06-01", {})
|
|
185
|
+
assert "tags: []" in result
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── {{AUTHOR}} rendering ──────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_render_template_author_configured(tmp_path):
|
|
192
|
+
"""{{AUTHOR}} pulls the author value from config."""
|
|
193
|
+
template = tmp_path / "t.md"
|
|
194
|
+
template.write_text("author: {{AUTHOR}}\n")
|
|
195
|
+
|
|
196
|
+
result = render_template(str(template), "2026-06-01", {"author": "opsdev"})
|
|
197
|
+
assert "author: opsdev" in result
|
|
198
|
+
assert "{{AUTHOR}}" not in result
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_render_template_author_fallback(tmp_path):
|
|
202
|
+
"""{{AUTHOR}} falls back to 'unknown' when not in config."""
|
|
203
|
+
template = tmp_path / "t.md"
|
|
204
|
+
template.write_text("author: {{AUTHOR}}\n")
|
|
205
|
+
|
|
206
|
+
result = render_template(str(template), "2026-06-01", {})
|
|
207
|
+
assert "author: unknown" in result
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ── Full-entry template edge cases ────────────────────────────────
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_generate_content_template_defines_full_entry(tmp_path):
|
|
214
|
+
"""Template with custom YAML fields — the tool only substitutes placeholders."""
|
|
215
|
+
template = tmp_path / "custom.md"
|
|
216
|
+
template.write_text(
|
|
217
|
+
"---\n"
|
|
218
|
+
'title: "{{TITLE}}"\n'
|
|
219
|
+
"date: {{DATE}}\n"
|
|
220
|
+
"author: {{AUTHOR}}\n"
|
|
221
|
+
"tags: {{TAGS}}\n"
|
|
222
|
+
"mood: creative\n"
|
|
223
|
+
"project: outcome-engineering\n"
|
|
224
|
+
"draft: false\n"
|
|
225
|
+
"---\n\n"
|
|
226
|
+
"# {{TITLE}}\n\n"
|
|
227
|
+
"Custom body content."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
config = {
|
|
231
|
+
"author": "opsdev",
|
|
232
|
+
"default_tags": ["dev"],
|
|
233
|
+
"template": str(template),
|
|
234
|
+
}
|
|
235
|
+
content = generate_content(config, "2026-06-05")
|
|
236
|
+
|
|
237
|
+
# Custom fields preserved verbatim
|
|
238
|
+
assert "mood: creative" in content
|
|
239
|
+
assert "project: outcome-engineering" in content
|
|
240
|
+
|
|
241
|
+
# Placeholders substituted
|
|
242
|
+
assert 'title: "Work Log - 2026-06-05"' in content
|
|
243
|
+
assert "date: 2026-06-05" in content
|
|
244
|
+
assert "author: opsdev" in content
|
|
245
|
+
assert " - dev" in content
|
|
246
|
+
|
|
247
|
+
# Body preserved
|
|
248
|
+
assert "Custom body content." in content
|
|
249
|
+
|
|
250
|
+
# No auto-generated frontmatter prepended
|
|
251
|
+
assert content.count("---") == 2 # only the template's opening/closing
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def test_generate_content_template_no_frontmatter(tmp_path):
|
|
255
|
+
"""Template without frontmatter — entry has no frontmatter at all."""
|
|
256
|
+
template = tmp_path / "body-only.md"
|
|
257
|
+
template.write_text("# {{TITLE}}\n\nJust body content.")
|
|
258
|
+
|
|
259
|
+
config = {"template": str(template)}
|
|
260
|
+
content = generate_content(config, "2026-06-05")
|
|
261
|
+
|
|
262
|
+
# No frontmatter markers, just the rendered template
|
|
263
|
+
assert "---" not in content
|
|
264
|
+
assert "# Work Log - 2026-06-05" in content
|
|
265
|
+
assert "Just body content." in content
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_render_template_case_sensitive(tmp_path):
|
|
269
|
+
"""Lowercase placeholders are NOT substituted."""
|
|
270
|
+
template = tmp_path / "t.md"
|
|
271
|
+
template.write_text(
|
|
272
|
+
"title: {{title}}\n"
|
|
273
|
+
"date: {{date}}\n"
|
|
274
|
+
"author: {{author}}\n"
|
|
275
|
+
"tags: {{tags}}\n"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
config = {"author": "opsdev", "default_tags": ["dev"]}
|
|
279
|
+
result = render_template(str(template), "2026-06-05", config)
|
|
280
|
+
|
|
281
|
+
# Lowercase placeholders left untouched
|
|
282
|
+
assert "{{title}}" in result
|
|
283
|
+
assert "{{date}}" in result
|
|
284
|
+
assert "{{author}}" in result
|
|
285
|
+
assert "{{tags}}" in result
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""Frontmatter and body generation for worklog entries."""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def generate_frontmatter(
|
|
7
|
-
config: dict[str, Any],
|
|
8
|
-
iso_date: str,
|
|
9
|
-
) -> str:
|
|
10
|
-
"""Generate YAML frontmatter."""
|
|
11
|
-
title = f"Work Log - {iso_date}"
|
|
12
|
-
tags = "\n".join(f" - {t}" for t in config.get("default_tags", []))
|
|
13
|
-
author = config.get("author", "unknown")
|
|
14
|
-
|
|
15
|
-
return f"""---
|
|
16
|
-
title: "{title}"
|
|
17
|
-
date: {iso_date}
|
|
18
|
-
author: {author}
|
|
19
|
-
tags:
|
|
20
|
-
{tags}
|
|
21
|
-
draft: false
|
|
22
|
-
---
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def generate_body(config: dict[str, Any]) -> str:
|
|
27
|
-
"""Generate section headers from config."""
|
|
28
|
-
sections = config.get("sections", [])
|
|
29
|
-
lines = []
|
|
30
|
-
for section in sections:
|
|
31
|
-
title = section.get("title", "")
|
|
32
|
-
content = section.get("content", "")
|
|
33
|
-
lines.append(f"## {title}\n")
|
|
34
|
-
if content:
|
|
35
|
-
lines.append(f"{content}\n")
|
|
36
|
-
return "\n".join(lines)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def generate_content(
|
|
40
|
-
config: dict[str, Any],
|
|
41
|
-
iso_date: str,
|
|
42
|
-
) -> str:
|
|
43
|
-
"""Generate full worklog content."""
|
|
44
|
-
frontmatter = generate_frontmatter(config, iso_date)
|
|
45
|
-
body = generate_body(config)
|
|
46
|
-
return f"{frontmatter}\n{body}"
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"""Tests for frontmatter and body generation."""
|
|
2
|
-
|
|
3
|
-
from worklog_opsdevnz.template import generate_frontmatter, generate_body, generate_content
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_generate_frontmatter():
|
|
7
|
-
config = {
|
|
8
|
-
"author": "Test Author",
|
|
9
|
-
"default_tags": ["internal", "test"],
|
|
10
|
-
}
|
|
11
|
-
fm = generate_frontmatter(config, "2026-05-23")
|
|
12
|
-
assert "title: " in fm
|
|
13
|
-
assert "Work Log - 2026-05-23" in fm
|
|
14
|
-
assert "date: 2026-05-23" in fm
|
|
15
|
-
assert "author: Test Author" in fm
|
|
16
|
-
assert " - internal" in fm
|
|
17
|
-
assert " - test" in fm
|
|
18
|
-
assert "draft: false" in fm
|
|
19
|
-
assert fm.startswith("---")
|
|
20
|
-
assert fm.strip().endswith("---")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_generate_body_with_sections():
|
|
24
|
-
config = {
|
|
25
|
-
"sections": [
|
|
26
|
-
{"title": "Focus", "content": ""},
|
|
27
|
-
{"title": "Notes", "content": "some note"},
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
body = generate_body(config)
|
|
31
|
-
assert "## Focus" in body
|
|
32
|
-
assert "## Notes" in body
|
|
33
|
-
assert "some note" in body
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_generate_content_full():
|
|
37
|
-
config = {
|
|
38
|
-
"author": "opsdev",
|
|
39
|
-
"default_tags": ["log"],
|
|
40
|
-
"sections": [{"title": "Today", "content": ""}],
|
|
41
|
-
}
|
|
42
|
-
content = generate_content(config, "2026-05-23")
|
|
43
|
-
assert content.startswith("---")
|
|
44
|
-
assert "Work Log - 2026-05-23" in content
|
|
45
|
-
assert "## Today" in content
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{worklog_opsdevnz-0.1.2 → worklog_opsdevnz-0.2.0}/src/worklog_opsdevnz.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|