From af621401c9b1a9abcf522f8e2b0d250f6170fd96 Mon Sep 17 00:00:00 2001 From: Katrin Torsunova Date: Fri, 10 Jul 2026 13:30:03 +0200 Subject: [PATCH 1/5] prepare tap structure --- .git_archival.txt | 4 +++ .gitattributes | 1 + docs/homebrew.md | 49 +++++++++++++++++++++++++++++++++++ packaging/homebrew/roll.rb | 32 +++++++++++++++++++++++ pyproject.toml | 1 + src/roll/app/root_commands.py | 22 +++------------- src/roll/messages/cli.py | 5 ++++ src/roll/version.py | 12 +++++++++ tests/test_cli_smoke.py | 20 ++++++++++++++ tests/test_version.py | 16 +++++++++++- 10 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 .git_archival.txt create mode 100644 .gitattributes create mode 100644 docs/homebrew.md create mode 100644 packaging/homebrew/roll.rb diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..8d015c8 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=v[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a94cb2f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/docs/homebrew.md b/docs/homebrew.md new file mode 100644 index 0000000..7968b1f --- /dev/null +++ b/docs/homebrew.md @@ -0,0 +1,49 @@ +# Homebrew tap + +`roll` is a good fit for a personal Homebrew tap before aiming at `homebrew/core`. + +## Why a tap first + +- no central review queue; +- easier iteration on formula changes; +- suitable for niche CLI tools; +- users can still install with a single `brew install owner/tap/roll`. + +## Expected repository layout + +Create a separate repository named `homebrew-tap`: + +```text +homebrew-tap/ +└── Formula/ + └── roll.rb +``` + +Use [`packaging/homebrew/roll.rb`](../packaging/homebrew/roll.rb) as the starting point. + +## Install flow + +```bash +brew tap katrinio/tap +brew install roll +``` + +Or in one command: + +```bash +brew install katrinio/tap/roll +``` + +## Release checklist + +1. Push a tagged release such as `v0.8.0`. +2. Compute the tarball checksum for that exact tag. +3. Update `url`, `sha256`, and `version` in the formula. +4. Run `brew install --build-from-source ./Formula/roll.rb`. +5. Run `brew test roll`. + +## Notes for `roll` + +- `rl update` intentionally does not self-update. Homebrew users should use `brew upgrade roll`. +- package versioning is configured to work from git archives used by tagged source tarballs. +- a meaningful `brew test` should initialize a temporary archive and verify that `.roll` was created. diff --git a/packaging/homebrew/roll.rb b/packaging/homebrew/roll.rb new file mode 100644 index 0000000..919050e --- /dev/null +++ b/packaging/homebrew/roll.rb @@ -0,0 +1,32 @@ +class Roll < Formula + include Language::Python::Virtualenv + + desc "Personal film roll index" + homepage "https://github.com/katrinio/roll" + url "https://github.com/katrinio/roll/archive/refs/tags/v0.7.0.tar.gz" + sha256 "__REPLACE_WITH_RELEASE_SHA256__" + license "MIT" + + depends_on "python@3.12" + + resource "typer" do + url "https://files.pythonhosted.org/packages/source/t/typer/typer-0.16.0.tar.gz" + sha256 "__REPLACE_WITH_TYPER_SHA256__" + end + + resource "prompt-toolkit" do + url "https://files.pythonhosted.org/packages/source/p/prompt_toolkit/prompt_toolkit-3.0.51.tar.gz" + sha256 "__REPLACE_WITH_PROMPT_TOOLKIT_SHA256__" + end + + def install + virtualenv_install_with_resources + end + + test do + archive = testpath/"archive" + system bin/"rl", "init", archive + assert_predicate archive/".roll", :directory? + assert_predicate archive/".roll"/"stock.toml", :exist? + end +end diff --git a/pyproject.toml b/pyproject.toml index 982af57..672d06a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,4 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] version_scheme = "no-guess-dev" local_scheme = "no-local-version" +version_file = "src/roll/_version.py" diff --git a/src/roll/app/root_commands.py b/src/roll/app/root_commands.py index 4663900..f3e7e52 100644 --- a/src/roll/app/root_commands.py +++ b/src/roll/app/root_commands.py @@ -1,8 +1,6 @@ from __future__ import annotations from pathlib import Path -import subprocess -import sys import typer @@ -18,7 +16,7 @@ from roll.helpers.guards import require_config, require_directory from roll.helpers.output import echo_lines, echo_section from roll.messages import Msg -from roll.version import get_latest_version, get_version, is_outdated +from roll.version import get_latest_version, get_update_hint, get_version, is_outdated def version() -> None: @@ -26,7 +24,7 @@ def version() -> None: typer.echo(current) latest = get_latest_version() if latest and is_outdated(current=current, latest=latest): - typer.echo(f"New version available: {latest}. Run `rl update`.") + typer.echo(f"New version available: {latest}. {get_update_hint()}") raise typer.Exit() @@ -50,20 +48,8 @@ def init(archive: Path) -> None: def update() -> None: - result = subprocess.run( - [ - sys.executable, - "-m", - "pip", - "install", - "--no-input", - "--upgrade", - "--force-reinstall", - "git+https://github.com/katrinio/roll.git@main", - ], - check=False, - ) - raise typer.Exit(code=result.returncode) + typer.echo(str(Msg.UPDATE_USE_PACKAGE_MANAGER)) + raise typer.Exit(code=1) def config() -> None: diff --git a/src/roll/messages/cli.py b/src/roll/messages/cli.py index e896f0e..203e208 100644 --- a/src/roll/messages/cli.py +++ b/src/roll/messages/cli.py @@ -163,6 +163,11 @@ class Msg(Headers): "Нужен запрос или фильтр.", "Query or filter required.", ) + UPDATE_USE_PACKAGE_MANAGER = Message( + "cli.update_use_package_manager", + "roll не обновляет себя сам. Используйте пакетный менеджер, например `brew upgrade roll`, или переустановите пакет из исходников.", + "roll does not self-update. Use your package manager, for example `brew upgrade roll`, or reinstall the package from source.", + ) TAGS_NORMALIZED = Message( "cli.tags_normalized", "Теги нормализованы.", "Tags normalized." ) diff --git a/src/roll/version.py b/src/roll/version.py index fba89af..d48783a 100644 --- a/src/roll/version.py +++ b/src/roll/version.py @@ -5,6 +5,7 @@ from importlib.metadata import PackageNotFoundError, version as package_version from pathlib import Path import subprocess +import sys @lru_cache(maxsize=1) @@ -27,6 +28,12 @@ def get_latest_version() -> str: return _git_tag() +def get_update_hint() -> str: + if _is_homebrew_install(): + return "Update with `brew upgrade roll`." + return "Update using your package manager or reinstall from source." + + def is_outdated(current: str | None = None, latest: str | None = None) -> bool: current_version = _version_tuple(current or get_version()) latest_version = _version_tuple(latest or get_latest_version()) @@ -95,3 +102,8 @@ def _max_version(values: list[str]) -> str: if not parsed: return "" return max(parsed, key=lambda item: item[0])[1] + + +def _is_homebrew_install() -> bool: + executable = Path(sys.executable).resolve() + return any(part in {"Cellar", "Homebrew"} for part in executable.parts) diff --git a/tests/test_cli_smoke.py b/tests/test_cli_smoke.py index 792a55b..f52af06 100644 --- a/tests/test_cli_smoke.py +++ b/tests/test_cli_smoke.py @@ -36,6 +36,26 @@ def test_batch_help(self) -> None: self.assertEqual(result.returncode, 0) self.assertIn("process", self._output(result)) + def test_init_creates_workspace(self) -> None: + with tempfile.TemporaryDirectory() as home, tempfile.TemporaryDirectory() as tmp: + env = os.environ.copy() + env["HOME"] = home + archive = Path(tmp) / "archive" + archive.mkdir() + + result = subprocess.run( + [PYTHON, "-c", CLI_ENTRY, "init", str(archive)], + cwd=ROOT, + env=env, + text=True, + capture_output=True, + check=False, + ) + + self.assertEqual(result.returncode, 0) + self.assertTrue((archive / ".roll").is_dir()) + self.assertTrue((archive / ".roll" / "stock.toml").exists()) + def test_stats_help(self) -> None: result = self._run("stats", "--help") self.assertEqual(result.returncode, 0) diff --git a/tests/test_version.py b/tests/test_version.py index 8b03df4..82924c2 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -5,7 +5,7 @@ from pathlib import Path from unittest.mock import patch -from roll.version import get_version, is_outdated +from roll.version import get_update_hint, get_version, is_outdated class VersionTests(unittest.TestCase): @@ -33,3 +33,17 @@ def test_is_outdated_compares_versions(self) -> None: self.assertTrue(is_outdated(current="0.4.0", latest="0.4.1")) self.assertFalse(is_outdated(current="0.4.1", latest="0.4.1")) self.assertFalse(is_outdated(current="0.4.2", latest="0.4.1")) + + def test_update_hint_prefers_brew_for_homebrew_installs(self) -> None: + with patch( + "roll.version.sys.executable", + "/opt/homebrew/Cellar/python@3.12/bin/python3.12", + ): + self.assertEqual(get_update_hint(), "Update with `brew upgrade roll`.") + + def test_update_hint_is_generic_outside_homebrew(self) -> None: + with patch("roll.version.sys.executable", "/usr/bin/python3"): + self.assertEqual( + get_update_hint(), + "Update using your package manager or reinstall from source.", + ) From ce2b854cd31e8c76d60f8331c3adf962b93ac1cc Mon Sep 17 00:00:00 2001 From: Katrin Torsunova Date: Fri, 10 Jul 2026 13:37:36 +0200 Subject: [PATCH 2/5] prepare tap package --- .github/workflows/package.yml | 37 +++++++++++++++++++++++++++++++++++ .gitignore | 1 + README.md | 2 ++ docs/homebrew.md | 23 ++++++++++++++++++---- pyproject.toml | 6 ++++++ tests/test_root_commands.py | 21 ++++++++++++++++++++ 6 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/package.yml create mode 100644 tests/test_root_commands.py diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..4627bbf --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,37 @@ +name: package + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build frontend + run: python -m pip install --upgrade build + + - name: Build sdist and wheel + run: python -m build + + - name: Show checksums + run: sha256sum dist/* + + - name: Upload distribution artifacts + uses: actions/upload-artifact@v4 + with: + name: roll-dist-${{ github.ref_name }} + path: dist/* diff --git a/.gitignore b/.gitignore index 042acc4..9b094ec 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ dist/ downloads/ eggs/ .eggs/ +src/roll/_version.py lib/ lib64/ parts/ diff --git a/README.md b/README.md index f3ba1da..e88962f 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ or uv tool install git+https://github.com/katrinio/roll.git ``` +For Homebrew tap packaging, see [docs/homebrew.md](docs/homebrew.md). + --- ## Getting started diff --git a/docs/homebrew.md b/docs/homebrew.md index 7968b1f..b3a72ca 100644 --- a/docs/homebrew.md +++ b/docs/homebrew.md @@ -37,10 +37,25 @@ brew install katrinio/tap/roll ## Release checklist 1. Push a tagged release such as `v0.8.0`. -2. Compute the tarball checksum for that exact tag. -3. Update `url`, `sha256`, and `version` in the formula. -4. Run `brew install --build-from-source ./Formula/roll.rb`. -5. Run `brew test roll`. +2. Wait for the `package` GitHub Actions workflow to build `sdist` and wheel artifacts for that tag. +3. Compute the GitHub tag tarball checksum for that exact release: + +```bash +curl -L -o /tmp/roll-v0.8.0.tar.gz \ + https://github.com/katrinio/roll/archive/refs/tags/v0.8.0.tar.gz +shasum -a 256 /tmp/roll-v0.8.0.tar.gz +``` + +4. Copy [`packaging/homebrew/roll.rb`](../packaging/homebrew/roll.rb) into the tap as `Formula/roll.rb`. +5. Update `url` and top-level `sha256` in the formula. +6. Fill Python dependency resource checksums. If Homebrew developer commands are available, run: + +```bash +brew update-python-resources Formula/roll.rb +``` + +7. Run `brew install --build-from-source ./Formula/roll.rb`. +8. Run `brew test roll`. ## Notes for `roll` diff --git a/pyproject.toml b/pyproject.toml index 672d06a..2b22477 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,12 @@ rl = "roll.cli:app" requires = ["setuptools>=69", "setuptools-scm>=8"] build-backend = "setuptools.build_meta" +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + [tool.setuptools_scm] version_scheme = "no-guess-dev" local_scheme = "no-local-version" diff --git a/tests/test_root_commands.py b/tests/test_root_commands.py new file mode 100644 index 0000000..d69f864 --- /dev/null +++ b/tests/test_root_commands.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from io import StringIO +import unittest +from contextlib import redirect_stdout + +import typer + +from roll.app import root_commands + + +class RootCommandTests(unittest.TestCase): + def test_update_prints_package_manager_guidance(self) -> None: + output = StringIO() + + with redirect_stdout(output): + with self.assertRaises(typer.Exit) as exc: + root_commands.update() + + self.assertEqual(exc.exception.exit_code, 1) + self.assertIn("does not self-update", output.getvalue()) From 1f48b9f0e1ed07ae5dc45ba2af06b2bb47355aec Mon Sep 17 00:00:00 2001 From: Katrin Torsunova Date: Fri, 10 Jul 2026 13:44:03 +0200 Subject: [PATCH 3/5] actual docs/ --- CONTRIBUTING.md | 2 +- docs/{index.md => _index.md} | 2 +- docs/architecture.md | 10 ++++--- docs/development.md | 11 ++++---- docs/editing.md | 51 ++++++++++++++++++------------------ docs/getting-started.md | 23 ++++++++-------- docs/reference.md | 6 ++--- 7 files changed, 54 insertions(+), 51 deletions(-) rename docs/{index.md => _index.md} (88%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2691b0a..fd12ac9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ ## Before you start -- Start here — [docs/index.md](docs/index.md) +- Start here — [docs/index.md](docs/_index.md) - Project scope — [docs/getting-started.md](docs/getting-started.md): if a feature doesn't help find a roll from memory faster, let's discuss it separately before a PR - Architecture and command effects — [docs/architecture.md](docs/architecture.md) - Terms and exact rules — [docs/reference.md](docs/reference.md) diff --git a/docs/index.md b/docs/_index.md similarity index 88% rename from docs/index.md rename to docs/_index.md index f78a968..05c0a1b 100644 --- a/docs/index.md +++ b/docs/_index.md @@ -5,7 +5,7 @@ Start here: | Doc | What it answers | |---|---| | [Getting Started](getting-started.md) | What the tool does and how to begin | -| [Editing](editing.md) | How to use `stock edit` and `batch` | +| [Editing](editing.md) | How to update metadata and run batch changes | | [Architecture](architecture.md) | What files exist, what commands read and write, how the archive is structured | | [Reference](reference.md) | Exact terms, behavior, and rules | | [Development](development.md) | Setup, checks, and CI | diff --git a/docs/architecture.md b/docs/architecture.md index 1bb169f..67ee4e2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,24 +41,26 @@ archives = ["/path/to/archive"] | Command | Reads | Writes | Auto-fix | Scope | |---|---|---|---|---| -| `rl version` | package metadata, git tag | no | no | meta | +| `rl --version` | package metadata, git tag | no | no | meta | | `rl init` | filesystem | global config, workspace | no | setup | +| `rl config` | global config | no | no | config | | `rl config lang` | global config | global config | yes, with `rl doctor --fix` | config | +| `rl update` | no | no | no | meta | | `rl stock add` | global config, workspace, vocabulary, stock | stock | no | workspace | | `rl load` | global config, stock, vocabulary | stock, roll | no | roll creation | | `rl load --manual` | global config, vocabulary | roll | no | roll creation | | `rl stock process` / `rl stock failed` | global config, workspace, roll | roll | no | roll status | +| `rl batch process` | global config, workspace, roll | roll | no | roll status | | `rl features add` / `rl tags add` | global config, workspace, vocabulary, roll | roll, vocabulary | no | roll editing | | `rl search` / `rl scan` / `rl status` / `rl stats` / `rl vocab` | global config, workspace, roll, vocabulary | no | no | read-only | | `rl doctor` | global config, workspace, stock, roll, vocabulary | no | yes with `--fix` | integrity | | `rl normalize` | current archive workspace, roll, vocabulary | roll, vocabulary | yes | normalization | | `rl normalize --photos` | photo folders in current archive workspace | archive folders | yes | photo import | -| `rl stock edit` | global config, workspace, roll, vocabulary | roll, vocabulary | no | single-roll editing | | `rl batch` | global config, workspace, roll | roll | no | batch update | These scopes are deliberate: -- `rl stock edit` is the manual, one-roll editor; +- `rl tags add` and `rl features add` update one roll at a time; - `rl batch` is the bulk editor; - `rl search` shares the same filter language as `rl batch`, but never writes; - `rl normalize` is for structural cleanup and normalization; @@ -104,7 +106,7 @@ rl stock process ```bash rl search kir balcony rl status -rl stats -v +rl stats --verbose ``` ### Integrity pass diff --git a/docs/development.md b/docs/development.md index 7154eff..377e6e3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -29,7 +29,7 @@ pre-commit run --all-files ```bash ruff check . -python -m unittest discover -s tests +PYTHONPATH=src python -m unittest discover -s tests ``` ## CLI Map @@ -37,22 +37,21 @@ python -m unittest discover -s tests | Flow | Command | Reads | Writes | Auto-fix | |---|---|---|---|---| | Setup | `rl init /path/to/archive` | filesystem | global config, workspace | no | -| Language | `rl config lang`, `rl config lang EN`, `rl config lang RU` | global config | global config | yes, via `rl doctor --fix` | +| Config | `rl config`, `rl config lang`, `rl config lang EN`, `rl config lang RU` | global config | global config | yes, via `rl doctor --fix` | | Stock | `rl stock add`, `rl stock list` | config, vocab, stock | stock | no | | Roll creation | `rl load`, `rl load --manual` | stock, vocab | roll, stock | no | -| Roll status | `rl stock process`, `rl stock failed` | roll | roll | no | -| Editing | `rl features add`, `rl tags add` | roll, vocab | roll, vocab | no | +| Roll status | `rl stock process`, `rl stock failed`, `rl batch process` | roll | roll | no | +| Metadata | `rl features add`, `rl tags add`, `rl batch` | roll, vocab | roll, vocab | no | | Read-only | `rl search`, `rl scan`, `rl status`, `rl stats`, `rl vocab` | global config, workspace, roll, vocab | no | no | | Integrity | `rl doctor`, `rl doctor --fix` | global config, workspace, stock, roll, vocab | no | yes | | Normalization | `rl normalize --tags` | workspace, roll, vocab | roll, vocab | yes | -| Batch | `rl batch` | workspace, roll | roll | no | `rl load --manual` creates a roll from the film dictionary without changing stock. For `rl features add` and `rl tags add` you can enter several values separated by commas. Autocomplete works per value, duplicates aren't written, `_` is allowed inside a value. `rl config lang` applies immediately in the current process because user-facing messages resolve the language at render time. `rl doctor` checks the global config, workspace config, stock, roll metadata, and vocabularies. Its diagnostics stay in English; only the user-facing UI is localized. Package version comes from git tags at build time. In a source checkout, `rl --version` falls back to the latest git tag if package metadata is not installed. -`rl update` is a runtime convenience command for local installs; it runs `pip install --no-input --upgrade --force-reinstall git+https://github.com/katrinio/roll.git@main` in the current interpreter. +`rl update` prints package manager update guidance. It does not reinstall the package. ## CI diff --git a/docs/editing.md b/docs/editing.md index 63358f0..1d85239 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -1,17 +1,17 @@ # Editing -`roll` has two editing surfaces: +`roll` has two write paths: -- `rl stock edit` for one roll at a time; -- `rl batch` for many rolls at once. +- `rl tags add` and `rl features add` update one roll at a time; +- `rl batch` applies one change to many rolls. -They solve different problems and should stay separate. +Use `rl search` to preview a set before `rl batch`. ## Boundaries | Command | Scope | Typical use | |---|---|---| -| `rl stock edit` | one roll | manual correction, field-by-field judgment | +| `rl tags add` / `rl features add` | one roll | add or correct roll metadata | | `rl batch` | many rolls | repeated update across a filtered set | | `rl search` | many rolls | structured lookup, optionally with free text | | `rl normalize` | archive structure | folder shape, keywords normalization, photo import | @@ -19,29 +19,28 @@ They solve different problems and should stay separate. --- -## `rl stock edit` +## Single-roll metadata updates -Use this when you want to inspect and adjust one roll by hand. +Use `rl tags add` or `rl features add` to update one roll. -What it does: +What they do: - selects a single roll; -- edits its metadata fields directly; -- keeps the current values available in the prompts; -- applies changes only after you confirm each value in the interactive flow. +- updates either `keywords` or `features`; +- writes new values to the roll and the matching vocabulary; +- skips duplicates. Best for: - correcting one record; -- changing a camera on one roll; - refining features or keywords on a single roll; -- adjusting the origin fields on one roll. +- adding new vocabulary values during normal use. Not for: - mass changes across many rolls; -- filtering by year or film name; -- bulk status updates. +- camera changes across many rolls; +- repeated status updates. --- @@ -61,8 +60,7 @@ Selection is based on filters such as: - year; - film name; - camera; -- status; -- tags or keywords when needed. +- status. Within one filter, comma-separated values mean "match any of these". Across filters, the selection is cumulative. @@ -86,7 +84,7 @@ Not for: Use this rule: -- one roll, manual judgment, many fields -> `rl stock edit`; +- one roll, tags or features only -> `rl tags add` or `rl features add`; - many rolls, one repeated operation -> `rl batch`. ## `rl search` @@ -104,8 +102,7 @@ Selection is based on the same filters: - year; - film name; - camera; -- status; -- tags or keywords. +- status. Within one filter, comma-separated values mean "match any of these". Across filters, the selection is cumulative. @@ -123,7 +120,7 @@ Not for: - structural normalization; - integrity repair. -If you need both at different times, start with `stock edit` for the outlier and use `batch` for the rest. +If you need both at different times, start with `rl search` to preview the set, then run `rl batch`. --- @@ -134,19 +131,23 @@ rl search --year 2025 --status loaded ``` ```bash -rl search --film "Kodak Gold 200, Ilford HP5 Plus" --query balcony +rl search --film "Kodak Gold 200, Ilford HP5 Plus" balcony +``` + +```bash +rl tags add ``` ```bash -rl stock edit +rl features add ``` ```bash -rl batch --year 2025 --film "Kodak Gold 200, Ilford HP5 Plus" --set camera="Pentax K1000" +rl batch --year 2025 --film "Kodak Gold 200, Ilford HP5 Plus" --set-camera "Pentax K1000" ``` ```bash -rl batch --year 2025 --set status=processed +rl batch --year 2025 --set-status processed ``` ```bash diff --git a/docs/getting-started.md b/docs/getting-started.md index c52ab78..7e388af 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,6 +1,6 @@ # Getting Started -> Find the right roll from memory: a person, a place, an event, a mood - months later. +Track film stock, loaded rolls, and archive metadata. ## Main Flow @@ -12,28 +12,29 @@ rl stock process # or: rl stock failed rl search kir balcony # half a year later — found it ``` -## What This Covers +## Command Groups -| Flow | Command | +| Area | Commands | |---|---| -| Start | `rl init`, `rl config lang` | +| Setup | `rl init`, `rl config`, `rl config lang` | | Film stock | `rl stock add`, `rl stock list` | | Roll creation | `rl load`, `rl load --manual` | -| Status update | `rl stock process`, `rl stock failed` | -| Fill in a roll | `rl features add`, `rl tags add` | +| Status update | `rl stock process`, `rl stock failed`, `rl batch process` | +| Metadata | `rl features add`, `rl tags add`, `rl batch` | | Find / inspect | `rl search`, `rl scan`, `rl status`, `rl stats [-v]`, `rl vocab` | | Integrity | `rl doctor`, `rl doctor --fix`, `rl normalize --tags` | | Photo import | `rl normalize --photos` | -| Editing | `rl stock edit`, `rl batch` | ## Out of Scope sync between machines · cloud · web UI · migrating old formats · image processing -The CLI defaults to English in the global config and `rl config lang` changes it. -`rl --version` prints the current version. If a newer git tag is available in the current checkout, it also prints a short update hint and points to `rl update`. -`rl update` reinstalls the app from the GitHub repository. -`rl normalize --photos` works in the current archive workspace and can turn a raw photo folder into an archive month based on the dominant photo date. +## Notes + +- Run `rl config lang EN` or `rl config lang RU` to set the UI language. +- Run `rl --version` to print the current version. +- Run `rl update` to print package manager update guidance. +- Run `rl normalize --photos` in the current archive to sort raw photo folders by dominant photo date. ## Rule diff --git a/docs/reference.md b/docs/reference.md index f7fa94e..db204a3 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -12,13 +12,13 @@ A folder with a film archive: years of rolls plus `.roll/` (workspace). `roll` d ## Workspace -`.roll/` inside the archive: workspace config, [[Stock]], and vocabularies. It should stay portable together with the archive. +`.roll/` inside the archive. It stores workspace config, stock, and vocabularies. Keep it together with the archive. --- ## Stock -Film that has not been loaded into a camera yet. Stored in `stock.toml`, separate from `roll.toml`: while a film is in stock, it is not a [[Roll]] yet and does not take up a folder in the archive. +Film that has not been loaded into a camera yet. It lives in `stock.toml`. It is not a roll yet and does not create an archive folder. --- @@ -84,7 +84,7 @@ Brings folder names to a consistent shape: builds a plan, asks for confirmation, ## Editing -`rl stock edit` is a single-roll manual editor. `rl batch` is a bulk editor that applies one change to many rolls selected by filters. `rl search` uses the same filter language for read-only lookup and can also keep free-text search. +`rl tags add` and `rl features add` update one roll at a time. `rl batch` applies one change to many rolls selected by filters. `rl search` uses the same filters for read-only lookup and also accepts a positional free-text query. See [docs/editing.md](editing.md) for the shared editing model and the boundary between the two commands. From 5bcda4a93fe8dc9ce7d3cdfd263f70c389aab52e Mon Sep 17 00:00:00 2001 From: Katrin Torsunova Date: Fri, 10 Jul 2026 13:54:36 +0200 Subject: [PATCH 4/5] upd readme --- README.md | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e88962f..16ebebe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ## `roll` -
- -
+[//]: # (
) +[//]: # ( ) +[//]: # (
) A small CLI for managing a film photography archive. @@ -25,6 +25,21 @@ The goal is to keep this information next to the archive instead of in my head. --- +## What it does + +Use `roll` to: + +- keep film stock in one place; +- record when a film was loaded and into which camera; +- mark a roll as processed or failed; +- add features and tags later, when the memory is still fresh; +- search the archive by film, camera, status, year, or a short text query; +- normalize archive metadata without moving the archive into a separate system. + +It is a CLI for small, durable records around the archive, not for managing the photos themselves. + +--- + ## Principles `roll` doesn't try to replace your photo archive. @@ -43,21 +58,15 @@ Some principles behind the project: ## Installation -```text -pip install git+https://github.com/katrinio/roll.git -``` - -or +`brew install katrinio/tap/roll` -```text -uv tool install git+https://github.com/katrinio/roll.git -``` +Alternative install methods: `uv tool install git+https://github.com/katrinio/roll.git`, `pip install git+https://github.com/katrinio/roll.git`. -For Homebrew tap packaging, see [docs/homebrew.md](docs/homebrew.md). +Tap details: [docs/homebrew.md](docs/homebrew.md). --- -## Getting started +## Main flow | Status | Command | Description | |--------|----------|-------------| @@ -70,6 +79,16 @@ For Homebrew tap packaging, see [docs/homebrew.md](docs/homebrew.md). | `processed` | `rl search` | Search the archive | | `processed` | `rl normalize` | Normalize folder names | +Typical use: + +```bash +rl init ~/Pictures/plenka +rl stock add +rl load +rl stock process +rl search balcony +``` + --- ## Storage From 51eb2e79f8e1611a449c3d4bcd5e471b18f5c81f Mon Sep 17 00:00:00 2001 From: Katrin Torsunova Date: Fri, 10 Jul 2026 18:38:26 +0200 Subject: [PATCH 5/5] fix Homebrew builds from GitHub archives --- docs/homebrew.md | 2 +- packaging/homebrew/roll.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/homebrew.md b/docs/homebrew.md index b3a72ca..4397de7 100644 --- a/docs/homebrew.md +++ b/docs/homebrew.md @@ -60,5 +60,5 @@ brew update-python-resources Formula/roll.rb ## Notes for `roll` - `rl update` intentionally does not self-update. Homebrew users should use `brew upgrade roll`. -- package versioning is configured to work from git archives used by tagged source tarballs. +- the formula exports `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_ROLL`, so Homebrew does not depend on git metadata surviving the GitHub archive step. - a meaningful `brew test` should initialize a temporary archive and verify that `.roll` was created. diff --git a/packaging/homebrew/roll.rb b/packaging/homebrew/roll.rb index 919050e..df8438f 100644 --- a/packaging/homebrew/roll.rb +++ b/packaging/homebrew/roll.rb @@ -20,6 +20,9 @@ class Roll < Formula end def install + # GitHub source archives do not reliably preserve enough VCS metadata for + # setuptools-scm, so pin the package version from the formula itself. + ENV["SETUPTOOLS_SCM_PRETEND_VERSION_FOR_ROLL"] = version.to_s virtualenv_install_with_resources end