yinv 0.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.
yinv-0.1.0/.gitignore ADDED
@@ -0,0 +1,28 @@
1
+ # Python build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # IDE
17
+ .vscode/
18
+ .idea/
19
+ *.swp
20
+
21
+ # OS
22
+ .DS_Store
23
+
24
+ # Privacy: never commit real invoice data or generated PDFs
25
+ # from any stray local runs at the repo root.
26
+ /*.pdf
27
+ /*.yaml
28
+ /invoices/
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to yinv will be documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-04-12
11
+
12
+ ### Added
13
+ - Initial implementation: `yinv new`, `yinv render`, `yinv config set/get`.
14
+ - YAML schema with explicit `service_month`, `client`, line-item descriptions.
15
+ - Bundled `seed.yaml` placeholder for first-run bootstrap.
16
+ - HTML/CSS → PDF rendering via WeasyPrint.
17
+ - Multi-client-ready data layout and config shape (single-client CLI in v0.1).
yinv-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nicolas Maldonado
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.
yinv-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: yinv
3
+ Version: 0.1.0
4
+ Summary: CLI to generate monthly consulting invoices as PDFs from YAML
5
+ Author: Nicolas Maldonado
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Nicolas Maldonado
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
+ License-File: LICENSE
28
+ Keywords: billing,cli,invoice,pdf,yaml
29
+ Classifier: Development Status :: 3 - Alpha
30
+ Classifier: Environment :: Console
31
+ Classifier: Intended Audience :: End Users/Desktop
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: MacOS
34
+ Classifier: Operating System :: POSIX :: Linux
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Topic :: Office/Business
39
+ Requires-Python: >=3.11
40
+ Requires-Dist: jinja2>=3.1
41
+ Requires-Dist: pyyaml>=6.0
42
+ Requires-Dist: weasyprint>=60.0
43
+ Provides-Extra: dev
44
+ Requires-Dist: build>=1.0; extra == 'dev'
45
+ Requires-Dist: pytest>=8.0; extra == 'dev'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # yinv
49
+
50
+ CLI to generate monthly consulting invoices as PDFs. You edit a YAML draft in
51
+ your `$EDITOR`, save, and get a PDF that matches a consistent template.
52
+
53
+ ## Install (dev)
54
+
55
+ ```sh
56
+ brew install pango gdk-pixbuf libffi
57
+ uv tool install . # or: pipx install .
58
+ ```
59
+
60
+ ## Configure
61
+
62
+ ```sh
63
+ yinv config set invoices.dir ~/Documents/Invoices
64
+ yinv config set client.default <your-client-subdir-name>
65
+ ```
66
+
67
+ `invoices.dir` is the parent dir containing one subdir per client. Each
68
+ client's invoices live under `{invoices.dir}/{client}/{year}/`.
69
+
70
+ ## Use
71
+
72
+ ### First run
73
+
74
+ ```sh
75
+ yinv new
76
+ ```
77
+
78
+ If no prior invoice exists for `client.default`, `yinv new` writes a
79
+ placeholder YAML into the correct path and opens it in `$EDITOR`.
80
+
81
+ Fill in the placeholders (name, address, bank details, line items), then save
82
+ and exit your editor. `yinv new` will validate the YAML and generate the PDF
83
+ automatically after the editor exits successfully.
84
+
85
+ The seed file still contains placeholder contact, bank, and pricing values,
86
+ but the default `Consulting fees for <Month YYYY>` description is adjusted to
87
+ the target service month automatically.
88
+
89
+ ### Monthly
90
+
91
+ ```sh
92
+ yinv new
93
+ ```
94
+
95
+ Forks the latest invoice, increments the number, rolls the month forward
96
+ (updating dates and exact `Month YYYY` tokens in line-item descriptions),
97
+ opens in `$EDITOR` for review, and renders the PDF after the editor exits
98
+ successfully.
99
+
100
+ Example: if the previous invoice contains `Consulting fees for April 2026`,
101
+ the next one will become `Consulting fees for May 2026`.
102
+
103
+ The month rewrite is intentionally narrow: it only replaces the exact previous
104
+ invoice service-month token. If the prior invoice still says `January 2026` or
105
+ uses a different format such as `Jan 2026`, that text will be carried forward
106
+ unchanged.
107
+
108
+ `yinv new --month 2026-07` targets a specific month (skip ahead / regenerate).
109
+ `yinv new --force` overwrites an existing target.
110
+
111
+ ### Re-render
112
+
113
+ ```sh
114
+ yinv render <file.yaml>
115
+ ```
116
+
117
+ Writes a fresh PDF next to the YAML. Useful after manually editing a YAML.
118
+ This is mainly for re-rendering an existing invoice; `yinv new` already renders
119
+ the new month's PDF automatically after a successful editor exit.
120
+
121
+ ## Commands (v0.1)
122
+
123
+ | Command | Purpose |
124
+ |---|---|
125
+ | `yinv new [--month YYYY-MM] [--force]` | First-run bootstrap or monthly fork. |
126
+ | `yinv render <file.yaml>` | Render one YAML to PDF. |
127
+ | `yinv config set <key> <value>` | Write a config key. |
128
+ | `yinv config get <key>` | Read a config key. |
129
+
130
+ ## Data layout
131
+
132
+ ```
133
+ ~/Documents/Invoices/ ← invoices.dir
134
+ └── <client>/ ← one subdir per client
135
+ └── 2026/
136
+ ├── March2026.yaml ← source of truth
137
+ └── March2026.pdf ← generated from the YAML
138
+ ```
139
+
140
+ Nothing else. No database, no index file. Add another client tomorrow by
141
+ creating another subdir.
142
+
143
+ ## License
144
+
145
+ MIT. See `LICENSE`.
yinv-0.1.0/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # yinv
2
+
3
+ CLI to generate monthly consulting invoices as PDFs. You edit a YAML draft in
4
+ your `$EDITOR`, save, and get a PDF that matches a consistent template.
5
+
6
+ ## Install (dev)
7
+
8
+ ```sh
9
+ brew install pango gdk-pixbuf libffi
10
+ uv tool install . # or: pipx install .
11
+ ```
12
+
13
+ ## Configure
14
+
15
+ ```sh
16
+ yinv config set invoices.dir ~/Documents/Invoices
17
+ yinv config set client.default <your-client-subdir-name>
18
+ ```
19
+
20
+ `invoices.dir` is the parent dir containing one subdir per client. Each
21
+ client's invoices live under `{invoices.dir}/{client}/{year}/`.
22
+
23
+ ## Use
24
+
25
+ ### First run
26
+
27
+ ```sh
28
+ yinv new
29
+ ```
30
+
31
+ If no prior invoice exists for `client.default`, `yinv new` writes a
32
+ placeholder YAML into the correct path and opens it in `$EDITOR`.
33
+
34
+ Fill in the placeholders (name, address, bank details, line items), then save
35
+ and exit your editor. `yinv new` will validate the YAML and generate the PDF
36
+ automatically after the editor exits successfully.
37
+
38
+ The seed file still contains placeholder contact, bank, and pricing values,
39
+ but the default `Consulting fees for <Month YYYY>` description is adjusted to
40
+ the target service month automatically.
41
+
42
+ ### Monthly
43
+
44
+ ```sh
45
+ yinv new
46
+ ```
47
+
48
+ Forks the latest invoice, increments the number, rolls the month forward
49
+ (updating dates and exact `Month YYYY` tokens in line-item descriptions),
50
+ opens in `$EDITOR` for review, and renders the PDF after the editor exits
51
+ successfully.
52
+
53
+ Example: if the previous invoice contains `Consulting fees for April 2026`,
54
+ the next one will become `Consulting fees for May 2026`.
55
+
56
+ The month rewrite is intentionally narrow: it only replaces the exact previous
57
+ invoice service-month token. If the prior invoice still says `January 2026` or
58
+ uses a different format such as `Jan 2026`, that text will be carried forward
59
+ unchanged.
60
+
61
+ `yinv new --month 2026-07` targets a specific month (skip ahead / regenerate).
62
+ `yinv new --force` overwrites an existing target.
63
+
64
+ ### Re-render
65
+
66
+ ```sh
67
+ yinv render <file.yaml>
68
+ ```
69
+
70
+ Writes a fresh PDF next to the YAML. Useful after manually editing a YAML.
71
+ This is mainly for re-rendering an existing invoice; `yinv new` already renders
72
+ the new month's PDF automatically after a successful editor exit.
73
+
74
+ ## Commands (v0.1)
75
+
76
+ | Command | Purpose |
77
+ |---|---|
78
+ | `yinv new [--month YYYY-MM] [--force]` | First-run bootstrap or monthly fork. |
79
+ | `yinv render <file.yaml>` | Render one YAML to PDF. |
80
+ | `yinv config set <key> <value>` | Write a config key. |
81
+ | `yinv config get <key>` | Read a config key. |
82
+
83
+ ## Data layout
84
+
85
+ ```
86
+ ~/Documents/Invoices/ ← invoices.dir
87
+ └── <client>/ ← one subdir per client
88
+ └── 2026/
89
+ ├── March2026.yaml ← source of truth
90
+ └── March2026.pdf ← generated from the YAML
91
+ ```
92
+
93
+ Nothing else. No database, no index file. Add another client tomorrow by
94
+ creating another subdir.
95
+
96
+ ## License
97
+
98
+ MIT. See `LICENSE`.
@@ -0,0 +1,95 @@
1
+ # Releasing yinv
2
+
3
+ ## Pre-release checklist
4
+
5
+ Before pushing to a public GitHub repo or publishing to PyPI:
6
+
7
+ ### Privacy audit
8
+
9
+ Maintain a **local, uncommitted** list of your real private values — client
10
+ name, personal name, home address tokens, client address tokens, account
11
+ number, routing number, SWIFT/BIC, bank name. Call it something you'll
12
+ recognize (e.g. `~/.yinv-privacy-audit-terms` — add it to your global
13
+ `~/.gitignore_global`).
14
+
15
+ Then, from the repo root:
16
+
17
+ ```sh
18
+ # Every term must return zero matches:
19
+ while IFS= read -r term; do
20
+ echo "=== $term ==="
21
+ git grep -i -- "$term" || true
22
+ done < ~/.yinv-privacy-audit-terms
23
+ ```
24
+
25
+ As a positive control, confirm the synthetic fixture tokens **do** appear —
26
+ this proves the fixture is still synthetic and hasn't been silently replaced
27
+ with real data:
28
+
29
+ ```sh
30
+ git grep -i "0000000000" # should return tests/fixtures/example.yaml + seed.yaml
31
+ git grep -i "TESTUS00" # should return tests/fixtures/example.yaml
32
+ ```
33
+
34
+ If the real terms return any matches, **stop**. Do not push. Remove the
35
+ leaked data and re-run.
36
+
37
+ ## Release steps
38
+
39
+ 1. Update `CHANGELOG.md` — move `[Unreleased]` entries under a new
40
+ `[X.Y.Z] - YYYY-MM-DD` section.
41
+ 2. Bump version in `pyproject.toml`.
42
+ 3. Commit + tag: `git commit -am "Release vX.Y.Z" && git tag vX.Y.Z`.
43
+ 4. Push: `git push && git push --tags`.
44
+ 5. Build: `uv build` (or `python -m build`).
45
+ 6. Publish: `uv publish` (or `twine upload dist/*`).
46
+ 7. Verify the package landed on PyPI: https://pypi.org/project/yinv/
47
+
48
+ ## Homebrew formula (first release only)
49
+
50
+ 1. Generate resource blocks from PyPI:
51
+ ```sh
52
+ pipx run homebrew-pypi-poet -f yinv
53
+ ```
54
+ 2. Create a personal tap repo on GitHub: `homebrew-yinv`.
55
+ 3. Add `Formula/yinv.rb`:
56
+ ```ruby
57
+ class Yinv < Formula
58
+ include Language::Python::Virtualenv
59
+
60
+ desc "CLI to generate monthly consulting invoices as PDFs"
61
+ homepage "https://github.com/<user>/invoiceGenerator"
62
+ url "https://files.pythonhosted.org/packages/source/y/yinv/yinv-X.Y.Z.tar.gz"
63
+ sha256 "<sha from PyPI>"
64
+ license "MIT"
65
+
66
+ depends_on "pango"
67
+ depends_on "gdk-pixbuf"
68
+ depends_on "libffi"
69
+ depends_on "python@3.12"
70
+
71
+ # <resource blocks from homebrew-pypi-poet output>
72
+
73
+ def install
74
+ virtualenv_install_with_resources
75
+ end
76
+
77
+ test do
78
+ assert_match "yinv", shell_output("#{bin}/yinv --help")
79
+ end
80
+ end
81
+ ```
82
+ 4. Commit and push the tap.
83
+ 5. Install from the tap:
84
+ ```sh
85
+ brew tap <user>/yinv
86
+ brew install yinv
87
+ ```
88
+
89
+ ## Subsequent releases
90
+
91
+ 1. Do the privacy audit again (secrets can accumulate over time).
92
+ 2. Update `CHANGELOG.md`, bump version, tag, push, `uv build && uv publish`.
93
+ 3. In the tap repo, update `url`, `sha256`, and re-run
94
+ `homebrew-pypi-poet -f yinv` to refresh the resource blocks.
95
+ 4. Commit the updated formula, push the tap.
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "yinv"
7
+ version = "0.1.0"
8
+ description = "CLI to generate monthly consulting invoices as PDFs from YAML"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Nicolas Maldonado" }]
13
+ keywords = ["invoice", "pdf", "yaml", "cli", "billing"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Office/Business",
25
+ ]
26
+ dependencies = [
27
+ "PyYAML>=6.0",
28
+ "Jinja2>=3.1",
29
+ "weasyprint>=60.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=8.0",
35
+ "build>=1.0",
36
+ ]
37
+
38
+ [project.scripts]
39
+ yinv = "yinv.cli:main"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/yinv"]
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = [
46
+ "/src",
47
+ "/tests",
48
+ "/README.md",
49
+ "/LICENSE",
50
+ "/CHANGELOG.md",
51
+ "/RELEASING.md",
52
+ "/pyproject.toml",
53
+ ]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ pythonpath = ["src"]
@@ -0,0 +1,10 @@
1
+ """yinv — YAML-driven monthly invoice generator."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("yinv")
7
+ except PackageNotFoundError: # package is not installed (e.g. running from source)
8
+ __version__ = "0.0.0+unknown"
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Allow `python -m yinv`."""
2
+
3
+ from yinv.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())