From 29aaca3816c5760a19fa0259948fc60e19a2041e Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 22 Nov 2011 13:43:43 +0100 Subject: [PATCH] Initial commit --- afo.py | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100755 afo.py diff --git a/afo.py b/afo.py new file mode 100755 index 0000000..35b8d2d --- /dev/null +++ b/afo.py @@ -0,0 +1,203 @@ +#!/usr/bin/python +# afo - automatized file opener. +# +# ----------------------------------------------------- +# Macros are: +# +# %f or %0 = the full path to the file name +# %r = %f without the file extension +# %e = the extension of %f only +# %d = dirname(%f) +# %b = basename(%f) +# %B = basename(%f) without the file extension +# %@ = the list of positional arguments passed to afo +# %0..%9 = one specific argument passed to afo +# %W0..%W9 = the program entry for this given way +# +# They can be used in a program entry and will be substituted for their value. +# +# ----------------------------------------------------- +# Configuration file: +# +# It's a yaml dictionary, in the form of: +# : +# +# For exapmle: +# py$: python2.7 %f +# (png|jpg)$: feh $f $@ +# +# The first regular expression that matches the given file argument +# is used and its respective program is run. Sometimes it is necessary +# to open files in different ways. For example, one way to compile it +# and one way to execute it: +# +# tex$: +# 0: pdflatex %B +# 1: epdfview %B.pdf +# c$: +# - "%W1; %W2" +# - gcc %f -o /tmp/a.out +# - /tmp/a.out +# /home/me/my_project/: +# - cd /home/me/my_project; make +# - /home/me/my_project/my_executable +# +# You can then use the --way option to specify which way to run it. +# For example, "afo --way 1,2 test.c" would compile and run test.c. +# "afo test.c" would do the same, because way 0 runs way 1 and 2 in order. +# +# If the first word in the program description starts with a "-", it will +# be interpreted as a set of additional afo options: +# avi$: -qd mplayer %f +# txt$: -p cat %f + +import optparse +import os.path +import re +import subprocess +import string +import sys +try: + import yaml +except ImportError: + print("afo: error: python-yaml required.") + +class AFO(object): + def __init__(self, file, options=[], ways=[0], list_ways=False, args=[]): + self.__dict__.update(locals()) + self.file = os.path.abspath(file) + self.config = self._load_config() + + for entry, program in self.config.items(): + try: + regex = re.compile(entry) + except: + print("afo: warning: Bad regexp: %s" % entry) + else: + if regex.search(self.file): + return self._run(program) + + try: + first_line = open(self.file).readline() + if first_line[0:2] == '#!': + program = first_line.strip()[2:] + if program: + return self._run(program + ' %f %@') + except: + pass + + print("afo: error: Unknown type") + + def _normalize_program_entry(self, program): + if isinstance(program, str): + return {'0': program} + elif isinstance(program, dict): + return dict(map((lambda k,v: (str(k), v)), program)) + elif isinstance(program, list): + return dict(map((lambda k,v: (str(k), v)), enumerate(program))) + + def _get_configuration_path(self): + basedir = os.path.expanduser(os.getenv('XDG_CONFIG_PATH', '~/.config')) + return os.path.join(basedir, 'afo', 'config.yaml') + + def _load_config(self): + try: + return yaml.load(open(self._get_configuration_path())) + except Exception as e: + print("afo: error: Failed to read config file:\n", e) + return {} + + def _run(self, program): + program = self._normalize_program_entry(program) + if self.list_ways: + print("\n".join(("%d: %s" % (n, line)) for n, line in program.items())) + + else: + for way in self.ways: + if way in program: + self._shell(self._expand(program, way)) + else: + print("afo: warning: Unknown way `%s'" % str(way)) + + def _shell(self, command): + if set('vt') & set(self.options): + print(command) + if 't' not in self.options: + if command[0] == '-': + options, command = command.split(' ', 1) + self.options += options[1:] + popen_kws = {'shell': True} + if 'q' in self.options: + for key in ('stdout', 'stderr', 'stdin'): + popen_kws[key] = open(os.devnull, 'a') + p = subprocess.Popen(command, **popen_kws) + 'd' in self.options or p.wait() + if 'w' in self.options: + print("Press ENTER to continue") + try: raw_input() + except: input() + + def _expand(self, program, way): + macros = self._get_macros(program) + return self.Template(program[way]).safe_substitute(macros) + + def _get_macros(self, program): + macros = { + '0': self.file, + 'f': self.file, + 'r': os.path.splitext(self.file)[0], + 'e': os.path.splitext(self.file)[1][1:], + 'd': os.path.dirname(self.file), + 'b': os.path.basename(self.file), + 'B': os.path.splitext(os.path.basename(self.file))[0], + '@': " ".join("'" + arg.replace("'", "'\''") + "'" for arg in self.args), + } + for way in program: + if type(way) == int: + macros['W%d' % way] = self.Template(program[way]).safe_substitute(macros) + for i in range(0, 100): + if i < len(self.args): + macros[str(i)] = self.args[i] + else: + macros[str(i)] = "" + return macros + + @staticmethod + def get_parameters_from_argv(argv=None): + class MoreOptions(optparse.Option): + TYPES = optparse.Option.TYPES + ('list', ) + TYPE_CHECKER = dict(optparse.Option.TYPE_CHECKER, + list=lambda _, __, value: value.split(',')) + + p = optparse.OptionParser(option_class=MoreOptions, + usage="%prog [options] path [-- args...]") + p.add_option('-p', action='store_true', help='pipe output into a pager') + p.add_option('-w', action='store_true', + help='wait for a key press afterwards') + p.add_option('-q', action='store_true', help='discard output') + p.add_option('-t', action='store_true', help='test only') + p.add_option('-v', action='store_true', help='be verbose') + p.add_option('-d', action='store_true', help='detach (fork) process') + p.add_option('--ways', type='list', default='0', metavar='N,M,..', + help="open the file in what way(s)?") + p.add_option('--list-ways', action='store_true', + help="list all possible ways to run this file") + keywords, args = p.parse_args(argv) + if not len(args) > 1: + p.print_help() + raise SystemExit() + opts = ''.join(f for f,v in keywords.__dict__.items() if len(f) == 1 and v) + + return { + 'options': opts, + 'file': args[1], + 'ways': keywords.ways, + 'list_ways': keywords.list_ways, + 'args': args[1:] } + + class Template(string.Template): + delimiter = '%' + idpattern = r'(?:W[0-9]+|[_@a-z0-9])' + +if __name__ == '__main__': + AFO(**AFO.get_parameters_from_argv(sys.argv)) -- 2.11.4.GIT