feat: store tags in plain-text .otag files with .otag.toml config and document all commands
This commit is contained in:
parent
f04554a52c
commit
b2ab1421df
8 changed files with 222 additions and 369 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
import orvanitag.orvani as orvani
|
||||
from orvanitag.orvani import OrvaniTag, DEFAULT_PLUGINS
|
||||
|
|
@ -21,8 +20,8 @@ def found(o, *query, either=False, path=''):
|
|||
return o.list(path, True, either, True, False, False, *query)['found']
|
||||
|
||||
|
||||
def items(path='.'):
|
||||
return yaml.safe_load((path / orvani.CONTEXT_FILE).read_text())['items']
|
||||
def lines(path):
|
||||
return (path / orvani.CONTEXT_FILE).read_text().splitlines()
|
||||
|
||||
|
||||
def test_and_search_within_one_category(tree):
|
||||
|
|
@ -65,12 +64,12 @@ def test_empty_query_lists_everything(tree):
|
|||
|
||||
def test_multi_word_note(tree):
|
||||
OrvaniTag().add('a.mp3', '#lofi', 'my', 'long', 'note')
|
||||
assert items(tree)['a.mp3']['note'] == 'my long note'
|
||||
assert lines(tree) == ['a.mp3 --- #lofi my long note']
|
||||
|
||||
|
||||
def test_trailing_commas_are_stripped(tree):
|
||||
OrvaniTag().add('a.mp3', '#lofi,', '#snare')
|
||||
assert items(tree)['a.mp3']['tags'] == ['lofi', 'snare']
|
||||
assert lines(tree) == ['a.mp3 --- #lofi #snare']
|
||||
|
||||
|
||||
def test_clean_keeps_tagged_directories(tree):
|
||||
|
|
@ -79,7 +78,7 @@ def test_clean_keeps_tagged_directories(tree):
|
|||
o.add('a.mp3', '#lofi')
|
||||
(tree / 'a.mp3').unlink()
|
||||
assert o.clean('', True)[2] == 1
|
||||
assert list(items(tree)) == ['sub']
|
||||
assert lines(tree) == ['sub/ --- #folder']
|
||||
|
||||
|
||||
def test_instances_do_not_share_state(tree):
|
||||
|
|
@ -106,16 +105,38 @@ def test_move_tagged_file(tree):
|
|||
o.add('a.mp3', '#lofi')
|
||||
assert o.move('a.mp3', 'sub/', False, True)[0] == 0
|
||||
assert (tree / 'sub' / 'a.mp3').exists()
|
||||
assert items(tree / 'sub')['a.mp3']['tags'] == ['lofi']
|
||||
assert lines(tree / 'sub') == ['a.mp3 --- #lofi']
|
||||
assert not (tree / orvani.CONTEXT_FILE).exists()
|
||||
|
||||
|
||||
def test_save_keeps_config(tree):
|
||||
(tree / orvani.CONTEXT_FILE).write_text('config:\n plugins: [pdf]\nitems: {}\n')
|
||||
OrvaniTag().add('a.mp3', '#lofi')
|
||||
data = yaml.safe_load((tree / orvani.CONTEXT_FILE).read_text())
|
||||
assert data['config'] == {'plugins': ['pdf']}
|
||||
assert data['items']['a.mp3']['tags'] == ['lofi']
|
||||
def test_config_without_tags(tree):
|
||||
(tree / orvani.CONFIG_FILE).write_text('plugins = ["pdf"]\n')
|
||||
assert OrvaniTag().CONFIG == {'plugins': ['pdf']}
|
||||
|
||||
|
||||
def test_removing_last_entry_keeps_config(tree):
|
||||
(tree / orvani.CONFIG_FILE).write_text('plugins = ["pdf"]\n')
|
||||
o = OrvaniTag()
|
||||
o.add('a.mp3', '#lofi')
|
||||
o.delete('a.mp3')
|
||||
assert not (tree / orvani.CONTEXT_FILE).exists()
|
||||
assert (tree / orvani.CONFIG_FILE).exists()
|
||||
|
||||
|
||||
def test_file_names_with_spaces_and_separator_in_note(tree):
|
||||
(tree / 'My Track.flac').touch()
|
||||
o = OrvaniTag()
|
||||
o.add('My Track.flac', '#boombap', 'a', '---', 'b')
|
||||
assert lines(tree) == ['My Track.flac --- #boombap a --- b']
|
||||
assert found(o, '#boombap') == ['./My Track.flac']
|
||||
assert o.index('')['items']['./My Track.flac']['note'] == 'a --- b'
|
||||
|
||||
|
||||
def test_malformed_line_raises_and_keeps_file(tree):
|
||||
(tree / orvani.CONTEXT_FILE).write_text('a.mp3 --- #lofi\nb.mp3 #broken\n')
|
||||
with pytest.raises(ValueError, match=r'\.otag:2'):
|
||||
OrvaniTag().add('c.mp3', '#new')
|
||||
assert lines(tree) == ['a.mp3 --- #lofi', 'b.mp3 #broken']
|
||||
|
||||
|
||||
def test_failed_save_keeps_old_file(tree, monkeypatch):
|
||||
|
|
@ -123,9 +144,9 @@ def test_failed_save_keeps_old_file(tree, monkeypatch):
|
|||
o.add('a.mp3', '#lofi')
|
||||
before = (tree / orvani.CONTEXT_FILE).read_text()
|
||||
|
||||
def broken_dump(*args, **kwargs):
|
||||
def broken_replace(*args, **kwargs):
|
||||
raise RuntimeError('disk full')
|
||||
monkeypatch.setattr(orvani.yaml, 'dump', broken_dump)
|
||||
monkeypatch.setattr(orvani.os, 'replace', broken_replace)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
o.add('a.mp3', '#snare')
|
||||
|
|
@ -141,7 +162,7 @@ def test_remove_saves_once(tree, monkeypatch):
|
|||
monkeypatch.setattr(o, '_OrvaniTag__save', lambda: saves.append(1) or save())
|
||||
assert o.remove('a.mp3', '#lofi', '#snare')[0] == 0
|
||||
assert len(saves) == 1
|
||||
assert items(tree)['a.mp3']['tags'] == ['keep']
|
||||
assert lines(tree) == ['a.mp3 --- #keep']
|
||||
|
||||
|
||||
def test_cache_is_rebuilt_when_context_changes(tree):
|
||||
|
|
@ -165,9 +186,8 @@ def test_import_untagged_files(tree):
|
|||
(tree / 'sub' / orvani.CONTEXT_FILE).unlink()
|
||||
|
||||
assert OrvaniTag().load('context.txt') == [0, 'context.txt', 2]
|
||||
assert items(tree)['a.mp3']['tags'] == ['lofi']
|
||||
assert items(tree)['a.mp3']['note'] == 'a note'
|
||||
assert items(tree / 'sub')['d.txt']['contexts'] == ['docs']
|
||||
assert lines(tree) == ['a.mp3 --- #lofi a note']
|
||||
assert lines(tree / 'sub') == ['d.txt --- @docs']
|
||||
|
||||
|
||||
def test_import_skips_known_tags_and_empty_lines(tree):
|
||||
|
|
@ -175,5 +195,4 @@ def test_import_skips_known_tags_and_empty_lines(tree):
|
|||
o.add('a.mp3', '#lofi')
|
||||
(tree / 'context.txt').write_text('./a.mp3 --- #lofi #snare\n./b.mp3 --- \n')
|
||||
assert OrvaniTag().load('context.txt') == [0, 'context.txt', 1]
|
||||
assert items(tree)['a.mp3']['tags'] == ['lofi', 'snare']
|
||||
assert 'b.mp3' not in items(tree)
|
||||
assert lines(tree) == ['a.mp3 --- #lofi #snare']
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import sys
|
|||
import zipfile
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
import orvanitag.orvani as orvani
|
||||
|
|
@ -48,10 +47,7 @@ def roundtrip(cwd, name):
|
|||
assert o.write_metadata(name, 'office')[0] == 0
|
||||
(cwd / orvani.CONTEXT_FILE).unlink()
|
||||
OrvaniTag().read_metadata(name)
|
||||
entry = yaml.safe_load((cwd / orvani.CONTEXT_FILE).read_text())['items'][name]
|
||||
assert entry['tags'] == ['lofi']
|
||||
assert entry['projects'] == ['proj']
|
||||
assert entry['note'] == 'R&B C:\\1'
|
||||
assert (cwd / orvani.CONTEXT_FILE).read_text() == f'{name} --- +proj #lofi R&B C:\\1\n'
|
||||
assert not list(cwd.glob('unzip__*'))
|
||||
|
||||
|
||||
|
|
@ -86,8 +82,7 @@ def test_pdf_roundtrip(cwd):
|
|||
|
||||
(cwd / orvani.CONTEXT_FILE).unlink()
|
||||
OrvaniTag().read_metadata('doc.pdf')
|
||||
entry = yaml.safe_load((cwd / orvani.CONTEXT_FILE).read_text())['items']['doc.pdf']
|
||||
assert entry == {'projects': [], 'contexts': ['desk'], 'tags': ['lofi'], 'note': 'a note'}
|
||||
assert (cwd / orvani.CONTEXT_FILE).read_text() == 'doc.pdf --- @desk #lofi a note\n'
|
||||
|
||||
|
||||
def test_cmd_write(cwd, monkeypatch):
|
||||
|
|
@ -101,9 +96,9 @@ def test_cmd_write(cwd, monkeypatch):
|
|||
|
||||
def test_cmd_prints_errors(cwd, monkeypatch, capsys):
|
||||
(cwd / 'a.mp3').touch()
|
||||
(cwd / orvani.CONTEXT_FILE).write_text('items: [unclosed')
|
||||
(cwd / orvani.CONTEXT_FILE).write_text('a.mp3 #broken\n')
|
||||
monkeypatch.setattr(sys, 'argv', ['otag', 'a', 'a.mp3', '#lofi'])
|
||||
with pytest.raises(SystemExit) as exit:
|
||||
main()
|
||||
assert exit.value.code == 1
|
||||
assert 'expected' in capsys.readouterr().out
|
||||
assert '.otag:1' in capsys.readouterr().out
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue