diff --git a/README.md b/README.md index 4c6110c..3a0a80c 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,61 @@ 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, FlickSave will make a copy the current version of your file, 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. +You can even do an automatic Git commit the same way. + ## Usage 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 -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`. + ### 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 -`target`: The file to save each time it's modified. +`target`: The file to watch. ### Optional arguments * `-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: .) * `-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: _) * `-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 @@ -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: - $ ./flicksave.py my_file.svg + $ ./flicksave.py --save my_file.svg Then edit your file and save it. 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: - $ ./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 - $ ./flicksave.py -v INFO test.txt & + $ ./flicksave.py --log -v INFO test.txt & [1] 4303 echo "." >> test.txt 2017-11-29T21:15:51 -- ./test.txt -> ./test_2017-11-29T21:15:51.txt diff --git a/flicksave.py b/flicksave.py index e7c5088..691202b 100755 --- a/flicksave.py +++ b/flicksave.py @@ -65,10 +65,12 @@ class Save(Operator): def __repr__(self): 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.save_dir = save_dir self.date_sep = date_sep + self.no_overwrite = no_overwrite + # Make a glob search expression with the date template. self.fields = {'Y':4,'m':2,'d':2,'H':2,'M':2,'S':2} self.glob_template = self.date_template @@ -85,7 +87,6 @@ class Save(Operator): flickname = name + self.date_sep + tag + ext return os.path.join(self.save_dir, flickname) - def find_last_save(self, target): full = os.path.expanduser(target) head = os.path.basename(full) @@ -112,6 +113,12 @@ class Save(Operator): def save(self, 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) try: shutil.copy(target, flickfile) @@ -148,11 +155,12 @@ class Command(Save): You can omit one of the named arguments. 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) self.cmd = command self.alt_ext = alt_ext + self.no_overwrite = no_overwrite def __repr__(self): 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', 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', help="Verbosity level.") log_as = { 'DEBUG' :logging.DEBUG, @@ -289,7 +300,6 @@ if __name__=="__main__": 'ERROR' :logging.ERROR } - # TODO HERE: keep help in existing, put instance in available existing = { "save": ["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) + # Add instances, now that we have all parameters. available = existing; - available["save"][1] = Save(asked.directory, asked.separator, asked.template) - available["inkscape"][1] = Command("inkscape {target} --without-gui --export-png={flick} --export-area-page", asked.directory, asked.separator, asked.template, "png") + 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", asked.no_overwrite) available["git"][1] = Command("git add {target} ; git commit -m 'Automatic flicksave commit: {flick}'") available["log"][1] = Log() if __debug__: + # Check that both available and existing are aligned. for op in existing: assert op in available for op in available: