#!/opt/anaconda1anaconda2anaconda3/bin/python
'''
    A simpler interface to Debian APT commands. (C) 2012-2014 Mike Miller
    License: GPLv3+

    %prog [options] COMMAND ARGS
    %prog           # with no arguments to list available commands
'''
import sys, os
from subprocess import call
from optparse import OptionParser, BadOptionError, AmbiguousOptionError
from urlparse import urlparse

# initialization
__version__ = '1.08'
use_sudo = ('apt-get', 'apt-get remove', 'aptitude', 'apt-key',
            'dpkg --install', 'add-apt-repository ', 'apt-get install')

# map commands to their appropriate binaries:
cmd_map = {
    # apt-get
    'autoclean': 'apt-get',
    'autoremove': 'apt-get',
    'build-dep': 'apt-get',
    'changelog': 'apt-get',
    'check': 'apt-get',
    'clean': 'apt-get',
    'dist-upgrade': 'apt-get',
    'download': 'apt-get',
    'dselect-upgrade': 'apt-get',
    'install': 'apt-get',
    'in': 'apt-get install',        # alias
    'markauto': 'apt-get',
    'purge': 'apt-get',
    'remove': 'apt-get',
    'rm': 'apt-get remove',         # alias
    'source': 'apt-get',
    'unmarkauto': 'apt-get',
    'update': 'apt-get',
    'upgrade': 'apt-get',

    # cache
    'depends': 'apt-cache',
    'dotty': 'apt-cache',
    'dump': 'apt-cache',
    'dumpavail': 'apt-cache',
    'gencaches': 'apt-cache',
    'pkgnames': 'apt-cache',
    'policy': 'apt-cache',
    'rdepends': 'apt-cache',
    'search': 'apt-cache',
    'se': 'apt-cache search',       # alias
    'show': 'apt-cache',
    'showauto': 'apt-cache',
    'showpkg': 'apt-cache',
    'showsrc': 'apt-cache',
    'stats': 'apt-cache',
    'unmet': 'apt-cache',
    'xvcg': 'apt-cache',

    # config
    'shell': 'apt-config',
    'dumpcon': 'apt-config',

    # aptitude
    'hold': 'aptitude',
    'unhold': 'aptitude',
    'reinstall': 'aptitude',
    'why': 'aptitude',
    'why-not': 'aptitude',

    # more
    'fingerprint': 'apt-key',
    'dir': 'dpkg-query --list',
    'ls': 'dpkg --get-selections',          # alias
    'status': 'dpkg --status',
    'listfiles': 'dpkg-query --listfiles',
    'searchfiles': 'dpkg-query --search',
    'who-owns': 'dpkg-query --search',      # alias
    'instdeb': 'dpkg --install',
    'addrepo': 'add-apt-repository ',  # trailing space hack to override cmd
}
commands = sorted(set(cmd_map.keys()))  # make a list for the user


class PassThroughParser(OptionParser):
    ''' An unknown option pass-through implementation of OptionParser.
        http://stackoverflow.com/a/9307174/450917

        When unknown options are encountered, bundle with args and try
        again, until arguments are depleted.
        sys.exit(status) will still be called if a known argument is passed
        incorrectly (e.g. missing arguments or bad argument types, etc.)
    '''
    def _process_args(self, largs, rargs, values):
        while rargs:
            try:
                OptionParser._process_args(self, largs, rargs, values)
            except (BadOptionError, AmbiguousOptionError), e:
                largs.append(e.opt_str)


def download(url):
    ''' Download a file to $TEMP and return its path, with progress.
        http://stackoverflow.com/a/22776/450917
    '''
    import urllib2
    import tempfile
    from os.path import join

    file_size, downloaded = 0, 0
    block_sz = 8192
    try:
        filename = url.split('/')[-1]
        tempfile = join(tempfile.gettempdir(), filename)
        infile = urllib2.urlopen(url)
        outfile = open(tempfile, 'wb')
        meta = infile.info()
        try:
            file_size = int(meta.getheaders('Content-Length')[0])
        except IndexError:
            pass
        print 'Downloading: "%s"  Bytes: %s' % (filename, file_size)
        while True:
            buffer = infile.read(block_sz)
            if not buffer:
                break
            outfile.write(buffer)
            if file_size:
                downloaded += len(buffer)
                print '\r  %10d  [%3.2f%%]' % (downloaded,
                                               downloaded * 100.0 / file_size),
        outfile.close()
        print
    except Exception, e:
        print 'Error:', e.__class__.__name__, str(e)
        tempfile = None
    return tempfile


def is_uniq(fragment):
    ''' Check whether a given command fragment uniquely identifies a command.
        Returns:  boolean, first command or None, all matches.
    '''
    results = [ c  for c in commands  if c.startswith(fragment) ]
    uniq = (len(results) == 1)
    return uniq, (results or [None])[0], results


# check & prepare args
parser = PassThroughParser(usage=__doc__.rstrip(), version=__version__)
parser.add_option('-d', '--debug', action='store_true', default=False,
                  help='turn on debugging output.')
opts, args = parser.parse_args()

if not args:
    sys.exit('Error: no command given.  Must be one of: \n\n(%s).\n' %
             ', '.join(commands))
else:
    command = args[0]


# find applicable binary, command
if command in cmd_map:
    binary = cmd_map[command]
else:
    uniq, command, matches = is_uniq(command)
    if opts.debug:
        print 'Unique:', args[0], command
    if not uniq:
        sys.exit('Error: %s command, %s --> %s' % (
                 ('ambiguous' if matches else 'unknown'),
                 args[0], matches))
    binary = cmd_map[command]

# download .deb urls
if command == 'instdeb':
    if len(args) > 1 and urlparse(args[1]).scheme:
        args[1] = download(args[1])

# should this be elevated?
prefix = ''
if binary in use_sudo and os.geteuid() != 0:
    prefix = 'sudo '


# check if binary uses a flag instead of command
if ' ' in binary:
    binary, command = binary.split(' ', 1)


# build command line
cmdline = (prefix + ' '.join([binary, command] + args[1:])).replace('  ', ' ')
if opts.debug:
    print 'Running:', cmdline
    print

# http://youtu.be/0hiUuL5uTKc?t=8s
try:
    sys.exit(call(cmdline, shell=True))
except KeyboardInterrupt:
    print >> sys.stderr, '\nWarning: Ctrl+C entered, exiting.'
