2 # afo - automatized file opener.
3 # Copyright (C) 2010, 2011 Roman Zimbelmann <romanz@lavabit.com>
4 # This software is licensed under the terms of the GNU GPL version 3.
6 # -----------------------------------------------------
7 # Environment Variables are:
9 # $f = the full path to the file name
10 # $F = $f without the file extension
11 # $e = the extension of $f only
14 # $B = basename($f) without the file extension
15 # $args = the list of positional arguments passed to afo
16 # $a0..$a999 = one specific argument passed to afo
18 # -----------------------------------------------------
21 # It's a yaml dictionary, in the form of:
22 # <regexp>: <programs>
26 # (png|jpg)$: feh $f $args
28 # -----------------------------------------------------
29 # <regexp> is a regular expression that matches against this string:
30 # <mime-type>///<file-path>. This way you can check for complicated
31 # relationships with a single regexp. For example, this regular expression
32 # matches images (by mime-type) except SVG's: ^image.*(?<!svg)$
34 # The first regular expression that matches is used and its respective program
37 # -----------------------------------------------------
38 # <program> is either a string, a list or a dictionary. In case of a string,
39 # the command in the string is simply executed. But sometimes it is necessary
40 # to open files in different ways. For example, one way to compile it and one
44 # compile: pdflatex $B
45 # view: epdfview $B.pdf
47 # - gcc $f -o /tmp/a.out
49 # /home/me/my_project/:
50 # - cd /home/me/my_project; make
51 # - /home/me/my_project/my_executable
53 # You can then use the --ways option to specify which way to run it.
54 # For example, "afo --ways 0,1 test.c" would compile and run test.c.
56 # -----------------------------------------------------
57 # Sometimes you can't be sure whether a program is installed or not.
58 # In this case, you can specify multiple programs which will be run in succession
59 # until one ends with an exit code which is not 127 (sh's exit code when a
60 # command is not found.) Examples:
68 # - totem --fullscreen $f
74 # -----------------------------------------------------
75 # If the first word in the program description starts with a "-", it will
76 # be interpreted as a set of additional afo options:
77 # avi$: -qf mplayer $f
82 __author__
= 'Roman Zimbelmann'
94 print("afo: error: python-yaml required.")
97 def __init__(self
, file, options
=[], ways
=[0], list_ways
=False, args
=[]):
98 self
.__dict
__.update(locals())
99 self
.file = os
.path
.abspath(file)
100 self
.config
= self
._load
_config
()
102 mimetypes
.knownfiles
.append(os
.path
.expanduser('~/.mime.types'))
103 basename
= os
.path
.basename(self
.file)
104 self
.mimetype
= mimetypes
.MimeTypes().guess_type(basename
, False)[0] or ""
105 match_string
= '%s///%s' % (self
.mimetype
, self
.file)
107 for entry
, program
in self
.config
.items():
109 regex
= re
.compile(entry
)
111 print("afo: warning: Bad regexp: %s" % entry
)
113 if regex
.search(match_string
):
114 return self
._run
(program
)
117 first_line
= open(self
.file).readline()
118 if first_line
[0:2] == '#!':
119 program
= first_line
.strip()[2:]
121 return self
._run
(program
+ ' $f $@')
125 print("afo: error: Unknown type")
127 def _normalize_program_entry(self
, program
):
128 if isinstance(program
, str):
129 return {'0': program
}
130 elif isinstance(program
, dict):
131 return dict(map((lambda v
: (str(v
[0]), v
[1])), program
.items()))
132 elif isinstance(program
, list):
133 return dict(map((lambda v
: (str(v
[0]), v
[1])), enumerate(program
)))
135 def _load_config(self
):
136 basedir
= os
.path
.expanduser(os
.getenv('XDG_CONFIG_PATH', '~/.config'))
137 confpath
= os
.path
.join(basedir
, 'afo', 'config.yaml')
139 return yaml
.load(open(confpath
))
140 except Exception as e
:
141 print("afo: error: Failed to read config file:\n", e
)
144 def _run(self
, program
):
145 program
= self
._normalize
_program
_entry
(program
)
147 print("\n".join(("%d: %s" % (n
, line
)) for n
, line
in program
.items()))
149 for way
in self
.ways
:
151 if isinstance(program
[way
], list):
152 self
._shell
(program
[way
])
154 self
._shell
([program
[way
]])
156 print("afo: warning: Unknown way `%s'" % str(way
))
158 def _generate_env(self
, program
):
159 env
= dict(os
.environ
)
162 'F': os
.path
.splitext(self
.file)[0],
163 'e': os
.path
.splitext(self
.file)[1][1:],
164 'd': os
.path
.dirname(self
.file),
165 'b': os
.path
.basename(self
.file),
166 'B': os
.path
.splitext(os
.path
.basename(self
.file))[0],
168 'args': " ".join(self
.args
),
170 for i
, arg
in enumerate(self
.args
):
171 env
["a%d" % (i
+1)] = self
.args
[i
]
174 def _shell(self
, commands
):
175 if set('vt') & set(self
.options
):
178 if 't' not in self
.options
:
179 for i
, command
in enumerate(commands
):
180 is_last
= len(commands
) is i
+ 1
182 if command
[0] == '-':
183 options
, command
= command
.split(' ', 1)
184 self
.options
+= options
[1:]
186 popen_kws
= {'shell': True, 'env': self
._generate
_env
(command
)}
187 if set('qf') & set(self
.options
):
188 for key
in ('stdout', 'stderr', 'stdin'):
189 popen_kws
[key
] = open(os
.devnull
, 'a')
190 p
= subprocess
.Popen(command
, **popen_kws
)
192 if 'f' not in self
.options
:
197 if p
.poll() is not None:
201 if p
.poll() is not 127:
204 if is_last
and 'w' in self
.options
:
205 print("Press ENTER to continue")
213 def get_parameters_from_argv(argv
=None):
214 class MoreOptions(optparse
.Option
):
215 TYPES
= optparse
.Option
.TYPES
+ ('list', )
216 TYPE_CHECKER
= dict(optparse
.Option
.TYPE_CHECKER
,
217 list=lambda _
, __
, value
: value
.split(','))
219 p
= optparse
.OptionParser(option_class
=MoreOptions
,
220 version
='%prog ' + str(__version__
),
221 usage
="%prog [options] path [-- args...]")
222 p
.add_option('-p', action
='store_true', help='pipe output into a pager')
223 p
.add_option('-w', action
='store_true',
224 help='wait for a key press afterwards')
225 p
.add_option('-q', action
='store_true', help='discard output')
226 p
.add_option('-t', action
='store_true', help='test only')
227 p
.add_option('-v', action
='store_true', help='be verbose')
228 p
.add_option('-f', action
='store_true', help='fork process')
229 p
.add_option('--ways', type='list', default
='0', metavar
='N,M,..',
230 help="open the file in what way(s)?")
231 p
.add_option('--list-ways', action
='store_true',
232 help="list all possible ways to run this file")
233 keywords
, args
= p
.parse_args(argv
)
234 if not len(args
) > 1:
237 opts
= ''.join(f
for f
,v
in keywords
.__dict
__.items() if len(f
) == 1 and v
)
242 'ways': keywords
.ways
,
243 'list_ways': keywords
.list_ways
,
246 if __name__
== '__main__':
247 AFO(**AFO
.get_parameters_from_argv(sys
.argv
))