rekordbox-edit 0.3.0.dev16__tar.gz → 0.4.0.dev18__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.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/workflows/publish.yml +1 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/CHANGELOG.md +10 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/Makefile +3 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/PKG-INFO +11 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/README.md +10 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/pyproject.toml +2 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/_click.py +12 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/commands/convert.py +4 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/commands/search.py +8 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/query.py +68 -4
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/commands/test_search.py +13 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/test_query.py +162 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/uv.lock +48 -1
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/commitizen-bump/action.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/commitizen-bump/commitizen-bump.sh +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/install/action.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/lint/action.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/test/action.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/workflows/cd.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/workflows/ci.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/workflows/release.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.gitignore +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.pre-commit-config.yaml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/CONTRIBUTING.md +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/LICENSE +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/codecov.yml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/__init__.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/cli.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/commands/__init__.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/logger.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/utils.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/ruff.toml +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/__init__.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/commands/__init__.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/commands/test_convert.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/conftest.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/test_logger.py +0 -0
- {rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
## v0.
|
|
1
|
+
## v0.4.0.dev18 (2026-05-16)
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
- feat: Add --path and --exact-path search filters
|
|
5
|
+
- chore: add pytest-watcher and watch task in Make
|
|
6
|
+
- feat(CollectionQuery): add by_path query filter
|
|
7
|
+
|
|
8
|
+
## v0.3.1 (2026-04-17)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
- chore: version bump 0.3.1
|
|
12
|
+
- ci: allow dispatching of publish workflow
|
|
4
13
|
- chore: big 'ol project rename cause it was too long
|
|
5
14
|
- docs: update readme and contributing
|
|
6
15
|
- chore: change commitizen config and providers to work with uv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rekordbox-edit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0.dev18
|
|
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
|
|
@@ -151,6 +151,8 @@ Both commands support all filters. Multiple values create an OR filter unless `-
|
|
|
151
151
|
- `--playlist TEXT`: Playlist name contains TEXT
|
|
152
152
|
- `--exact-playlist TEXT`: Playlist name exactly matches TEXT
|
|
153
153
|
- `--format [mp3|flac|aiff|wav|m4a]`: File format matches
|
|
154
|
+
- `--path TEXT`: File path contains TEXT (matched as a substring against the folder path, filename, or both)
|
|
155
|
+
- `--exact-path TEXT`: File path exactly matches TEXT (resolved to an absolute path before matching)
|
|
154
156
|
- `--match-all`: Use AND logic (all filters must match)
|
|
155
157
|
- `ids` args: Specifying any other input to a command that is not a defined option is interpreted as one or more Track IDs. This is useful for scripting.
|
|
156
158
|
|
|
@@ -174,6 +176,14 @@ rbe search --playlist "house" --playlist "disco"
|
|
|
174
176
|
|
|
175
177
|
# Find all the songs in my library that aren't in any playlist
|
|
176
178
|
rbe search --playlist ""
|
|
179
|
+
|
|
180
|
+
# Find tracks whose path contains a folder or filename substring
|
|
181
|
+
rbe search --path "Favorites/" --path "track.wav"
|
|
182
|
+
rbe search --path "Music/Artist/song.mp3"
|
|
183
|
+
|
|
184
|
+
# Find a track at an exact location
|
|
185
|
+
rbe search --exact-path "/Users/djmustard/Music/banger.mp3"
|
|
186
|
+
rbe search --exact-path "../Artist/track.mp3"
|
|
177
187
|
```
|
|
178
188
|
|
|
179
189
|
## Output
|
|
@@ -133,6 +133,8 @@ Both commands support all filters. Multiple values create an OR filter unless `-
|
|
|
133
133
|
- `--playlist TEXT`: Playlist name contains TEXT
|
|
134
134
|
- `--exact-playlist TEXT`: Playlist name exactly matches TEXT
|
|
135
135
|
- `--format [mp3|flac|aiff|wav|m4a]`: File format matches
|
|
136
|
+
- `--path TEXT`: File path contains TEXT (matched as a substring against the folder path, filename, or both)
|
|
137
|
+
- `--exact-path TEXT`: File path exactly matches TEXT (resolved to an absolute path before matching)
|
|
136
138
|
- `--match-all`: Use AND logic (all filters must match)
|
|
137
139
|
- `ids` args: Specifying any other input to a command that is not a defined option is interpreted as one or more Track IDs. This is useful for scripting.
|
|
138
140
|
|
|
@@ -156,6 +158,14 @@ rbe search --playlist "house" --playlist "disco"
|
|
|
156
158
|
|
|
157
159
|
# Find all the songs in my library that aren't in any playlist
|
|
158
160
|
rbe search --playlist ""
|
|
161
|
+
|
|
162
|
+
# Find tracks whose path contains a folder or filename substring
|
|
163
|
+
rbe search --path "Favorites/" --path "track.wav"
|
|
164
|
+
rbe search --path "Music/Artist/song.mp3"
|
|
165
|
+
|
|
166
|
+
# Find a track at an exact location
|
|
167
|
+
rbe search --exact-path "/Users/djmustard/Music/banger.mp3"
|
|
168
|
+
rbe search --exact-path "../Artist/track.mp3"
|
|
159
169
|
```
|
|
160
170
|
|
|
161
171
|
## Output
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rekordbox-edit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0.dev18"
|
|
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"
|
|
@@ -26,6 +26,7 @@ dev = [
|
|
|
26
26
|
"ruff>=0.12.8,<1",
|
|
27
27
|
"pytest-cov>=6.2.1,<7",
|
|
28
28
|
"pytest-mock>=3.14.1,<4",
|
|
29
|
+
"pytest-watcher>=0.6.3,<1",
|
|
29
30
|
"callee>=0.3.1,<1",
|
|
30
31
|
"ty>=0.0.1,<1",
|
|
31
32
|
]
|
|
@@ -75,6 +75,18 @@ global_click_filters = [
|
|
|
75
75
|
multiple=True,
|
|
76
76
|
help="Find tracks whose Album names are exactly this value",
|
|
77
77
|
),
|
|
78
|
+
click.option(
|
|
79
|
+
"--path",
|
|
80
|
+
type=str,
|
|
81
|
+
multiple=True,
|
|
82
|
+
help="Find tracks whose file paths include this value",
|
|
83
|
+
),
|
|
84
|
+
click.option(
|
|
85
|
+
"--exact-path",
|
|
86
|
+
type=str,
|
|
87
|
+
multiple=True,
|
|
88
|
+
help="Find tracks whose file paths are exactly this value",
|
|
89
|
+
),
|
|
78
90
|
click.option(
|
|
79
91
|
"--format",
|
|
80
92
|
type=click.Choice(["mp3", "flac", "aiff", "wav", "m4a"], case_sensitive=False),
|
{rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/rekordbox_edit/commands/convert.py
RENAMED
|
@@ -295,6 +295,8 @@ def convert_command(
|
|
|
295
295
|
exact_artist: List[str] | None,
|
|
296
296
|
playlist: List[str] | None,
|
|
297
297
|
exact_playlist: List[str] | None,
|
|
298
|
+
path: List[str] | None,
|
|
299
|
+
exact_path: List[str] | None,
|
|
298
300
|
format: List[str] | None,
|
|
299
301
|
match_all: bool,
|
|
300
302
|
print_opt: PrintChoice | None,
|
|
@@ -395,6 +397,8 @@ def convert_command(
|
|
|
395
397
|
exact_albums=exact_album,
|
|
396
398
|
titles=title,
|
|
397
399
|
exact_titles=exact_title,
|
|
400
|
+
paths=path,
|
|
401
|
+
exact_paths=exact_path,
|
|
398
402
|
match_all=match_all,
|
|
399
403
|
)
|
|
400
404
|
filtered_content = result.scalars().all()
|
|
@@ -38,6 +38,8 @@ def search_command(
|
|
|
38
38
|
exact_artist: List[str] | None,
|
|
39
39
|
title: List[str] | None,
|
|
40
40
|
exact_title: List[str] | None,
|
|
41
|
+
path: List[str] | None,
|
|
42
|
+
exact_path: List[str] | None,
|
|
41
43
|
format: List[str] | None,
|
|
42
44
|
match_all: bool,
|
|
43
45
|
print_opt: PrintChoice | None,
|
|
@@ -75,6 +77,10 @@ def search_command(
|
|
|
75
77
|
filters.append(f"playlist={playlist}")
|
|
76
78
|
if exact_playlist:
|
|
77
79
|
filters.append(f"exact_playlist={exact_playlist}")
|
|
80
|
+
if path:
|
|
81
|
+
filters.append(f"path={path}")
|
|
82
|
+
if exact_path:
|
|
83
|
+
filters.append(f"exact_path={exact_path}")
|
|
78
84
|
if format:
|
|
79
85
|
filters.append(f"format={format}")
|
|
80
86
|
if match_all:
|
|
@@ -97,6 +103,8 @@ def search_command(
|
|
|
97
103
|
exact_albums=exact_album,
|
|
98
104
|
titles=title,
|
|
99
105
|
exact_titles=exact_title,
|
|
106
|
+
paths=path,
|
|
107
|
+
exact_paths=exact_path,
|
|
100
108
|
formats=format,
|
|
101
109
|
match_all=match_all,
|
|
102
110
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
from typing import List, Tuple, Union
|
|
3
4
|
|
|
4
5
|
from pyrekordbox import Rekordbox6Database
|
|
@@ -9,7 +10,7 @@ from pyrekordbox.db6.tables import (
|
|
|
9
10
|
DjmdPlaylist,
|
|
10
11
|
DjmdSongPlaylist,
|
|
11
12
|
)
|
|
12
|
-
from sqlalchemy import Result, and_, func, or_, select
|
|
13
|
+
from sqlalchemy import ColumnElement, Result, and_, func, or_, select
|
|
13
14
|
from sqlalchemy.orm import aliased
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -18,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
18
19
|
class CollectionQuery:
|
|
19
20
|
def __init__(self, match_all=False):
|
|
20
21
|
self._stmt = select(DjmdContent)
|
|
21
|
-
self._conditions = []
|
|
22
|
+
self._conditions: list[ColumnElement[bool]] = []
|
|
22
23
|
self._limit_count = None
|
|
23
24
|
self._match_all = match_all
|
|
24
25
|
|
|
@@ -143,6 +144,59 @@ class CollectionQuery:
|
|
|
143
144
|
logger.warning(f"Invalid format: {format_name}")
|
|
144
145
|
return new_inst
|
|
145
146
|
|
|
147
|
+
def by_path(self, path_str: str, exact: bool = False) -> "CollectionQuery":
|
|
148
|
+
"""Filter by file path, matching against FolderPath and/or FileNameL.
|
|
149
|
+
|
|
150
|
+
Paths get normalized to posix format.
|
|
151
|
+
--path args arg matched as substrings.
|
|
152
|
+
--exact-path argsl are resolved to absolute paths and must match exact.
|
|
153
|
+
|
|
154
|
+
Args with a trailing '/' only query against FolderPath.
|
|
155
|
+
Args with no parent folders in the path only query against FileNameL.
|
|
156
|
+
"""
|
|
157
|
+
new_inst = self._copy()
|
|
158
|
+
|
|
159
|
+
is_dir_only = path_str.endswith("/") or path_str.endswith("\\")
|
|
160
|
+
|
|
161
|
+
if exact:
|
|
162
|
+
resolved = Path(path_str).resolve()
|
|
163
|
+
if is_dir_only:
|
|
164
|
+
folder_part = resolved.as_posix() + "/"
|
|
165
|
+
name_part = ""
|
|
166
|
+
elif "/" not in path_str and "\\" not in path_str:
|
|
167
|
+
folder_part = ""
|
|
168
|
+
name_part = resolved.name
|
|
169
|
+
else:
|
|
170
|
+
folder_part = resolved.parent.as_posix() + "/"
|
|
171
|
+
name_part = resolved.name
|
|
172
|
+
else:
|
|
173
|
+
normalized = Path(path_str)
|
|
174
|
+
if is_dir_only:
|
|
175
|
+
folder_part = normalized.as_posix()
|
|
176
|
+
name_part = ""
|
|
177
|
+
else:
|
|
178
|
+
parent_str = normalized.parent.as_posix()
|
|
179
|
+
folder_part = "" if parent_str == "." else parent_str
|
|
180
|
+
name_part = normalized.name
|
|
181
|
+
|
|
182
|
+
conditions: list[ColumnElement[bool]] = []
|
|
183
|
+
if folder_part:
|
|
184
|
+
if exact:
|
|
185
|
+
conditions.append(DjmdContent.FolderPath == folder_part)
|
|
186
|
+
else:
|
|
187
|
+
conditions.append(DjmdContent.FolderPath.ilike(f"%{folder_part}%"))
|
|
188
|
+
if name_part:
|
|
189
|
+
if exact:
|
|
190
|
+
conditions.append(DjmdContent.FileNameL == name_part)
|
|
191
|
+
else:
|
|
192
|
+
conditions.append(DjmdContent.FileNameL.ilike(f"%{name_part}%"))
|
|
193
|
+
|
|
194
|
+
if conditions:
|
|
195
|
+
combined = and_(*conditions) if len(conditions) > 1 else conditions[0]
|
|
196
|
+
new_inst._conditions.append(combined)
|
|
197
|
+
|
|
198
|
+
return new_inst
|
|
199
|
+
|
|
146
200
|
def limit(self, count: int) -> "CollectionQuery":
|
|
147
201
|
"""Limit query results to the first {count} items."""
|
|
148
202
|
new_inst = self._copy()
|
|
@@ -204,6 +258,8 @@ def get_filtered_content(
|
|
|
204
258
|
exact_albums: List[str] | None = None,
|
|
205
259
|
titles: List[str] | None = None,
|
|
206
260
|
exact_titles: List[str] | None = None,
|
|
261
|
+
paths: List[str] | None = None,
|
|
262
|
+
exact_paths: List[str] | None = None,
|
|
207
263
|
match_all: bool = False,
|
|
208
264
|
) -> Result[Tuple[DjmdContent]]:
|
|
209
265
|
"""Query the Rekordbox database with the provided filters."""
|
|
@@ -254,8 +310,16 @@ def get_filtered_content(
|
|
|
254
310
|
query = query.by_title(title)
|
|
255
311
|
|
|
256
312
|
if exact_titles:
|
|
257
|
-
for
|
|
258
|
-
query = query.by_title(
|
|
313
|
+
for title in exact_titles:
|
|
314
|
+
query = query.by_title(title, exact=True)
|
|
315
|
+
|
|
316
|
+
if paths:
|
|
317
|
+
for path in paths:
|
|
318
|
+
query = query.by_path(path)
|
|
319
|
+
|
|
320
|
+
if exact_paths:
|
|
321
|
+
for exact_path in exact_paths:
|
|
322
|
+
query = query.by_path(exact_path, exact=True)
|
|
259
323
|
|
|
260
324
|
if match_all:
|
|
261
325
|
query = query.match_all()
|
|
@@ -137,12 +137,24 @@ class TestSearchCommand:
|
|
|
137
137
|
|
|
138
138
|
CliRunner().invoke(
|
|
139
139
|
search_command,
|
|
140
|
-
[
|
|
140
|
+
[
|
|
141
|
+
"--artist",
|
|
142
|
+
"Daft Punk",
|
|
143
|
+
"--format",
|
|
144
|
+
"flac",
|
|
145
|
+
"--path",
|
|
146
|
+
"song.mp3",
|
|
147
|
+
"--exact-path",
|
|
148
|
+
"/Music/album/track.wav",
|
|
149
|
+
"--match-all",
|
|
150
|
+
],
|
|
141
151
|
)
|
|
142
152
|
|
|
143
153
|
call_kwargs = mock_get_filtered_content.call_args.kwargs
|
|
144
154
|
assert call_kwargs["artists"] == ("Daft Punk",)
|
|
145
155
|
assert call_kwargs["formats"] == ("flac",)
|
|
156
|
+
assert call_kwargs["paths"] == ("song.mp3",)
|
|
157
|
+
assert call_kwargs["exact_paths"] == ("/Music/album/track.wav",)
|
|
146
158
|
assert call_kwargs["match_all"] is True
|
|
147
159
|
|
|
148
160
|
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Tests for the CollectionQuery class."""
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from unittest.mock import MagicMock
|
|
6
8
|
|
|
9
|
+
import pytest
|
|
10
|
+
from sqlalchemy import ColumnElement
|
|
11
|
+
|
|
7
12
|
from rekordbox_edit.query import CollectionQuery, get_filtered_content
|
|
8
13
|
|
|
9
14
|
|
|
15
|
+
def _compile(condition: ColumnElement[bool]) -> str:
|
|
16
|
+
return str(condition.compile(compile_kwargs={"literal_binds": True}))
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
class TestCollectionQuery:
|
|
11
20
|
"""Test the CollectionQuery class."""
|
|
12
21
|
|
|
@@ -414,6 +423,150 @@ class TestCollectionQuery:
|
|
|
414
423
|
assert " and " in stmt_str
|
|
415
424
|
assert " or " not in stmt_str
|
|
416
425
|
|
|
426
|
+
def test_by_path_filename_only(self):
|
|
427
|
+
"""A bare filename (no directory) adds a FileNameL ilike condition only.
|
|
428
|
+
The FolderPath condition is skipped — the filter still runs with just FileNameL.
|
|
429
|
+
"""
|
|
430
|
+
query = CollectionQuery()
|
|
431
|
+
new_query = query.by_path("track.mp3")
|
|
432
|
+
|
|
433
|
+
assert new_query is not query
|
|
434
|
+
assert len(new_query._conditions) == 1
|
|
435
|
+
condition_str = str(new_query._conditions[0])
|
|
436
|
+
assert "FileNameL" in condition_str
|
|
437
|
+
assert "LIKE lower" in condition_str
|
|
438
|
+
assert "FolderPath" not in condition_str
|
|
439
|
+
|
|
440
|
+
def test_by_path_folder_only_forward_slash(self):
|
|
441
|
+
"""A trailing-forward-slash string adds a FolderPath ilike condition only.
|
|
442
|
+
The FileNameL condition is skipped — the filter still runs with just FolderPath.
|
|
443
|
+
"""
|
|
444
|
+
query = CollectionQuery()
|
|
445
|
+
new_query = query.by_path("./Test/Folder/")
|
|
446
|
+
|
|
447
|
+
assert new_query is not query
|
|
448
|
+
assert len(new_query._conditions) == 1
|
|
449
|
+
condition_str = str(new_query._conditions[0])
|
|
450
|
+
assert "FileNameL" not in condition_str
|
|
451
|
+
assert "LIKE lower" in condition_str
|
|
452
|
+
assert "FolderPath" in condition_str
|
|
453
|
+
|
|
454
|
+
def test_by_path_folder_and_filename(self):
|
|
455
|
+
"""A path with both parts adds a compound AND condition covering both columns."""
|
|
456
|
+
query = CollectionQuery()
|
|
457
|
+
new_query = query.by_path("Music/Artist/track.mp3")
|
|
458
|
+
|
|
459
|
+
assert new_query is not query
|
|
460
|
+
assert len(new_query._conditions) == 1
|
|
461
|
+
condition_str = str(new_query._conditions[0])
|
|
462
|
+
assert "FolderPath" in condition_str
|
|
463
|
+
assert "FileNameL" in condition_str
|
|
464
|
+
assert " AND " in condition_str
|
|
465
|
+
assert condition_str.count("LIKE lower") == 2
|
|
466
|
+
|
|
467
|
+
@pytest.mark.skipif(
|
|
468
|
+
platform.system() != "Windows", reason="backslash path parsing is Windows-only"
|
|
469
|
+
)
|
|
470
|
+
def test_by_path_backslash_normalised_to_forward_slash(self):
|
|
471
|
+
"""Backslash separators in --path input are normalised to forward slashes."""
|
|
472
|
+
query = CollectionQuery()
|
|
473
|
+
new_query = query.by_path("Music\\Artist\\track.mp3")
|
|
474
|
+
|
|
475
|
+
query_str = _compile(new_query._conditions[0])
|
|
476
|
+
assert "\\" not in query_str
|
|
477
|
+
assert "Music/Artist" in query_str
|
|
478
|
+
|
|
479
|
+
def test_by_path_no_resolve(self):
|
|
480
|
+
"""by_path does NOT resolve the path."""
|
|
481
|
+
query = CollectionQuery()
|
|
482
|
+
new_query = query.by_path("../some/relative/track.mp3")
|
|
483
|
+
|
|
484
|
+
condition_str = _compile(new_query._conditions[0])
|
|
485
|
+
# The raw string (or parts of it) must appear, not a resolved absolute path
|
|
486
|
+
assert ".." in condition_str and "relative" in condition_str
|
|
487
|
+
|
|
488
|
+
def test_by_path_ilike_wraps_with_wildcards(self):
|
|
489
|
+
"""The ilike condition includes % wildcards for substring matching."""
|
|
490
|
+
query = CollectionQuery()
|
|
491
|
+
new_query = query.by_path("track.mp3")
|
|
492
|
+
|
|
493
|
+
condition_str = _compile(new_query._conditions[0])
|
|
494
|
+
assert "%" in condition_str
|
|
495
|
+
|
|
496
|
+
# --- by_path exact mode ---
|
|
497
|
+
|
|
498
|
+
def test_by_exact_path_filename_only(self):
|
|
499
|
+
"""Exact match on a bare filename produces an equality condition on FileNameL only."""
|
|
500
|
+
query = CollectionQuery()
|
|
501
|
+
new_query = query.by_path("track.mp3", exact=True)
|
|
502
|
+
|
|
503
|
+
assert len(new_query._conditions) == 1
|
|
504
|
+
condition_str = str(new_query._conditions[0])
|
|
505
|
+
assert "FolderPath" not in condition_str
|
|
506
|
+
assert "FileNameL" in condition_str
|
|
507
|
+
assert "=" in condition_str
|
|
508
|
+
assert "LIKE lower" not in condition_str
|
|
509
|
+
|
|
510
|
+
def test_by_exact_path_folder_only(self):
|
|
511
|
+
"""Exact match on a only folder path produces equality check on FolderPath only."""
|
|
512
|
+
query = CollectionQuery()
|
|
513
|
+
# Build a cross-platform absolute path using pathlib
|
|
514
|
+
path = "/Test/Artist/"
|
|
515
|
+
new_query = query.by_path(path, exact=True)
|
|
516
|
+
|
|
517
|
+
assert len(new_query._conditions) == 1
|
|
518
|
+
condition_str = _compile(new_query._conditions[0])
|
|
519
|
+
assert "FolderPath" in condition_str
|
|
520
|
+
assert "FileNameL" not in condition_str
|
|
521
|
+
assert "=" in condition_str
|
|
522
|
+
assert path in condition_str
|
|
523
|
+
assert "like lower" not in condition_str
|
|
524
|
+
|
|
525
|
+
def test_by_exact_path_folder_and_filename(self):
|
|
526
|
+
"""Exact match on a full path produces a compound AND with equality on both columns."""
|
|
527
|
+
query = CollectionQuery()
|
|
528
|
+
# Build a cross-platform absolute path using pathlib
|
|
529
|
+
abs_path = "/Artist/track.mp3"
|
|
530
|
+
new_query = query.by_path(abs_path, exact=True)
|
|
531
|
+
|
|
532
|
+
assert len(new_query._conditions) == 1
|
|
533
|
+
condition_str = str(new_query._conditions[0])
|
|
534
|
+
assert "FolderPath" in condition_str
|
|
535
|
+
assert "FileNameL" in condition_str
|
|
536
|
+
assert " AND " in condition_str
|
|
537
|
+
assert "LIKE lower" not in condition_str
|
|
538
|
+
|
|
539
|
+
def test_by_exact_path_resolves_relative_path(self):
|
|
540
|
+
"""Exact match resolves relative paths to absolute before querying."""
|
|
541
|
+
cwd = Path(os.getcwd()).as_posix()
|
|
542
|
+
query = CollectionQuery()
|
|
543
|
+
new_query = query.by_path("Album/track.mp3", exact=True)
|
|
544
|
+
|
|
545
|
+
condition_str = _compile(new_query._conditions[0])
|
|
546
|
+
# cwd should appear as the resolved folder part
|
|
547
|
+
assert cwd in condition_str
|
|
548
|
+
|
|
549
|
+
@pytest.mark.skipif(
|
|
550
|
+
platform.system() != "Windows", reason="backslash path parsing is Windows-only"
|
|
551
|
+
)
|
|
552
|
+
def test_by_exact_path_backslash_normalised_to_forward_slash(self):
|
|
553
|
+
"""Backslash separators in --exact-path input are normalised via as_posix()."""
|
|
554
|
+
query = CollectionQuery()
|
|
555
|
+
# Pass a Windows-style absolute path; as_posix() must produce forward slashes
|
|
556
|
+
new_query = query.by_path(
|
|
557
|
+
"C:\\Users\\foo\\music\\Artist\\track.mp3", exact=True
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
condition_str = _compile(new_query._conditions[0])
|
|
561
|
+
assert "\\" not in condition_str
|
|
562
|
+
assert "/" in condition_str
|
|
563
|
+
|
|
564
|
+
def test_by_path_returns_new_instance(self):
|
|
565
|
+
"""by_path always returns a new CollectionQuery instance."""
|
|
566
|
+
query = CollectionQuery()
|
|
567
|
+
assert query.by_path("track.mp3") is not query
|
|
568
|
+
assert query.by_path("track.mp3", exact=True) is not query
|
|
569
|
+
|
|
417
570
|
|
|
418
571
|
@pytest.fixture
|
|
419
572
|
def mock_query(mocker):
|
|
@@ -495,6 +648,14 @@ class TestGetFilteredContent:
|
|
|
495
648
|
get_filtered_content(mock_db, exact_playlists=["My Playlist"])
|
|
496
649
|
mock_query.by_playlist.assert_called_once_with("My Playlist", exact=True)
|
|
497
650
|
|
|
651
|
+
def test_path(self, mock_db, mock_query):
|
|
652
|
+
get_filtered_content(mock_db, paths=["Music/track.mp3"])
|
|
653
|
+
mock_query.by_path.assert_called_once_with("Music/track.mp3")
|
|
654
|
+
|
|
655
|
+
def test_exact_path(self, mock_db, mock_query):
|
|
656
|
+
get_filtered_content(mock_db, exact_paths=["/Music/track.wav"])
|
|
657
|
+
mock_query.by_path.assert_called_once_with("/Music/track.wav", exact=True)
|
|
658
|
+
|
|
498
659
|
def test_format(self, mock_db, mock_query):
|
|
499
660
|
get_filtered_content(mock_db, formats=["flac"])
|
|
500
661
|
mock_query.by_format.assert_called_once_with("flac")
|
|
@@ -869,6 +869,19 @@ wheels = [
|
|
|
869
869
|
{ url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" },
|
|
870
870
|
]
|
|
871
871
|
|
|
872
|
+
[[package]]
|
|
873
|
+
name = "pytest-watcher"
|
|
874
|
+
version = "0.6.3"
|
|
875
|
+
source = { registry = "https://pypi.org/simple" }
|
|
876
|
+
dependencies = [
|
|
877
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
878
|
+
{ name = "watchdog" },
|
|
879
|
+
]
|
|
880
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e6/d2/80606077b7fa8784417687f494ff801d7ab817d9a17fc94305811d5919bb/pytest_watcher-0.6.3.tar.gz", hash = "sha256:842dc904264df0ad2d5264153a66bb452fccfa46598cd6e0a5ef1d19afed9b13", size = 601878, upload-time = "2026-01-10T23:28:18.805Z" }
|
|
881
|
+
wheels = [
|
|
882
|
+
{ url = "https://files.pythonhosted.org/packages/fc/3f/172d73600ad2771774cda108efb813fc724fc345e5240a81a1085f1ade5d/pytest_watcher-0.6.3-py3-none-any.whl", hash = "sha256:83e7748c933087e8276edb6078663e6afa9926434b4fd8b85cf6b32b1d5bec89", size = 12431, upload-time = "2026-01-10T23:28:17.64Z" },
|
|
883
|
+
]
|
|
884
|
+
|
|
872
885
|
[[package]]
|
|
873
886
|
name = "python-dateutil"
|
|
874
887
|
version = "2.9.0.post0"
|
|
@@ -972,7 +985,7 @@ wheels = [
|
|
|
972
985
|
|
|
973
986
|
[[package]]
|
|
974
987
|
name = "rekordbox-edit"
|
|
975
|
-
version = "0.
|
|
988
|
+
version = "0.4.0.dev18"
|
|
976
989
|
source = { editable = "." }
|
|
977
990
|
dependencies = [
|
|
978
991
|
{ name = "click" },
|
|
@@ -989,6 +1002,7 @@ dev = [
|
|
|
989
1002
|
{ name = "pytest" },
|
|
990
1003
|
{ name = "pytest-cov" },
|
|
991
1004
|
{ name = "pytest-mock" },
|
|
1005
|
+
{ name = "pytest-watcher" },
|
|
992
1006
|
{ name = "ruff" },
|
|
993
1007
|
{ name = "ty" },
|
|
994
1008
|
]
|
|
@@ -1009,6 +1023,7 @@ dev = [
|
|
|
1009
1023
|
{ name = "pytest", specifier = ">=8.4.1,<9" },
|
|
1010
1024
|
{ name = "pytest-cov", specifier = ">=6.2.1,<7" },
|
|
1011
1025
|
{ name = "pytest-mock", specifier = ">=3.14.1,<4" },
|
|
1026
|
+
{ name = "pytest-watcher", specifier = ">=0.6.3,<1" },
|
|
1012
1027
|
{ name = "ruff", specifier = ">=0.12.8,<1" },
|
|
1013
1028
|
{ name = "ty", specifier = ">=0.0.1,<1" },
|
|
1014
1029
|
]
|
|
@@ -1332,6 +1347,38 @@ wheels = [
|
|
|
1332
1347
|
{ url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" },
|
|
1333
1348
|
]
|
|
1334
1349
|
|
|
1350
|
+
[[package]]
|
|
1351
|
+
name = "watchdog"
|
|
1352
|
+
version = "6.0.0"
|
|
1353
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1354
|
+
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
|
|
1355
|
+
wheels = [
|
|
1356
|
+
{ url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
|
|
1357
|
+
{ url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
|
|
1358
|
+
{ url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
|
|
1359
|
+
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
|
|
1360
|
+
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
|
|
1361
|
+
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
|
|
1362
|
+
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
|
|
1363
|
+
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
|
|
1364
|
+
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
|
|
1365
|
+
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
|
|
1366
|
+
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
|
|
1367
|
+
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
|
|
1368
|
+
{ url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
|
|
1369
|
+
{ url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
|
|
1370
|
+
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
|
|
1371
|
+
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
|
|
1372
|
+
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
|
|
1373
|
+
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
|
|
1374
|
+
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
|
|
1375
|
+
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
|
|
1376
|
+
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
|
|
1377
|
+
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
|
|
1378
|
+
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
|
|
1379
|
+
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
|
|
1380
|
+
]
|
|
1381
|
+
|
|
1335
1382
|
[[package]]
|
|
1336
1383
|
name = "wcwidth"
|
|
1337
1384
|
version = "0.6.0"
|
{rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/commitizen-bump/action.yml
RENAMED
|
File without changes
|
|
File without changes
|
{rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/.github/actions/install/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
|
{rekordbox_edit-0.3.0.dev16 → rekordbox_edit-0.4.0.dev18}/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
|