plumber - plumber - Plumber – a modern approach to plumbing
(HTM) git clone git://r-36.net/plumber
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
plumber (7994B)
---
1 #!/usr/bin/env python3
2 # coding=utf-8
3 #
4 # Copy me if you can.
5 # by 20h
6 #
7
8 import sys
9 import os
10 import os.path
11 import re
12 import getopt
13 from subprocess import Popen, PIPE
14 import time
15 import logging
16 import logging.handlers
17
18 # regexp command (%s as file)
19 plumbrules = [
20 ["^/.*", "fileopener '%s'"],
21 ["^> .*", "run '%s'"],
22 ["^file://.*", "mailcapopener '%s'"],
23 ["^.*://.*wikipedia.*/.*\\.(jpeg|jpg|png|gif|xpm|JPG|JPEG|PNG|XPM|GIF)$", \
24 "webopener '%s'"],
25 ["^.*://.*\\.(jpeg|jpg|png|gif|xpm|JPG|JPEG|PNG|XPM|GIF|svg|SVG|svgz|SVGZ|webp|WEBP)$", \
26 "imageopener '%s'"],
27 ["^.*://.*\\.(pdf|PDF)$", "pdfopener '%s'"],
28 ["^gopher(|s)://.*\\.(txt|TXT|patch|PATCH|diff|DIFF)$", "textgopheropener '%s'"],
29 ["^.*://.*\\.(txt|TXT|patch|PATCH|diff|DIFF)$", "textwebopener '%s'"],
30 ["^.*://.*\\.(mpg|MPG|mp3|MP3|mp4|MP4|FLAC|flac|ogg|OGG|m3u|M3U|m3u8|M3U8|flv|FLV|webm|WEBM|opus|OPUS|mov|MOV|mkv|MKV|ogv|OGV|wav|WAV|avi|AVI)$",\
31 "mediaopener '%s'"],
32 ["^dvb://.*", "tvopener '%s'"],
33 ["^geo:.*", "geoopener '%s'"],
34 ["^gopher(|s)://.*", "gopheropener '%s'"],
35 ["^http://sprunge.us/.*", "textwebopener '%s'"],
36 ["^http://ix.io/.*", "textwebopener '%s'"],
37 ["^http(|s)://(|www\\.)youtube.com/feeds/videos.xml\\?channel_id=.*", "feedopener '%s'"],
38 ["^http(|s)://(|www\\.)yewtu.be/feed/channel/.*", "feedopener '%s'"],
39 ["^http(|s)://(|www\\.)youtube.com/(watch|embed).*", "ytopener '%s'"],
40 ["^http(|s)://(|www\\.)youtu.be/[\\w\\-]{10,}\\?t=\\d+", "ytopener '%s'"],
41 ["^http(|s)://(|www\\.)yewtu.be/(watch|embed).*", "ytopener '%s'"],
42 ["^http(|s)://.*", "webopener '%s'"],
43 ["^mailto:.*", "mailcomposer '%s'"],
44 ["^dance:.*", "danceopener '%s'"],
45 ["^dict:.*", "dictopener '%s'"],
46 ["^dhl:.*", "dhlopener '%s'"],
47 ["^doi:.*", "doiopener '%s'"],
48 ["^finger://.*", "fingeropener '%s'"],
49 ["^ftp(|s)://.*", "ftpopener '%s'"],
50 ["^sftp://.*", "ftpopener '%s'"],
51 ["^ldap(|s)://.*", "ldapopener '%s'"],
52 ["^moz://:*", "mozopener '%s'"],
53 ["^mms://.*", "mediaopener '%s'"],
54 ["^news://.*", "newsopener '%s'"],
55 ["^newspost://.*", "newsopener '%s'"],
56 ["^newsreply://.*", "newsopener '%s'"],
57 ["^nex://.*", "nexopener '%s'"],
58 ["^nntp://.*", "newsopener '%s'"],
59 ["^snews://.*", "newsopener '%s'"],
60 ["^paper:.*", "paperopener '%s'"],
61 ["^pubmed:.*", "pubmedopener '%s'"],
62 ["^rfc:.*", "rfcopener '%s'"],
63 ["^rp:.*", "rpopener '%s'"],
64 ["^rpo:.*/", "rpopener -o '%s'"],
65 ["^rtmp://.*", "mediaopener '%s'"],
66 ["^rtmfp://.*", "mediaopener '%s'"],
67 ["^rtsp://.*", "mediaopener '%s'"],
68 ["^searx:.*", "searxopener '%s'"],
69 ["^udp://.*", "mediaopener '%s'"],
70 ["^telnet(s|)(4|6|)://.*", "telnetopener '%s'"],
71 ["^scm:.*", "scmopener '%s'"],
72 ["^ssh://.*", "sshopener '%s'"],
73 ["^tv://.*", "tvopener '%s'"],
74 ["^yt://.*", "ytopener '%s'"],
75 ["^ytdl://.*", "ytopener '%s'"],
76 ["^youtube://.*", "ytopener '%s'"],
77 ["^addr:.*", "addropener '%s'"],
78 ["^blog://.*", "blogopener '%s'"],
79 ["^portage:.*", "portageopener '%s'"],
80 # Just a try to imitate Ubuntu.
81 ["^apt:.*", "portageopener '%s'"],
82 ["^cso:.*", "csoopener '%s'"],
83 ["^wais:.*", "waisopener '%s'"],
84 ["^wikipedia:.*", "wikipediaopener '%s'"],
85 ["^wikipedia-[a-zA-Z]*:.*", "wikipediaopener '%s'"],
86 ["^wiki:.*", "wikiopener '%s'"],
87 ["^w:.*", "wikiopener '%s'"],
88 ["^@.*", False],
89 [".*@.*", "mailcomposer '%s'"],
90 [".*", ["fileopener '%s'", "webopener '%s'"]],
91 ]
92
93 menucmd = "dmenu -p \"URI to plumb> \" -i"
94
95 def runcmd(cmd, arg=None):
96 fd = open("/dev/null")
97 if arg != None:
98 cmd = cmd % (arg)
99 # Run in background all the time.
100 p = Popen("%s &" % (cmd), shell=True, stdout=fd, stderr=fd)
101 p.wait()
102 fd.close()
103
104 def runmenu(menucmd, selectlines):
105 fd = open("/dev/null")
106 p = Popen(menucmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=fd)
107 output = p.communicate(input=("\n".join(selectlines)).encode("utf-8"))[0]
108 fd.close()
109 return output.decode().strip()
110
111 def parsetext(text):
112 urire = re.compile("[a-z0-9A-Z]+:[a-z0-9A-Z/\\-\\.?=%+_]+")
113 return re.findall(urire, text)
114
115 def trimstr(s):
116 framing = [
117 ["<", ">"], ["\"", "\""], ["'", "'"],\
118 ["(", ")"], ["[", "]"] \
119 ]
120
121 matchstr = s
122
123 didtrim = 1
124 while didtrim == 1:
125 if len(matchstr) == 0:
126 break
127
128 didtrim = 0
129 for frame in framing:
130 if matchstr[0] == frame[0] and \
131 matchstr[-1] == frame[1]:
132 matchstr = matchstr[1:-1]
133 didtrim = 1
134 break
135 elif matchstr[0] == frame[0] and \
136 matchstr[-2] == frame[1]:
137 # When the delimiter is at the end of some
138 # sentence.
139 matchstr = matchstr[1:-2]
140 didtrim = 1
141 break
142
143 if len(matchstr) == 0:
144 return matchstr
145
146 # markdown links
147 if matchstr[0] == "[" and matchstr[-1] == ")":
148 if "](" in matchstr:
149 matchstr = matchstr[:-1].rsplit("](",1)[1]
150 if len(matchstr) == 0:
151 return matchstr
152
153 if matchstr[0] == "[" and matchstr[-1] == "]":
154 if "][" in matchstr:
155 matchstr = matchstr[1:].rsplit("][",1)[0]
156 else:
157 matchstr = matchstr[1:-1]
158
159 return matchstr
160
161 def usage(app):
162 app = os.path.basename(app)
163 sys.stderr.write("usage: %s [-hdemty] string\n" % (app))
164 sys.stderr.write("-h Show this help.\n")
165 sys.stderr.write("-d Set debug loglevel.\n")
166 sys.stderr.write("-e Do not handle arbitrary results.\n")
167 sys.stderr.write("-m Use menu.\n")
168 sys.stderr.write("-t Parse text.\n")
169 sys.stderr.write("-y Dry run.\n")
170 sys.exit(1)
171
172 def main(args):
173 global menucmd
174
175 try:
176 opts, largs = getopt.getopt(args[1:], "hdemty")
177 except getopt.GetoptError as err:
178 print(str(err))
179 usage(args[0])
180
181 logger = logging.getLogger("plumber")
182 formatter = logging.Formatter("%(name)s: %(message)s")
183 shandler = logging.handlers.SysLogHandler(\
184 facility=logging.handlers.SysLogHandler.LOG_DAEMON,\
185 address="/dev/log")
186 shandler.setFormatter(formatter)
187 logger.addHandler(shandler)
188
189 if os.getenv("PLUMBER_DEBUG"):
190 logger.setLevel(logging.DEBUG)
191
192 dodebug = False
193 dodryrun = False
194 dotextparsing = False
195 dodefault = True
196 domenu = False
197 for o, a in opts:
198 if o == "-h":
199 usage(args[0])
200 elif o == "-d":
201 logger.setLevel(logging.DEBUG)
202 elif o == "-e":
203 dodefault = False
204 elif o == "-m":
205 domenu = True
206 elif o == "-t":
207 dotextparsing = True
208 elif o == "-y":
209 dodryrun = True
210 else:
211 assert False, "unhandled option"
212
213 if dotextparsing == True:
214 if len(largs) < 1:
215 itext = sys.stdin.read()
216 else:
217 itext = " ".join(largs)
218 largs = parsetext(itext)
219 logger.debug("text parsing returned: %s" % (largs))
220 # Do not handle arbitrary results.
221 dodefault = False
222 else:
223 if len(largs) < 1:
224 largs = sys.stdin.read().split("\n")
225
226 if domenu == True:
227 rval = runmenu(menucmd, largs)
228 if rval == "":
229 return 1
230 largs = [rval]
231 logger.debug("menu selection returned: %s" % (largs))
232
233 for arg in largs:
234 if len(arg) < 1:
235 continue
236 logger.debug("matchstr: '%s'" % (arg))
237
238 matchstr = trimstr(arg)
239 if len(matchstr) == 0:
240 logger.debug("Trimming reduced string too much.")
241 break
242
243 matchstr = matchstr.replace("'", "'\\''")
244 if matchstr[0:2] == "//":
245 matchstr = "http:%s" % (matchstr)
246 if matchstr != arg:
247 logger.debug("'%s' -> '%s'" % (arg, matchstr))
248
249 frule = None
250 for rule in plumbrules:
251 if re.search(rule[0], matchstr) != None:
252 frule = rule
253 break
254
255 if frule == False:
256 if logger.level == logging.DEBUG:
257 logger.debug("Plumb string is invalid.")
258 else:
259 logger.info("Plumb string is invalid.")
260 continue
261
262 if frule == None:
263 if logger.level == logging.DEBUG:
264 logger.debug("No match found.")
265 else:
266 logger.info("No match found.")
267 continue
268
269 if dodefault == False and frule == plumbrules[-1][0]:
270 logger.debug("found default match, won't continue")
271 continue
272
273 logger.debug("found match: '%s' -> '%s'" % (frule[0], frule[1]))
274
275 if frule[0] == ".*":
276 if os.path.exists(matchstr):
277 rcmd = frule[1][0]
278 matchstr = os.path.realpath(matchstr)
279 else:
280 rcmd = frule[1][1]
281 else:
282 rcmd = frule[1]
283
284 if dodebug:
285 logger.debug("running cmd: '%s'" % (rcmd % (matchstr)))
286
287 if dodryrun == False:
288 runcmd(rcmd, matchstr)
289
290 return 0
291
292 if __name__ == "__main__":
293 sys.exit(main(sys.argv))
294