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>: <program entry>
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:
36 # - gcc $f -o /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
61 print("afo: error: python-yaml required.")
64 def __init__(self
, file, options
=[], ways
=[0], list_ways
=False, args
=[]):
65 self
.__dict
__.update(locals())
66 self
.file = os
.path
.abspath(file)
67 self
.config
= self
._load
_config
()
69 mimetypes
.knownfiles
.append(os
.path
.expanduser('~/.mime.types'))
70 basename
= os
.path
.basename(self
.file)
71 self
.mimetype
= mimetypes
.MimeTypes().guess_type(basename
, False)[0] or ""
72 match_string
= '%s///%s' % (self
.mimetype
, self
.file)
74 for entry
, program
in self
.config
.items():
76 regex
= re
.compile(entry
)
78 print("afo: warning: Bad regexp: %s" % entry
)
80 if regex
.search(match_string
):
81 return self
._run
(program
)
84 first_line
= open(self
.file).readline()
85 if first_line
[0:2] == '#!':
86 program
= first_line
.strip()[2:]
88 return self
._run
(program
+ ' $f $@')
92 print("afo: error: Unknown type")
94 def _normalize_program_entry(self
, program
):
95 if isinstance(program
, str):
97 elif isinstance(program
, dict):
98 return dict(map((lambda v
: (str(v
[0]), v
[1])), program
.items()))
99 elif isinstance(program
, list):
100 return dict(map((lambda v
: (str(v
[0]), v
[1])), enumerate(program
)))
102 def _load_config(self
):
103 basedir
= os
.path
.expanduser(os
.getenv('XDG_CONFIG_PATH', '~/.config'))
104 confpath
= os
.path
.join(basedir
, 'afo', 'config.yaml')
106 return yaml
.load(open(confpath
))
107 except Exception as e
:
108 print("afo: error: Failed to read config file:\n", e
)
111 def _run(self
, program
):
112 program
= self
._normalize
_program
_entry
(program
)
114 print("\n".join(("%d: %s" % (n
, line
)) for n
, line
in program
.items()))
116 for way
in self
.ways
:
118 if isinstance(program
[way
], list):
119 self
._shell
(program
[way
])
121 self
._shell
([program
[way
]])
123 print("afo: warning: Unknown way `%s'" % str(way
))
125 def _generate_env(self
, program
):
126 env
= dict(os
.environ
)
129 'F': os
.path
.splitext(self
.file)[0],
130 'e': os
.path
.splitext(self
.file)[1][1:],
131 'd': os
.path
.dirname(self
.file),
132 'b': os
.path
.basename(self
.file),
133 'B': os
.path
.splitext(os
.path
.basename(self
.file))[0],
135 'args': " ".join(self
.args
),
137 for i
, arg
in enumerate(self
.args
):
138 env
[str(i
+1)] = self
.args
[i
]
141 def _shell(self
, commands
):
142 if set('vt') & set(self
.options
):
145 if 't' not in self
.options
:
146 for i
, command
in enumerate(commands
):
147 is_last
= len(commands
) is i
+ 1
149 if command
[0] == '-':
150 options
, command
= command
.split(' ', 1)
151 self
.options
+= options
[1:]
153 popen_kws
= {'shell': True, 'env': self
._generate
_env
(command
)}
154 if set('qf') & set(self
.options
):
155 for key
in ('stdout', 'stderr', 'stdin'):
156 popen_kws
[key
] = open(os
.devnull
, 'a')
157 p
= subprocess
.Popen(command
, **popen_kws
)
159 if 'f' not in self
.options
:
164 if p
.poll() is not None:
168 if p
.poll() is not 127:
171 if is_last
and 'w' in self
.options
:
172 print("Press ENTER to continue")
180 def get_parameters_from_argv(argv
=None):
181 class MoreOptions(optparse
.Option
):
182 TYPES
= optparse
.Option
.TYPES
+ ('list', )
183 TYPE_CHECKER
= dict(optparse
.Option
.TYPE_CHECKER
,
184 list=lambda _
, __
, value
: value
.split(','))
186 p
= optparse
.OptionParser(option_class
=MoreOptions
,
187 usage
="%prog [options] path [-- args...]")
188 p
.add_option('-p', action
='store_true', help='pipe output into a pager')
189 p
.add_option('-w', action
='store_true',
190 help='wait for a key press afterwards')
191 p
.add_option('-q', action
='store_true', help='discard output')
192 p
.add_option('-t', action
='store_true', help='test only')
193 p
.add_option('-v', action
='store_true', help='be verbose')
194 p
.add_option('-f', action
='store_true', help='fork process')
195 p
.add_option('--ways', type='list', default
='0', metavar
='N,M,..',
196 help="open the file in what way(s)?")
197 p
.add_option('--list-ways', action
='store_true',
198 help="list all possible ways to run this file")
199 keywords
, args
= p
.parse_args(argv
)
200 if not len(args
) > 1:
203 opts
= ''.join(f
for f
,v
in keywords
.__dict
__.items() if len(f
) == 1 and v
)
208 'ways': keywords
.ways
,
209 'list_ways': keywords
.list_ways
,
212 if __name__
== '__main__':
213 AFO(**AFO
.get_parameters_from_argv(sys
.argv
))