checkmarks 0.2.0__tar.gz → 0.3.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.
Files changed (24) hide show
  1. checkmarks-0.3.0/MANIFEST.in +2 -0
  2. {checkmarks-0.2.0/src/checkmarks.egg-info → checkmarks-0.3.0}/PKG-INFO +30 -23
  3. {checkmarks-0.2.0 → checkmarks-0.3.0}/README.md +29 -22
  4. {checkmarks-0.2.0 → checkmarks-0.3.0}/pyproject.toml +1 -1
  5. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks/parse.py +2 -29
  6. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks/render.py +0 -2
  7. {checkmarks-0.2.0 → checkmarks-0.3.0/src/checkmarks.egg-info}/PKG-INFO +30 -23
  8. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks.egg-info/SOURCES.txt +6 -1
  9. checkmarks-0.3.0/tests/fixtures/empty.md +3 -0
  10. checkmarks-0.3.0/tests/fixtures/heading_in_fence.md +6 -0
  11. checkmarks-0.3.0/tests/fixtures/quoted_title.md +6 -0
  12. checkmarks-0.3.0/tests/fixtures/unclosed_frontmatter.md +4 -0
  13. {checkmarks-0.2.0 → checkmarks-0.3.0}/tests/test_cli.py +7 -7
  14. {checkmarks-0.2.0 → checkmarks-0.3.0}/tests/test_parse.py +5 -62
  15. {checkmarks-0.2.0 → checkmarks-0.3.0}/tests/test_render.py +19 -22
  16. {checkmarks-0.2.0 → checkmarks-0.3.0}/LICENSE +0 -0
  17. {checkmarks-0.2.0 → checkmarks-0.3.0}/setup.cfg +0 -0
  18. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks/__init__.py +0 -0
  19. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks/cli.py +0 -0
  20. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks/collect.py +0 -0
  21. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks.egg-info/dependency_links.txt +0 -0
  22. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks.egg-info/entry_points.txt +0 -0
  23. {checkmarks-0.2.0 → checkmarks-0.3.0}/src/checkmarks.egg-info/top_level.txt +0 -0
  24. {checkmarks-0.2.0 → checkmarks-0.3.0}/tests/test_collect.py +0 -0
@@ -0,0 +1,2 @@
1
+ graft tests
2
+ global-exclude __pycache__ *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: checkmarks
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Progress dashboard for Markdown checklists
5
5
  Author-email: menisadi <menisadi@gmail.com>
6
6
  License: MIT License
@@ -39,10 +39,10 @@ A small command-line dashboard for your progress. Point it at a folder of Markdo
39
39
 
40
40
  ```
41
41
  $ checkmarks ~/progress
42
- Thinking, Fast and Slow [████------] 143/320 pages 45%
43
- Gödel, Escher, Bach [██--------] 180/777 pages 23%
44
- Rust tutorial [████------] 12/28 tasks 43%
45
- NN hands-on [██--------] 2/8 tasks 25%
42
+ Thinking, Fast and Slow [████------] 17/38 chapters 45%
43
+ Gödel, Escher, Bach [██--------] 5/22 chapters 23%
44
+ Rust tutorial [████------] 12/28 tasks 43%
45
+ NN hands-on [██--------] 2/8 tasks 25%
46
46
  ```
47
47
 
48
48
  Your Markdown files are the source of truth: checkmarks only reads and reports. It never changes your files and keeps no state of its own.
@@ -96,27 +96,30 @@ By default, checkmarks counts checkboxes:
96
96
  - List items can start with `-`, `*`, `+`, `1.` or `1)`.
97
97
  - Checkboxes inside fenced code blocks are ignored.
98
98
 
99
- ### Counters
99
+ ### Big things: break them down
100
100
 
101
- For things that aren't a list of tasks, like the pages of a book, add frontmatter with a `progress` key. It replaces the checkbox count:
101
+ checkmarks only counts checkboxes. To track a book, split it into chapters or page ranges, each one a task small enough to finish in a sitting:
102
102
 
103
103
  ```markdown
104
104
  ---
105
- progress: 143/320
106
- unit: pages
105
+ unit: chunks
107
106
  ---
108
107
  # Dune
108
+
109
+ - [x] pp. 1–40
110
+ - [x] pp. 41–80
111
+ - [ ] pp. 81–120
112
+ - [ ] pp. 121–160
109
113
  ```
110
114
 
111
- Supported keys:
115
+ Supported frontmatter keys:
112
116
 
