feat: add --no-overwrite

This commit is contained in:
Johann Dreo 2025-08-09 17:57:22 +02:00
commit a8d1cfed84
2 changed files with 56 additions and 17 deletions

View file

@ -1,40 +1,61 @@
FlickSave -- automatic snapshot each time you hit save FlickSave -- automatic snapshot each time you hit save
====================================================== ======================================================
FlickSave automagically backup a timestamped snapshot of a file that you edit with any program, without getting in your way. FlickSave automagically perform an action each time you touch a watched file.
For instance, FlickSave can backup a timestamped snapshot of a file that you edit with any program, without getting in your way.
For example, each time you hit `Ctrl-S` within a drawing software, For example, each time you hit `Ctrl-S` within a drawing software,
FlickSave will make a copy the current version of your file, FlickSave will make a copy the current version of your file,
along with the precise date of this version. along with the precise date of this version.
This may be useful for digital artists that want to prepare a time-lapse of their on-going work without having to think about it. This may be useful for digital artists that want to prepare a time-lapse of their on-going work without having to think about it.
You can even do an automatic Git commit the same way.
## Usage ## Usage
You should start FlickSave with the file you want to save as an argument You should start FlickSave with the file you want to save as an argument
and let it run during the time you want to create snapshots. and let it run during the time you want to let it perform your action.
Once started, it will watch for modifications on the given file Once started, it will watch for modifications on the given file
and create the snapshots by himself each time something happens. and, for instance, create the snapshots by himself each time something happens.
By default, the snapshot has the same name than the targeted file, with the addition of the timestamp. Currently, FlickSave can perform the following actions:
- `--save`: save a timestamped snapshot of the watched file,
- `--inkscape`: export a timestamped PNG of a watched SVG,
- `--git`: commit the modifications of the watched file,
- `--log`: print a message stating that the watched file was touched.
You can pass multiple actions.
For instance, you can both export a PNG and do a Git commit.
By default, timestamped snapshots have the same name than the targeted file, with the addition of the timestamp.
For instance, with a target file named `test.svg`, a snapshot will look like `test_2017-11-28T21:02:59.svg`. For instance, with a target file named `test.svg`, a snapshot will look like `test_2017-11-28T21:02:59.svg`.
### Synopsis ### Synopsis
`flicksave.py [-h] [-d DIRECTORY] [-y DELAY] [-s SEPARATOR] [-t TEMPLATE] [-v {DEBUG,INFO,WARNING,ERROR}] target` `flicksave.py [-h] [--save] [--inkscape] [--git] [--log] [-d DIRECTORY] [-y DELAY] [-s SEPARATOR] [-t TEMPLATE] [-w] [-v {DEBUG,INFO,WARNING,ERROR}] [-e {opened,moved,deleted,created,modified,closed}...] target`
### Required positional argument ### Required positional argument
`target`: The file to save each time it's modified. `target`: The file to watch.
### Optional arguments ### Optional arguments
* `-h`, `--help`: show this help message and exit. * `-h`, `--help`: show this help message and exit.
* `--save`: Save a snapshot of the watched file.
* `--inkscape`: Save a PNG snpashot of the watched SVG file.
* `--git`: Commit the watched file if it has been modified.
* `--log`: Print a message when the watched file is touched.
* `-d DIRECTORY`, `--directory DIRECTORY`: The directory in which to copy the saved versions. (default: .) * `-d DIRECTORY`, `--directory DIRECTORY`: The directory in which to copy the saved versions. (default: .)
* `-y DELAY`, `--delay DELAY`: The minimum time (in seconds) between the creation of different saved files. (default: 10) * `-y DELAY`, `--delay DELAY`: The minimum time (in seconds) between the creation of different saved files. (default: 10)
* `-s SEPARATOR`, `--separator SEPARATOR`: Separator character between the file name and the date stamp. (default: _) * `-s SEPARATOR`, `--separator SEPARATOR`: Separator character between the file name and the date stamp. (default: _)
* `-t TEMPLATE`, `--template TEMPLATE`: Template of the date stamp. (default: %Y-%m-%dT%H:%M:%S) * `-t TEMPLATE`, `--template TEMPLATE`: Template of the date stamp. (default: %Y-%m-%dT%H:%M:%S)
* `-v {DEBUG,INFO,WARNING,ERROR}`, `--verbose {DEBUG,INFO,WARNING,ERROR}`: Verbosity level. (default: WARNING) * `-e {opened,moved,deleted,created,modified,closed}...`, `--events`: The events touching the watched file for which you want your action perfomed. You can pass multiple (space-separated) events, then the action will be performed for each of them. (default: modified)
* `-w`, `--no-overwrite`: Do not overwrite snapshots created at the same time, but append a number to their name (especially useful if you watch several events).
* `-v {DEBUG,INFO,WARNING,ERROR}`, `--verbose {DEBUG,INFO,WARNING,ERROR}`: Verbosity level. (default: WARNING)
## Examples ## Examples
@ -43,19 +64,25 @@ For instance, with a target file named `test.svg`, a snapshot will look like `te
Just start FlickSave with your target file as an argument: Just start FlickSave with your target file as an argument:
$ ./flicksave.py my_file.svg $ ./flicksave.py --save my_file.svg
Then edit your file and save it. Then edit your file and save it.
As usual, hit `Ctrl-C` (or close the terminal) to stop FlickSave. As usual, hit `Ctrl-C` (or close the terminal) to stop FlickSave.
If you want to specify a directory in which to put your snapshots, with no more than one separate file every minute: If you want to specify a directory in which to put your snapshots, with no more than one separate file every minute:
$ ./flicksave.py -d flicksave -y 60 my_file.xcf $ ./flicksave.py --inkscape -d flicksave -y 60 my_file.svg
If you want to see what's going on and when the snapshots are created, ask it to be more verbose: You may want to save both the file before and after it was modified by any
program:
$ ./flicksave.py --save --events opened closed --no-overwrite my_file
If you want to see what's going on and when the action(s) are called,
ask it to be more verbose:
$ touch test.txt $ touch test.txt
$ ./flicksave.py -v INFO test.txt & $ ./flicksave.py --log -v INFO test.txt &
[1] 4303 [1] 4303
echo "." >> test.txt echo "." >> test.txt
2017-11-29T21:15:51 -- ./test.txt -> ./test_2017-11-29T21:15:51.txt 2017-11-29T21:15:51 -- ./test.txt -> ./test_2017-11-29T21:15:51.txt

View file

@ -65,10 +65,12 @@ class Save(Operator):
def __repr__(self): def __repr__(self):
return f"Save({self.last_date})" return f"Save({self.last_date})"
def __init__(self, save_dir, date_sep, date_template): def __init__(self, save_dir, date_sep, date_template, no_overwrite = False):
self.date_template = date_template self.date_template = date_template
self.save_dir = save_dir self.save_dir = save_dir
self.date_sep = date_sep self.date_sep = date_sep
self.no_overwrite = no_overwrite
# Make a glob search expression with the date template. # Make a glob search expression with the date template.
self.fields = {'Y':4,'m':2,'d':2,'H':2,'M':2,'S':2} self.fields = {'Y':4,'m':2,'d':2,'H':2,'M':2,'S':2}
self.glob_template = self.date_template self.glob_template = self.date_template
@ -85,7 +87,6 @@ class Save(Operator):
flickname = name + self.date_sep + tag + ext flickname = name + self.date_sep + tag + ext
return os.path.join(self.save_dir, flickname) return os.path.join(self.save_dir, flickname)
def find_last_save(self, target): def find_last_save(self, target):
full = os.path.expanduser(target) full = os.path.expanduser(target)
head = os.path.basename(full) head = os.path.basename(full)
@ -112,6 +113,12 @@ class Save(Operator):
def save(self, flick): def save(self, flick):
flickfile = self.as_file(flick) flickfile = self.as_file(flick)
if self.no_overwrite and os.path.is_file(flickfile):
index = 1
while os.path.is_file(flickfile):
name,ext = os.path.splitext(flickfile)
flickfile = name+self.date_sep+index+ext
index += 1
logging.info("Copy %s as %s", target, flickfile) logging.info("Copy %s as %s", target, flickfile)
try: try:
shutil.copy(target, flickfile) shutil.copy(target, flickfile)
@ -148,11 +155,12 @@ class Command(Save):
You can omit one of the named arguments. You can omit one of the named arguments.
For example: 'touch {flick}'""" For example: 'touch {flick}'"""
def __init__(self, command, save_dir = ".", date_sep = "_", date_template = '%Y-%m-%dT%H:%M:%S', alt_ext = None): def __init__(self, command, save_dir = ".", date_sep = "_", date_template = '%Y-%m-%dT%H:%M:%S', alt_ext = None, no_overwrite = False):
super(type(self),self).__init__(save_dir, date_sep, date_template) super(type(self),self).__init__(save_dir, date_sep, date_template)
self.cmd = command self.cmd = command
self.alt_ext = alt_ext self.alt_ext = alt_ext
self.no_overwrite = no_overwrite
def __repr__(self): def __repr__(self):
return f"Command(\"{self.cmd}\",{self.save_dir}, {self.date_sep},{self.date_template},{self.alt_ext})" return f"Command(\"{self.cmd}\",{self.save_dir}, {self.date_sep},{self.date_template},{self.alt_ext})"
@ -281,6 +289,9 @@ if __name__=="__main__":
parser.add_argument("-t","--template", default='%Y-%m-%dT%H:%M:%S', parser.add_argument("-t","--template", default='%Y-%m-%dT%H:%M:%S',
help="Template of the date stamp.") help="Template of the date stamp.")
parser.add_argument("-w", "--no-overwrite", action="store_true",
help="Do not overwrite snapshots created at the same time, but append a number to their name.")
parser.add_argument("-v","--verbose", choices=['DEBUG','INFO','WARNING','ERROR'], default='INFO', parser.add_argument("-v","--verbose", choices=['DEBUG','INFO','WARNING','ERROR'], default='INFO',
help="Verbosity level.") help="Verbosity level.")
log_as = { 'DEBUG' :logging.DEBUG, log_as = { 'DEBUG' :logging.DEBUG,
@ -289,7 +300,6 @@ if __name__=="__main__":
'ERROR' :logging.ERROR } 'ERROR' :logging.ERROR }
# TODO HERE: keep help in existing, put instance in available
existing = { existing = {
"save": "save":
["Save a snapshot of the target file.", ["Save a snapshot of the target file.",
@ -317,13 +327,15 @@ if __name__=="__main__":
logging.basicConfig(level=log_as[asked.verbose], format='%(asctime)s -- %(message)s', datefmt=asked.template) logging.basicConfig(level=log_as[asked.verbose], format='%(asctime)s -- %(message)s', datefmt=asked.template)
# Add instances, now that we have all parameters.
available = existing; available = existing;
available["save"][1] = Save(asked.directory, asked.separator, asked.template) available["save"][1] = Save(asked.directory, asked.separator, asked.template, asked.no_overwrite)
available["inkscape"][1] = Command("inkscape {target} --without-gui --export-png={flick} --export-area-page", asked.directory, asked.separator, asked.template, "png") available["inkscape"][1] = Command("inkscape {target} --without-gui --export-png={flick} --export-area-page", asked.directory, asked.separator, asked.template, "png", asked.no_overwrite)
available["git"][1] = Command("git add {target} ; git commit -m 'Automatic flicksave commit: {flick}'") available["git"][1] = Command("git add {target} ; git commit -m 'Automatic flicksave commit: {flick}'")
available["log"][1] = Log() available["log"][1] = Log()
if __debug__: if __debug__:
# Check that both available and existing are aligned.
for op in existing: for op in existing:
assert op in available assert op in available
for op in available: for op in available: