# HG changeset patch # User Jeff Hammel # Date 1367416572 25200 # Node ID a62fbff067f884d63a412dc28f899203bea894df # Parent f11ce7b1a349d579e7ee6dedcf95a90ddd7d88d3 start bringing this whole thing up to speed diff -r f11ce7b1a349 -r a62fbff067f8 README.txt --- a/README.txt Thu Apr 04 17:13:18 2013 -0700 +++ b/README.txt Wed May 01 06:56:12 2013 -0700 @@ -2,7 +2,8 @@ ========= smartopen is a command line program that will transform text according -to handlers to URLs and open them +to handlers to URLs and open them + Usage ----- @@ -43,6 +44,8 @@ Wikipedia: a Wikipedia article GoogleMaps: an address in google maps +etc. + Configuration ------------- @@ -50,6 +53,9 @@ smartopen uses a configuration file that defines the order of the handlers and their behavior. +A simple example is shown here: +http://k0s.org/hg/smartopen/file/tip/smartopen.ini + Interfacing With Your Environment --------------------------------- @@ -57,10 +63,48 @@ While smartopen is useful of its own right, its utility may be enhanced by using as part of an environment. For instance, I run the fluxbox window manager which allows the use of hotkeys. By binding -smartopen to Ctrl+Alt+s and using xclip, I can open a URL that is +smartopen to ``Ctrl+Alt+s`` and using ``xclip``, I can open a URL that is mappable to a smartopen handler with a keyboard shortcut. The -relevant line from my ~/.fluxbox/keys file reads: +relevant line from my ``~/.fluxbox/keys`` file reads: Control Mod1 s :ExecCommand /home/jhammel/python/smartopen.py "$(xclip -o)" # smartopen +---- + + +Posterity is gained... +---------------------- + +I mostly wrote smartopen to help me. That it has. +However...a few things.... + +- better way of setting up handlers? +- really, i want to push down to choose what i think the handler is, + but only should we I release the hand that Jesus becomes Magic + (sorry, I hate the movie too) + +....and more? + +Was reading about catfish....e.g. +http://www.addictivetips.com/ubuntu-linux-tips/catfish-file-searching-tool-for-ubuntu-linux/ +... + +""" +file search tool that support several different engines + +A file search tool using different backends which is configurable via +the command line. + +This program acts as a frontend for different file search engines. +The interface is intentionally lightweight and simple. But it takes +configuration options from the command line. + +Currently find, locate, tracker, strigi, pinot, and beagle are +supported as backends +""". + +Should smartopen....be a pluggable thing that takes text and finds +both *the handler* and *what to do with it*??? + +I mean, short of it being a neural network anyway.... diff -r f11ce7b1a349 -r a62fbff067f8 setup.py --- a/setup.py Thu Apr 04 17:13:18 2013 -0700 +++ b/setup.py Wed May 01 06:56:12 2013 -0700 @@ -2,11 +2,11 @@ import sys, os try: - description = file('README.txt').read() + description = file(os.path.join(os.path.dirname(__file__), 'README.txt')).read() except: description = '' -version = '0.1.6' +version = '0.2' setup(name='smartopen', version=version, @@ -39,5 +39,6 @@ Bugzilla = smartopen.handlers:Bugzilla FedEx = smartopen.handlers:FedEx MercurialRevision = smartopen.handlers:MercurialRevision + UbuntuPackage = smartopen.handlers:UbuntuPackage """, ) diff -r f11ce7b1a349 -r a62fbff067f8 smartopen/handlers.py --- a/smartopen/handlers.py Thu Apr 04 17:13:18 2013 -0700 +++ b/smartopen/handlers.py Wed May 01 06:56:12 2013 -0700 @@ -2,6 +2,7 @@ import string import urllib import urllib2 +from subprocess import call class Location(object): """ @@ -10,6 +11,7 @@ def __init__(self, baseurl=""): self.baseurl = baseurl + # should/could strip here? def url(self, query): return self.baseurl + self.process(query) @@ -80,17 +82,17 @@ def test(self, query): return bool(self.process(query)) - + class Wikipedia(Location): """try to open the query in wikipedia""" - def __init__(self): + def __init__(self): wikiurl = 'http://en.wikipedia.org/wiki/' Location.__init__(self, wikiurl) def process(self, query): return urllib.quote_plus('_'.join(query.split())) - + def test(self, query): 'test to see if the article exists' @@ -118,12 +120,12 @@ return False if self.exists(self.url(query)): return True - + class Bugzilla(Location): def __init__(self): baseurl = 'https://bugzilla.mozilla.org/show_bug.cgi?id=' Location.__init__(self, baseurl) - + def test(self, query): try: int(query) @@ -132,10 +134,10 @@ return False class Google(Location): - def __init__(self): + def __init__(self): googleurl = 'http://www.google.com/search?hl=en&q=' Location.__init__(self, googleurl) - + def process(self, query): return urllib.quote_plus(query) @@ -146,7 +148,7 @@ def process(self, query): if query.count(' ') == 2: - query = ''.join(query.split(' ')) + query = ''.join(query.split(' ')) return query def test(self, query): @@ -170,5 +172,45 @@ return True return False +class UbuntuPackage(Location): + def __init__(self): + baseurl = 'http://packages.ubuntu.com/' + Location.__init__(self, baseurl) + + def test(self, query): + if len(self.baseurl.strip().split()) > 1: + return False # no spaces in packages + + # use `apt-cache show` for the package name + # the output could be captured:, e.g. + # ... + # Filename: pool/main/h/hello/hello_2.8-2_amd64.deb + # Size: 28124 + # MD5sum: 73ad59a7920e2adcff9b84c00e38418a + # SHA1: 70ef927cfa40b8e367f9ca7821e0064a946726c5 + # SHA256: f8341711f6b803e032be9aff2206dfdb4045a0c6c296b0ea0d310d415f10ff4d + # Description-en: The classic greeting, and a good example + # The GNU hello program produces a familiar, friendly greeting. It + # allows non-programmers to use a classic computer science tool which + # would otherwise be unavailable to them. + # . + # Seriously, though: this is an example of how to do a Debian package. + # It is the Debian version of the GNU Project's `hello world' program + # (which is itself an example for the GNU Project). + # Homepage: http://www.gnu.org/software/hello/ + # Description-md5: b7df6fe7ffb325083a3a60819a7df548 + # Bugs: https://bugs.launchpad.net/ubuntu/+filebug + # Origin: Ubuntu + # Supported: 18m + + # in theory both the home page and the ubuntu page could be interesting + # (different handlers?) + + returncode = call(['apt-cache', 'show', self.baseurl]) + if returncode: + return False + # TODO: +# - https://mozillians.org/en-US/u/jhammel/ handler # - https://mozillians.org/en-US/u/williamr/ handler +# ... but no 404??? diff -r f11ce7b1a349 -r a62fbff067f8 smartopen/smartopen.py --- a/smartopen/smartopen.py Thu Apr 04 17:13:18 2013 -0700 +++ b/smartopen/smartopen.py Wed May 01 06:56:12 2013 -0700 @@ -1,31 +1,110 @@ #!/usr/bin/env python -""" smart open the data passed in """ +""" +open what you give in your browser +""" import os +import subprocess import sys from optparse import OptionParser from pkg_resources import iter_entry_points from ConfigParser import ConfigParser -def locations(names=None, config=None): + +### method to open a browser + +### It turns out finding the default browser is very hard :( +# webbrowser is standard...just...what does that mean? for gnome-terminal, +# I get firefox; however, despite what +# http://stackoverflow.com/questions/4216985/python-call-to-operating-system-to-open-url +# says (and also because of it), I actually get `links` from xdg-open(!) +# (which of course it doesn't even tell me) + +# xdg-settings --list +# Known properties: +# default-web-browser Default web browser +# xdg-settings get default-web-browser +# xdg-settings: unknown desktop environment + +# The solution....is here: +# http://askubuntu.com/questions/96080/how-to-set-google-chrome-as-the-default-browser/96081#96081 +# and indeed in ~/.local/share/applications/mimeapps.list +# I do have:: + +# text/html=firefox.desktop;thunderbird.desktop; + +# in `[Added Associations]` +# (fwiw, ``pprint(mailcap.getcaps())`` was interesting but not very useful, +# referencing /usr/bin/sensible-browser which is in fact links (!) + +# TODO: + +# probably some fancy module should be written that *actually* does what +# its supposed to here; maybe https://pypi.python.org/pypi/desktop is that +# fancy pants module; I don't know! pypi doesn't let me browse tarballs! +# in any case, this is mostly for me and it must be portable; + +# So what we'll actually do: +# (Not surprisingly, explicit wins) +# - if specified on the command line (which it can't be yet) that wins +# - use browser/BROWSER if set in config +# - if not...you're back to guessing +# - if `which` was stdlib I'd probably guess on that since I know I can't rely +# on `xdg-open`... +# - ...and there are other platforms; again, I don't care.... +# - so we're back to Firefox being #1 + +# Which despite my love for Firefox, I'd love to help with autoselection if +# anyone would throw their hat in + +# * let's not forget http://freedesktop.org/wiki/Software/pyxdg if we're +# talking about non-stdlib solutions for linux only + +def open_in_browser(url, browser=None): + """yeah, that's right...""" + + if browser is None: + browser = 'firefox' # we'll cheat because of course its easy + + # control via env variable; might as well keep it set o_O + os.environ.setdefault('BROWSER', browser) + # see e.g. http://hg.python.org/cpython/file/2.7/Lib/webbrowser.py + # sadly these are different:: + # xdg-open http://k0s.org/ + # BROWSER=firefox xdg-open http://k0s.org/ + + # now invoke the damn thing + import webbrowser + return webbrowser.open(url) # XXX lord almighty + # why bother returning it even? + + +### methods for inspecting the locations + +def locations(names=None, config=None, verbose=False): """ list of 2-tuples of location handlers; * names: order names of handlers * config: nested dictionary of configuration from names """ + # setup _handlers = {} _names = [] if config is None: config = {} - + + # load the handlers + # (really, these should come of a dict that is shared with an entry point) for i in iter_entry_points('smartopen.locations'): try: handler = i.load() - except: - continue # TODO: warn/debug with --verbose flag + except Exception, e: + if verbose: + print >> sys.stderr, "Error loading handler:\n%s" % e + continue _handlers[i.name] = handler if not names: _names.append(i.name) @@ -41,12 +120,17 @@ if _name in _handlers: try: handler = _handlers[_name](**config.get(name, {})) - except: + except Exception, e: + if verbose: + print >> sys.stderr, "blah blah blah" continue handlers.append((name, handler)) return handlers + def urls(query, handlers=None): + """returns available urls in order of preference for a query""" + if handlers is None: handlers = locations() urls = [] @@ -55,28 +139,41 @@ urls.append((name, handler.url(query))) return urls + def url(query, handlers=None): + """return the top url for a query""" + if handlers is None: handlers = locations() for name, handler in handlers: if handler.test(query): return handler.url(query) +### command line entry point + def main(args=sys.argv[1:]): # parse command line optioins - parser = OptionParser() + default_browser = os.environ.get('BROWSER', 'firefox') # ^ see LONG note above + usage = '%prog [options] query' + parser = OptionParser(usage=usage) + parser.add_option('-b', '--browser', dest='browser', + default=None, + help="browser to use; can also be set in config and BROWSER [default: %default]") parser.add_option('-c', '--config', dest="config", help="config file to read") - parser.add_option('-u', '--url', dest="url", + parser.add_option('-u', '--url', dest="url", action='store_true', default=False, help="print the first url handled") - parser.add_option('-a', '--all', dest="all", + parser.add_option('-a', '--all', dest="all", action='store_true', default=False, help="print all handlers that match the query") parser.add_option('-H', '--handler', dest="handlers", action='append', help="name of the handler to use, in order") + parser.add_option('-v', '--verbose', dest='verbose', + action='store_true', default=True, + help="be verbose with output") parser.add_option('--print-handlers', dest="print_handlers", action='store_true', help="print all handlers in order they would be tried") @@ -87,23 +184,34 @@ if not options.handlers: options.handlers = None - # config + # read config, if available config = ConfigParser() if not options.config: options.config = os.path.join(os.environ.get('HOME', ''), '.smartopen.ini') if os.path.exists(options.config): config.read(options.config) + + # select handlers if not options.handlers and config.has_option('DEFAULTS', 'handlers'): - options.handlers = [ i.strip() for i in config.get('DEFAULTS', 'handlers').split(',') ] + options.handlers = [i.strip() for i in config.get('DEFAULTS', 'handlers').split(',')] + + # select browser + if not options.browser: + for key in 'BROWSER', 'browser': + if config.has_option('DEFAULTS', key): + options.browser = config.get('DEFAULTS', key) + break + + # the remaining config is arguments to the handlers _config = {} for section in config.sections(): _config[section] = dict(config.items(section)) # get the handlers - _locations = locations(options.handlers, _config) + _locations = locations(options.handlers, _config, verbose=options.verbose) - # print the handlers if options.print_handlers: + # print the handlers for name, loc in _locations: print name sys.exit(0) @@ -112,26 +220,29 @@ if args: data = ' '.join(args) else: + # read from stdin data = sys.stdin.read() - # print the URLs if options.all: + # print the URLs _urls = urls(data, _locations) for name, _url in _urls: print '%s: %s' % (name, _url) sys.exit(0) + # select the url _url = url(data, _locations) - # print a URL if options.url: - print _url + # print a URL + print _url or 'No handler found for your query' sys.exit(0) # open the URL in a browser - os.system("firefox '%s'" % _url) - sys.exit(0) - + if _url: + open_in_browser(_url) + else: + parser.error("No handler found") if __name__ == '__main__': main()