113
- | Key | Meaning |
114
- |------------|--------------------------------------------------------------------|
115
- | `progress` | `done/total`, e.g. `143/320` |
116
- | `unit` | Label after the count (default `tasks`, or `units` with `progress`) |
117
- | `title` | Row title |
117
+ | Key | Meaning |
118
+ |---------|------------------------------------------|
119
+ | `unit` | Label after the count (default `tasks`) |
120
+ | `title` | Row title |
118
121
 
119
- `unit` and `title` also work in checklist files. Other keys are ignored.
122
+ Other keys are ignored.
120
123
 
121
124
  ### Titles
122
125
 
@@ -129,7 +132,7 @@ On a terminal, checkmarks prints the aligned table shown above. Rows at 100% are
129
132
  When output is piped, it switches to tab-separated rows with no header:
130
133
 
131
134
  ```
132
- title done total pct unit mode status path
135
+ title done total pct unit status path
133
136
  ```
134
137
 
135
138
  Use `--json` for a JSON array:
@@ -139,18 +142,17 @@ $ checkmarks --json dune.md
139
142
  [
140
143
  {
141
144
  "title": "Dune",
142
- "done": 143,
143
- "total": 320,
144
- "pct": 44.7,
145
- "unit": "pages",
146
- "mode": "counter",
145
+ "done": 9,
146
+ "total": 22,
147
+ "pct": 40.9,
148
+ "unit": "chapters",
147
149
  "status": "ok",
148
150
  "path": "dune.md"
149
151
  }
150
152
  ]
151
153
  ```
152
154
 
153
- `status` is `ok`, `empty` (no checkboxes) or `error` (e.g. an invalid `progress` value). Problems are reported on stderr, and the row is still shown.
155
+ `status` is `ok`, `empty` (no checkboxes) or `error` (the file can't be read). Problems are reported on stderr, and the row is still shown.
154
156
 
155
157
  Empty files found in folders are hidden; pass `--all` to show them. Files you name directly are always shown.
156
158
 
@@ -167,6 +169,11 @@ Empty files found in folders are hidden; pass `--all` to show them. Files you na
167
169
 
168
170
  Exit code is `0` if at least one file has progress to show, `1` if none does, and `2` for usage errors.
169
171
 
172
+ ## Upgrading from 0.2
173
+
174
+ - The `progress: N/M` frontmatter key (counter mode) is gone. Replace it with a checklist of chapters or page ranges.
175
+ - The `mode` column (TSV) and field (JSON) are removed.
176
+
170
177
  ## Upgrading from 0.1
171
178
 
172
179
  - Output is now a table row per file instead of the old "📊 Progress for:" block.
@@ -4,10 +4,10 @@ A small command-line dashboard for your progress. Point it at a folder of Markdo
4
4
 
5
5
  ```
6
6
  $ checkmarks ~/progress
7
- Thinking, Fast and Slow [████------] 143/320 pages 45%
8
- Gödel, Escher, Bach [██--------] 180/777 pages 23%
9
- Rust tutorial [████------] 12/28 tasks 43%
10
- NN hands-on [██--------] 2/8 tasks 25%
7
+ Thinking, Fast and Slow [████------] 17/38 chapters 45%
8
+ Gödel, Escher, Bach [██--------] 5/22 chapters 23%
9
+ Rust tutorial [████------] 12/28 tasks 43%
10
+ NN hands-on [██--------] 2/8 tasks 25%
11
11
  ```
12
12
 
13
13
  Your Markdown files are the source of truth: checkmarks only reads and reports. It never changes your files and keeps no state of its own.
@@ -61,27 +61,30 @@ By default, checkmarks counts checkboxes:
61
61
  - List items can start with `-`, `*`, `+`, `1.` or `1)`.
62
62
  - Checkboxes inside fenced code blocks are ignored.
63
63
 
64
- ### Counters
64
+ ### Big things: break them down
65
65
 
66
- For things that aren't a list of tasks, like the pages of a book, add frontmatter with a `progress` key. It replaces the checkbox count:
66
+ checkmarks only counts checkboxes. To track a book, split it into chapters or page ranges, each one a task small enough to finish in a sitting:
67
67
 
68
68
  ```markdown
69
69
  ---
70
- progress: 143/320
71
- unit: pages
70
+ unit: chunks
72
71
  ---
73
72
  # Dune
