rename "watchyap" and clean up a bit

This commit is contained in:
Johann Dreo 2025-08-10 20:30:54 +02:00
commit 4d6ae12296
4 changed files with 59 additions and 52 deletions

View file

@ -1,12 +1,12 @@
FlickSave -- automatic snapshot each time you hit save WatchYap -- automatic action each time you hit save
====================================================== ===================================================
FlickSave automagically perform an action each time you touch watched files. WatchYap automagically perform an action each time you touch watched files.
For instance, FlickSave can backup a timestamped snapshot of a file that you edit with any program, without getting in your way. For instance, WatchYap 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, WatchYap 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.
@ -15,12 +15,12 @@ You can even do an automatic Git commit the same way.
## Usage ## Usage
You should start FlickSave with the files you want to save as argument(s) You should start WatchYap with the files you want to save as argument(s)
and let it run during the time you want to perform your action(s). and let it run during the time you want to perform your action(s).
Once started, it will watch for modifications on the given file(s) Once started, it will watch for modifications on the given file(s)
and, for instance, create the snapshots by himself each time something happens. and, for instance, create the snapshots by himself each time something happens.
Currently, FlickSave can perform the following actions: Currently, WatchYap can perform the following actions:
- `--save`, - `--save`,
- `--inkscape`, - `--inkscape`,
@ -40,7 +40,7 @@ For instance, with a target file named `test.svg`, a snapshot will look like `te
### Synopsis ### Synopsis
`flicksave.py [-h] [--save] [--inkscape] [--git] [--log] [--dbus] [-d DIRECTORY] [-y DELAY] [-s SEPARATOR] [-t TEMPLATE] [-w] [-v {DEBUG,INFO,WARNING,ERROR}] [-e {opened,moved,deleted,created,modified,closed}...] target [target ...]` `watchyap.py [-h] [--save] [--inkscape] [--git] [--log] [--dbus] [-d DIRECTORY] [-y DELAY] [-s SEPARATOR] [-t TEMPLATE] [-w] [-v {DEBUG,INFO,WARNING,ERROR}] [-e {opened,moved,deleted,created,modified,closed}...] target [target ...]`
### Required positional argument ### Required positional argument
@ -89,49 +89,49 @@ Other:
### Command line ### Command line
Just start FlickSave with your target file as an argument: Just start WatchYap with your target file as an argument:
$ flicksave --save my_file.svg $ watchyap --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 WatchYap.
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 --inkscape --directory flicksave --delay 60 my_file.svg $ watchyap --inkscape --directory watchyap --delay 60 my_file.svg
You can call sevral actions, for instance export a PNG and commit the source SVG: You can call sevral actions, for instance export a PNG and commit the source SVG:
$ flicksave --inkscape --git my_file.svg $ watchyap --inkscape --git my_file.svg
You can use your shell to create the file list: You can use your shell to create the file list:
$ flicksave --git *.py $ watchyap --git *.py
And even handle files in subdirectories: And even handle files in subdirectories:
$ flicksave --log */*.log $ watchyap --log */*.log
You may want to save both the file before and after it was modified by any You may want to save both the file before and after it was modified by any
program: program:
$ flicksave --save --events opened closed --no-overwrite my_file $ watchyap --save --events opened closed --no-overwrite my_file
You can export PNGs and commit their source across sub-directories, You can export PNGs and commit their source across sub-directories,
and be notified when it's done: and be notified when it's done:
$ flicksave --inkscape --git --dbus --events closed */*.svg $ watchyap --inkscape --git --dbus --events closed */*.svg
You can also prepare the list of files to watch by using a subcommand: You can also prepare the list of files to watch by using a subcommand:
$ flicksave --log $(find . -type f -name *.png | grep -v test) $ watchyap --log $(find . -type f -name *.png | grep -v test)
If you want to pass your own command: If you want to pass your own command:
$ flicksave --cmd "git commit -a -m 'whatever'" my_file $ watchyap --cmd "git commit -a -m 'whatever'" my_file
$ flicksave --cmd "git add {target} ; git commit -m 'Automated commit' ; git tag '{timestamp}'" $ watchyap --cmd "git add {target} ; git commit -m 'Automated commit' ; git tag '{timestamp}'"
$ flicksave --cmd "echo '{flick}' > watching_pipe" *.log $ watchyap --cmd "echo '{flick}' > watching_pipe" *.log
$ watchyap --alt-ext .jpg --cmd "convert -antialias {target} {flick}" my_file.png
## Authors ## Authors

View file

@ -1 +0,0 @@
flicksave.py

1
watchyap Symbolic link
View file

@ -0,0 +1 @@
watchyap.py

View file

@ -19,6 +19,8 @@ except Exception as e:
else: else:
HAS_DBUS = True HAS_DBUS = True
from operator import attrgetter
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler from watchdog.events import LoggingEventHandler
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
@ -67,10 +69,10 @@ class Flicker:
class Handler(FileSystemEventHandler): class Handler(FileSystemEventHandler):
"""Event handler, will call a sequence of operators at each event matching the target.""" """Event handler, will call a sequence of actions at each event matching the target."""
def __init__(self, operators, flicker, watched_events = ["modified"] ): def __init__(self, actions, flicker, watched_events = ["modified"] ):
self.flicker = flicker self.flicker = flicker
self.ops = operators self.ops = actions
self.watched_events = watched_events self.watched_events = watched_events
def on_any_event(self, event): def on_any_event(self, event):
@ -102,14 +104,14 @@ class Handler(FileSystemEventHandler):
logging.debug("Not handling event:" + ", ".join([i for i in [is_dir, is_not_target, is_not_watched] if i != ""])) logging.debug("Not handling event:" + ", ".join([i for i in [is_dir, is_not_target, is_not_watched] if i != ""]))
class Operator: class Action:
"""Interface example for an Operator (but can actually be any callable with the same signature).""" """Interface example for an Action (but can actually be any callable with the same signature)."""
def __call__(self, target, flick, event, alt_ext = None): def __call__(self, target, flick, event, alt_ext = None):
raise NotImplemented raise NotImplemented
class Save(Operator): class Save(Action):
"""Make a copy of the target file. """Make a copy of the target file.
Takes care to create a missing directory if necessary.""" Takes care to create a missing directory if necessary."""
def __repr__(self): def __repr__(self):
@ -209,7 +211,7 @@ class Command(Save):
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, no_overwrite = False): 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(Command,self).__init__(save_dir, date_sep, date_template)
self.cmd = command self.cmd = command
self.alt_ext = alt_ext self.alt_ext = alt_ext
@ -249,7 +251,7 @@ class Command(Save):
logging.debug("Command ended.") logging.debug("Command ended.")
class Log(Operator): class Log(Action):
def __repr__(self): def __repr__(self):
return "Log()" return "Log()"
@ -258,9 +260,9 @@ class Log(Operator):
if HAS_DBUS: if HAS_DBUS:
class DBus(Operator): class DBus(Action):
def __repr__(self): def __repr__(self):
return "Log()" return "DBus()"
def __call__(self, target, flick, event, alt_ext = None): def __call__(self, target, flick, event, alt_ext = None):
logging.info(f"File {target} was {event.event_type}") logging.info(f"File {target} was {event.event_type}")
@ -278,7 +280,7 @@ if HAS_DBUS:
) )
def flicksave(globbed_targets, operators=None, delay=10, watched=["modified"]): def watchyap(globbed_targets, actions=None, delay=10, watched=["modified"]):
"""Start the watch thread.""" """Start the watch thread."""
targets = [os.path.abspath(p) for p in globbed_targets] targets = [os.path.abspath(p) for p in globbed_targets]
@ -291,7 +293,7 @@ def flicksave(globbed_targets, operators=None, delay=10, watched=["modified"]):
flicker = Flicker(targets, delay) flicker = Flicker(targets, delay)
handler = Handler(operators, flicker, watched) handler = Handler(actions, flicker, watched)
# Start the watch thread. # Start the watch thread.
observer = Observer() observer = Observer()
@ -310,8 +312,13 @@ if __name__=="__main__":
import sys import sys
import argparse import argparse
class SortingHelpFormatter(argparse.HelpFormatter):
def add_arguments(self, actions):
actions = sorted(actions, key=attrgetter('option_strings'))
super(SortingHelpFormatter, self).add_arguments(actions)
class SaneHelpFormatter( class SaneHelpFormatter(
argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter SortingHelpFormatter, argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter
): ):
pass pass
@ -323,18 +330,18 @@ if __name__=="__main__":
Examples: Examples:
Copy the file each time it is modified: Copy the file each time it is modified:
flicksave --save my_file watchyap --save my_file
Copy the file in a subdirectory each time it is modified: Copy the file in a subdirectory each time it is modified:
flicksave --directory snapshots --save my_file watchyap --directory snapshots --save my_file
Export a PNG from a watched SVG, each time you hit 'save' in inkscape: Export a PNG from a watched SVG, each time you hit 'save' in inkscape:
flicksave --inkscape my_file watchyap --inkscape my_file
Git commit the file if it has been modified, but not before 30 seconds after the previous commit: Git commit the file if it has been modified, but not before 30 seconds after the previous commit:
flicksave --git --delay 30 my_file watchyap --git --delay 30 my_file
Copy the file each time is is opened or closed: Copy the file each time is is opened or closed:
flicksave --events opened closed --save my_file watchyap --events opened closed --save my_file
You may want to save both the file before and after it was modified by any You may want to save both the file before and after it was modified by any
program: program:
flicksave --save --events opened closed --no-overwrite my_file watchyap --save --events opened closed --no-overwrite my_file
""") """)
# Optional arguments. # Optional arguments.
@ -367,7 +374,7 @@ Examples:
'WARNING':logging.WARNING, 'WARNING':logging.WARNING,
'ERROR' :logging.ERROR } 'ERROR' :logging.ERROR }
parser.add_argument("--version", action="store_true", parser.add_argument("-V", "--version", action="store_true",
help="Show program's version number and exit.") help="Show program's version number and exit.")
asked = parser.parse_known_args()[0] asked = parser.parse_known_args()[0]
@ -430,7 +437,7 @@ Examples:
if HAS_GIT: if HAS_GIT:
logging.debug("`git` command found") logging.debug("`git` command found")
available["git"][1] = Command("git add {target} ; git commit -m 'Automatic flicksave commit of {target} at {timestamp}'") available["git"][1] = Command("git add {target} ; git commit -m 'Automatic watchyap commit of {target} at {timestamp}'")
else: else:
print("WARNING: `git` command not found, the --inkscape action is disabled.", file=sys.stderr) print("WARNING: `git` command not found, the --inkscape action is disabled.", file=sys.stderr)
@ -463,7 +470,7 @@ Examples:
for op in available: for op in available:
assert op in existing assert op in existing
logging.debug("Available operators:") logging.debug("Available actions:")
for name in available: for name in available:
logging.debug("\t%s",name) logging.debug("\t%s",name)
@ -475,27 +482,27 @@ Examples:
asked = parser.parse_args() asked = parser.parse_args()
operators = [] actions = []
requested = vars(asked) requested = vars(asked)
def instance(name): def instance(name):
return available[name][1] return available[name][1]
for it in [iz for iz in requested if iz in available and requested[iz]]: for it in [iz for iz in requested if iz in available and requested[iz]]:
operators.append(instance(it)) actions.append(instance(it))
if len(operators) == 0: if len(actions) == 0:
logging.warning( logging.warning(
"WARNING: you did not asked for any snapshot command, " "WARNING: you did not asked for any snapshot command, "
"I will only log when you save the file, but not perform any action. " "I will only log when you save the file, but not perform any action. "
"Use one of the following option if you want me to do something: " "Use one of the following option if you want me to do something: "
+", ".join(["--"+str(k) for k in available.keys()]) +", ".join(["--"+str(k) for k in available.keys()])
) )
operators.append(Log()) actions.append(Log())
logging.debug("Used operators:") logging.debug("Used actions:")
for op in operators: for op in actions:
logging.debug("\t%s", op) logging.debug("\t%s", op)
# Start it. # Start it.
logging.debug(asked.targets) logging.debug(asked.targets)
flicksave(asked.targets, operators, asked.delay, asked.events) watchyap(asked.targets, actions, asked.delay, asked.events)