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