73
+
74
+ - [x] pp. 1–40
75
+ - [x] pp. 41–80
76
+ - [ ] pp. 81–120
77
+ - [ ] pp. 121–160
74
78
  ```
75
79
 
76
- Supported keys:
80
+ Supported frontmatter keys:
77
81
 
78
- | Key | Meaning |
79
- |------------|--------------------------------------------------------------------|
80
- | `progress` | `done/total`, e.g. `143/320` |
81
- | `unit` | Label after the count (default `tasks`, or `units` with `progress`) |
82
- | `title` | Row title |
82
+ | Key | Meaning |
83
+ |---------|------------------------------------------|
84
+ | `unit` | Label after the count (default `tasks`) |
85
+ | `title` | Row title |
83
86
 
84
- `unit` and `title` also work in checklist files. Other keys are ignored.
87
+ Other keys are ignored.
85
88
 
86
89
  ### Titles
87
90
 
@@ -94,7 +97,7 @@ On a terminal, checkmarks prints the aligned table shown above. Rows at 100% are
94
97
  When output is piped, it switches to tab-separated rows with no header:
95
98
 
96
99
  ```
97
- title done total pct unit mode status path
100
+ title done total pct unit status path
98
101
  ```
99
102
 
100
103
  Use `--json` for a JSON array:
@@ -104,18 +107,17 @@ $ checkmarks --json dune.md
104
107
  [
105
108
  {
106
109
  "title": "Dune",
107
- "done": 143,
108
- "total": 320,
109
- "pct": 44.7,
110
- "unit": "pages",
111
- "mode": "counter",
110
+ "done": 9,
111
+ "total": 22,
112
+ "pct": 40.9,
113
+ "unit": "chapters",
112
114
  "status": "ok",
113
115
  "path": "dune.md"
114
116
  }
115
117
  ]
116
118
  ```
117
119
 
118
- `status` is `ok`, `empty` (no checkboxes) or `error` (e.g. an invalid `progress` value). Problems are reported on stderr, and the row is still shown.
120
+ `status` is `ok`, `empty` (no checkboxes) or `error` (the file can't be read). Problems are reported on stderr, and the row is still shown.
119
121
 
120
122
  Empty files found in folders are hidden; pass `--all` to show them. Files you name directly are always shown.
121
123
 
@@ -132,6 +134,11 @@ Empty files found in folders are hidden; pass `--all` to show them. Files you na
132
134
 
133
135
  Exit code is `0` if at least one file has progress to show, `1` if none does, and `2` for usage errors.
134
136
 
137
+ ## Upgrading from 0.2
138
+
139
+ - The `progress: N/M` frontmatter key (counter mode) is gone. Replace it with a checklist of chapters or page ranges.
140
+ - The `mode` column (TSV) and field (JSON) are removed.
141
+
135
142
  ## Upgrading from 0.1
136
143
 
137
144
  - Output is now a table row per file instead of the old "📊 Progress for:" block.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "checkmarks"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Progress dashboard for Markdown checklists"
5
5
  authors = [
6
6
  { name = "menisadi", email = "menisadi@gmail.com" }
@@ -7,7 +7,6 @@ from typing import Literal
7
7
  CHECKBOX_RE = re.compile(r"^\s*(?:[-*+]|\d+[.)])\s+\[( |x|X)\](?:\s|$)")
8
8
  FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})")
9
9
  HEADING_RE = re.compile(r"^#\s+(.+)")
10
- PROGRESS_RE = re.compile(r"^(\d+)\s*/\s*(\d+)$")
11
10
 
12
11
 
13
12
  @dataclass
@@ -17,7 +16,6 @@ class Item:
17
16
  done: int | None
18
17
  total: int | None
19
18
  unit: str
20
- mode: Literal["checklist", "counter"]
21
19
  status: Literal["ok", "empty", "error"]
22
20
  message: str | None = None
23
21
 
@@ -89,20 +87,6 @@ def parse_frontmatter(lines: list[str]) -> tuple[dict[str, str], int]:
89
87
  return meta, end + 1
90
88
 
91
89
 
92
- def parse_progress(value: str) -> tuple[int, int] | str:
93
- """Returns (done, total), or an error message if the value is invalid."""
94
- progress_match = PROGRESS_RE.match(value)
95
- if not progress_match:
96
- return f"unparseable progress: {value!r}"
97
-
98
- done, total = int(progress_match.group(1)), int(progress_match.group(2))
99
- if total == 0:
100
- return "progress total is 0"
101
- if done > total:
102
- return f"progress {done}/{total} exceeds total"
103
- return done, total
104
-
105
-
106
90
  def parse_file(path: Path) -> Item:
107
91
  """Parses a Markdown file into an Item."""
108
92
  try:
@@ -114,7 +98,6 @@ def parse_file(path: Path) -> Item:
114
98
  done=None,
115
99
  total=None,
116
100
  unit="tasks",
117
- mode="checklist",
118
101
  status="error",
119
102
  message=str(e),
120
103
  )
@@ -124,18 +107,8 @@ def parse_file(path: Path) -> Item:
124
107
  body = lines[body_start:]
