checkmarks 0.2.1__tar.gz → 0.4.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.
- {checkmarks-0.2.1/src/checkmarks.egg-info → checkmarks-0.4.0}/PKG-INFO +33 -47
- {checkmarks-0.2.1 → checkmarks-0.4.0}/README.md +31 -24
- {checkmarks-0.2.1 → checkmarks-0.4.0}/pyproject.toml +4 -3
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks/cli.py +3 -1
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks/collect.py +10 -7
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks/parse.py +2 -29
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks/render.py +0 -2
- {checkmarks-0.2.1 → checkmarks-0.4.0/src/checkmarks.egg-info}/PKG-INFO +33 -47
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks.egg-info/SOURCES.txt +0 -2
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/test_cli.py +20 -7
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/test_collect.py +8 -1
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/test_parse.py +5 -62
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/test_render.py +19 -22
- checkmarks-0.2.1/tests/fixtures/bad_progress.md +0 -4
- checkmarks-0.2.1/tests/fixtures/counter.md +0 -7
- {checkmarks-0.2.1 → checkmarks-0.4.0}/LICENSE +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/MANIFEST.in +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/setup.cfg +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks/__init__.py +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks.egg-info/dependency_links.txt +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks.egg-info/entry_points.txt +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/src/checkmarks.egg-info/top_level.txt +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/fixtures/empty.md +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/fixtures/heading_in_fence.md +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/fixtures/quoted_title.md +0 -0
- {checkmarks-0.2.1 → checkmarks-0.4.0}/tests/fixtures/unclosed_frontmatter.md +0 -0
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: checkmarks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Progress dashboard for Markdown checklists
|
|
5
5
|
Author-email: menisadi <menisadi@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2025 Meni
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Project-URL: Homepage, https://github.com/menisadi/checkmarks
|
|
29
8
|
Project-URL: Issues, https://github.com/menisadi/checkmarks/issues
|
|
30
9
|
Keywords: cli,markdown,checklists
|
|
@@ -39,10 +18,10 @@ A small command-line dashboard for your progress. Point it at a folder of Markdo
|
|
|
39
18
|
|
|
40
19
|
```
|
|
41
20
|
$ checkmarks ~/progress
|
|
42
|
-
Thinking, Fast and Slow [████------]
|
|
43
|
-
Gödel, Escher, Bach [██--------]
|
|
44
|
-
Rust tutorial [████------]
|
|
45
|
-
NN hands-on [██--------]
|
|
21
|
+
Thinking, Fast and Slow [████------] 17/38 chapters 45%
|
|
22
|
+
Gödel, Escher, Bach [██--------] 5/22 chapters 23%
|
|
23
|
+
Rust tutorial [████------] 12/28 tasks 43%
|
|
24
|
+
NN hands-on [██--------] 2/8 tasks 25%
|
|
46
25
|
```
|
|
47
26
|
|
|
48
27
|
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 +75,30 @@ By default, checkmarks counts checkboxes:
|
|
|
96
75
|
- List items can start with `-`, `*`, `+`, `1.` or `1)`.
|
|
97
76
|
- Checkboxes inside fenced code blocks are ignored.
|
|
98
77
|
|
|
99
|
-
###
|
|
78
|
+
### Big things: break them down
|
|
100
79
|
|
|
101
|
-
|
|
80
|
+
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
81
|
|
|
103
82
|
```markdown
|
|
104
83
|
---
|
|
105
|
-
|
|
106
|
-
unit: pages
|
|
84
|
+
unit: chunks
|
|
107
85
|
---
|
|
108
86
|
# Dune
|
|
87
|
+
|
|
88
|
+
- [x] pp. 1–40
|
|
89
|
+
- [x] pp. 41–80
|
|
90
|
+
- [ ] pp. 81–120
|
|
91
|
+
- [ ] pp. 121–160
|
|
109
92
|
```
|
|
110
93
|
|
|
111
|
-
Supported keys:
|
|
94
|
+
Supported frontmatter keys:
|
|
112
95
|
|
|
113
|
-
| Key
|
|
114
|
-
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `title` | Row title |
|
|
96
|
+
| Key | Meaning |
|
|
97
|
+
|---------|------------------------------------------|
|
|
98
|
+
| `unit` | Label after the count (default `tasks`) |
|
|
99
|
+
| `title` | Row title |
|
|
118
100
|
|
|
119
|
-
|
|
101
|
+
Other keys are ignored.
|
|
120
102
|
|
|
121
103
|
### Titles
|
|
122
104
|
|
|
@@ -129,7 +111,7 @@ On a terminal, checkmarks prints the aligned table shown above. Rows at 100% are
|
|
|
129
111
|
When output is piped, it switches to tab-separated rows with no header:
|
|
130
112
|
|
|
131
113
|
```
|
|
132
|
-
title done total pct unit
|
|
114
|
+
title done total pct unit status path
|
|
133
115
|
```
|
|
134
116
|
|
|
135
117
|
Use `--json` for a JSON array:
|
|
@@ -139,27 +121,26 @@ $ checkmarks --json dune.md
|
|
|
139
121
|
[
|
|
140
122
|
{
|
|
141
123
|
"title": "Dune",
|
|
142
|
-
"done":
|
|
143
|
-
"total":
|
|
144
|
-
"pct":
|
|
145
|
-
"unit": "
|
|
146
|
-
"mode": "counter",
|
|
124
|
+
"done": 9,
|
|
125
|
+
"total": 22,
|
|
126
|
+
"pct": 40.9,
|
|
127
|
+
"unit": "chapters",
|
|
147
128
|
"status": "ok",
|
|
148
129
|
"path": "dune.md"
|
|
149
130
|
}
|
|
150
131
|
]
|
|
151
132
|
```
|
|
152
133
|
|
|
153
|
-
`status` is `ok`, `empty` (no checkboxes) or `error` (
|
|
134
|
+
`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
135
|
|
|
155
|
-
Empty files found in folders are hidden; pass `--all` to show them. Files you name
|
|
136
|
+
Empty files found in folders or read from stdin are hidden; pass `--all` to show them. Files you name as arguments are always shown.
|
|
156
137
|
|
|
157
138
|
## Options
|
|
158
139
|
|
|
159
140
|
| Option | Description |
|
|
160
141
|
|------------------------------|---------------------------------------------|
|
|
161
142
|
| `-r`, `--recursive` | Descend into subfolders |
|
|
162
|
-
| `--all` | Show empty files
|
|
143
|
+
| `--all` | Show empty files from folders or stdin |
|
|
163
144
|
| `--format {pretty,tsv,json}` | Choose the output format |
|
|
164
145
|
| `--json` | Same as `--format json` |
|
|
165
146
|
| `--no-color` | Disable colour |
|
|
@@ -167,6 +148,11 @@ Empty files found in folders are hidden; pass `--all` to show them. Files you na
|
|
|
167
148
|
|
|
168
149
|
Exit code is `0` if at least one file has progress to show, `1` if none does, and `2` for usage errors.
|
|
169
150
|
|
|
151
|
+
## Upgrading from 0.2
|
|
152
|
+
|
|
153
|
+
- The `progress: N/M` frontmatter key (counter mode) is gone. Replace it with a checklist of chapters or page ranges.
|
|
154
|
+
- The `mode` column (TSV) and field (JSON) are removed.
|
|
155
|
+
|
|
170
156
|
## Upgrading from 0.1
|
|
171
157
|
|
|
172
158
|
- 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 [████------]
|
|
8
|
-
Gödel, Escher, Bach [██--------]
|
|
9
|
-
Rust tutorial [████------]
|
|
10
|
-
NN hands-on [██--------]
|
|
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
|
-
###
|
|
64
|
+
### Big things: break them down
|
|
65
65
|
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `title` | Row title |
|
|
82
|
+
| Key | Meaning |
|
|
83
|
+
|---------|------------------------------------------|
|
|
84
|
+
| `unit` | Label after the count (default `tasks`) |
|
|
85
|
+
| `title` | Row title |
|
|
83
86
|
|
|
84
|
-
|
|
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
|
|
100
|
+
title done total pct unit status path
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
Use `--json` for a JSON array:
|
|
@@ -104,27 +107,26 @@ $ checkmarks --json dune.md
|
|
|
104
107
|
[
|
|
105
108
|
{
|
|
106
109
|
"title": "Dune",
|
|
107
|
-
"done":
|
|
108
|
-
"total":
|
|
109
|
-
"pct":
|
|
110
|
-
"unit": "
|
|
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` (
|
|
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
|
-
Empty files found in folders are hidden; pass `--all` to show them. Files you name
|
|
122
|
+
Empty files found in folders or read from stdin are hidden; pass `--all` to show them. Files you name as arguments are always shown.
|
|
121
123
|
|
|
122
124
|
## Options
|
|
123
125
|
|
|
124
126
|
| Option | Description |
|
|
125
127
|
|------------------------------|---------------------------------------------|
|
|
126
128
|
| `-r`, `--recursive` | Descend into subfolders |
|
|
127
|
-
| `--all` | Show empty files
|
|
129
|
+
| `--all` | Show empty files from folders or stdin |
|
|
128
130
|
| `--format {pretty,tsv,json}` | Choose the output format |
|
|
129
131
|
| `--json` | Same as `--format json` |
|
|
130
132
|
| `--no-color` | Disable colour |
|
|
@@ -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.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Progress dashboard for Markdown checklists"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "menisadi", email = "menisadi@gmail.com" }
|
|
@@ -8,7 +8,8 @@ authors = [
|
|
|
8
8
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
|
|
11
|
-
license =
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
12
13
|
|
|
13
14
|
keywords = ["cli", "markdown", "checklists"]
|
|
14
15
|
|
|
@@ -27,5 +28,5 @@ checkmarks = "checkmarks.cli:main"
|
|
|
27
28
|
dev = ["pytest", "ruff", "ty"]
|
|
28
29
|
|
|
29
30
|
[build-system]
|
|
30
|
-
requires = ["setuptools>=
|
|
31
|
+
requires = ["setuptools>=77"]
|
|
31
32
|
build-backend = "setuptools.build_meta"
|
|
@@ -23,7 +23,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
23
23
|
"-r", "--recursive", action="store_true", help="descend into subdirectories"
|
|
24
24
|
)
|
|
25
25
|
parser.add_argument(
|
|
26
|
-
"--all",
|
|
26
|
+
"--all",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="show empty files found in directories or read from stdin",
|
|
27
29
|
)
|
|
28
30
|
fmt = parser.add_mutually_exclusive_group()
|
|
29
31
|
fmt.add_argument(
|
|
@@ -30,17 +30,19 @@ def expand_dir(directory: Path, recursive: bool) -> list[Path]:
|
|
|
30
30
|
return sorted(found)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def iter_args(
|
|
34
|
-
|
|
33
|
+
def iter_args(
|
|
34
|
+
paths: list[str], stdin: TextIO, env: Mapping[str, str]
|
|
35
|
+
) -> Iterator[tuple[str, bool]]:
|
|
36
|
+
"""Yields (path, from_stdin) pairs, expanding `-` and the no-argument default."""
|
|
35
37
|
if not paths:
|
|
36
|
-
yield env.get("CHECKMARKS_DIR") or "."
|
|
38
|
+
yield env.get("CHECKMARKS_DIR") or ".", False
|
|
37
39
|
return
|
|
38
40
|
|
|
39
41
|
for arg in paths:
|
|
40
42
|
if arg == "-":
|
|
41
|
-
yield from (line.strip() for line in stdin if line.strip())
|
|
43
|
+
yield from ((line.strip(), True) for line in stdin if line.strip())
|
|
42
44
|
else:
|
|
43
|
-
yield arg
|
|
45
|
+
yield arg, False
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
def collect(
|
|
@@ -61,13 +63,14 @@ def collect(
|
|
|
61
63
|
seen[key] = source
|
|
62
64
|
sources.append(source)
|
|
63
65
|
|
|
64
|
-
for arg in iter_args(paths, stdin, env):
|
|
66
|
+
for arg, from_stdin in iter_args(paths, stdin, env):
|
|
65
67
|
path = Path(arg)
|
|
66
68
|
if path.is_dir():
|
|
67
69
|
for file in expand_dir(path, recursive):
|
|
68
70
|
add(file, explicit=False)
|
|
69
71
|
elif path.exists():
|
|
70
|
-
|
|
72
|
+
# Piped paths are usually a bulk listing, so treat them like a folder
|
|
73
|
+
add(path, explicit=not from_stdin)
|
|
71
74
|
else:
|
|
72
75
|
warnings.append(f"{arg}: no such file or directory")
|
|
73
76
|
|
|
@@ -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
|
-
|
|
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,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: checkmarks
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Progress dashboard for Markdown checklists
|
|
5
5
|
Author-email: menisadi <menisadi@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2025 Meni
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Project-URL: Homepage, https://github.com/menisadi/checkmarks
|
|
29
8
|
Project-URL: Issues, https://github.com/menisadi/checkmarks/issues
|
|
30
9
|
Keywords: cli,markdown,checklists
|
|
@@ -39,10 +18,10 @@ A small command-line dashboard for your progress. Point it at a folder of Markdo
|
|
|
39
18
|
|
|
40
19
|
```
|
|
41
20
|
$ checkmarks ~/progress
|
|
42
|
-
Thinking, Fast and Slow [████------]
|
|
43
|
-
Gödel, Escher, Bach [██--------]
|
|
44
|
-
Rust tutorial [████------]
|
|
45
|
-
NN hands-on [██--------]
|
|
21
|
+
Thinking, Fast and Slow [████------] 17/38 chapters 45%
|
|
22
|
+
Gödel, Escher, Bach [██--------] 5/22 chapters 23%
|
|
23
|
+
Rust tutorial [████------] 12/28 tasks 43%
|
|
24
|
+
NN hands-on [██--------] 2/8 tasks 25%
|
|
46
25
|
```
|
|
47
26
|
|
|
48
27
|
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 +75,30 @@ By default, checkmarks counts checkboxes:
|
|
|
96
75
|
- List items can start with `-`, `*`, `+`, `1.` or `1)`.
|
|
97
76
|
- Checkboxes inside fenced code blocks are ignored.
|
|
98
77
|
|
|
99
|
-
###
|
|
78
|
+
### Big things: break them down
|
|
100
79
|
|
|
101
|
-
|
|
80
|
+
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
81
|
|
|
103
82
|
```markdown
|
|
104
83
|
---
|
|
105
|
-
|
|
106
|
-
unit: pages
|
|
84
|
+
unit: chunks
|
|
107
85
|
---
|
|
108
86
|
# Dune
|
|
87
|
+
|
|
88
|
+
- [x] pp. 1–40
|
|
89
|
+
- [x] pp. 41–80
|
|
90
|
+
- [ ] pp. 81–120
|
|
91
|
+
- [ ] pp. 121–160
|
|
109
92
|
```
|
|
110
93
|
|
|
111
|
-
Supported keys:
|
|
94
|
+
Supported frontmatter keys:
|
|
112
95
|
|
|
113
|
-
| Key
|
|
114
|
-
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `title` | Row title |
|
|
96
|
+
| Key | Meaning |
|
|
97
|
+
|---------|------------------------------------------|
|
|
98
|
+
| `unit` | Label after the count (default `tasks`) |
|
|
99
|
+
| `title` | Row title |
|
|
118
100
|
|
|
119
|
-
|
|
101
|
+
Other keys are ignored.
|
|
120
102
|
|
|
121
103
|
### Titles
|
|
122
104
|
|
|
@@ -129,7 +111,7 @@ On a terminal, checkmarks prints the aligned table shown above. Rows at 100% are
|
|
|
129
111
|
When output is piped, it switches to tab-separated rows with no header:
|
|
130
112
|
|
|
131
113
|
```
|
|
132
|
-
title done total pct unit
|
|
114
|
+
title done total pct unit status path
|
|
133
115
|
```
|
|
134
116
|
|
|
135
117
|
Use `--json` for a JSON array:
|
|
@@ -139,27 +121,26 @@ $ checkmarks --json dune.md
|
|
|
139
121
|
[
|
|
140
122
|
{
|
|
141
123
|
"title": "Dune",
|
|
142
|
-
"done":
|
|
143
|
-
"total":
|
|
144
|
-
"pct":
|
|
145
|
-
"unit": "
|
|
146
|
-
"mode": "counter",
|
|
124
|
+
"done": 9,
|
|
125
|
+
"total": 22,
|
|
126
|
+
"pct": 40.9,
|
|
127
|
+
"unit": "chapters",
|
|
147
128
|
"status": "ok",
|
|
148
129
|
"path": "dune.md"
|
|
149
130
|
}
|
|
150
131
|
]
|
|
151
132
|
```
|
|
152
133
|
|
|
153
|
-
`status` is `ok`, `empty` (no checkboxes) or `error` (
|
|
134
|
+
`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
135
|
|
|
155
|
-
Empty files found in folders are hidden; pass `--all` to show them. Files you name
|
|
136
|
+
Empty files found in folders or read from stdin are hidden; pass `--all` to show them. Files you name as arguments are always shown.
|
|
156
137
|
|
|
157
138
|
## Options
|
|
158
139
|
|
|
159
140
|
| Option | Description |
|
|
160
141
|
|------------------------------|---------------------------------------------|
|
|
161
142
|
| `-r`, `--recursive` | Descend into subfolders |
|
|
162
|
-
| `--all` | Show empty files
|
|
143
|
+
| `--all` | Show empty files from folders or stdin |
|
|
163
144
|
| `--format {pretty,tsv,json}` | Choose the output format |
|
|
164
145
|
| `--json` | Same as `--format json` |
|
|
165
146
|
| `--no-color` | Disable colour |
|
|
@@ -167,6 +148,11 @@ Empty files found in folders are hidden; pass `--all` to show them. Files you na
|
|
|
167
148
|
|
|
168
149
|
Exit code is `0` if at least one file has progress to show, `1` if none does, and `2` for usage errors.
|
|
169
150
|
|
|
151
|
+
## Upgrading from 0.2
|
|
152
|
+
|
|
153
|
+
- The `progress: N/M` frontmatter key (counter mode) is gone. Replace it with a checklist of chapters or page ranges.
|
|
154
|
+
- The `mode` column (TSV) and field (JSON) are removed.
|
|
155
|
+
|
|
170
156
|
## Upgrading from 0.1
|
|
171
157
|
|
|
172
158
|
- Output is now a table row per file instead of the old "📊 Progress for:" block.
|
|
@@ -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
|
-
"---\
|
|
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").
|
|
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\
|
|
35
|
-
"Beta\
|
|
36
|
-
"bad\t\t\t\
|
|
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
|
|
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\
|
|
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:
|
|
@@ -127,6 +127,19 @@ def test_stdin_paths(
|
|
|
127
127
|
assert capsys.readouterr().out.startswith("Beta\t")
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def test_stdin_hides_empty_unless_all(
|
|
131
|
+
tree: Path, capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch
|
|
132
|
+
) -> None:
|
|
133
|
+
import io
|
|
134
|
+
|
|
135
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO("a.md\nnotes.md\n"))
|
|
136
|
+
main(["-"])
|
|
137
|
+
assert "Notes" not in capsys.readouterr().out
|
|
138
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO("a.md\nnotes.md\n"))
|
|
139
|
+
main(["-", "--all"])
|
|
140
|
+
assert "Notes\t\t\t\ttasks\tempty\tnotes.md" in capsys.readouterr().out
|
|
141
|
+
|
|
142
|
+
|
|
130
143
|
def test_env_dir(
|
|
131
144
|
tree: Path,
|
|
132
145
|
tmp_path_factory: pytest.TempPathFactory,
|
|
@@ -68,11 +68,18 @@ def test_stdin_paths(tmp_path: Path) -> None:
|
|
|
68
68
|
stdin = io.StringIO(f"{tmp_path / 'a.md'}\n\n \n{tmp_path / 'dir'}\n")
|
|
69
69
|
sources, _ = collect(["-"], False, stdin, {})
|
|
70
70
|
assert [(n, s.explicit) for n, s in zip(names(sources, tmp_path), sources)] == [
|
|
71
|
-
("a.md",
|
|
71
|
+
("a.md", False),
|
|
72
72
|
("dir/b.md", False),
|
|
73
73
|
]
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def test_stdin_path_explicit_when_also_an_arg(tmp_path: Path) -> None:
|
|
77
|
+
make_tree(tmp_path, ["a.md"])
|
|
78
|
+
stdin = io.StringIO(f"{tmp_path / 'a.md'}\n")
|
|
79
|
+
sources, _ = collect(["-", str(tmp_path / "a.md")], False, stdin, {})
|
|
80
|
+
assert sources == [Source(tmp_path / "a.md", True)]
|
|
81
|
+
|
|
82
|
+
|
|
76
83
|
def test_stdin_not_read_without_dash(
|
|
77
84
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
78
85
|
) -> 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(["---", "
|
|
97
|
-
assert meta == {"
|
|
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
|
|
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.
|
|
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.
|
|
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", "
|
|
18
|
-
EMPTY = Item(
|
|
19
|
-
|
|
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\
|
|
34
|
-
"Notes\t\t\t\ttasks\
|
|
35
|
-
"Bad\t\t\t\
|
|
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", "
|
|
41
|
-
assert render_tsv([item]) == "A B C \t1\t2\t50.0\tx y\
|
|
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", "
|
|
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", "
|
|
110
|
-
Item(Path("b"), "Gödel, Escher, Bach", 180, 777, "pages", "
|
|
111
|
-
Item(Path("c"), "Rust tutorial", 12, 28, "tasks", "
|
|
112
|
-
Item(Path("d"), "NN hands-on", 2, 8, "tasks", "
|
|
113
|
-
Item(Path("e"), "Broken file", None, None, "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|