rekordbox-edit 0.4.0.dev23__tar.gz → 0.4.0.dev26__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.
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/install/action.yml +2 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/workflows/cd.yml +2 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/workflows/ci.yml +3 -3
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/workflows/publish.yml +5 -5
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/workflows/release.yml +2 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.pre-commit-config.yaml +2 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/AGENTS.md +1 -1
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/CHANGELOG.md +24 -1
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/PKG-INFO +2 -1
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/pyproject.toml +2 -1
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/convert.py +6 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/edit.py +7 -2
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/search.py +1 -3
- rekordbox_edit-0.4.0.dev26/rekordbox_edit/display.py +119 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/utils.py +0 -104
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/renovate.json5 +6 -4
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/commands/test_convert.py +21 -4
- rekordbox_edit-0.4.0.dev26/tests/test_display.py +149 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/test_utils.py +0 -157
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/uv.lock +75 -38
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.agent-style/RULES.md +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.agent-style/claude-code.md +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/commitizen-bump/action.yml +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/lint/action.yml +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/test/action.yml +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.gitignore +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/CLAUDE.md +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/CONTRIBUTING.md +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/LICENSE +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/Makefile +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/README.md +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/codecov.yml +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/__init__.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/_click.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/cli.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/__init__.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/logger.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/query.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/ruff.toml +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/__init__.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/commands/__init__.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/commands/test_edit.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/commands/test_search.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/conftest.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/test_logger.py +0 -0
- {rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/tests/test_query.py +0 -0
{rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/install/action.yml
RENAMED
|
@@ -5,12 +5,12 @@ runs:
|
|
|
5
5
|
using: "composite"
|
|
6
6
|
steps:
|
|
7
7
|
- name: Install UV
|
|
8
|
-
uses: astral-sh/setup-uv@v4
|
|
8
|
+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
|
|
9
9
|
with:
|
|
10
10
|
enable-cache: true
|
|
11
11
|
|
|
12
12
|
- name: Set up Python
|
|
13
|
-
uses: actions/setup-python@v4
|
|
13
|
+
uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4
|
|
14
14
|
with:
|
|
15
15
|
python-version: ${{ env.PYTHON }}
|
|
16
16
|
|
|
@@ -64,12 +64,12 @@ jobs:
|
|
|
64
64
|
git push origin v${{ steps.version.outputs.version }}
|
|
65
65
|
|
|
66
66
|
- name: Publish to PyPI
|
|
67
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
67
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
68
68
|
with:
|
|
69
69
|
skip-existing: true
|
|
70
70
|
|
|
71
71
|
- name: Create GitHub Pre-Release
|
|
72
|
-
uses: softprops/action-gh-release@v1
|
|
72
|
+
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
|
73
73
|
with:
|
|
74
74
|
tag_name: v${{ steps.version.outputs.version }}
|
|
75
75
|
name: v${{ steps.version.outputs.version }}
|
|
@@ -15,11 +15,11 @@ jobs:
|
|
|
15
15
|
code: ${{ steps.filter.outputs.code }}
|
|
16
16
|
steps:
|
|
17
17
|
- id: skip_check
|
|
18
|
-
uses: fkirc/skip-duplicate-actions@master
|
|
18
|
+
uses: fkirc/skip-duplicate-actions@04a1aebece824b56e6ad6a401d015479cd1c50b3 # master
|
|
19
19
|
|
|
20
20
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
21
21
|
|
|
22
|
-
- uses: dorny/paths-filter@v3
|
|
22
|
+
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
|
|
23
23
|
id: filter
|
|
24
24
|
with:
|
|
25
25
|
filters: |
|
|
@@ -74,7 +74,7 @@ jobs:
|
|
|
74
74
|
runs-on: Ubuntu-latest
|
|
75
75
|
|
|
76
76
|
steps:
|
|
77
|
-
- uses: re-actors/alls-green@release/v1
|
|
77
|
+
- uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
|
78
78
|
with:
|
|
79
79
|
allowed-skips: unit-tests, lint
|
|
80
80
|
jobs: ${{ toJSON(needs) }}
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
- name: Build distribution
|
|
22
22
|
run: |
|
|
23
23
|
uv build
|
|
24
|
-
- uses: actions/upload-artifact@v4
|
|
24
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
25
25
|
with:
|
|
26
26
|
name: dist
|
|
27
27
|
path: dist/
|
|
@@ -34,12 +34,12 @@ jobs:
|
|
|
34
34
|
permissions:
|
|
35
35
|
id-token: write
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/download-artifact@v4
|
|
37
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
38
38
|
with:
|
|
39
39
|
name: dist
|
|
40
40
|
path: dist/
|
|
41
41
|
- name: Publish package distributions 📦 to TestPyPI
|
|
42
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
43
43
|
with:
|
|
44
44
|
repository-url: https://test.pypi.org/legacy/
|
|
45
45
|
|
|
@@ -51,10 +51,10 @@ jobs:
|
|
|
51
51
|
permissions:
|
|
52
52
|
id-token: write
|
|
53
53
|
steps:
|
|
54
|
-
- uses: actions/download-artifact@v4
|
|
54
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
|
55
55
|
with:
|
|
56
56
|
pattern: dist
|
|
57
57
|
path: dist
|
|
58
58
|
merge-multiple: true
|
|
59
59
|
- name: Publish to PyPI
|
|
60
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
fetch-depth: 0
|
|
24
24
|
|
|
25
25
|
- name: Import GPG key
|
|
26
|
-
uses: crazy-max/ghaction-import-gpg@v6
|
|
26
|
+
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6
|
|
27
27
|
with:
|
|
28
28
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
29
29
|
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
|
@@ -40,7 +40,7 @@ jobs:
|
|
|
40
40
|
gpg_sign: true
|
|
41
41
|
|
|
42
42
|
- name: Release
|
|
43
|
-
uses: softprops/action-gh-release@v1
|
|
43
|
+
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
|
44
44
|
with:
|
|
45
45
|
body_path: "body.md"
|
|
46
46
|
tag_name: ${{ env.REVISION }}
|
|
@@ -15,7 +15,7 @@ repos:
|
|
|
15
15
|
- id: check-toml
|
|
16
16
|
- id: check-case-conflict
|
|
17
17
|
- repo: https://github.com/commitizen-tools/commitizen
|
|
18
|
-
rev: v4.
|
|
18
|
+
rev: v4.16.2
|
|
19
19
|
hooks:
|
|
20
20
|
- id: commitizen
|
|
21
21
|
stages: [commit-msg]
|
|
@@ -28,7 +28,7 @@ repos:
|
|
|
28
28
|
files: pyproject\.toml$
|
|
29
29
|
pass_filenames: false
|
|
30
30
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
31
|
-
rev: v0.15.
|
|
31
|
+
rev: v0.15.13
|
|
32
32
|
hooks:
|
|
33
33
|
- id: ruff-check
|
|
34
34
|
args: [--fix]
|
|
@@ -89,7 +89,7 @@ Every module should get and use its own `logger` for all logging purposes and fo
|
|
|
89
89
|
- Implement the smallest functional slice first, then layer features in follow-up commits. Each commit should be "green" and independently deployable.
|
|
90
90
|
- Follow Conventional Commits (see `CONTRIBUTING.md` and the schema in `pyproject.toml`). No `Co-Authored-By` trailer.
|
|
91
91
|
- Commit descriptions should be terse and use active tenses (e.g. "add feature")
|
|
92
|
-
- Commit bodies should describe changes,
|
|
92
|
+
- Commit bodies are completely optional, and should be used to describe changes when they aren't easily inferred by the main message, they should not include agent conversation context, decisions, or plan notes.
|
|
93
93
|
- The agent may commit. But the user always handles pushes, PR creation, and rebases after merges.
|
|
94
94
|
- Do not commit spec, brainstorming, or design documents (e.g. anything under `docs/superpowers/specs/`).
|
|
95
95
|
|
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
## v0.4.0.
|
|
1
|
+
## v0.4.0.dev26 (2026-05-24)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
- chore(deps): update dependency ty to v0.0.38 (#35)
|
|
5
|
+
- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
|
6
|
+
- chore(deps): update dependency ruff to v0.15.13 (#34)
|
|
7
|
+
- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
|
8
|
+
- chore(deps): pin dependencies (#33)
|
|
9
|
+
- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
|
10
|
+
- chore: correct renovate config
|
|
11
|
+
- fix(display): split up the unified Location column into FolderPath and FileName
|
|
12
|
+
- docs: update AGENTS.md
|
|
13
|
+
- feat(display): add before/after change preview to print_track_info
|
|
14
|
+
- refactor(display): render print_track_info with rich.table.Table
|
|
15
|
+
- Replace the fixed-width f-string loop with a rich Table so column widths
|
|
16
|
+
adapt to content and embedded ANSI sequences no longer skew alignment.
|
|
17
|
+
PRINT_HEADERS becomes plain column labels (rich handles padding). Drain
|
|
18
|
+
the recorded output to the debug log after each render.
|
|
19
|
+
- refactor(display): extract print_track_info into display module
|
|
20
|
+
- Add rich dependency and move PrintableField, PRINT_WIDTHS, PRINT_HEADERS,
|
|
21
|
+
truncate_field, and print_track_info from utils to a new display module
|
|
22
|
+
with a module-level Console for upcoming rich-based rendering. Update
|
|
23
|
+
edit, convert, and search command imports. Move associated tests from
|
|
24
|
+
test_utils.py to test_display.py. Pure move; no behavior change.
|
|
25
|
+
- chore(deps): update pre-commit hooks (#29)
|
|
26
|
+
- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
|
4
27
|
- feat(edit): add --multi to allow batch edits past single-track guard
|
|
5
28
|
- feat(edit): add --match for literal find/replace within field value
|
|
6
29
|
- Introduces a _compute_new_value helper and --match option so that
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rekordbox-edit
|
|
3
|
-
Version: 0.4.0.
|
|
3
|
+
Version: 0.4.0.dev26
|
|
4
4
|
Summary: Tools for managing and modifying a RekordBox library en-masse
|
|
5
5
|
Project-URL: Homepage, https://github.com/jviall/rekordbox-edit
|
|
6
6
|
Project-URL: Repository, https://github.com/jviall/rekordbox-edit
|
|
@@ -14,6 +14,7 @@ Requires-Dist: click<=9.0.0,>=8.0.0
|
|
|
14
14
|
Requires-Dist: ffmpeg-python>=0.2.0
|
|
15
15
|
Requires-Dist: platformdirs<5.0.0,>=4.3.8
|
|
16
16
|
Requires-Dist: pyrekordbox==0.4.4
|
|
17
|
+
Requires-Dist: rich<15.0.0,>=13.0.0
|
|
17
18
|
Description-Content-Type: text/markdown
|
|
18
19
|
|
|
19
20
|
# rekordbox-edit
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rekordbox-edit"
|
|
7
|
-
version = "0.4.0.
|
|
7
|
+
version = "0.4.0.dev26"
|
|
8
8
|
description = "Tools for managing and modifying a RekordBox library en-masse"
|
|
9
9
|
authors = [{ name = "James Viall", email= "jamesviall@pm.me"}]
|
|
10
10
|
license = "MIT"
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
"ffmpeg-python>=0.2.0",
|
|
17
17
|
"click>=8.0.0,<=9.0.0",
|
|
18
18
|
"platformdirs>=4.3.8,<5.0.0",
|
|
19
|
+
"rich>=13.0.0,<15.0.0",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
[dependency-groups]
|
{rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/convert.py
RENAMED
|
@@ -22,6 +22,7 @@ from rekordbox_edit._click import (
|
|
|
22
22
|
)
|
|
23
23
|
from rekordbox_edit.logger import get_debug_file_path, set_level
|
|
24
24
|
from rekordbox_edit.query import get_filtered_content
|
|
25
|
+
from rekordbox_edit.display import PrintableField, print_track_info
|
|
25
26
|
from rekordbox_edit.utils import (
|
|
26
27
|
OutputFormats,
|
|
27
28
|
UserQuit,
|
|
@@ -30,7 +31,6 @@ from rekordbox_edit.utils import (
|
|
|
30
31
|
get_extension_for_format,
|
|
31
32
|
get_file_type_for_format,
|
|
32
33
|
get_file_type_name,
|
|
33
|
-
print_track_info,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
@@ -456,7 +456,11 @@ def convert_command(
|
|
|
456
456
|
logger.info(
|
|
457
457
|
f"Found {len(files_to_process)} files to convert to {format_out.upper()}"
|
|
458
458
|
)
|
|
459
|
-
print_track_info(
|
|
459
|
+
print_track_info(
|
|
460
|
+
files_to_process,
|
|
461
|
+
changed_field=PrintableField.FileType,
|
|
462
|
+
new_values=[format_out.upper()] * len(files_to_process),
|
|
463
|
+
)
|
|
460
464
|
|
|
461
465
|
if dry_run:
|
|
462
466
|
if print_opt is PrintChoice.IDS:
|
|
@@ -16,7 +16,8 @@ from rekordbox_edit._click import (
|
|
|
16
16
|
)
|
|
17
17
|
from rekordbox_edit.logger import get_debug_file_path, set_level
|
|
18
18
|
from rekordbox_edit.query import get_filtered_content
|
|
19
|
-
from rekordbox_edit.
|
|
19
|
+
from rekordbox_edit.display import PrintableField, print_track_info
|
|
20
|
+
from rekordbox_edit.utils import UserQuit, confirm
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -169,7 +170,11 @@ def edit_command(
|
|
|
169
170
|
"Refine your filters, use --dry-run to inspect, or pass --multi to edit all."
|
|
170
171
|
)
|
|
171
172
|
|
|
172
|
-
print_track_info(
|
|
173
|
+
print_track_info(
|
|
174
|
+
[t for t, _ in edits],
|
|
175
|
+
changed_field=PrintableField[field],
|
|
176
|
+
new_values=[str(v) for _, v in edits],
|
|
177
|
+
)
|
|
173
178
|
|
|
174
179
|
if dry_run:
|
|
175
180
|
if print_opt is PrintChoice.IDS:
|
|
@@ -16,9 +16,7 @@ from rekordbox_edit._click import (
|
|
|
16
16
|
)
|
|
17
17
|
from rekordbox_edit.logger import get_debug_file_path, set_level
|
|
18
18
|
from rekordbox_edit.query import get_filtered_content
|
|
19
|
-
from rekordbox_edit.
|
|
20
|
-
print_track_info,
|
|
21
|
-
)
|
|
19
|
+
from rekordbox_edit.display import print_track_info
|
|
22
20
|
|
|
23
21
|
logger = logging.getLogger(__name__)
|
|
24
22
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Rich-based rendering for rekordbox-edit.
|
|
2
|
+
|
|
3
|
+
All rich Console output should go through the module-level ``console`` and be
|
|
4
|
+
drained to the debug log immediately after printing:
|
|
5
|
+
|
|
6
|
+
console.print(...)
|
|
7
|
+
logger.debug(console.export_text(clear=True))
|
|
8
|
+
|
|
9
|
+
Plain text output should continue to use ``logger.info()``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Dict, Sequence
|
|
16
|
+
|
|
17
|
+
from pyrekordbox.db6 import DjmdContent
|
|
18
|
+
from rich import box
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
|
|
22
|
+
from rekordbox_edit.utils import get_file_type_name
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
console = Console(record=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PrintableField(Enum):
|
|
30
|
+
"""Columns of DjmdContent that you can print"""
|
|
31
|
+
|
|
32
|
+
ID = "ID"
|
|
33
|
+
FileNameL = "FileNameL"
|
|
34
|
+
FolderPath = "FolderPath"
|
|
35
|
+
FileType = "FileType"
|
|
36
|
+
SampleRate = "SampleRate"
|
|
37
|
+
BitDepth = "BitDepth"
|
|
38
|
+
BitRate = "BitRate"
|
|
39
|
+
ArtistName = "ArtistName"
|
|
40
|
+
AlbumName = "AlbumName"
|
|
41
|
+
Title = "Title"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Column headers shown in the rendered table
|
|
45
|
+
PRINT_HEADERS: Dict[PrintableField, str] = {
|
|
46
|
+
PrintableField.ID: "ID",
|
|
47
|
+
PrintableField.FileNameL: "File",
|
|
48
|
+
PrintableField.Title: "Title",
|
|
49
|
+
PrintableField.ArtistName: "Artist",
|
|
50
|
+
PrintableField.AlbumName: "Album",
|
|
51
|
+
PrintableField.FileType: "Type",
|
|
52
|
+
PrintableField.SampleRate: "SampleRt",
|
|
53
|
+
PrintableField.BitRate: "BitRt",
|
|
54
|
+
PrintableField.BitDepth: "BitDp",
|
|
55
|
+
PrintableField.FolderPath: "Folder",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _cell_value(content: DjmdContent, column: PrintableField) -> str:
|
|
60
|
+
"""Render a single DjmdContent field as a string for a table cell."""
|
|
61
|
+
if column is PrintableField.ID:
|
|
62
|
+
return str(content.ID)
|
|
63
|
+
if column is PrintableField.FileType:
|
|
64
|
+
return get_file_type_name(content.FileType)
|
|
65
|
+
if column is PrintableField.FolderPath:
|
|
66
|
+
return os.path.dirname(content.FolderPath or "")
|
|
67
|
+
value = getattr(content, column.value)
|
|
68
|
+
return "" if value is None else str(value)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def print_track_info(
|
|
72
|
+
content_list: Sequence[DjmdContent],
|
|
73
|
+
print_columns: Sequence[PrintableField] | None = None,
|
|
74
|
+
changed_field: PrintableField | None = None,
|
|
75
|
+
new_values: Sequence[str] | None = None,
|
|
76
|
+
):
|
|
77
|
+
"""Print formatted track information.
|
|
78
|
+
|
|
79
|
+
When ``changed_field`` and ``new_values`` are both provided, the matching
|
|
80
|
+
column renders each row as a before/after preview: the old value is
|
|
81
|
+
struck through and the new value is appended.
|
|
82
|
+
"""
|
|
83
|
+
if (changed_field is None) != (new_values is None):
|
|
84
|
+
raise ValueError("changed_field and new_values must be provided together")
|
|
85
|
+
if new_values is not None and len(new_values) != len(content_list):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"new_values length ({len(new_values)}) must match content_list length ({len(content_list)})"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if not content_list:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
print_columns = print_columns or [
|
|
94
|
+
PrintableField.ID,
|
|
95
|
+
PrintableField.Title,
|
|
96
|
+
PrintableField.FileType,
|
|
97
|
+
PrintableField.SampleRate,
|
|
98
|
+
PrintableField.BitDepth,
|
|
99
|
+
PrintableField.FolderPath,
|
|
100
|
+
PrintableField.FileNameL,
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
table = Table(show_header=True, box=box.SIMPLE)
|
|
104
|
+
table.add_column("#", justify="right")
|
|
105
|
+
for column in print_columns:
|
|
106
|
+
table.add_column(PRINT_HEADERS[column], no_wrap=True, overflow="ellipsis")
|
|
107
|
+
|
|
108
|
+
for i, content in enumerate(content_list, 1):
|
|
109
|
+
cells = []
|
|
110
|
+
for col in print_columns:
|
|
111
|
+
old = _cell_value(content, col)
|
|
112
|
+
if col is changed_field and new_values is not None:
|
|
113
|
+
cells.append(f"[strike]{old}[/strike] [bold]{new_values[i - 1]}[/bold]")
|
|
114
|
+
else:
|
|
115
|
+
cells.append(old)
|
|
116
|
+
table.add_row(str(i), *cells)
|
|
117
|
+
|
|
118
|
+
console.print(table)
|
|
119
|
+
logger.debug(console.export_text(clear=True))
|
|
@@ -4,11 +4,9 @@ import logging
|
|
|
4
4
|
import platform
|
|
5
5
|
import shutil
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import Dict, Sequence
|
|
8
7
|
|
|
9
8
|
import click
|
|
10
9
|
import ffmpeg
|
|
11
|
-
from pyrekordbox.db6 import DjmdContent
|
|
12
10
|
|
|
13
11
|
logger = logging.getLogger(__name__)
|
|
14
12
|
|
|
@@ -77,108 +75,6 @@ class InputFormats(Enum):
|
|
|
77
75
|
WAV = "wav"
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
class PrintableField(Enum):
|
|
81
|
-
"""Columns of DjmdContent that you can print"""
|
|
82
|
-
|
|
83
|
-
ID = "ID"
|
|
84
|
-
FileNameL = "FileNameL"
|
|
85
|
-
FolderPath = "FolderPath"
|
|
86
|
-
FileType = "FileType"
|
|
87
|
-
SampleRate = "SampleRate"
|
|
88
|
-
BitDepth = "BitDepth"
|
|
89
|
-
BitRate = "BitRate"
|
|
90
|
-
ArtistName = "ArtistName"
|
|
91
|
-
AlbumName = "AlbumName"
|
|
92
|
-
Title = "Title"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Column widths (total ≈ 240 chars with spacing)
|
|
96
|
-
PRINT_WIDTHS: Dict[PrintableField, int] = {
|
|
97
|
-
PrintableField.ID: 10,
|
|
98
|
-
PrintableField.FileNameL: 25,
|
|
99
|
-
PrintableField.Title: 25,
|
|
100
|
-
PrintableField.ArtistName: 20,
|
|
101
|
-
PrintableField.AlbumName: 20,
|
|
102
|
-
PrintableField.FileType: 4,
|
|
103
|
-
PrintableField.SampleRate: 8,
|
|
104
|
-
PrintableField.BitRate: 5,
|
|
105
|
-
PrintableField.BitDepth: 5,
|
|
106
|
-
PrintableField.FolderPath: 80,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
# Print header
|
|
110
|
-
PRINT_HEADERS: Dict[PrintableField, str] = {
|
|
111
|
-
PrintableField.ID: f"{'ID':<{PRINT_WIDTHS[PrintableField.ID]}}",
|
|
112
|
-
PrintableField.FileNameL: f"{'File':<{PRINT_WIDTHS[PrintableField.FileNameL]}}",
|
|
113
|
-
PrintableField.Title: f"{'Title':<{PRINT_WIDTHS[PrintableField.Title]}}",
|
|
114
|
-
PrintableField.ArtistName: f"{'Artist':<{PRINT_WIDTHS[PrintableField.ArtistName]}}",
|
|
115
|
-
PrintableField.AlbumName: f"{'Album':<{PRINT_WIDTHS[PrintableField.AlbumName]}}",
|
|
116
|
-
PrintableField.FileType: f"{'Type':<{PRINT_WIDTHS[PrintableField.FileType]}}",
|
|
117
|
-
PrintableField.SampleRate: f"{'SampleRt':<{PRINT_WIDTHS[PrintableField.SampleRate]}}",
|
|
118
|
-
PrintableField.BitRate: f"{'BitRt':<{PRINT_WIDTHS[PrintableField.BitRate]}}",
|
|
119
|
-
PrintableField.BitDepth: f"{'BitDp':<{PRINT_WIDTHS[PrintableField.BitDepth]}}",
|
|
120
|
-
PrintableField.FolderPath: f"{'FolderPath':<{PRINT_WIDTHS[PrintableField.FolderPath]}}",
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def truncate_field(field: PrintableField, value: str | None):
|
|
125
|
-
if value is None:
|
|
126
|
-
return ""
|
|
127
|
-
if len(value) <= PRINT_WIDTHS[field]:
|
|
128
|
-
return value
|
|
129
|
-
available = PRINT_WIDTHS[field] - 3 # Reserve 3 chars for "..."
|
|
130
|
-
start_chars = available // 5 * 2
|
|
131
|
-
end_chars = available - start_chars
|
|
132
|
-
return f"{value[:start_chars]}...{value[-end_chars:]}"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def print_track_info(
|
|
136
|
-
content_list: Sequence[DjmdContent],
|
|
137
|
-
print_columns: Sequence[PrintableField] | None = None,
|
|
138
|
-
):
|
|
139
|
-
"""Print formatted track information"""
|
|
140
|
-
if not content_list:
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
print_columns = print_columns or [
|
|
144
|
-
PrintableField.ID,
|
|
145
|
-
PrintableField.Title,
|
|
146
|
-
PrintableField.FileType,
|
|
147
|
-
PrintableField.SampleRate,
|
|
148
|
-
PrintableField.BitDepth,
|
|
149
|
-
PrintableField.FolderPath,
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
# Calculate width for position column: 2 spaces + digits needed for max position
|
|
153
|
-
pos_width = 2 + len(str(len(content_list)))
|
|
154
|
-
header = f"{'#':<{pos_width}}" + " ".join(
|
|
155
|
-
map(lambda col: PRINT_HEADERS[col], print_columns)
|
|
156
|
-
)
|
|
157
|
-
logger.info(header)
|
|
158
|
-
logger.info("-" * len(header))
|
|
159
|
-
|
|
160
|
-
# Print each track
|
|
161
|
-
for i, content in enumerate(content_list, 1):
|
|
162
|
-
# Print row
|
|
163
|
-
rows = {
|
|
164
|
-
PrintableField.ID: f"{content.ID:<{PRINT_WIDTHS[PrintableField.ID]}}",
|
|
165
|
-
PrintableField.FileNameL: f"{truncate_field(PrintableField.FileNameL, content.FileNameL):<{PRINT_WIDTHS[PrintableField.FileNameL]}}",
|
|
166
|
-
PrintableField.Title: f"{truncate_field(PrintableField.Title, content.Title):<{PRINT_WIDTHS[PrintableField.Title]}}",
|
|
167
|
-
PrintableField.AlbumName: f"{truncate_field(PrintableField.AlbumName, content.AlbumName):<{PRINT_WIDTHS[PrintableField.AlbumName]}}",
|
|
168
|
-
PrintableField.ArtistName: f"{truncate_field(PrintableField.ArtistName, content.ArtistName):<{PRINT_WIDTHS[PrintableField.ArtistName]}}",
|
|
169
|
-
PrintableField.FileType: f"{get_file_type_name(content.FileType):<{PRINT_WIDTHS[PrintableField.FileType]}}",
|
|
170
|
-
PrintableField.SampleRate: f"{content.SampleRate:<{PRINT_WIDTHS[PrintableField.SampleRate]}}",
|
|
171
|
-
PrintableField.BitRate: f"{content.BitRate:<{PRINT_WIDTHS[PrintableField.BitRate]}}",
|
|
172
|
-
PrintableField.BitDepth: f"{content.BitDepth:<{PRINT_WIDTHS[PrintableField.BitDepth]}}",
|
|
173
|
-
PrintableField.FolderPath: f"{truncate_field(PrintableField.FolderPath, content.FolderPath):<{PRINT_WIDTHS[PrintableField.FolderPath]}}",
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
row = f"{i:<{pos_width}}" + " ".join(map(lambda col: rows[col], print_columns))
|
|
177
|
-
|
|
178
|
-
logger.info(row)
|
|
179
|
-
logger.info("")
|
|
180
|
-
|
|
181
|
-
|
|
182
78
|
def ffmpeg_in_path():
|
|
183
79
|
"""Check availability of ffmpeg program via which command"""
|
|
184
80
|
return shutil.which("ffmpeg") is not None
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
":label(dependencies)",
|
|
8
8
|
":enablePreCommit",
|
|
9
9
|
":automergePr",
|
|
10
|
+
"helpers:pinGitHubActionDigests",
|
|
10
11
|
"schedule:daily",
|
|
11
12
|
"schedule:automergeDaily",
|
|
12
13
|
],
|
|
@@ -14,18 +15,19 @@
|
|
|
14
15
|
branchPrefix: "chore/deps/",
|
|
15
16
|
commitMessageAction: "update",
|
|
16
17
|
minimumReleaseAge: "3 days",
|
|
18
|
+
// Keep version ranges; bump uv.lock to the newest in-range version.
|
|
19
|
+
rangeStrategy: "update-lockfile",
|
|
17
20
|
|
|
18
21
|
packageRules: [
|
|
19
22
|
{
|
|
20
|
-
description: "GitHub Actions:
|
|
23
|
+
description: "GitHub Actions: auto-merge digest pins and minor/patch",
|
|
21
24
|
matchManagers: ["github-actions"],
|
|
22
|
-
|
|
23
|
-
matchUpdateTypes: ["minor", "patch", "digest"],
|
|
25
|
+
matchUpdateTypes: ["minor", "patch", "digest", "pinDigest"],
|
|
24
26
|
automerge: true,
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
description: "Python deps: auto-merge minor/patch for uv-managed packages",
|
|
28
|
-
matchManagers: ["
|
|
30
|
+
matchManagers: ["pep621"],
|
|
29
31
|
matchUpdateTypes: ["minor", "patch"],
|
|
30
32
|
automerge: true,
|
|
31
33
|
},
|
|
@@ -16,6 +16,7 @@ from rekordbox_edit.commands.convert import (
|
|
|
16
16
|
rollback_and_cleanup,
|
|
17
17
|
update_database_record,
|
|
18
18
|
)
|
|
19
|
+
from rekordbox_edit.display import PrintableField
|
|
19
20
|
from rekordbox_edit.utils import OutputFormats, UserQuit
|
|
20
21
|
|
|
21
22
|
|
|
@@ -671,7 +672,11 @@ class TestConvertCommand:
|
|
|
671
672
|
result = CliRunner().invoke(convert_command, ["--dry-run"])
|
|
672
673
|
|
|
673
674
|
assert result.exit_code == 0
|
|
674
|
-
mock_print_track_info.assert_called_once_with(
|
|
675
|
+
mock_print_track_info.assert_called_once_with(
|
|
676
|
+
[mock_content],
|
|
677
|
+
changed_field=PrintableField.FileType,
|
|
678
|
+
new_values=["AIFF"],
|
|
679
|
+
)
|
|
675
680
|
mock_db.session.commit.assert_not_called()
|
|
676
681
|
|
|
677
682
|
@patch("rekordbox_edit.commands.convert.get_rekordbox_pid")
|
|
@@ -804,7 +809,11 @@ class TestConvertCommand:
|
|
|
804
809
|
result = runner.invoke(convert_command, ["--dry-run"])
|
|
805
810
|
|
|
806
811
|
assert result.exit_code == 0
|
|
807
|
-
mock_print_track_info.assert_called_once_with(
|
|
812
|
+
mock_print_track_info.assert_called_once_with(
|
|
813
|
+
[mock_flac_content],
|
|
814
|
+
changed_field=PrintableField.FileType,
|
|
815
|
+
new_values=["AIFF"],
|
|
816
|
+
)
|
|
808
817
|
|
|
809
818
|
@patch("rekordbox_edit.commands.convert.get_rekordbox_pid")
|
|
810
819
|
@patch("rekordbox_edit.commands.convert.get_filtered_content")
|
|
@@ -1570,7 +1579,11 @@ class TestConvertCommandErrorPaths:
|
|
|
1570
1579
|
result = CliRunner().invoke(convert_command, ["--yes", "--dry-run"])
|
|
1571
1580
|
|
|
1572
1581
|
assert result.exit_code == 0
|
|
1573
|
-
mock_print_track_info.assert_called_once_with(
|
|
1582
|
+
mock_print_track_info.assert_called_once_with(
|
|
1583
|
+
[content2],
|
|
1584
|
+
changed_field=PrintableField.FileType,
|
|
1585
|
+
new_values=["AIFF"],
|
|
1586
|
+
)
|
|
1574
1587
|
|
|
1575
1588
|
@patch("rekordbox_edit.commands.convert.print_track_info")
|
|
1576
1589
|
@patch("rekordbox_edit.commands.convert.get_rekordbox_pid")
|
|
@@ -1613,7 +1626,11 @@ class TestConvertCommandErrorPaths:
|
|
|
1613
1626
|
|
|
1614
1627
|
assert result.exit_code == 0
|
|
1615
1628
|
mock_logger.warning.assert_called()
|
|
1616
|
-
mock_print_track_info.assert_called_once_with(
|
|
1629
|
+
mock_print_track_info.assert_called_once_with(
|
|
1630
|
+
[content2],
|
|
1631
|
+
changed_field=PrintableField.FileType,
|
|
1632
|
+
new_values=["AIFF"],
|
|
1633
|
+
)
|
|
1617
1634
|
|
|
1618
1635
|
@patch("rekordbox_edit.commands.convert.sys")
|
|
1619
1636
|
@patch("rekordbox_edit.commands.convert.confirm")
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Unit tests for the display module."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from rekordbox_edit import display
|
|
7
|
+
from rekordbox_edit.display import (
|
|
8
|
+
PrintableField,
|
|
9
|
+
print_track_info,
|
|
10
|
+
)
|
|
11
|
+
from rekordbox_edit.utils import get_file_type_name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def wide_console(monkeypatch):
|
|
16
|
+
"""Swap the module console for one wide enough to render without truncation.
|
|
17
|
+
|
|
18
|
+
Rich's default non-TTY width is 80 cols, which truncates long values
|
|
19
|
+
(e.g. FolderPath) with an ellipsis and makes substring assertions flaky.
|
|
20
|
+
"""
|
|
21
|
+
monkeypatch.setattr(display, "console", Console(record=True, width=400))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestPrintTrackInfo:
|
|
25
|
+
"""Test print_track_info function."""
|
|
26
|
+
|
|
27
|
+
TEST_PRINT_COLUMNS = [
|
|
28
|
+
PrintableField.ID,
|
|
29
|
+
PrintableField.FileNameL,
|
|
30
|
+
PrintableField.Title,
|
|
31
|
+
PrintableField.ArtistName,
|
|
32
|
+
PrintableField.AlbumName,
|
|
33
|
+
PrintableField.FileType,
|
|
34
|
+
PrintableField.SampleRate,
|
|
35
|
+
PrintableField.BitDepth,
|
|
36
|
+
PrintableField.BitRate,
|
|
37
|
+
PrintableField.FolderPath,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
def test_empty_content_list(self, capsys):
|
|
41
|
+
"""Test printing with empty content list."""
|
|
42
|
+
print_track_info([])
|
|
43
|
+
|
|
44
|
+
captured = capsys.readouterr()
|
|
45
|
+
assert captured.out == ""
|
|
46
|
+
|
|
47
|
+
def test_default_columns(
|
|
48
|
+
self,
|
|
49
|
+
capsys,
|
|
50
|
+
wide_console,
|
|
51
|
+
make_djmd_content_item,
|
|
52
|
+
):
|
|
53
|
+
"""Default columns include ID, Title, FileType, SampleRate, BitDepth, FileNameL, FolderPath."""
|
|
54
|
+
mock_content = make_djmd_content_item(
|
|
55
|
+
FileNameL="my-song.flac",
|
|
56
|
+
FolderPath="/music/library/my-song.flac",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print_track_info([mock_content])
|
|
60
|
+
|
|
61
|
+
captured = capsys.readouterr()
|
|
62
|
+
assert mock_content.Title in captured.out
|
|
63
|
+
assert get_file_type_name(mock_content.FileType) in captured.out
|
|
64
|
+
assert str(mock_content.SampleRate) in captured.out
|
|
65
|
+
assert str(mock_content.BitDepth) in captured.out
|
|
66
|
+
assert "my-song.flac" in captured.out
|
|
67
|
+
# FolderPath renders the directory portion, not the full path
|
|
68
|
+
assert "/music/library" in captured.out
|
|
69
|
+
|
|
70
|
+
def test_track_with_zero_values(self, capsys, wide_console, make_djmd_content_item):
|
|
71
|
+
"""Test printing track with zero values."""
|
|
72
|
+
# Setup mock content with zero values
|
|
73
|
+
mock_content = make_djmd_content_item(
|
|
74
|
+
ID=123,
|
|
75
|
+
SampleRate=0,
|
|
76
|
+
BitRate=0,
|
|
77
|
+
BitDepth=0,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
print_track_info([mock_content], self.TEST_PRINT_COLUMNS)
|
|
81
|
+
|
|
82
|
+
captured = capsys.readouterr()
|
|
83
|
+
lines = captured.out.split("\n")
|
|
84
|
+
data_line = [line for line in lines if "test" in line][0]
|
|
85
|
+
assert data_line.count("0") == 3
|
|
86
|
+
|
|
87
|
+
def test_change_preview_renders_old_struck_through_with_new(
|
|
88
|
+
self, capsys, wide_console, make_djmd_content_item
|
|
89
|
+
):
|
|
90
|
+
"""When changed_field + new_values are provided, both old and new appear in the cell."""
|
|
91
|
+
mock_content = make_djmd_content_item(Title="Old Name")
|
|
92
|
+
|
|
93
|
+
print_track_info(
|
|
94
|
+
[mock_content],
|
|
95
|
+
print_columns=[PrintableField.Title],
|
|
96
|
+
changed_field=PrintableField.Title,
|
|
97
|
+
new_values=["New Name"],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
captured = capsys.readouterr()
|
|
101
|
+
assert "Old Name" in captured.out
|
|
102
|
+
assert "New Name" in captured.out
|
|
103
|
+
|
|
104
|
+
def test_change_preview_requires_both_args(self, make_djmd_content_item):
|
|
105
|
+
"""Providing only one of changed_field/new_values raises ValueError."""
|
|
106
|
+
mock_content = make_djmd_content_item()
|
|
107
|
+
|
|
108
|
+
with pytest.raises(ValueError, match="must be provided together"):
|
|
109
|
+
print_track_info(
|
|
110
|
+
[mock_content], changed_field=PrintableField.Title
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
with pytest.raises(ValueError, match="must be provided together"):
|
|
114
|
+
print_track_info([mock_content], new_values=["x"])
|
|
115
|
+
|
|
116
|
+
def test_change_preview_length_mismatch_raises(self, make_djmd_content_item):
|
|
117
|
+
"""new_values length must match content_list length."""
|
|
118
|
+
mock_content = make_djmd_content_item()
|
|
119
|
+
|
|
120
|
+
with pytest.raises(ValueError, match="length"):
|
|
121
|
+
print_track_info(
|
|
122
|
+
[mock_content],
|
|
123
|
+
changed_field=PrintableField.Title,
|
|
124
|
+
new_values=["a", "b"],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def test_multiple_tracks(self, capsys, wide_console, make_djmd_content_item):
|
|
128
|
+
"""Test printing multiple tracks."""
|
|
129
|
+
mock_content1 = make_djmd_content_item(
|
|
130
|
+
ID=123,
|
|
131
|
+
FileNameL="track1.flac",
|
|
132
|
+
FileType=5,
|
|
133
|
+
FolderPath="/path/track1.flac",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
mock_content2 = make_djmd_content_item(
|
|
137
|
+
ID=456,
|
|
138
|
+
FileNameL="track2.mp3",
|
|
139
|
+
FileType=1,
|
|
140
|
+
FolderPath="/path/track2.mp3",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
print_track_info([mock_content1, mock_content2])
|
|
144
|
+
|
|
145
|
+
captured = capsys.readouterr()
|
|
146
|
+
assert "track1.flac" in captured.out
|
|
147
|
+
assert "track2.mp3" in captured.out
|
|
148
|
+
assert "FLAC" in captured.out
|
|
149
|
+
assert "MP3" in captured.out
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
"""Unit tests for utils module functionality."""
|
|
2
2
|
|
|
3
|
-
from typing import Callable
|
|
4
3
|
from unittest.mock import patch
|
|
5
4
|
|
|
6
5
|
import pytest
|
|
7
|
-
from pyrekordbox.db6 import DjmdContent
|
|
8
6
|
|
|
9
7
|
from rekordbox_edit.utils import (
|
|
10
|
-
PRINT_WIDTHS,
|
|
11
|
-
PrintableField,
|
|
12
8
|
UserQuit,
|
|
13
9
|
get_audio_info,
|
|
14
10
|
get_extension_for_format,
|
|
15
11
|
get_file_type_for_format,
|
|
16
12
|
get_file_type_name,
|
|
17
|
-
print_track_info,
|
|
18
13
|
)
|
|
19
14
|
|
|
20
15
|
|
|
@@ -96,158 +91,6 @@ class TestGetGetExtensionForFormat:
|
|
|
96
91
|
get_extension_for_format(None) # ty: ignore[invalid-argument-type]
|
|
97
92
|
|
|
98
93
|
|
|
99
|
-
class TestTruncateField:
|
|
100
|
-
"""Test truncate_field function."""
|
|
101
|
-
|
|
102
|
-
def test_truncate_field_none_value(self):
|
|
103
|
-
"""Test truncate_field returns empty string for None value."""
|
|
104
|
-
from rekordbox_edit.utils import PrintableField, truncate_field
|
|
105
|
-
|
|
106
|
-
result = truncate_field(PrintableField.Title, None)
|
|
107
|
-
assert result == ""
|
|
108
|
-
|
|
109
|
-
def test_truncate_field_empty_string(self):
|
|
110
|
-
"""Test truncate_field with empty string."""
|
|
111
|
-
from rekordbox_edit.utils import PrintableField, truncate_field
|
|
112
|
-
|
|
113
|
-
result = truncate_field(PrintableField.Title, "")
|
|
114
|
-
assert result == ""
|
|
115
|
-
|
|
116
|
-
def test_truncate_field_short_value(self):
|
|
117
|
-
"""Test truncate_field returns value as-is when it fits."""
|
|
118
|
-
from rekordbox_edit.utils import PrintableField, truncate_field
|
|
119
|
-
|
|
120
|
-
short_title = "Short Title"
|
|
121
|
-
result = truncate_field(PrintableField.Title, short_title)
|
|
122
|
-
assert result == short_title
|
|
123
|
-
|
|
124
|
-
def test_truncate_field_exact_width(self):
|
|
125
|
-
"""Test truncate_field with value exactly at width limit."""
|
|
126
|
-
from rekordbox_edit.utils import (
|
|
127
|
-
PRINT_WIDTHS,
|
|
128
|
-
PrintableField,
|
|
129
|
-
truncate_field,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Create a value exactly the width of Title field (25 chars)
|
|
133
|
-
exact_width_title = "X" * PRINT_WIDTHS[PrintableField.Title]
|
|
134
|
-
result = truncate_field(PrintableField.Title, exact_width_title)
|
|
135
|
-
assert result == exact_width_title
|
|
136
|
-
|
|
137
|
-
def test_truncate_field_long_value(self):
|
|
138
|
-
"""Test truncate_field truncates long values with ellipsis."""
|
|
139
|
-
from rekordbox_edit.utils import PrintableField, truncate_field
|
|
140
|
-
|
|
141
|
-
long_title = "This is a very long title that exceeds the width limit"
|
|
142
|
-
result = truncate_field(PrintableField.Title, long_title)
|
|
143
|
-
|
|
144
|
-
assert "..." in result
|
|
145
|
-
assert len(result) == PRINT_WIDTHS[PrintableField.Title]
|
|
146
|
-
|
|
147
|
-
def test_truncate_field_minimal_truncation(self):
|
|
148
|
-
"""Test truncate_field with value just over the limit."""
|
|
149
|
-
from rekordbox_edit.utils import (
|
|
150
|
-
PRINT_WIDTHS,
|
|
151
|
-
PrintableField,
|
|
152
|
-
truncate_field,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# Create a value just 1 char over the limit
|
|
156
|
-
over_limit_title = "X" * (PRINT_WIDTHS[PrintableField.Title] + 1)
|
|
157
|
-
result = truncate_field(PrintableField.Title, over_limit_title)
|
|
158
|
-
|
|
159
|
-
assert "..." in result
|
|
160
|
-
assert len(result) == PRINT_WIDTHS[PrintableField.Title]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class TestPrintTrackInfo:
|
|
164
|
-
"""Test print_track_info function."""
|
|
165
|
-
|
|
166
|
-
TEST_PRINT_COLUMNS = [
|
|
167
|
-
PrintableField.ID,
|
|
168
|
-
PrintableField.FileNameL,
|
|
169
|
-
PrintableField.Title,
|
|
170
|
-
PrintableField.ArtistName,
|
|
171
|
-
PrintableField.AlbumName,
|
|
172
|
-
PrintableField.FileType,
|
|
173
|
-
PrintableField.SampleRate,
|
|
174
|
-
PrintableField.BitDepth,
|
|
175
|
-
PrintableField.BitRate,
|
|
176
|
-
PrintableField.FolderPath,
|
|
177
|
-
]
|
|
178
|
-
|
|
179
|
-
def test_empty_content_list(self, capsys):
|
|
180
|
-
"""Test printing with empty content list."""
|
|
181
|
-
print_track_info([])
|
|
182
|
-
|
|
183
|
-
captured = capsys.readouterr()
|
|
184
|
-
assert captured.out == ""
|
|
185
|
-
|
|
186
|
-
def test_default_columns(
|
|
187
|
-
self, capsys, make_djmd_content_item: Callable[[], DjmdContent]
|
|
188
|
-
):
|
|
189
|
-
"""Test printing a single track with the default print_columns.
|
|
190
|
-
|
|
191
|
-
Default columns are: ID, Title, FileType, SampleRate, BitDepth, FolderPath
|
|
192
|
-
(ArtistName and AlbumName are NOT included by default)
|
|
193
|
-
"""
|
|
194
|
-
# Setup mock content
|
|
195
|
-
mock_content = make_djmd_content_item()
|
|
196
|
-
|
|
197
|
-
print_track_info([mock_content])
|
|
198
|
-
|
|
199
|
-
captured = capsys.readouterr()
|
|
200
|
-
# Check default columns are present
|
|
201
|
-
assert mock_content.Title in captured.out
|
|
202
|
-
assert get_file_type_name(mock_content.FileType) in captured.out
|
|
203
|
-
assert str(mock_content.SampleRate) in captured.out
|
|
204
|
-
assert str(mock_content.BitDepth) in captured.out
|
|
205
|
-
# FolderPath should be in output (may or may not be truncated depending on length)
|
|
206
|
-
# The default test path is 66 chars, column width is 80, so no truncation
|
|
207
|
-
assert "test_track.wav" in captured.out
|
|
208
|
-
|
|
209
|
-
def test_track_with_zero_values(self, capsys, make_djmd_content_item):
|
|
210
|
-
"""Test printing track with zero values."""
|
|
211
|
-
# Setup mock content with zero values
|
|
212
|
-
mock_content = make_djmd_content_item(
|
|
213
|
-
ID=123,
|
|
214
|
-
SampleRate=0,
|
|
215
|
-
BitRate=0,
|
|
216
|
-
BitDepth=0,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
print_track_info([mock_content], self.TEST_PRINT_COLUMNS)
|
|
220
|
-
|
|
221
|
-
captured = capsys.readouterr()
|
|
222
|
-
lines = captured.out.split("\n")
|
|
223
|
-
data_line = [line for line in lines if "test" in line][0]
|
|
224
|
-
assert data_line.count("0") == 3
|
|
225
|
-
|
|
226
|
-
def test_multiple_tracks(self, capsys, make_djmd_content_item):
|
|
227
|
-
"""Test printing multiple tracks."""
|
|
228
|
-
mock_content1 = make_djmd_content_item(
|
|
229
|
-
ID=123,
|
|
230
|
-
FileNameL="track1.flac",
|
|
231
|
-
FileType=5,
|
|
232
|
-
FolderPath="/path/track1.flac",
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
mock_content2 = make_djmd_content_item(
|
|
236
|
-
ID=456,
|
|
237
|
-
FileNameL="track2.mp3",
|
|
238
|
-
FileType=1,
|
|
239
|
-
FolderPath="/path/track2.mp3",
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
print_track_info([mock_content1, mock_content2])
|
|
243
|
-
|
|
244
|
-
captured = capsys.readouterr()
|
|
245
|
-
assert "track1.flac" in captured.out
|
|
246
|
-
assert "track2.mp3" in captured.out
|
|
247
|
-
assert "FLAC" in captured.out
|
|
248
|
-
assert "MP3" in captured.out
|
|
249
|
-
|
|
250
|
-
|
|
251
94
|
class TestGetAudioInfo:
|
|
252
95
|
"""Test get_audio_info function."""
|
|
253
96
|
|
|
@@ -472,6 +472,18 @@ wheels = [
|
|
|
472
472
|
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
|
473
473
|
]
|
|
474
474
|
|
|
475
|
+
[[package]]
|
|
476
|
+
name = "markdown-it-py"
|
|
477
|
+
version = "4.2.0"
|
|
478
|
+
source = { registry = "https://pypi.org/simple" }
|
|
479
|
+
dependencies = [
|
|
480
|
+
{ name = "mdurl" },
|
|
481
|
+
]
|
|
482
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
|
|
483
|
+
wheels = [
|
|
484
|
+
{ url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
|
|
485
|
+
]
|
|
486
|
+
|
|
475
487
|
[[package]]
|
|
476
488
|
name = "markupsafe"
|
|
477
489
|
version = "3.0.3"
|
|
@@ -557,6 +569,15 @@ wheels = [
|
|
|
557
569
|
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
|
558
570
|
]
|
|
559
571
|
|
|
572
|
+
[[package]]
|
|
573
|
+
name = "mdurl"
|
|
574
|
+
version = "0.1.2"
|
|
575
|
+
source = { registry = "https://pypi.org/simple" }
|
|
576
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
577
|
+
wheels = [
|
|
578
|
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
579
|
+
]
|
|
580
|
+
|
|
560
581
|
[[package]]
|
|
561
582
|
name = "nodeenv"
|
|
562
583
|
version = "1.10.0"
|
|
@@ -985,13 +1006,14 @@ wheels = [
|
|
|
985
1006
|
|
|
986
1007
|
[[package]]
|
|
987
1008
|
name = "rekordbox-edit"
|
|
988
|
-
version = "0.4.0.
|
|
1009
|
+
version = "0.4.0.dev26"
|
|
989
1010
|
source = { editable = "." }
|
|
990
1011
|
dependencies = [
|
|
991
1012
|
{ name = "click" },
|
|
992
1013
|
{ name = "ffmpeg-python" },
|
|
993
1014
|
{ name = "platformdirs" },
|
|
994
1015
|
{ name = "pyrekordbox" },
|
|
1016
|
+
{ name = "rich" },
|
|
995
1017
|
]
|
|
996
1018
|
|
|
997
1019
|
[package.dev-dependencies]
|
|
@@ -1013,6 +1035,7 @@ requires-dist = [
|
|
|
1013
1035
|
{ name = "ffmpeg-python", specifier = ">=0.2.0" },
|
|
1014
1036
|
{ name = "platformdirs", specifier = ">=4.3.8,<5.0.0" },
|
|
1015
1037
|
{ name = "pyrekordbox", specifier = "==0.4.4" },
|
|
1038
|
+
{ name = "rich", specifier = ">=13.0.0,<15.0.0" },
|
|
1016
1039
|
]
|
|
1017
1040
|
|
|
1018
1041
|
[package.metadata.requires-dev]
|
|
@@ -1028,29 +1051,42 @@ dev = [
|
|
|
1028
1051
|
{ name = "ty", specifier = ">=0.0.1,<1" },
|
|
1029
1052
|
]
|
|
1030
1053
|
|
|
1054
|
+
[[package]]
|
|
1055
|
+
name = "rich"
|
|
1056
|
+
version = "14.3.4"
|
|
1057
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1058
|
+
dependencies = [
|
|
1059
|
+
{ name = "markdown-it-py" },
|
|
1060
|
+
{ name = "pygments" },
|
|
1061
|
+
]
|
|
1062
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" }
|
|
1063
|
+
wheels = [
|
|
1064
|
+
{ url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" },
|
|
1065
|
+
]
|
|
1066
|
+
|
|
1031
1067
|
[[package]]
|
|
1032
1068
|
name = "ruff"
|
|
1033
|
-
version = "0.15.
|
|
1069
|
+
version = "0.15.14"
|
|
1034
1070
|
source = { registry = "https://pypi.org/simple" }
|
|
1035
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1071
|
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" }
|
|
1036
1072
|
wheels = [
|
|
1037
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1038
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1039
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1040
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1041
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1042
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1043
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1044
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1045
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1046
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1047
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1048
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1049
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1050
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1051
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1052
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1053
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1073
|
+
{ url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" },
|
|
1074
|
+
{ url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" },
|
|
1075
|
+
{ url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" },
|
|
1076
|
+
{ url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" },
|
|
1077
|
+
{ url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" },
|
|
1078
|
+
{ url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" },
|
|
1079
|
+
{ url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" },
|
|
1080
|
+
{ url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" },
|
|
1081
|
+
{ url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" },
|
|
1082
|
+
{ url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" },
|
|
1083
|
+
{ url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" },
|
|
1084
|
+
{ url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" },
|
|
1085
|
+
{ url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" },
|
|
1086
|
+
{ url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" },
|
|
1087
|
+
{ url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" },
|
|
1088
|
+
{ url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" },
|
|
1089
|
+
{ url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" },
|
|
1054
1090
|
]
|
|
1055
1091
|
|
|
1056
1092
|
[[package]]
|
|
@@ -1300,26 +1336,27 @@ wheels = [
|
|
|
1300
1336
|
|
|
1301
1337
|
[[package]]
|
|
1302
1338
|
name = "ty"
|
|
1303
|
-
version = "0.0.
|
|
1339
|
+
version = "0.0.39"
|
|
1304
1340
|
source = { registry = "https://pypi.org/simple" }
|
|
1305
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1341
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d0/8d/7b5c74dc287fbcb37bae9853cec13bf44717c1735298500e4aeba31579a9/ty-0.0.39.tar.gz", hash = "sha256:f750277e76a01ecd86185960eca73823c26a53c51103568d56d4d904575159fd", size = 5702365, upload-time = "2026-05-22T21:09:56.403Z" }
|
|
1306
1342
|
wheels = [
|
|
1307
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1308
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1309
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1310
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1311
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1312
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1313
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1314
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1315
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1316
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1317
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1318
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1319
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1320
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1321
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1322
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1343
|
+
{ url = "https://files.pythonhosted.org/packages/08/17/9b89802c26d12d0f7a27bc25d4066d941d42891e8898f9f26499f0067e32/ty-0.0.39-py3-none-linux_armv6l.whl", hash = "sha256:c1bb7ac70f1f7d70cc6655fd96558039e4562b10f489fa49c7ebfd5fcee73ad1", size = 11360431, upload-time = "2026-05-22T21:09:18.689Z" },
|
|
1344
|
+
{ url = "https://files.pythonhosted.org/packages/9c/c6/663ded50e823dbf9fb9d002eca46b7cb1fb2c72b744b84f22ce732a0ee0b/ty-0.0.39-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3435b64c1e59c14c9aa39c20cc018823937cd38d55db853e74d95b8f420569b0", size = 11096281, upload-time = "2026-05-22T21:09:15.383Z" },
|
|
1345
|
+
{ url = "https://files.pythonhosted.org/packages/8b/ae/5d38ba9a6456ff4c78d212cf464fd8b9a25d8118465197b0b2dc891c0b19/ty-0.0.39-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5f136377ce46c73677701a9e1ad730bf72f699bcec046e422eb79d0886cac3ab", size = 10529674, upload-time = "2026-05-22T21:09:46.471Z" },
|
|
1346
|
+
{ url = "https://files.pythonhosted.org/packages/be/6f/43638cb8106445d3c8817256a0731cde9dd7b6a53ae2e881294bc1930ca3/ty-0.0.39-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b65fb0cc17f03e851d40e210d420be94ab8bc52d041328ad1e45f616036a61", size = 11055561, upload-time = "2026-05-22T21:09:36.981Z" },
|
|
1347
|
+
{ url = "https://files.pythonhosted.org/packages/91/17/95e62cf4458527ce78dc386eba18f8b10c3fb64cd8c9e7e59b262ff6029d/ty-0.0.39-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4967967bfadf3860ff84c3fccdbaec8edf8aa20d0d727521084733d853de6657", size = 11127185, upload-time = "2026-05-22T21:09:31.395Z" },
|
|
1348
|
+
{ url = "https://files.pythonhosted.org/packages/4e/c0/93666c213db5c71ab1b1f1a0db5f66bf8c7c0e0b0bf59859f5da8f0b3c36/ty-0.0.39-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e10ecb1297099ddf9a1f054f8bd921d1863ce85fb819a3c96ed27865a1ba6ed", size = 11608459, upload-time = "2026-05-22T21:09:12.862Z" },
|
|
1349
|
+
{ url = "https://files.pythonhosted.org/packages/79/85/3b26585afc8b50230d6464bb0642feef4fab3f847e38b1f0ffa971a81446/ty-0.0.39-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b19cca70e465d71b0510656343883d62372bbe74b7845cae7c0e701d6d5264b", size = 12177101, upload-time = "2026-05-22T21:09:40.519Z" },
|
|
1350
|
+
{ url = "https://files.pythonhosted.org/packages/49/4a/1039e4f6afc576dc1c3a4d22a6478904a1ad3766597cd0b93c077ab9dfce/ty-0.0.39-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56c6704b01b9b3d80ff26b2918423b742516d1e469bef830e9254dcedc9185bf", size = 11827815, upload-time = "2026-05-22T21:09:49.89Z" },
|
|
1351
|
+
{ url = "https://files.pythonhosted.org/packages/e2/c5/4688652870e350a76a8157f7ffb59ad54f37d5d10725aa7076f66ac94ec8/ty-0.0.39-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b7840ff46764b6a6757f4ade1cd0530fc3e8a0b435ca93e7602360e4cb90b6", size = 11694429, upload-time = "2026-05-22T21:09:21.568Z" },
|
|
1352
|
+
{ url = "https://files.pythonhosted.org/packages/fc/72/8a1c4e823bb5bdc935a1c8140e100304e36a68a4139592f170aa9736fdb7/ty-0.0.39-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1c62a3a87ce26b50819f0dbf03bd95f23f19eeb87bbc7aa732ec64277c77f1aa", size = 11869846, upload-time = "2026-05-22T21:09:28.053Z" },
|
|
1353
|
+
{ url = "https://files.pythonhosted.org/packages/17/9f/cf982457b861ae22d657c5dcdbc631199f7f90264279db1d17230dfbc3ff/ty-0.0.39-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f8c34bc81a9c3516e49904e9d8330aac385377cca98390193ea02b903a40fcf0", size = 11029763, upload-time = "2026-05-22T21:09:06.791Z" },
|
|
1354
|
+
{ url = "https://files.pythonhosted.org/packages/46/c9/95b64f6d43ae6e8f0b7e13dacf9c196d35819af22b1924171fba31383156/ty-0.0.39-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:66f5ab11586a64e79cb692ad685ee5469325c31b5f30bd3554f52f36dbe28cc4", size = 11146761, upload-time = "2026-05-22T21:09:10.178Z" },
|
|
1355
|
+
{ url = "https://files.pythonhosted.org/packages/52/69/0a89cfb06f7632a05bf56c78e0affb4a40f81759e275376cea75c9c5abe9/ty-0.0.39-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e8d89732bcbbcb091f439e556dfc4932f198b118b47d5b85212c60662099670e", size = 11281843, upload-time = "2026-05-22T21:09:34.234Z" },
|
|
1356
|
+
{ url = "https://files.pythonhosted.org/packages/0e/53/64c4a27067a46643fea2b3fcf21a8a2f838d91a65ffdd14f2e82945b9538/ty-0.0.39-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:eceb6c91dcd05a231119f82abdd9aa337513de23ca6ac990bc44f88791dc1799", size = 11792477, upload-time = "2026-05-22T21:09:24.923Z" },
|
|
1357
|
+
{ url = "https://files.pythonhosted.org/packages/1a/e8/02f4dd4a12bcdbda0006f9c7ff3b99a4be06bd0d257d3bd4a5b66de074e6/ty-0.0.39-py3-none-win32.whl", hash = "sha256:891c3262314dbc80bf3e872634d23dd216306945daa9a9fcc206ce5ed21ac4c9", size = 10615377, upload-time = "2026-05-22T21:09:43.167Z" },
|
|
1358
|
+
{ url = "https://files.pythonhosted.org/packages/b5/5a/aaeb22faa8d4dae90a287d4c3636c671edcff3b99be5f4fc8b79ad71eef6/ty-0.0.39-py3-none-win_amd64.whl", hash = "sha256:ba7f2d54452535419e90f6f03ff39282999e87b43c21c00559f6d7ad711a36d5", size = 11710711, upload-time = "2026-05-22T21:09:53.179Z" },
|
|
1359
|
+
{ url = "https://files.pythonhosted.org/packages/a3/17/ae7339651bfcaa5f54698c8c70eaf5031baa400ecb67baec31d03a56cbd4/ty-0.0.39-py3-none-win_arm64.whl", hash = "sha256:eb4cf0fefbbfedf9a352597bb2431ebdcb7eb3a595c0f825f228e897a0ec285d", size = 11081409, upload-time = "2026-05-22T21:09:03.741Z" },
|
|
1323
1360
|
]
|
|
1324
1361
|
|
|
1325
1362
|
[[package]]
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/.github/actions/commitizen-bump/action.yml
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.4.0.dev23 → rekordbox_edit-0.4.0.dev26}/rekordbox_edit/commands/__init__.py
RENAMED
|
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
|