125
108
  title = meta.get("title") or find_heading(body) or path.stem
126
109
 
127
- if "progress" in meta:
128
- unit = meta.get("unit") or "units"
129
- progress = parse_progress(meta["progress"])
130
- if isinstance(progress, str):
131
- return Item(path, title, None, None, unit, "counter", "error", progress)
132
- done, total = progress
133
- return Item(path, title, done, total, unit, "counter", "ok")
134
-
135
110
  unit = meta.get("unit") or "tasks"
136
111
  done, total = count_checkboxes("\n".join(body))
137
112
  if total == 0:
138
- return Item(
139
- path, title, None, None, unit, "checklist", "empty", "no checkboxes found"
140
- )
141
- return Item(path, title, done, total, unit, "checklist", "ok")
113
+ return Item(path, title, None, None, unit, "empty", "no checkboxes found")
114
+ return Item(path, title, done, total, unit, "ok")
@@ -33,7 +33,6 @@ def render_tsv(items: list[Item]) -> str:
33
33
  str(item.total) if ok else "",
34
34
  f"{p:.1f}" if ok else "",
35
35
  clean(item.unit),
36
- item.mode,
37
36
  item.status,
38
37
  str(item.path),
39
38
  ]
@@ -53,7 +52,6 @@ def render_json(items: list[Item]) -> str:
53
52
  "total": item.total if ok else None,
54
53
  "pct": round(p, 1) if p is not None else None,
55
54
  "unit": item.unit,
56
- "mode": item.mode,
57
55
  "status": item.status,
58
56
  "path": str(item.path),
59
57
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: checkmarks
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Progress dashboard for Markdown checklists
5
5
  Author-email: menisadi <menisadi@gmail.com>
6
6
  License: MIT License
@@ -39,10 +39,10 @@ A small command-line dashboard for your progress. Point it at a folder of Markdo
39
39
 
40
40
  ```
41
41
  $ checkmarks ~/progress
42
- Thinking, Fast and Slow [████------] 143/320 pages 45%
43
- Gödel, Escher, Bach [██--------] 180/777 pages 23%
44
- Rust tutorial [████------] 12/28 tasks 43%
45
- NN hands-on [██--------] 2/8 tasks 25%
42
+ Thinking, Fast and Slow [████------] 17/38 chapters 45%
43
+ Gödel, Escher, Bach [██--------] 5/22 chapters 23%
44
+ Rust tutorial [████------] 12/28 tasks 43%
45
+ NN hands-on [██--------] 2/8 tasks 25%
46
46
  ```
47
47
 
48
48
  Your Markdown files are the source of truth: checkmarks only reads and reports. It never changes your files and keeps no state of its own.
@@ -96,27 +96,30 @@ By default, checkmarks counts checkboxes:
96
96
  - List items can start with `-`, `*`, `+`, `1.` or `1)`.
97
97
  - Checkboxes inside fenced code blocks are ignored.
98
98
 
99
- ### Counters
99
+ ### Big things: break them down
100
100
 
101
- For things that aren't a list of tasks, like the pages of a book, add frontmatter with a `progress` key. It replaces the checkbox count:
101
+ checkmarks only counts checkboxes. To track a book, split it into chapters or page ranges, each one a task small enough to finish in a sitting:
102
102
 
103
103
  ```markdown
104
104
  ---
105
- progress: 143/320
106
- unit: pages
105
+ unit: chunks
107
106
  ---
108
107
  # Dune
108
+
109
+ - [x] pp. 1–40
110
+ - [x] pp. 41–80
111
+ - [ ] pp. 81–120
112
+ - [ ] pp. 121–160
109
113
  ```
110
114
 
111
- Supported keys:
115
+ Supported frontmatter keys:
112
116
 
113
- | Key | Meaning |
114
- |------------|--------------------------------------------------------------------|
115
- | `progress` | `done/total`, e.g. `143/320` |
116
- | `unit` | Label after the count (default `tasks`, or `units` with `progress`) |
117
- | `title` | Row title |
117
+ | Key | Meaning |
118
+ |---------|------------------------------------------|
119
+ | `unit` | Label after the count (default `tasks`) |
120
+ | `title` | Row title |
118
121
 
119
- `unit` and `title` also work in checklist files. Other keys are ignored.
122
+ Other keys are ignored.
120
123
 
121
124
  ### Titles
122
125
 
@@ -129,7 +132,7 @@ On a terminal, checkmarks prints the aligned table shown above. Rows at 100% are
129
132
  When output is piped, it switches to tab-separated rows with no header:
130
133
 
131
134
  ```
132
- title done total pct unit mode status path
135
+ title done total pct unit status path
133
136
  ```
