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
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 # -----------------------------------------------------
19 # It's a yaml dictionary, in the form of:
20 # <regexp>: <programs>
24 # (png|jpg)$: feh $f $args
26 # -----------------------------------------------------
27 # <regexp> is a regular expression that matches against this string:
28 # <mime-type>///<file-path>. This way you can check for complicated
29 # relationships with a single regexp. For example, this regular expression
30 # matches images (by mime-type) except SVG's: ^image.*(?<!svg)$
32 # The first regular expression that matches is used and its respective program
35 # -----------------------------------------------------
36 # <program> is either a string, a list or a dictionary. In case of a string,
37 # the command in the string is simply executed. But sometimes it is necessary
38 # to open files in different ways. For example, one way to compile it and one
42 # compile: pdflatex $B
43 # view: epdfview $B.pdf
45 # - gcc $f -o /tmp/a.out
47 # /home/me/my_project/:
48 # - cd /home/me/my_project; make
49 # - /home/me/my_project/my_executable
51 # You can then use the --ways option to specify which way to run it.
52 # For example, "afo --ways 0,1 test.c" would compile and run test.c.
54 # -----------------------------------------------------
55 # Sometimes you can't be sure whether a program is installed or not.
56 # In this case, you can specify multiple programs which will be run in succession
57 # until one ends with an exit code which is not 127 (sh's exit code when a
58 # command is not found.) Examples:
66 # - totem --fullscreen $f
72 # -----------------------------------------------------
73 # If the first word in the program description starts with a "-", it will
74 # be interpreted as a set of additional afo options:
75 # avi$: -qf mplayer $f
88 print("afo: error: python-yaml required.")
91 def __init__(self
, file, options
=[], ways
=[0], list_ways
=False, args
=[]):
92 self
.__dict
__.update(locals())
93 self
.file = os
.path
.abspath(file)
94 self
.config
= self
._load
_config
()
96 mimetypes
.knownfiles
.append(os
.path
.expanduser('~/.mime.types'))
97 basename
= os
.path
.basename(self
.file)
98 self
.mimetype
= mimetypes
.MimeTypes().guess_type(basename
, False)[0] or ""
99 match_string
= '%s///%s' % (self
.mimetype
, self
.file)
101 for entry
, program
in self
.config
.items():
103 regex
= re
.compile(entry
)
105 print("afo: warning: Bad regexp: %s" % entry
)
107 if regex
.search(match_string
):
108 return self
._run
(program
)
111 first_line
= open(self
.file).readline()
112 if first_line
[0:2] == '#!':
113 program
= first_line
.strip()[2:]
115 return self
._run
(program
+ ' $f $@')
119 print("afo: error: Unknown type")
121 def _normalize_program_entry(self
, program
):
122 if isinstance(program
, str):
123 return {'0': program
}
124 elif isinstance(program
, dict):
125 return dict(map((lambda v
: (str(v
[0]), v
[1])), program
.items()))
126 elif isinstance(program
, list):
127 return dict(map((lambda v
: (str(v
[0]), v
[1])), enumerate(program
)))
129 def _load_config(self
):
130 basedir
= os
.path
.expanduser(os
.getenv('XDG_CONFIG_PATH', '~/.config'))
131 confpath
= os
.path
.join(basedir
, 'afo', 'config.yaml')
133 return yaml
.load(open(confpath
))
134 except Exception as e
:
135 print("afo: error: Failed to read config file:\n", e
)
138 def _run(self
, program
):
139 program
= self
._normalize
_program
_entry
(program
)
141 print("\n".join(("%d: %s" % (n
, line
)) for n
, line
in program
.items()))
143 for way
in self
.ways
:
145 if isinstance(program
[way
], list):
146 self
._shell
(program
[way
])
148 self
._shell
([program
[way
]])
150 print("afo: warning: Unknown way `%s'" % str(way
))
152 def _generate_env(self
, program
):
153 env
= dict(os
.environ
)
156 'F': os
.path
.splitext(self
.file)[0],
157 'e': os
.path
.splitext(self
.file)[1][1:],
158 'd': os
.path
.dirname(self
.file),
159 'b': os
.path
.basename(self
.file),
160 'B': os
.path
.splitext(os
.path
.basename(self
.file))[0],
162 'args': " ".join(self
.args
),
164 for i
, arg
in enumerate(self
.args
):
165 env
[str(i
+1)] = self
.args
[i
]
168 def _shell(self
, commands
):
169 if set('vt') & set(self
.options
):
172 if 't' not in self
.options
:
173 for i
, command
in enumerate(commands
):
174 is_last
= len(commands
) is i
+ 1
176 if command
[0] == '-':
177 options
, command
= command
.split(' ', 1)
178 self
.options
+= options
[1:]
180 popen_kws
= {'shell': True, 'env': self
._generate
_env
(command
)}
181 if set('qf') & set(self
.options
):
182 for key
in ('stdout', 'stderr', 'stdin'):
183 popen_kws
[key
] = open(os
.devnull
, 'a')
184 p
= subprocess
.Popen(command
, **popen_kws
)
186 if 'f' not in self
.options
:
191 if p
.poll() is not None:
195 if p
.poll() is not 127:
198 if is_last
and 'w' in self
.options
:
199 print("Press ENTER to continue")
207 def get_parameters_from_argv(argv
=None):
208 class MoreOptions(optparse
.Option
):
209 TYPES
= optparse
.Option
.TYPES
+ ('list', )
210 TYPE_CHECKER
= dict(optparse
.Option
.TYPE_CHECKER
,
211 list=lambda _
, __
, value
: value
.split(','))
213 p
= optparse
.OptionParser(option_class
=MoreOptions
,
214 usage
="%prog [options] path [-- args...]")
215 p
.add_option('-p', action
='store_true', help='pipe output into a pager')
216 p
.add_option('-w', action
='store_true',
217 help='wait for a key press afterwards')
218 p
.add_option('-q', action
='store_true', help='discard output')
219 p
.add_option('-t', action
='store_true', help='test only')
220 p
.add_option('-v', action
='store_true', help='be verbose')
221 p
.add_option('-f', action
='store_true', help='fork process')
222 p
.add_option('--ways', type='list', default
='0', metavar
='N,M,..',
223 help="open the file in what way(s)?")
224 p
.add_option('--list-ways', action
='store_true',
225 help="list all possible ways to run this file")
226 keywords
, args
= p
.parse_args(argv
)
227 if not len(args
) > 1:
230 opts
= ''.join(f
for f
,v
in keywords
.__dict
__.items() if len(f
) == 1 and v
)
235 'ways': keywords
.ways
,
236 'list_ways': keywords
.list_ways
,
239 if __name__
== '__main__':
240 AFO(**AFO
.get_parameters_from_argv(sys
.argv
))