removed template stuff
[afo.git] / afo.py
blobfd72c5c7cbadabf7724468accc80dc3023f57e52
1 #!/usr/bin/python
2 # afo - automatized file opener.
4 # -----------------------------------------------------
5 # Environment Variables are:
7 # $f = the full path to the file name
8 # $F = $f without the file extension
9 # $e = the extension of $f only
10 # $d = dirname($f)
11 # $b = basename($f)
12 # $B = basename($f) without the file extension
13 # $args = the list of positional arguments passed to afo
14 # $a0..$a999 = one specific argument passed to afo
16 # -----------------------------------------------------
17 # Configuration file:
19 # It's a yaml dictionary, in the form of:
20 # <regexp>: <program entry>
22 # For example:
23 # py$: python2.7 $f
24 # (png|jpg)$: feh $f $args
26 # The first regular expression that matches the given file argument
27 # is used and its respective program is run. Sometimes it is necessary
28 # to open files in different ways. For example, one way to compile it
29 # and one way to execute it:
31 # tex$:
32 # 0: pdflatex $B
33 # 1: epdfview $B.pdf
34 # c$:
35 # - "$w1; $w2"
36 # - gcc $f -o /tmp/a.out
37 # - /tmp/a.out
38 # /home/me/my_project/:
39 # - cd /home/me/my_project; make
40 # - /home/me/my_project/my_executable
42 # You can then use the --way option to specify which way to run it.
43 # For example, "afo --way 1,2 test.c" would compile and run test.c.
44 # "afo test.c" would do the same, because way 0 runs way 1 and 2 in order.
46 # If the first word in the program description starts with a "-", it will
47 # be interpreted as a set of additional afo options:
48 # avi$: -qf mplayer $f
49 # txt$: -p cat $f
51 import mimetypes
52 import optparse
53 import os.path
54 import re
55 import subprocess
56 import sys
57 try:
58 import yaml
59 except ImportError:
60 print("afo: error: python-yaml required.")
62 def _shell_quote(string):
63 return "'" + string.replace("'", "'\''") + "'"
65 class AFO(object):
66 def __init__(self, file, options=[], ways=[0], list_ways=False, args=[]):
67 self.__dict__.update(locals())
68 self.file = os.path.abspath(file)
69 self.config = self._load_config()
71 mimetypes.knownfiles.append(os.path.expanduser('~/.mime.types'))
72 basename = os.path.basename(self.file)
73 self.mimetype = mimetypes.MimeTypes().guess_type(basename, False)[0] or ""
74 match_string = '%s///%s' % (self.mimetype, self.file)
76 for entry, program in self.config.items():
77 try:
78 regex = re.compile(entry)
79 except:
80 print("afo: warning: Bad regexp: %s" % entry)
81 else:
82 if regex.search(match_string):
83 return self._run(program)
85 try:
86 first_line = open(self.file).readline()
87 if first_line[0:2] == '#!':
88 program = first_line.strip()[2:]
89 if program:
90 return self._run(program + ' $f $@')
91 except:
92 pass
94 print("afo: error: Unknown type")
96 def _normalize_program_entry(self, program):
97 if isinstance(program, str):
98 return {'0': program}
99 elif isinstance(program, dict):
100 return dict(map((lambda v: (str(v[0]), v[1])), program.items()))
101 elif isinstance(program, list):
102 return dict(map((lambda v: (str(v[0]), v[1])), enumerate(program)))
104 def _load_config(self):
105 basedir = os.path.expanduser(os.getenv('XDG_CONFIG_PATH', '~/.config'))
106 confpath = os.path.join(basedir, 'afo', 'config.yaml')
107 try:
108 return yaml.load(open(confpath))
109 except Exception as e:
110 print("afo: error: Failed to read config file:\n", e)
111 return {}
113 def _run(self, program):
114 program = self._normalize_program_entry(program)
115 if self.list_ways:
116 print("\n".join(("%d: %s" % (n, line)) for n, line in program.items()))
117 else:
118 for way in self.ways:
119 if way in program:
120 self._shell(program[way])
121 else:
122 print("afo: warning: Unknown way `%s'" % str(way))
124 def _generate_env(self, program):
125 env = dict(os.environ)
126 env.update({
127 'f': self.file,
128 'F': os.path.splitext(self.file)[0],
129 'e': os.path.splitext(self.file)[1][1:],
130 'd': os.path.dirname(self.file),
131 'b': os.path.basename(self.file),
132 'B': os.path.splitext(os.path.basename(self.file))[0],
133 'm': self.mimetype,
134 'args': " ".join(map(_shell_quote, self.args)),
136 for i, arg in enumerate(self.args):
137 env[str(i+1)] = self.args[i]
138 return env
140 def _shell(self, command):
141 if set('vt') & set(self.options):
142 print(command)
143 if 't' not in self.options:
144 if command[0] == '-':
145 options, command = command.split(' ', 1)
146 self.options += options[1:]
147 popen_kws = {'shell': True, 'env': self._generate_env(command)}
148 if 'q' in self.options:
149 for key in ('stdout', 'stderr', 'stdin'):
150 popen_kws[key] = open(os.devnull, 'a')
151 p = subprocess.Popen(command, **popen_kws)
152 'd' in self.options or p.wait()
153 if 'w' in self.options:
154 print("Press ENTER to continue")
155 try: raw_input()
156 except: input()
158 @staticmethod
159 def get_parameters_from_argv(argv=None):
160 class MoreOptions(optparse.Option):
161 TYPES = optparse.Option.TYPES + ('list', )
162 TYPE_CHECKER = dict(optparse.Option.TYPE_CHECKER,
163 list=lambda _, __, value: value.split(','))
165 p = optparse.OptionParser(option_class=MoreOptions,
166 usage="%prog [options] path [-- args...]")
167 p.add_option('-p', action='store_true', help='pipe output into a pager')
168 p.add_option('-w', action='store_true',
169 help='wait for a key press afterwards')
170 p.add_option('-q', action='store_true', help='discard output')
171 p.add_option('-t', action='store_true', help='test only')
172 p.add_option('-v', action='store_true', help='be verbose')
173 p.add_option('-f', action='store_true', help='fork process')
174 p.add_option('--ways', type='list', default='0', metavar='N,M,..',
175 help="open the file in what way(s)?")
176 p.add_option('--list-ways', action='store_true',
177 help="list all possible ways to run this file")
178 keywords, args = p.parse_args(argv)
179 if not len(args) > 1:
180 p.print_help()
181 raise SystemExit()
182 opts = ''.join(f for f,v in keywords.__dict__.items() if len(f) == 1 and v)
184 return {
185 'options': opts,
186 'file': args[1],
187 'ways': keywords.ways,
188 'list_ways': keywords.list_ways,
189 'args': args[1:] }
191 if __name__ == '__main__':
192 AFO(**AFO.get_parameters_from_argv(sys.argv))