134
137
 
135
138
  Use `--json` for a JSON array:
@@ -139,18 +142,17 @@ $ checkmarks --json dune.md
139
142
  [
140
143
  {
141
144
  "title": "Dune",
142
- "done": 143,
143
- "total": 320,
144
- "pct": 44.7,
145
- "unit": "pages",
146
- "mode": "counter",
145
+ "done": 9,
146
+ "total": 22,
147
+ "pct": 40.9,
148
+ "unit": "chapters",
147
149
  "status": "ok",
148
150
  "path": "dune.md"
149
151
  }
150
152
  ]
151
153
  ```
152
154
 
153
- `status` is `ok`, `empty` (no checkboxes) or `error` (e.g. an invalid `progress` value). Problems are reported on stderr, and the row is still shown.
155
+ `status` is `ok`, `empty` (no checkboxes) or `error` (the file can't be read). Problems are reported on stderr, and the row is still shown.
154
156
 
155
157
  Empty files found in folders are hidden; pass `--all` to show them. Files you name directly are always shown.
156
158
 
@@ -167,6 +169,11 @@ Empty files found in folders are hidden; pass `--all` to show them. Files you na
167
169
 
168
170
  Exit code is `0` if at least one file has progress to show, `1` if none does, and `2` for usage errors.
169
171
 
172
+ ## Upgrading from 0.2
173
+
174
+ - The `progress: N/M` frontmatter key (counter mode) is gone. Replace it with a checklist of chapters or page ranges.
175
+ - The `mode` column (TSV) and field (JSON) are removed.
176
+
170
177
  ## Upgrading from 0.1
171
178
 
172
179
  - Output is now a table row per file instead of the old "📊 Progress for:" block.
@@ -1,4 +1,5 @@
1
1
  LICENSE
2
+ MANIFEST.in
2
3
  README.md
3
4
  pyproject.toml
4
5
  src/checkmarks/__init__.py
@@ -14,4 +15,8 @@ src/checkmarks.egg-info/top_level.txt
14
15
  tests/test_cli.py
15
16
  tests/test_collect.py
16
17
  tests/test_parse.py
17
- tests/test_render.py
18
+ tests/test_render.py
19
+ tests/fixtures/empty.md
20
+ tests/fixtures/heading_in_fence.md
21
+ tests/fixtures/quoted_title.md
22
+ tests/fixtures/unclosed_frontmatter.md
@@ -0,0 +1,3 @@
1
+ # Notes
2
+
3
+ Just prose, no checkboxes.
@@ -0,0 +1,6 @@
1
+ ```bash
2
+ # not a title
3
+ - [x] in code
4
+ ```
5
+ # Real title
6
+ - [ ] a
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: "Dune: Part One"
3
+ unit: 'chapters'
4
+ ---
5
+ # Ignored heading
6
+ - [x] a
@@ -0,0 +1,4 @@
1
+ ---
2
+ title: Never closed
3
+ - [x] a
4
+ - [ ] b
@@ -12,10 +12,10 @@ def tree(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
12
12
  """A progress folder, used as the current directory."""
13
13
  (tmp_path / "a.md").write_text("# Alpha\n- [x] a\n- [ ] b\n", encoding="utf-8")
14
14
  (tmp_path / "b.md").write_text(
15
- "---\nprogress: 5/5\n---\n# Beta\n", encoding="utf-8"
15
+ "---\nunit: chapters\n---\n# Beta\n- [x] a\n", encoding="utf-8"
16
16
  )
17
17
  (tmp_path / "notes.md").write_text("# Notes\nprose\n", encoding="utf-8")
18
- (tmp_path / "bad.md").write_text("---\nprogress: x\n---\n", encoding="utf-8")
18
+ (tmp_path / "bad.md").write_bytes(b"- [x] caf\xe9\n")
19
19
  monkeypatch.chdir(tmp_path)
20
20
  monkeypatch.delenv("CHECKMARKS_DIR", raising=False)
21
21
  monkeypatch.delenv("NO_COLOR", raising=False)
@@ -31,11 +31,11 @@ def test_default_is_tsv_when_piped(tree: Path, capsys: pytest.CaptureFixture) ->
31
31
  assert main([]) == 0
32
32
  out, err = capsys.readouterr()
33
33
  assert out.splitlines() == [
34
- "Alpha\t1\t2\t50.0\ttasks\tchecklist\tok\ta.md",
35
- "Beta\t5\t5\t100.0\tunits\tcounter\tok\tb.md",
36
- "bad\t\t\t\tunits\tcounter\terror\tbad.md",
34
+ "Alpha\t1\t2\t50.0\ttasks\tok\ta.md",
35
+ "Beta\t1\t1\t100.0\tchapters\tok\tb.md",
36
+ "bad\t\t\t\ttasks\terror\tbad.md",
37
37
  ]
38
- assert err == "checkmarks: bad.md: unparseable progress: 'x'\n"
38
+ assert err.startswith("checkmarks: bad.md: ")
39
39
 
40
40
 
41
41
  def test_default_is_pretty_on_tty(
@@ -86,7 +86,7 @@ def test_directory_hides_empty_unless_all(
86
86
  main(["."])
87
87
  assert "Notes" not in capsys.readouterr().out
88
88
  main([".", "--all"])
89
- assert "Notes\t\t\t\ttasks\tchecklist\tempty\tnotes.md" in capsys.readouterr().out
89
+ assert "Notes\t\t\t\ttasks\tempty\tnotes.md" in capsys.readouterr().out
90
90
 
91
91
 
92
92
  def test_explicit_empty_file_shown(tree: Path, capsys: pytest.CaptureFixture) -> None:
@@ -3,12 +3,10 @@ from pathlib import Path
3
3
  import pytest
4
4
 
5
5
  from checkmarks.parse import (
6
- Item,
7
6
  count_checkboxes,
8
7
  find_heading,
9
8
  parse_file,
10
9
  parse_frontmatter,
11
- parse_progress,
12
10
  )
13
11
 
14
12
 
@@ -93,8 +91,8 @@ FIXTURES = Path(__file__).parent / "fixtures"
93
91
 
94
92
 
95
93
  def test_parse_frontmatter_basic() -> None:
96
- meta, body_start = parse_frontmatter(["---", "progress: 1/2", "---", "# T"])
97
- assert meta == {"progress": "1/2"}
94
+ meta, body_start = parse_frontmatter(["---", "unit: pages", "---", "# T"])
95
+ assert meta == {"unit": "pages"}
98
96
  assert body_start == 3
99
97
 
100
98
 
@@ -124,44 +122,11 @@ def test_parse_frontmatter_quotes(value: str, expected: str) -> None:
124
122
  assert meta["title"] == expected
125
123
 
126
124
 
127
- @pytest.mark.parametrize(
128
- "value,expected",
129
- [
130
- ("143/320", (143, 320)),
131
- ("143 / 320", (143, 320)),
132
- ("0/5", (0, 5)),
133
- ("5/5", (5, 5)),
134
- ],
135
- )
136
- def test_parse_progress_valid(value: str, expected: tuple[int, int]) -> None:
137
- assert parse_progress(value) == expected
138
-
139
-
140
- @pytest.mark.parametrize("value", ["", "half", "1/", "/2", "-1/5", "1.5/3", "1/2/3"])
141
- def test_parse_progress_unparseable(value: str) -> None:
142
- assert isinstance(parse_progress(value), str)
143
-
144
-
145
- def test_parse_progress_zero_total() -> None:
146
- assert parse_progress("0/0") == "progress total is 0"
147
-
148
-
149
- def test_parse_progress_done_exceeds_total() -> None:
150
- assert parse_progress("6/5") == "progress 6/5 exceeds total"
151
-
152
-
153
125
  def test_find_heading_skips_fences() -> None:
154
126
  assert find_heading(["```", "# comment", "```", "# Real"]) == "Real"
