worklog-opsdevnz 0.2.0__tar.gz → 0.2.1__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.2.0/src/worklog_opsdevnz.egg-info → worklog_opsdevnz-0.2.1}/PKG-INFO +5 -1
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/README.md +4 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/pyproject.toml +6 -1
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz/cli.py +19 -3
- worklog_opsdevnz-0.2.1/src/worklog_opsdevnz/paths.py +73 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1/src/worklog_opsdevnz.egg-info}/PKG-INFO +5 -1
- worklog_opsdevnz-0.2.1/tests/test_cli.py +137 -0
- worklog_opsdevnz-0.2.1/tests/test_paths.py +79 -0
- worklog_opsdevnz-0.2.0/src/worklog_opsdevnz/paths.py +0 -28
- worklog_opsdevnz-0.2.0/tests/test_cli.py +0 -71
- worklog_opsdevnz-0.2.0/tests/test_paths.py +0 -29
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/LICENSE +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/setup.cfg +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz/__init__.py +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz/config.py +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz/template.py +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/SOURCES.txt +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/dependency_links.txt +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/entry_points.txt +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/requires.txt +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/top_level.txt +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/tests/test_config.py +0 -0
- {worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/tests/test_template.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: worklog-opsdevnz
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -50,6 +50,9 @@ worklog-opsdevnz --version
|
|
|
50
50
|
|
|
51
51
|
# Override the editor for this run
|
|
52
52
|
worklog-opsdevnz --editor nvim
|
|
53
|
+
|
|
54
|
+
# Open the most recent previous entry
|
|
55
|
+
worklog-opsdevnz -p
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
## Configuration
|
|
@@ -85,6 +88,7 @@ title = "Next"
|
|
|
85
88
|
- Configurable section headers per project
|
|
86
89
|
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
87
90
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
91
|
+
- `-p` / `--previous` flag to open the most recent past entry
|
|
88
92
|
- `--version` flag for installed version
|
|
89
93
|
|
|
90
94
|
## Requirements
|
|
@@ -16,6 +16,9 @@ worklog-opsdevnz --version
|
|
|
16
16
|
|
|
17
17
|
# Override the editor for this run
|
|
18
18
|
worklog-opsdevnz --editor nvim
|
|
19
|
+
|
|
20
|
+
# Open the most recent previous entry
|
|
21
|
+
worklog-opsdevnz -p
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
## Configuration
|
|
@@ -51,6 +54,7 @@ title = "Next"
|
|
|
51
54
|
- Configurable section headers per project
|
|
52
55
|
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
53
56
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
57
|
+
- `-p` / `--previous` flag to open the most recent past entry
|
|
54
58
|
- `--version` flag for installed version
|
|
55
59
|
|
|
56
60
|
## Requirements
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "worklog-opsdevnz"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Configurable worklog management CLI for dated development logs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -65,6 +65,11 @@ addopts = "-v --cov=src/worklog_opsdevnz --cov-report=term-missing"
|
|
|
65
65
|
line-length = 88
|
|
66
66
|
target-version = "py312"
|
|
67
67
|
|
|
68
|
+
# Explicit rule set (ruff's historical defaults) so CI is stable across
|
|
69
|
+
# ruff upgrades — DTZ/I/etc. are intentionally not enabled: local-date
|
|
70
|
+
# handling is this tool's core domain, not an oversight.
|
|
71
|
+
lint.select = ["E4", "E7", "E9", "F"]
|
|
72
|
+
|
|
68
73
|
[tool.mypy]
|
|
69
74
|
python_version = "3.12"
|
|
70
75
|
warn_return_any = true
|
|
@@ -9,7 +9,7 @@ import click
|
|
|
9
9
|
|
|
10
10
|
from worklog_opsdevnz import __version__
|
|
11
11
|
from worklog_opsdevnz.config import get_config
|
|
12
|
-
from worklog_opsdevnz.paths import resolve_path
|
|
12
|
+
from worklog_opsdevnz.paths import find_previous, resolve_path
|
|
13
13
|
from worklog_opsdevnz.template import generate_content
|
|
14
14
|
|
|
15
15
|
|
|
@@ -21,11 +21,27 @@ from worklog_opsdevnz.template import generate_content
|
|
|
21
21
|
default=None,
|
|
22
22
|
help="Override the editor command (overrides config, $VISUAL, $EDITOR).",
|
|
23
23
|
)
|
|
24
|
-
|
|
25
|
-
""
|
|
24
|
+
@click.option(
|
|
25
|
+
"-p",
|
|
26
|
+
"--previous",
|
|
27
|
+
is_flag=True,
|
|
28
|
+
help="Open the most recent worklog entry before today (never creates).",
|
|
29
|
+
)
|
|
30
|
+
def main(editor: str | None, previous: bool) -> None:
|
|
31
|
+
"""Create or open today's worklog entry, or the previous one with -p."""
|
|
26
32
|
entry_date = date.today()
|
|
27
33
|
|
|
28
34
|
config = get_config()
|
|
35
|
+
|
|
36
|
+
if previous:
|
|
37
|
+
target = find_previous(config, entry_date)
|
|
38
|
+
if target is None:
|
|
39
|
+
click.echo("No previous worklog entry found.", err=True)
|
|
40
|
+
return
|
|
41
|
+
print(f"Opening previous worklog: {target}")
|
|
42
|
+
_open_editor(target, editor, config.get("editor"))
|
|
43
|
+
return
|
|
44
|
+
|
|
29
45
|
target = resolve_path(config, entry_date)
|
|
30
46
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
31
47
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Path resolution for worklog files."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_path(
|
|
9
|
+
config: dict[str, Any],
|
|
10
|
+
entry_date: date,
|
|
11
|
+
) -> Path:
|
|
12
|
+
"""Compute the target file path based on config structure."""
|
|
13
|
+
base = Path(config.get("worklog_dir", "docs/worklog"))
|
|
14
|
+
structure = config.get("structure", "year")
|
|
15
|
+
suffix = config.get("suffix", "worklog")
|
|
16
|
+
|
|
17
|
+
if structure == "flat":
|
|
18
|
+
filename = f"{entry_date.isoformat()}-{suffix}.md"
|
|
19
|
+
return base / filename
|
|
20
|
+
elif structure == "year-month":
|
|
21
|
+
year = entry_date.strftime("%Y")
|
|
22
|
+
month = entry_date.strftime("%m")
|
|
23
|
+
filename = f"{entry_date.strftime('%d-%m-%Y')}-{suffix}.md"
|
|
24
|
+
return base / year / month / filename
|
|
25
|
+
else: # "year" (default)
|
|
26
|
+
year = entry_date.strftime("%Y")
|
|
27
|
+
filename = f"{entry_date.strftime('%d-%m-%Y')}-{suffix}.md"
|
|
28
|
+
return base / year / filename
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_DATE_FORMATS = ("%Y-%m-%d", "%d-%m-%Y")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_entry_date(filename: str, suffix: str) -> date | None:
|
|
35
|
+
"""Extract the entry date from a worklog filename, or None if not parseable."""
|
|
36
|
+
stem = filename.removesuffix(".md")
|
|
37
|
+
prefix = stem.removesuffix(f"-{suffix}")
|
|
38
|
+
for fmt in _DATE_FORMATS:
|
|
39
|
+
try:
|
|
40
|
+
return datetime.strptime(prefix, fmt).date()
|
|
41
|
+
except ValueError:
|
|
42
|
+
continue
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def find_previous(config: dict[str, Any], today: date) -> Path | None:
|
|
47
|
+
"""Locate the most recent worklog entry strictly before *today*.
|
|
48
|
+
|
|
49
|
+
Scans the directory tree implied by the configured structure mode
|
|
50
|
+
(flat, year, or year-month) for files matching ``*-{suffix}.md``,
|
|
51
|
+
parses their dates, and returns the newest one older than today.
|
|
52
|
+
Returns None when no previous entry exists. Never creates anything.
|
|
53
|
+
"""
|
|
54
|
+
base = Path(config.get("worklog_dir", "docs/worklog"))
|
|
55
|
+
suffix = config.get("suffix", "worklog")
|
|
56
|
+
structure = config.get("structure", "year")
|
|
57
|
+
|
|
58
|
+
pattern = f"*-{suffix}.md"
|
|
59
|
+
if structure == "flat":
|
|
60
|
+
candidates = base.glob(pattern)
|
|
61
|
+
elif structure == "year-month":
|
|
62
|
+
candidates = base.glob(f"*/*/{pattern}")
|
|
63
|
+
else: # "year" (default)
|
|
64
|
+
candidates = base.glob(f"*/{pattern}")
|
|
65
|
+
|
|
66
|
+
best: tuple[date, Path] | None = None
|
|
67
|
+
for path in candidates:
|
|
68
|
+
entry_date = _parse_entry_date(path.name, suffix)
|
|
69
|
+
if entry_date is None or entry_date >= today:
|
|
70
|
+
continue
|
|
71
|
+
if best is None or entry_date > best[0]:
|
|
72
|
+
best = (entry_date, path)
|
|
73
|
+
return best[1] if best else None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: worklog-opsdevnz
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -50,6 +50,9 @@ worklog-opsdevnz --version
|
|
|
50
50
|
|
|
51
51
|
# Override the editor for this run
|
|
52
52
|
worklog-opsdevnz --editor nvim
|
|
53
|
+
|
|
54
|
+
# Open the most recent previous entry
|
|
55
|
+
worklog-opsdevnz -p
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
## Configuration
|
|
@@ -85,6 +88,7 @@ title = "Next"
|
|
|
85
88
|
- Configurable section headers per project
|
|
86
89
|
- Custom body templates with `{{DATE}}` and `{{TITLE}}` placeholders
|
|
87
90
|
- Editor integration: `-e` flag → config → `$VISUAL` → `$EDITOR`
|
|
91
|
+
- `-p` / `--previous` flag to open the most recent past entry
|
|
88
92
|
- `--version` flag for installed version
|
|
89
93
|
|
|
90
94
|
## Requirements
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests for CLI entry point."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, timedelta
|
|
4
|
+
|
|
5
|
+
from click.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from worklog_opsdevnz.cli import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_main_creates_file(tmp_path, monkeypatch):
|
|
11
|
+
"""Running with no args creates today's worklog in tmp_path."""
|
|
12
|
+
monkeypatch.chdir(tmp_path)
|
|
13
|
+
runner = CliRunner()
|
|
14
|
+
result = runner.invoke(main, [])
|
|
15
|
+
assert result.exit_code == 0
|
|
16
|
+
assert "Created" in result.output
|
|
17
|
+
expected_leaf = (
|
|
18
|
+
f"docs/worklog/{date.today().year}/{date.today():%d-%m-%Y}-worklog.md"
|
|
19
|
+
)
|
|
20
|
+
assert expected_leaf in result.output
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_main_opens_existing(tmp_path, monkeypatch):
|
|
24
|
+
"""Running again opens the existing file."""
|
|
25
|
+
monkeypatch.chdir(tmp_path)
|
|
26
|
+
runner = CliRunner()
|
|
27
|
+
runner.invoke(main, [])
|
|
28
|
+
result = runner.invoke(main, [])
|
|
29
|
+
assert result.exit_code == 0
|
|
30
|
+
assert "Opening existing" in result.output
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_main_help():
|
|
34
|
+
"""--help works."""
|
|
35
|
+
runner = CliRunner()
|
|
36
|
+
result = runner.invoke(main, ["--help"])
|
|
37
|
+
assert result.exit_code == 0
|
|
38
|
+
assert "--editor" in result.output
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_main_version():
|
|
42
|
+
"""--version prints the installed version."""
|
|
43
|
+
runner = CliRunner()
|
|
44
|
+
result = runner.invoke(main, ["--version"])
|
|
45
|
+
assert result.exit_code == 0
|
|
46
|
+
assert "worklog-opsdevnz, version" in result.output
|
|
47
|
+
# Extract and verify it's a non-empty version string
|
|
48
|
+
version_str = result.output.strip().split(", version ")[-1]
|
|
49
|
+
assert version_str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_main_editor_from_config(tmp_path, monkeypatch):
|
|
53
|
+
"""Editor in worklog.toml is resolved before $VISUAL/$EDITOR."""
|
|
54
|
+
monkeypatch.chdir(tmp_path)
|
|
55
|
+
(tmp_path / "worklog.toml").write_text('editor = "fake-editor-test"\n')
|
|
56
|
+
runner = CliRunner()
|
|
57
|
+
result = runner.invoke(main, [])
|
|
58
|
+
assert result.exit_code == 0
|
|
59
|
+
# config-based editor picked up but not found in PATH
|
|
60
|
+
assert "Editor 'fake-editor-test' not found" in result.output
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_main_editor_cli_overrides_config(tmp_path, monkeypatch):
|
|
64
|
+
"""-e flag overrides the editor set in worklog.toml."""
|
|
65
|
+
monkeypatch.chdir(tmp_path)
|
|
66
|
+
(tmp_path / "worklog.toml").write_text('editor = "fake-editor-test"\n')
|
|
67
|
+
runner = CliRunner()
|
|
68
|
+
result = runner.invoke(main, ["-e", "override-editor-test"])
|
|
69
|
+
assert result.exit_code == 0
|
|
70
|
+
# CLI override takes priority over config
|
|
71
|
+
assert "Editor 'override-editor-test' not found" in result.output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_previous_no_entries(tmp_path, monkeypatch):
|
|
75
|
+
"""-p with no prior entries: message to stderr, exit 0, creates nothing."""
|
|
76
|
+
monkeypatch.chdir(tmp_path)
|
|
77
|
+
runner = CliRunner()
|
|
78
|
+
result = runner.invoke(main, ["-p"])
|
|
79
|
+
assert result.exit_code == 0
|
|
80
|
+
assert "No previous worklog entry found." in result.stderr
|
|
81
|
+
assert not (tmp_path / "docs").exists()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_previous_opens_most_recent_before_today(tmp_path, monkeypatch):
|
|
85
|
+
"""-p opens the newest prior entry, not today's, and creates no file."""
|
|
86
|
+
monkeypatch.chdir(tmp_path)
|
|
87
|
+
today = date.today()
|
|
88
|
+
year_dir = tmp_path / "docs" / "worklog" / str(today.year)
|
|
89
|
+
year_dir.mkdir(parents=True)
|
|
90
|
+
older = (today - timedelta(days=3)).strftime("%d-%m-%Y")
|
|
91
|
+
recent = (today - timedelta(days=1)).strftime("%d-%m-%Y")
|
|
92
|
+
(year_dir / f"{older}-worklog.md").write_text("older")
|
|
93
|
+
(year_dir / f"{recent}-worklog.md").write_text("recent")
|
|
94
|
+
today_file = year_dir / f"{today:%d-%m-%Y}-worklog.md"
|
|
95
|
+
today_file.write_text("today")
|
|
96
|
+
|
|
97
|
+
runner = CliRunner()
|
|
98
|
+
result = runner.invoke(main, ["--previous"])
|
|
99
|
+
assert result.exit_code == 0
|
|
100
|
+
assert recent in result.output
|
|
101
|
+
assert today.strftime("%d-%m-%Y") not in result.output
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_previous_does_not_create_today(tmp_path, monkeypatch):
|
|
105
|
+
"""-p must not create today's entry even though it does not exist."""
|
|
106
|
+
monkeypatch.chdir(tmp_path)
|
|
107
|
+
today = date.today()
|
|
108
|
+
year_dir = tmp_path / "docs" / "worklog" / str(today.year)
|
|
109
|
+
year_dir.mkdir(parents=True)
|
|
110
|
+
(year_dir / f"{(today - timedelta(days=2)):%d-%m-%Y}-worklog.md").write_text("x")
|
|
111
|
+
|
|
112
|
+
runner = CliRunner()
|
|
113
|
+
runner.invoke(main, ["-p"])
|
|
114
|
+
assert not (year_dir / f"{today:%d-%m-%Y}-worklog.md").exists()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_previous_editor_override(tmp_path, monkeypatch):
|
|
118
|
+
"""-p combined with -e applies the editor override to the previous entry."""
|
|
119
|
+
monkeypatch.chdir(tmp_path)
|
|
120
|
+
today = date.today()
|
|
121
|
+
year_dir = tmp_path / "docs" / "worklog" / str(today.year)
|
|
122
|
+
year_dir.mkdir(parents=True)
|
|
123
|
+
recent = (today - timedelta(days=1)).strftime("%d-%m-%Y")
|
|
124
|
+
(year_dir / f"{recent}-worklog.md").write_text("recent")
|
|
125
|
+
|
|
126
|
+
runner = CliRunner()
|
|
127
|
+
result = runner.invoke(main, ["-p", "-e", "override-editor-test"])
|
|
128
|
+
assert result.exit_code == 0
|
|
129
|
+
assert "Editor 'override-editor-test' not found" in result.output
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_previous_help():
|
|
133
|
+
"""--help mentions the --previous flag."""
|
|
134
|
+
runner = CliRunner()
|
|
135
|
+
result = runner.invoke(main, ["--help"])
|
|
136
|
+
assert result.exit_code == 0
|
|
137
|
+
assert "--previous" in result.output
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Tests for path resolution."""
|
|
2
|
+
|
|
3
|
+
from datetime import date
|
|
4
|
+
|
|
5
|
+
from worklog_opsdevnz.paths import find_previous, resolve_path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_resolve_path_flat():
|
|
9
|
+
config = {"worklog_dir": "docs/worklog", "structure": "flat", "suffix": "worklog"}
|
|
10
|
+
path = resolve_path(config, date(2026, 5, 23))
|
|
11
|
+
assert str(path) == "docs/worklog/2026-05-23-worklog.md"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_resolve_path_year():
|
|
15
|
+
config = {"worklog_dir": "logs", "structure": "year", "suffix": "log"}
|
|
16
|
+
path = resolve_path(config, date(2026, 5, 23))
|
|
17
|
+
assert str(path) == "logs/2026/23-05-2026-log.md"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_resolve_path_year_month():
|
|
21
|
+
config = {"worklog_dir": "logs", "structure": "year-month", "suffix": "ops"}
|
|
22
|
+
path = resolve_path(config, date(2026, 5, 23))
|
|
23
|
+
assert str(path) == "logs/2026/05/23-05-2026-ops.md"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_resolve_path_custom_suffix():
|
|
27
|
+
config = {"structure": "year", "suffix": "retro"}
|
|
28
|
+
path = resolve_path(config, date(2026, 1, 1))
|
|
29
|
+
assert str(path).endswith("-retro.md")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_find_previous_year(tmp_path):
|
|
33
|
+
base = tmp_path / "logs" / "2026"
|
|
34
|
+
base.mkdir(parents=True)
|
|
35
|
+
(base / "28-08-2026-worklog.md").write_text("old")
|
|
36
|
+
(base / "01-09-2026-worklog.md").write_text("prev")
|
|
37
|
+
(base / "03-09-2026-worklog.md").write_text("today")
|
|
38
|
+
config = {"worklog_dir": str(tmp_path / "logs"), "structure": "year", "suffix": "worklog"}
|
|
39
|
+
result = find_previous(config, date(2026, 9, 3))
|
|
40
|
+
assert result is not None
|
|
41
|
+
assert result.name == "01-09-2026-worklog.md"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_find_previous_year_month(tmp_path):
|
|
45
|
+
base = tmp_path / "docs" / "worklogs"
|
|
46
|
+
(base / "2026" / "08").mkdir(parents=True)
|
|
47
|
+
(base / "2026" / "09").mkdir(parents=True)
|
|
48
|
+
(base / "2026" / "08" / "27-08-2026-worklog.md").write_text("aug")
|
|
49
|
+
(base / "2026" / "09" / "01-09-2026-worklog.md").write_text("sep")
|
|
50
|
+
config = {"worklog_dir": str(base), "structure": "year-month", "suffix": "worklog"}
|
|
51
|
+
result = find_previous(config, date(2026, 9, 2))
|
|
52
|
+
assert result is not None
|
|
53
|
+
assert result.name == "01-09-2026-worklog.md"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_find_previous_flat_iso_names(tmp_path):
|
|
57
|
+
base = tmp_path / "wl"
|
|
58
|
+
base.mkdir()
|
|
59
|
+
(base / "2026-08-30-worklog.md").write_text("a")
|
|
60
|
+
(base / "2026-09-01-worklog.md").write_text("b")
|
|
61
|
+
config = {"worklog_dir": str(base), "structure": "flat", "suffix": "worklog"}
|
|
62
|
+
result = find_previous(config, date(2026, 9, 2))
|
|
63
|
+
assert result is not None
|
|
64
|
+
assert result.name == "2026-09-01-worklog.md"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_find_previous_ignores_today_unrelated_and_wrong_suffix(tmp_path):
|
|
68
|
+
base = tmp_path / "logs" / "2026"
|
|
69
|
+
base.mkdir(parents=True)
|
|
70
|
+
(base / "03-09-2026-worklog.md").write_text("today")
|
|
71
|
+
(base / "README.md").write_text("not an entry")
|
|
72
|
+
(base / "01-09-2026-journal.md").write_text("wrong suffix")
|
|
73
|
+
config = {"worklog_dir": str(tmp_path / "logs"), "structure": "year", "suffix": "worklog"}
|
|
74
|
+
assert find_previous(config, date(2026, 9, 3)) is None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_find_previous_missing_dir(tmp_path):
|
|
78
|
+
config = {"worklog_dir": str(tmp_path / "nothing"), "structure": "year", "suffix": "worklog"}
|
|
79
|
+
assert find_previous(config, date(2026, 9, 3)) is None
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""Path resolution for worklog files."""
|
|
2
|
-
|
|
3
|
-
from datetime import date
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def resolve_path(
|
|
9
|
-
config: dict[str, Any],
|
|
10
|
-
entry_date: date,
|
|
11
|
-
) -> Path:
|
|
12
|
-
"""Compute the target file path based on config structure."""
|
|
13
|
-
base = Path(config.get("worklog_dir", "docs/worklog"))
|
|
14
|
-
structure = config.get("structure", "year")
|
|
15
|
-
suffix = config.get("suffix", "worklog")
|
|
16
|
-
|
|
17
|
-
if structure == "flat":
|
|
18
|
-
filename = f"{entry_date.isoformat()}-{suffix}.md"
|
|
19
|
-
return base / filename
|
|
20
|
-
elif structure == "year-month":
|
|
21
|
-
year = entry_date.strftime("%Y")
|
|
22
|
-
month = entry_date.strftime("%m")
|
|
23
|
-
filename = f"{entry_date.strftime('%d-%m-%Y')}-{suffix}.md"
|
|
24
|
-
return base / year / month / filename
|
|
25
|
-
else: # "year" (default)
|
|
26
|
-
year = entry_date.strftime("%Y")
|
|
27
|
-
filename = f"{entry_date.strftime('%d-%m-%Y')}-{suffix}.md"
|
|
28
|
-
return base / year / filename
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"""Tests for CLI entry point."""
|
|
2
|
-
|
|
3
|
-
from datetime import date
|
|
4
|
-
|
|
5
|
-
from click.testing import CliRunner
|
|
6
|
-
|
|
7
|
-
from worklog_opsdevnz.cli import main
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_main_creates_file(tmp_path, monkeypatch):
|
|
11
|
-
"""Running with no args creates today's worklog in tmp_path."""
|
|
12
|
-
monkeypatch.chdir(tmp_path)
|
|
13
|
-
runner = CliRunner()
|
|
14
|
-
result = runner.invoke(main, [])
|
|
15
|
-
assert result.exit_code == 0
|
|
16
|
-
assert "Created" in result.output
|
|
17
|
-
expected_leaf = (
|
|
18
|
-
f"docs/worklog/{date.today().year}/{date.today():%d-%m-%Y}-worklog.md"
|
|
19
|
-
)
|
|
20
|
-
assert expected_leaf in result.output
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_main_opens_existing(tmp_path, monkeypatch):
|
|
24
|
-
"""Running again opens the existing file."""
|
|
25
|
-
monkeypatch.chdir(tmp_path)
|
|
26
|
-
runner = CliRunner()
|
|
27
|
-
runner.invoke(main, [])
|
|
28
|
-
result = runner.invoke(main, [])
|
|
29
|
-
assert result.exit_code == 0
|
|
30
|
-
assert "Opening existing" in result.output
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def test_main_help():
|
|
34
|
-
"""--help works."""
|
|
35
|
-
runner = CliRunner()
|
|
36
|
-
result = runner.invoke(main, ["--help"])
|
|
37
|
-
assert result.exit_code == 0
|
|
38
|
-
assert "--editor" in result.output
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_main_version():
|
|
42
|
-
"""--version prints the installed version."""
|
|
43
|
-
runner = CliRunner()
|
|
44
|
-
result = runner.invoke(main, ["--version"])
|
|
45
|
-
assert result.exit_code == 0
|
|
46
|
-
assert "worklog-opsdevnz, version" in result.output
|
|
47
|
-
# Extract and verify it's a non-empty version string
|
|
48
|
-
version_str = result.output.strip().split(", version ")[-1]
|
|
49
|
-
assert version_str
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_main_editor_from_config(tmp_path, monkeypatch):
|
|
53
|
-
"""Editor in worklog.toml is resolved before $VISUAL/$EDITOR."""
|
|
54
|
-
monkeypatch.chdir(tmp_path)
|
|
55
|
-
(tmp_path / "worklog.toml").write_text('editor = "fake-editor-test"\n')
|
|
56
|
-
runner = CliRunner()
|
|
57
|
-
result = runner.invoke(main, [])
|
|
58
|
-
assert result.exit_code == 0
|
|
59
|
-
# config-based editor picked up but not found in PATH
|
|
60
|
-
assert "Editor 'fake-editor-test' not found" in result.output
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def test_main_editor_cli_overrides_config(tmp_path, monkeypatch):
|
|
64
|
-
"""-e flag overrides the editor set in worklog.toml."""
|
|
65
|
-
monkeypatch.chdir(tmp_path)
|
|
66
|
-
(tmp_path / "worklog.toml").write_text('editor = "fake-editor-test"\n')
|
|
67
|
-
runner = CliRunner()
|
|
68
|
-
result = runner.invoke(main, ["-e", "override-editor-test"])
|
|
69
|
-
assert result.exit_code == 0
|
|
70
|
-
# CLI override takes priority over config
|
|
71
|
-
assert "Editor 'override-editor-test' not found" in result.output
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""Tests for path resolution."""
|
|
2
|
-
|
|
3
|
-
from datetime import date
|
|
4
|
-
|
|
5
|
-
from worklog_opsdevnz.paths import resolve_path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_resolve_path_flat():
|
|
9
|
-
config = {"worklog_dir": "docs/worklog", "structure": "flat", "suffix": "worklog"}
|
|
10
|
-
path = resolve_path(config, date(2026, 5, 23))
|
|
11
|
-
assert str(path) == "docs/worklog/2026-05-23-worklog.md"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def test_resolve_path_year():
|
|
15
|
-
config = {"worklog_dir": "logs", "structure": "year", "suffix": "log"}
|
|
16
|
-
path = resolve_path(config, date(2026, 5, 23))
|
|
17
|
-
assert str(path) == "logs/2026/23-05-2026-log.md"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_resolve_path_year_month():
|
|
21
|
-
config = {"worklog_dir": "logs", "structure": "year-month", "suffix": "ops"}
|
|
22
|
-
path = resolve_path(config, date(2026, 5, 23))
|
|
23
|
-
assert str(path) == "logs/2026/05/23-05-2026-ops.md"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_resolve_path_custom_suffix():
|
|
27
|
-
config = {"structure": "year", "suffix": "retro"}
|
|
28
|
-
path = resolve_path(config, date(2026, 1, 1))
|
|
29
|
-
assert str(path).endswith("-retro.md")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/requires.txt
RENAMED
|
File without changes
|
{worklog_opsdevnz-0.2.0 → worklog_opsdevnz-0.2.1}/src/worklog_opsdevnz.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|