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
60 print("afo: error: python-yaml required.")
62 def _shell_quote(string
):
63 return "'" + string
.replace("'", "'\''") + "'"
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():
78 regex
= re
.compile(entry
)
80 print("afo: warning: Bad regexp: %s" % entry
)
82 if regex
.search(match_string
):
83 return self
._run
(program
)
86 first_line
= open(self
.file).readline()
87 if first_line
[0:2] == '#!':
88 program
= first_line
.strip()[2:]
90 return self
._run
(program
+ ' $f $@')
94 print("afo: error: Unknown type")
96 def _normalize_program_entry(self
, program
):
97 if isinstance(program
, str):
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')
108 return yaml
.load(open(confpath
))
109 except Exception as e
:
110 print("afo: error: Failed to read config file:\n", e
)
113 def _run(self
, program
):
114 program
= self
._normalize
_program
_entry
(program
)
116 print("\n".join(("%d: %s" % (n
, line
)) for n
, line
in program
.items()))
118 for way
in self
.ways
:
120 self
._shell
(program
[way
])
122 print("afo: warning: Unknown way `%s'" % str(way
))
124 def _generate_env(self
, program
):
125 env
= dict(os
.environ
)
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],
134 'args': " ".join(map(_shell_quote
, self
.args
)),
136 for i
, arg
in enumerate(self
.args
):
137 env
[str(i
+1)] = self
.args
[i
]
140 def _shell(self
, command
):
141 if set('vt') & set(self
.options
):
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")
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:
182 opts
= ''.join(f
for f
,v
in keywords
.__dict
__.items() if len(f
) == 1 and v
)
187 'ways': keywords
.ways
,
188 'list_ways': keywords
.list_ways
,
191 if __name__
== '__main__':
192 AFO(**AFO
.get_parameters_from_argv(sys
.argv
))