155
127
  assert find_heading(["## Sub", "no heading"]) is None
156
128
 
157
129
 
158
- def test_file_counter_mode() -> None:
159
- item = parse_file(FIXTURES / "counter.md")
160
- assert item == Item(
161
- FIXTURES / "counter.md", "Dune", 143, 320, "pages", "counter", "ok"
162
- )
163
-
164
-
165
130
  def test_file_bom_and_crlf(tmp_path: Path) -> None:
166
131
  # Written here, not as a fixture, so git's autocrlf can't strip the CRLFs
167
132
  md_file = tmp_path / "bom_crlf.md"
@@ -175,33 +140,18 @@ def test_file_bom_and_crlf(tmp_path: Path) -> None:
175
140
  def test_file_unclosed_frontmatter_is_markdown() -> None:
176
141
  item = parse_file(FIXTURES / "unclosed_frontmatter.md")
177
142
  assert item.title == "unclosed_frontmatter"
178
- assert (item.done, item.total, item.mode) == (1, 2, "checklist")
143
+ assert (item.done, item.total) == (1, 2)
179
144
 
180
145
 
181
146
  def test_file_frontmatter_title_wins_over_heading() -> None:
182
147
  item = parse_file(FIXTURES / "quoted_title.md")
183
148
  assert item.title == "Dune: Part One"
