From be71728e1aca12a4fa76b905db3107a1bb66e6df Mon Sep 17 00:00:00 2001 From: randogoth Date: Sat, 24 Oct 2026 21:22:09 +0300 Subject: [PATCH] fix: allow cp and mv within the current directory and into folders without trailing slash --- README.md | 4 ++-- orvanitag/orvani.py | 13 ++++++++----- tests/test_orvani.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f929d2e..d66a56b 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,10 @@ A term with a prefix only matches that category; a term without a prefix matches ### Copying, Moving and Deleting -`copy` (`cp`) and `move` (`mv`) transfer the tags of a file to another path. By default only the tags are transferred; `-f` (`--file-operation`) also copies or moves the file itself. The destination needs a directory part, so write `./new.mp3` rather than `new.mp3`. With `-f`, a destination ending in `/` keeps the file name. +`copy` (`cp`) and `move` (`mv`) transfer the tags of a file to another path. By default only the tags are transferred; `-f` (`--file-operation`) also copies or moves the file itself. With `-f`, an existing folder as destination keeps the file name. Without `-f`, a folder as destination gets the tags itself. ``` -$ otag cp song.mp3 ./song-copy.mp3 +$ otag cp song.mp3 song-copy.mp3 $ otag mv -f song.mp3 archive/ ``` diff --git a/orvanitag/orvani.py b/orvanitag/orvani.py index 2f9ccef..39f542a 100644 --- a/orvanitag/orvani.py +++ b/orvanitag/orvani.py @@ -468,8 +468,12 @@ class OrvaniTag: """ # Let's use trusty old os lib over pathlib for this one - # Check if source file and destination folder exist - if os.path.exists(source) and os.path.isdir(os.path.dirname(dest)): + # An existing folder as destination of a file operation receives the file under its own name + if fop and os.path.isdir(dest): + dest = os.path.join(dest, '') + + # Check if source file and destination folder exist ('' is the current directory) + if os.path.exists(source) and os.path.isdir(os.path.dirname(dest) or '.'): sourcefolder = os.path.dirname(source) sourcefile = os.path.basename(source) @@ -494,8 +498,7 @@ class OrvaniTag: if sourcedata is None and not fop: # without an entry there is nothing to move return [2, source] if sourcedata is not None: - if not os.path.isdir(destfile): - self.__cd(destfolder) # load local context of destination folder + self.__cd(destfolder) # load local context of destination folder self.DB.update( { destfile : sourcedata } ) # update its DB with the context data of our source file self.__save() # and save it if not retain: # do we copy or move the data? @@ -508,7 +511,7 @@ class OrvaniTag: os.remove(source) return [0, source, dest, sourcedata or {}] else: - return [1, source, dest, os.path.isfile(source), os.path.isdir(os.path.dirname(dest))] + return [1, source, dest, os.path.isfile(source), os.path.isdir(os.path.dirname(dest) or '.')] def clean(self, path: str='.', recursive=True): diff --git a/tests/test_orvani.py b/tests/test_orvani.py index 3e893cf..33e9f28 100644 --- a/tests/test_orvani.py +++ b/tests/test_orvani.py @@ -109,6 +109,39 @@ def test_move_tagged_file(tree): assert not (tree / orvani.CONTEXT_FILE).exists() +def test_copy_tags_within_current_directory(tree): + o = OrvaniTag() + o.add('a.mp3', '#lofi') + assert o.move('a.mp3', 'b.mp3', True, False)[0] == 0 + assert lines(tree) == ['a.mp3 --- #lofi', 'b.mp3 --- #lofi'] + + +def test_rename_within_current_directory(tree): + o = OrvaniTag() + o.add('a.mp3', '#lofi') + assert o.move('a.mp3', 'renamed.mp3', False, True)[0] == 0 + assert not (tree / 'a.mp3').exists() + assert lines(tree) == ['renamed.mp3 --- #lofi'] + + +def test_move_into_directory_without_slash(tree): + o = OrvaniTag() + o.add('a.mp3', '#lofi') + assert o.move('a.mp3', 'sub', False, True)[0] == 0 + assert (tree / 'sub' / 'a.mp3').exists() + assert lines(tree / 'sub') == ['a.mp3 --- #lofi'] + assert not (tree / orvani.CONTEXT_FILE).exists() + + +def test_copy_tags_from_subfolder_to_folder(tree): + (tree / 'other').mkdir() + o = OrvaniTag() + o.add('sub/d.txt', '#doc') + assert o.move('sub/d.txt', 'other/', True, False)[0] == 0 + assert lines(tree) == ['other/ --- #doc'] + assert lines(tree / 'sub') == ['d.txt --- #doc'] + + def test_config_without_tags(tree): (tree / orvani.CONFIG_FILE).write_text('plugins = ["pdf"]\n') assert OrvaniTag().CONFIG == {'plugins': ['pdf']}