makenote-cli 1.1.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.
- makenote_cli-1.1.0/LICENSE +21 -0
- makenote_cli-1.1.0/PKG-INFO +145 -0
- makenote_cli-1.1.0/README.md +118 -0
- makenote_cli-1.1.0/pyproject.toml +43 -0
- makenote_cli-1.1.0/setup.cfg +4 -0
- makenote_cli-1.1.0/src/makenote/__init__.py +2 -0
- makenote_cli-1.1.0/src/makenote/cli.py +112 -0
- makenote_cli-1.1.0/src/makenote/config.py +135 -0
- makenote_cli-1.1.0/src/makenote/constants.py +3 -0
- makenote_cli-1.1.0/src/makenote/github.py +244 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/PKG-INFO +145 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/SOURCES.txt +17 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/dependency_links.txt +1 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/entry_points.txt +2 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/requires.txt +2 -0
- makenote_cli-1.1.0/src/makenote_cli.egg-info/top_level.txt +1 -0
- makenote_cli-1.1.0/tests/test_cli.py +308 -0
- makenote_cli-1.1.0/tests/test_config.py +63 -0
- makenote_cli-1.1.0/tests/test_github.py +340 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Hunter Finch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makenote-cli
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Fast terminal note logging to GitHub
|
|
5
|
+
Author-email: Hunter Finch <finchyhunter@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hfinchy12/makenote
|
|
8
|
+
Project-URL: Repository, https://github.com/hfinchy12/makenote
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/hfinchy12/makenote/issues
|
|
10
|
+
Keywords: notes,cli,github,productivity,terminal,journaling
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: click>=8.1
|
|
25
|
+
Requires-Dist: questionary>=2.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# makenote
|
|
29
|
+
|
|
30
|
+
Fast terminal note logging to GitHub. Log notes from your terminal and store them as JSONL files in a GitHub repo — no database, no server, just `gh` and Git.
|
|
31
|
+
|
|
32
|
+
## Why
|
|
33
|
+
|
|
34
|
+
Developers do a lot — shipped features, fixed bugs, unblocked teammates, learned something new — and most of it goes unrecorded. Come performance review time, or when updating your resume, that work is hard to reconstruct.
|
|
35
|
+
|
|
36
|
+
`makenote` is a lightweight habit for capturing those moments as they happen. A two-second note from the terminal is low enough friction to actually stick. Over time, your notes become a running log of your progress and impact, stored in a GitHub repo you already own and can query however you like.
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
`mn` appends timestamped notes to `notes/<subject>/notes.jsonl` in a GitHub repo you configure. Notes are stored as newline-delimited JSON records and written directly via the GitHub Contents API (through the `gh` CLI).
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Python 3.9+
|
|
45
|
+
- [gh CLI](https://cli.github.com) — authenticated (`gh auth login`)
|
|
46
|
+
- A GitHub repo to store your notes in
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
### Homebrew (recommended)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
brew tap hfinchy12/tap
|
|
54
|
+
brew install makenote
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### pip
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install makenote
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Setup
|
|
64
|
+
|
|
65
|
+
On first run, `mn` launches an interactive config wizard:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mn
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or run it explicitly at any time:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mn config
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
You'll be prompted to set:
|
|
78
|
+
- **GitHub repo** — the `owner/repo` where notes will be stored
|
|
79
|
+
- **Subjects** — categories for your notes (e.g. `work`, `ideas`, `todo`)
|
|
80
|
+
- **Default subject** — used by `mn d` for fast logging
|
|
81
|
+
|
|
82
|
+
Config is saved to `~/.config/makenote/config.json`.
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
### Interactive note (with subject picker)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
mn
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Prompts you to select a subject, then enter your note.
|
|
93
|
+
|
|
94
|
+
### Quick note to default subject
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mn d "your note here"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Or without an argument to be prompted:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
mn d
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### List recent notes
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
mn list
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Shows the 20 most recent notes across all subjects, sorted newest first.
|
|
113
|
+
|
|
114
|
+
To filter by a specific subject:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
mn list --subject work
|
|
118
|
+
mn list -s ideas
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Edit config
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
mn config
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Version
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
mn --version
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Contributing
|
|
134
|
+
|
|
135
|
+
Pull requests are welcome. For significant changes, please open an issue first to discuss the approach.
|
|
136
|
+
|
|
137
|
+
1. Fork the repo
|
|
138
|
+
2. Create a feature branch (`git checkout -b my-feature`)
|
|
139
|
+
3. Make your changes
|
|
140
|
+
4. Run the test suite (`pytest tests/ -x -q`)
|
|
141
|
+
5. Open a pull request
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# makenote
|
|
2
|
+
|
|
3
|
+
Fast terminal note logging to GitHub. Log notes from your terminal and store them as JSONL files in a GitHub repo — no database, no server, just `gh` and Git.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Developers do a lot — shipped features, fixed bugs, unblocked teammates, learned something new — and most of it goes unrecorded. Come performance review time, or when updating your resume, that work is hard to reconstruct.
|
|
8
|
+
|
|
9
|
+
`makenote` is a lightweight habit for capturing those moments as they happen. A two-second note from the terminal is low enough friction to actually stick. Over time, your notes become a running log of your progress and impact, stored in a GitHub repo you already own and can query however you like.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
`mn` appends timestamped notes to `notes/<subject>/notes.jsonl` in a GitHub repo you configure. Notes are stored as newline-delimited JSON records and written directly via the GitHub Contents API (through the `gh` CLI).
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Python 3.9+
|
|
18
|
+
- [gh CLI](https://cli.github.com) — authenticated (`gh auth login`)
|
|
19
|
+
- A GitHub repo to store your notes in
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Homebrew (recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
brew tap hfinchy12/tap
|
|
27
|
+
brew install makenote
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### pip
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install makenote
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
On first run, `mn` launches an interactive config wizard:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mn
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or run it explicitly at any time:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mn config
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
You'll be prompted to set:
|
|
51
|
+
- **GitHub repo** — the `owner/repo` where notes will be stored
|
|
52
|
+
- **Subjects** — categories for your notes (e.g. `work`, `ideas`, `todo`)
|
|
53
|
+
- **Default subject** — used by `mn d` for fast logging
|
|
54
|
+
|
|
55
|
+
Config is saved to `~/.config/makenote/config.json`.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Interactive note (with subject picker)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
mn
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Prompts you to select a subject, then enter your note.
|
|
66
|
+
|
|
67
|
+
### Quick note to default subject
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mn d "your note here"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or without an argument to be prompted:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
mn d
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### List recent notes
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mn list
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Shows the 20 most recent notes across all subjects, sorted newest first.
|
|
86
|
+
|
|
87
|
+
To filter by a specific subject:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mn list --subject work
|
|
91
|
+
mn list -s ideas
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Edit config
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mn config
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Version
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
mn --version
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Contributing
|
|
107
|
+
|
|
108
|
+
Pull requests are welcome. For significant changes, please open an issue first to discuss the approach.
|
|
109
|
+
|
|
110
|
+
1. Fork the repo
|
|
111
|
+
2. Create a feature branch (`git checkout -b my-feature`)
|
|
112
|
+
3. Make your changes
|
|
113
|
+
4. Run the test suite (`pytest tests/ -x -q`)
|
|
114
|
+
5. Open a pull request
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "makenote-cli"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
description = "Fast terminal note logging to GitHub"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Hunter Finch", email = "finchyhunter@gmail.com" }]
|
|
13
|
+
keywords = ["notes", "cli", "github", "productivity", "terminal", "journaling"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"click>=8.1",
|
|
28
|
+
"questionary>=2.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/hfinchy12/makenote"
|
|
33
|
+
Repository = "https://github.com/hfinchy12/makenote"
|
|
34
|
+
"Bug Tracker" = "https://github.com/hfinchy12/makenote/issues"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
mn = "makenote.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import questionary
|
|
7
|
+
|
|
8
|
+
import makenote.config as _cfg
|
|
9
|
+
import makenote.github as _gh
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group(invoke_without_command=True)
|
|
13
|
+
@click.version_option(package_name="makenote", prog_name="mn", message="%(prog)s %(version)s")
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def main(ctx: click.Context) -> None:
|
|
16
|
+
"""mn — fast terminal note logging."""
|
|
17
|
+
if not _cfg.config_exists() and ctx.invoked_subcommand != "config":
|
|
18
|
+
click.echo("No config found. Running first-time setup.")
|
|
19
|
+
_cfg.run_config_flow()
|
|
20
|
+
elif ctx.invoked_subcommand is None:
|
|
21
|
+
# Interactive note flow (mn with no subcommand)
|
|
22
|
+
cfg = _cfg.load_config()
|
|
23
|
+
subjects = cfg.get("subjects", [])
|
|
24
|
+
if not subjects:
|
|
25
|
+
click.echo("Error: no subjects configured. Run mn config to add subjects.")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
choices = ["Add New", questionary.Separator()] + subjects
|
|
28
|
+
subject = questionary.select("Subject:", choices=choices).ask()
|
|
29
|
+
if subject is None:
|
|
30
|
+
sys.exit(0)
|
|
31
|
+
if subject == "Add New":
|
|
32
|
+
subject = questionary.text("New subject name:").ask()
|
|
33
|
+
if subject is None:
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
subject = subject.strip()
|
|
36
|
+
cfg["subjects"].append(subject)
|
|
37
|
+
_cfg.save_config(cfg)
|
|
38
|
+
note_text = questionary.text("Note:").ask()
|
|
39
|
+
if note_text is None:
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
try:
|
|
42
|
+
_gh.write_note(cfg["repo"], subject, note_text)
|
|
43
|
+
except _gh.GhNotInstalledError:
|
|
44
|
+
click.echo("Error: gh CLI not found. Install from https://cli.github.com")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
except _gh.GhNotAuthError:
|
|
47
|
+
click.echo("Error: gh not authenticated. Run: gh auth login")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
except _gh.ShaConflictError:
|
|
50
|
+
click.echo("Error: write conflict — file may have changed. Try again.")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
click.echo("Note logged.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@main.command()
|
|
56
|
+
def config() -> None:
|
|
57
|
+
"""Edit configuration."""
|
|
58
|
+
_cfg.run_config_flow()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@main.command(name="d")
|
|
62
|
+
@click.argument("note_text", required=False, default=None)
|
|
63
|
+
def default_note(note_text: str | None) -> None:
|
|
64
|
+
"""Log a note using the default subject."""
|
|
65
|
+
cfg = _cfg.load_config()
|
|
66
|
+
subject = cfg["default_subject"]
|
|
67
|
+
if note_text is None:
|
|
68
|
+
note_text = questionary.text("Note:").ask()
|
|
69
|
+
if note_text is None:
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
try:
|
|
72
|
+
_gh.write_note(cfg["repo"], subject, note_text)
|
|
73
|
+
except _gh.GhNotInstalledError:
|
|
74
|
+
click.echo("Error: gh CLI not found. Install from https://cli.github.com")
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
except _gh.GhNotAuthError:
|
|
77
|
+
click.echo("Error: gh not authenticated. Run: gh auth login")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
except _gh.ShaConflictError:
|
|
80
|
+
click.echo("Error: write conflict — file may have changed. Try again.")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
click.echo("Note logged.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@main.command(name="list")
|
|
86
|
+
@click.option("--subject", "-s", default=None, help="Filter notes to a single subject.")
|
|
87
|
+
def list_notes(subject: str | None) -> None:
|
|
88
|
+
"""Print recent notes across all subjects."""
|
|
89
|
+
cfg = _cfg.load_config()
|
|
90
|
+
subjects = cfg.get("subjects", [])
|
|
91
|
+
if subject is not None:
|
|
92
|
+
if subject not in subjects:
|
|
93
|
+
click.echo(f"Error: unknown subject '{subject}'. Run mn config to manage subjects.")
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
subjects = [subject]
|
|
96
|
+
try:
|
|
97
|
+
records = _gh.read_notes(cfg["repo"], subjects)
|
|
98
|
+
except _gh.GhNotInstalledError:
|
|
99
|
+
click.echo("Error: gh CLI not found. Install from https://cli.github.com")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
except _gh.GhNotAuthError:
|
|
102
|
+
click.echo("Error: gh not authenticated. Run: gh auth login")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
except _gh.ShaConflictError:
|
|
105
|
+
click.echo("Error: write conflict — file may have changed. Try again.")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
if not records:
|
|
108
|
+
click.echo("No notes found.")
|
|
109
|
+
return
|
|
110
|
+
for r in records:
|
|
111
|
+
note_display = r["note"][:60] + "..." if len(r["note"]) > 60 else r["note"]
|
|
112
|
+
click.echo(f"{r['date']} {r['subject']:<15} {note_display}")
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import questionary
|
|
10
|
+
|
|
11
|
+
from makenote.github import GhError, list_subjects
|
|
12
|
+
|
|
13
|
+
CONFIG_PATH = Path.home() / ".config" / "makenote" / "config.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def config_exists() -> bool:
|
|
17
|
+
return CONFIG_PATH.exists()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_config() -> dict:
|
|
21
|
+
with CONFIG_PATH.open() as f:
|
|
22
|
+
return json.load(f)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save_config(data: dict) -> None:
|
|
26
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
with CONFIG_PATH.open("w") as f:
|
|
28
|
+
json.dump(data, f, indent=2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _offer_import_subjects(data: dict) -> None:
|
|
32
|
+
"""Offer to import subjects found in the remote repo that aren't local yet."""
|
|
33
|
+
try:
|
|
34
|
+
remote_subjects = list_subjects(data["repo"])
|
|
35
|
+
except GhError as e:
|
|
36
|
+
click.echo(f"Warning: could not fetch remote subjects: {e}")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
new_subjects = [s for s in remote_subjects if s not in data["subjects"]]
|
|
40
|
+
if not new_subjects:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
subject_list = ", ".join(new_subjects)
|
|
44
|
+
answer = questionary.select(
|
|
45
|
+
f"Found {len(new_subjects)} subject(s) in remote repo: {subject_list}. Import all?",
|
|
46
|
+
choices=["Yes", "No"],
|
|
47
|
+
).ask()
|
|
48
|
+
if answer is None:
|
|
49
|
+
return
|
|
50
|
+
confirmed = answer == "Yes"
|
|
51
|
+
|
|
52
|
+
if not confirmed:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
data["subjects"].extend(new_subjects)
|
|
56
|
+
click.echo(f"Imported {len(new_subjects)} subject(s).")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def run_config_flow(existing: dict | None = None) -> None:
|
|
60
|
+
"""Interactive config editor. Loops until user selects Save and exit."""
|
|
61
|
+
data = existing.copy() if existing else {"repo": "", "default_subject": "", "subjects": []}
|
|
62
|
+
|
|
63
|
+
# Pre-load existing config if it exists and no data passed in
|
|
64
|
+
if existing is None and config_exists():
|
|
65
|
+
data = load_config()
|
|
66
|
+
|
|
67
|
+
while True:
|
|
68
|
+
action = questionary.select(
|
|
69
|
+
"Configure makenote:",
|
|
70
|
+
choices=[
|
|
71
|
+
"Set GitHub repo",
|
|
72
|
+
"Set default subject",
|
|
73
|
+
questionary.Separator(),
|
|
74
|
+
"Add subject",
|
|
75
|
+
"Remove subject",
|
|
76
|
+
"List subjects",
|
|
77
|
+
questionary.Separator(),
|
|
78
|
+
"Save and exit",
|
|
79
|
+
],
|
|
80
|
+
).ask()
|
|
81
|
+
|
|
82
|
+
if action is None: # Ctrl-C: exit cleanly, no traceback
|
|
83
|
+
sys.exit(0)
|
|
84
|
+
|
|
85
|
+
if action == "Set GitHub repo":
|
|
86
|
+
repo = questionary.text("GitHub repo (owner/repo):").ask()
|
|
87
|
+
if repo is None:
|
|
88
|
+
sys.exit(0)
|
|
89
|
+
data["repo"] = repo.strip()
|
|
90
|
+
_offer_import_subjects(data)
|
|
91
|
+
|
|
92
|
+
elif action == "Set default subject":
|
|
93
|
+
if not data["subjects"]:
|
|
94
|
+
click.echo("Error: add at least one subject first.")
|
|
95
|
+
click.echo("")
|
|
96
|
+
continue
|
|
97
|
+
subject = questionary.select(
|
|
98
|
+
"Default subject:", choices=data["subjects"]
|
|
99
|
+
).ask()
|
|
100
|
+
if subject is None:
|
|
101
|
+
sys.exit(0)
|
|
102
|
+
data["default_subject"] = subject
|
|
103
|
+
|
|
104
|
+
elif action == "Add subject":
|
|
105
|
+
name = questionary.text("New subject name:").ask()
|
|
106
|
+
if name is None:
|
|
107
|
+
sys.exit(0)
|
|
108
|
+
name = name.strip()
|
|
109
|
+
if name and name not in data["subjects"]:
|
|
110
|
+
data["subjects"].append(name)
|
|
111
|
+
|
|
112
|
+
elif action == "List subjects":
|
|
113
|
+
if not data["subjects"]:
|
|
114
|
+
click.echo("No subjects configured.")
|
|
115
|
+
else:
|
|
116
|
+
click.echo("\n".join(data["subjects"]))
|
|
117
|
+
time.sleep(0.4)
|
|
118
|
+
|
|
119
|
+
elif action == "Remove subject":
|
|
120
|
+
if not data["subjects"]:
|
|
121
|
+
click.echo("Error: no subjects to remove.")
|
|
122
|
+
click.echo("")
|
|
123
|
+
continue
|
|
124
|
+
to_remove = questionary.select(
|
|
125
|
+
"Remove which subject?", choices=data["subjects"]
|
|
126
|
+
).ask()
|
|
127
|
+
if to_remove is None:
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
data["subjects"].remove(to_remove)
|
|
130
|
+
|
|
131
|
+
elif action == "Save and exit":
|
|
132
|
+
save_config(data)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
click.echo("")
|