184
149
  assert item.unit == "chapters"
185
- assert item.mode == "checklist"
186
-
187
-
188
- def test_file_bad_progress_is_error() -> None:
189
- item = parse_file(FIXTURES / "bad_progress.md")
190
- assert (item.mode, item.status) == ("counter", "error")
191
- assert (item.done, item.total) == (None, None)
192
- assert item.title == "Bad progress"
193
- assert item.unit == "units"
194
- assert item.message is not None
195
150
 
196
151
 
197
152
  def test_file_without_checkboxes_is_empty() -> None:
198
153
  item = parse_file(FIXTURES / "empty.md")
199
- assert (item.mode, item.status, item.done, item.total) == (
200
- "checklist",
201
- "empty",
202
- None,
203
- None,
204
- )
154
+ assert (item.status, item.done, item.total) == ("empty", None, None)
205
155
  assert item.unit == "tasks"
206
156
 
207
157
 
@@ -213,7 +163,7 @@ def test_file_heading_inside_fence_is_not_title() -> None:
213
163
 
214
164
  def test_file_missing_is_error(tmp_path: Path) -> None:
215
165
  item = parse_file(tmp_path / "missing.md")
216
- assert (item.mode, item.status, item.title) == ("checklist", "error", "missing")
166
+ assert (item.status, item.title) == ("error", "missing")
217
167
 
218
168
 
219
169
  def test_file_invalid_utf8_is_error(tmp_path: Path) -> None:
@@ -221,10 +171,3 @@ def test_file_invalid_utf8_is_error(tmp_path: Path) -> None:
221
171
  md_file.write_bytes(b"- [x] caf\xe9\n")
222
172
  item = parse_file(md_file)
223
173
  assert item.status == "error"
224
-
225
-
226
- def test_file_empty_progress_value_is_error(tmp_path: Path) -> None:
227
- md_file = tmp_path / "p.md"
228
- md_file.write_text("---\nprogress:\n---\n- [x] a\n", encoding="utf-8")
229
- item = parse_file(md_file)
230
- assert (item.mode, item.status) == ("counter", "error")
@@ -14,11 +14,9 @@ from checkmarks.render import (
14
14
  truncate,
15
15
  )
16
16
 
17
- OK = Item(Path("books/dune.md"), "Dune", 143, 320, "pages", "counter", "ok")
18
- EMPTY = Item(
19
- Path("notes.md"), "Notes", None, None, "tasks", "checklist", "empty", "no boxes"
20
- )
21
- ERROR = Item(Path("bad.md"), "Bad", None, None, "units", "counter", "error", "oops")
17
+ OK = Item(Path("books/dune.md"), "Dune", 143, 320, "pages", "ok")
18
+ EMPTY = Item(Path("notes.md"), "Notes", None, None, "tasks", "empty", "no boxes")
19
+ ERROR = Item(Path("bad.md"), "Bad", None, None, "tasks", "error", "oops")
22
20
 
23
21
 
24
22
  def bar_of(line: str) -> str:
@@ -30,15 +28,15 @@ def bar_of(line: str) -> str:
30
28
 
31
29
  def test_tsv_rows() -> None:
32
30
  assert render_tsv([OK, EMPTY, ERROR]).splitlines() == [
33
- "Dune\t143\t320\t44.7\tpages\tcounter\tok\tbooks/dune.md",
34
- "Notes\t\t\t\ttasks\tchecklist\tempty\tnotes.md",
35
- "Bad\t\t\t\tunits\tcounter\terror\tbad.md",
31
+ "Dune\t143\t320\t44.7\tpages\tok\tbooks/dune.md",
32
+ "Notes\t\t\t\ttasks\tempty\tnotes.md",
33
+ "Bad\t\t\t\ttasks\terror\tbad.md",
36
34
  ]
37
35
 
38
36
 
39
37
  def test_tsv_cleans_title_and_unit() -> None:
40
- item = Item(Path("a.md"), "A\tB\nC\r", 1, 2, "x\ty", "checklist", "ok")
41
- assert render_tsv([item]) == "A B C \t1\t2\t50.0\tx y\tchecklist\tok\ta.md\n"
38
+ item = Item(Path("a.md"), "A\tB\nC\r", 1, 2, "x\ty", "ok")
39
+ assert render_tsv([item]) == "A B C \t1\t2\t50.0\tx y\tok\ta.md\n"
42
40
 
43
41
 
44
42
  def test_tsv_empty_list() -> None:
@@ -56,7 +54,6 @@ def test_json_rows() -> None:
56
54
  "total": 320,
57
55
  "pct": 44.7,
