fix: allow cp and mv within the current directory and into folders without trailing slash

This commit is contained in:
randogoth 2026-10-24 21:22:09 +03:00
parent b2ab1421df
commit be71728e1a
3 changed files with 43 additions and 7 deletions

View file

@ -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/
```

View file

@ -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):

View file

@ -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']}