From 49e8b76c2d6f52f63cee39d96f65af05b21d4f75 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Tue, 23 Sep 2014 09:18:14 +0200 Subject: [PATCH] add the --group argument --- README.md | 38 +++++++++++++++++++++++++++++++++----- paternoster.py | 47 +++++++++++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8554366..387e8f5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A command line tool that call the given command for each input line matching the `paternoster` [-h] -`paternoster` [-w] PATTERN COMMAND(S) +`paternoster` [-w] [-e] PATTERN COMMAND(S) ## DESCRIPTION @@ -25,20 +25,48 @@ silently ignored. If you ask for fewer commands, the last one will be duplicated across remaining groups. +## OPTIONS + +* `-h`, `--help`: + Show a help message and exit + +* `-w`, `--nowait`: + Don't wait for the end of the current command before calling the next one. + +* `-e`, `--end`: + Run commands at the end of the input stream. + +* `-g`, `--group`: + Capture groups as arguments for the CMD, instead of running CMD for each group. + CMD must have as many formating placeholder as captured groups. + For example, if you capture 3 groups, you must put 3 formating placeholders in CMD: + `paternoster --group "^(.*):(.*):(.*)$" "echo '>>> 1:%s 2:%s 3:%s <<<'"` + + ## EXAMPLES * Make the speakers beep for every FIXME found in a source code: - `cat source.ext | paternoster "FIXME" "beep" + `cat paternoster.py | paternoster "FIXME" "beep" * Play a sound for each error or warning on a compiler output (you must have the - corresponding wav files somewhere in you path): - `make 2>&1 | paternoster ".*(error|warning): .*$" "play %s.wav"` + corresponding sound files in you path): + `make 2>&1 | paternoster ".*(error|warning): .*$" "ogg123 %s.ogg"` * Say out loud the error in a compiler output: `make 2>&1 | paternoster ".*(error: .*)$" "espeak --punct=';{}()' '%s'"` +* Play a series of sounds corresponding to the sequence of errors and warning + at the end of a compilation: + `make 2>&1 | paternoster --end ".*(error|warning): .*" "ogg123 %.ogg"` + +* Notify the desktop every time a user logs in: + `tail -n 2 -f /var/log/auth.log | paternoster ".*session opened for user ([^\s]+)\s*" "notify-send '%s login'"` + +* Notify the desktop every time a user logs in or out: + `tail -n 2 -f /var/log/auth.log | paternoster --group ".*session (opened|closed) for user ([^\s]+)\s*" "notify-send '%s %s'"` + ## CREDITS * Error sound by tcpp, licensed under CC-BY: http://www.freesound.org/people/tcpp/sounds/151309/ -* + diff --git a/paternoster.py b/paternoster.py index 9a34fca..d60197d 100755 --- a/paternoster.py +++ b/paternoster.py @@ -16,7 +16,7 @@ def run( cmd, text, nowait=False ): # If there is a string formatting mark try: # put the matching text in it - behest = cmd % text + behest = cmd % tuple(text) except TypeError: # else, do not behest = cmd @@ -35,7 +35,7 @@ def call( cmd, text, nowait=False, queue=None ): run( cmd, text, nowait ) -def parse( text, pattern, cmd=[""], nowait=False, queue=None ): +def parse( text, pattern, cmd=[""], nowait=False, queue=None, capture_groups = False ): regex = re.compile(pattern) for match in regex.finditer(text): @@ -47,18 +47,29 @@ def parse( text, pattern, cmd=[""], nowait=False, queue=None ): else: nb_groups = len(match.groups()) - # Build a list of colors that match the number of grouped, - # If there is not enough commands, duplicate the last one. - group_cmds = cmd + [cmd[-1]] * (nb_groups - len(cmd)) + if capture_groups: + # For each group index. + # Note that match.groups returns a tuple (thus being indexed in [0,n[), + # but that match.start(0) refers to the whole match, the groups being indexed in [1,n]. + # Thus, we need to range in [1,n+1[. + texts = [] + for group in range(1, nb_groups+1): + # If a group didn't match, there's nothing to do + if match.group(group) is not None: + texts.append( text[match.start(group):match.end(group)] ) - # For each group index. - # Note that match.groups returns a tuple (thus being indexed in [0,n[), - # but that match.start(0) refers to the whole match, the groups being indexed in [1,n]. - # Thus, we need to range in [1,n+1[. - for group in range(1, nb_groups+1): - # If a group didn't match, there's nothing to do - if match.group(group) is not None: - call( group_cmds[group-1], text[match.start(group):match.end(group)], nowait, queue ) + # Finally, call a single command with all the captured groups + call( cmd[0], texts, nowait, queue ) + + else: + # Build a list of commands that match the number of groups, + # If there is not enough commands, duplicate the last one. + group_cmds = cmd + [cmd[-1]] * (nb_groups - len(cmd)) + + for group in range(1, nb_groups+1): + # If a group didn't match, there's nothing to do + if match.group(group) is not None: + call( group_cmds[group-1], text[match.start(group):match.end(group)], nowait, queue ) def write( text, stream = sys.stdout): @@ -77,7 +88,7 @@ def write( text, stream = sys.stdout): pass -def read_parse( stream_in, stream_out, pattern, cmd, nowait = False, at_end = False ): +def read_parse( stream_in, stream_out, pattern, cmd, nowait = False, at_end = False, capture_groups = False ): """ Read the given file-like object as a non-blocking stream and call the function on each item (line), @@ -107,7 +118,7 @@ def read_parse( stream_in, stream_out, pattern, cmd, nowait = False, at_end = Fa # Write the line being processed write( item, stream_out ) # Then do something - parse(item, pattern, cmd, nowait, events) + parse(item, pattern, cmd, nowait, events, capture_groups) if at_end: while events.not_empty: @@ -140,7 +151,11 @@ if __name__ == "__main__": parser.add_argument("-e", "--end", action="store_true", help="Run commands at the end of the input stream.") + parser.add_argument("-g", "--groups", action="store_true", + help="Capture groups as arguments for the CMD, instead of running CMD for each group.\ + CMD must have as many formating placeholder as captured groups.") + args = parser.parse_args() - read_parse( sys.stdin, sys.stdout, args.pattern[0], args.commands, args.no_wait, args.end ) + read_parse( sys.stdin, sys.stdout, args.pattern[0], args.commands, args.no_wait, args.end, args.groups )