58
56
  "unit": "pages",
59
- "mode": "counter",
60
57
  "status": "ok",
61
58
  "path": "books/dune.md",
62
59
  }
@@ -66,7 +63,7 @@ def test_json_rows() -> None:
66
63
 
67
64
 
68
65
  def test_json_keeps_unicode() -> None:
69
- item = Item(Path("g.md"), "Gödel", 1, 2, "pages", "counter", "ok")
66
+ item = Item(Path("g.md"), "Gödel", 1, 2, "pages", "ok")
70
67
  assert "Gödel" in render_json([item])
71
68
 
72
69
 
@@ -106,11 +103,11 @@ def test_pretty_pct(done: int, total: int, expected: int) -> None:
106
103
 
107
104
  def test_pretty_spec_example() -> None:
108
105
  items = [
109
- Item(Path("a"), "Thinking, Fast and Slow", 143, 320, "pages", "counter", "ok"),
110
- Item(Path("b"), "Gödel, Escher, Bach", 180, 777, "pages", "counter", "ok"),
111
- Item(Path("c"), "Rust tutorial", 12, 28, "tasks", "checklist", "ok"),
112
- Item(Path("d"), "NN hands-on", 2, 8, "tasks", "checklist", "ok"),
113
- Item(Path("e"), "Broken file", None, None, "units", "counter", "error"),
106
+ Item(Path("a"), "Thinking, Fast and Slow", 143, 320, "pages", "ok"),
107
+ Item(Path("b"), "Gödel, Escher, Bach", 180, 777, "pages", "ok"),
108
+ Item(Path("c"), "Rust tutorial", 12, 28, "tasks", "ok"),
109
+ Item(Path("d"), "NN hands-on", 2, 8, "tasks", "ok"),
110
+ Item(Path("e"), "Broken file", None, None, "tasks", "error"),
114
111
  ]
115
112
  assert render_pretty(items, 50, False).splitlines() == [
116
113
  "Thinking, Fast… [████------] 143/320 pages 45%",
@@ -126,7 +123,7 @@ def test_pretty_lines_fit_width() -> None:
126
123
  OK,
127
124
  EMPTY,
128
125
  ERROR,
129
- Item(Path("w"), "日本語の本" * 5, 1, 2, "p", "counter", "ok"),
126
+ Item(Path("w"), "日本語の本" * 5, 1, 2, "p", "ok"),
130
127
  ]
131
128
  for width in (50, 60, 80):
132
129
  lines = render_pretty(items, width, False).splitlines()
@@ -134,7 +131,7 @@ def test_pretty_lines_fit_width() -> None:
134
131
 
135
132
 
136
133
  def test_pretty_columns_align() -> None:
137
- items = [OK, EMPTY, ERROR, Item(Path("w"), "日本", 1, 2, "p", "counter", "ok")]
134
+ items = [OK, EMPTY, ERROR, Item(Path("w"), "日本", 1, 2, "p", "ok")]
138
135
  lines = render_pretty(items, 80, False).splitlines()
139
136
  starts = {display_width(line[: line.index("[")]) for line in lines}
140
137
  widths = {display_width(line) for line in lines}
@@ -153,7 +150,7 @@ def test_pretty_bar_min_width() -> None:
153
150
 
154
151
 
155
152
  def test_pretty_bar_is_floored() -> None:
156
- item = Item(Path("a"), "A", 319, 320, "pages", "counter", "ok")
153
+ item = Item(Path("a"), "A", 319, 320, "pages", "ok")
157
154
  line = render_pretty([item], 200, False).splitlines()[0]
158
155
  assert bar_of(line) == "█" * 39 + "-"
159
156
  assert line.endswith(" 99%")
@@ -173,14 +170,14 @@ def test_pretty_only_non_ok_rows() -> None:
173
170
 
174
171
 
175
172
  def test_pretty_color_only_when_complete() -> None:
176
- complete = Item(Path("a"), "A", 5, 5, "tasks", "checklist", "ok")
173
+ complete = Item(Path("a"), "A", 5, 5, "tasks", "ok")
177
174
  out = render_pretty([OK, complete, ERROR], 60, True).splitlines()
178
175
  assert out[1].startswith(GREEN)
179
176
  assert "\033[" not in out[0] + out[2]
180
177
 
181
178
 
182
179
  def test_pretty_no_color() -> None:
183
- complete = Item(Path("a"), "A", 5, 5, "tasks", "checklist", "ok")
180
+ complete = Item(Path("a"), "A", 5, 5, "tasks", "ok")
184
181
  assert "\033[" not in render_pretty([complete], 60, False)
185
182
 
186
183
 
File without changes
File without changes