plumber

Plumber – a modern approach to plumbing
git clone git://r-36.net/plumber
Log | Files | Refs | LICENSE

plumber (7070B)


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