#!/usr/bin/env python
# vim: ts=4 expandtab sw=4

#THE FOLLOWING LICENSE APPLIES TO ALL CODE
#EXCEPT THE PART THAT BELONGS TO CLASS TerminalController
##########################################################
#The MIT License
#
#Copyright (c) 2008 Antonios Anastasiadis
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
##########################################################

import sys
#import sets
import glob
import getopt
import re
import os

global VERSION
VERSION='0.5.2'

class TerminalController:   #{{{
    #THE FOLLOWING LICENSE APPLIES TO
    #CODE THAT BELONGS TO CLASS TerminalController
    #
    #The MIT License
    #
    #Copyright (c) Edward Loper
    #
    #Permission is hereby granted, free of charge, to any person obtaining a copy
    #of this software and associated documentation files (the "Software"), to deal
    #in the Software without restriction, including without limitation the rights
    #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    #copies of the Software, and to permit persons to whom the Software is
    #furnished to do so, subject to the following conditions:
    #
    #The above copyright notice and this permission notice shall be included in
    #all copies or substantial portions of the Software.
    #
    #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    #THE SOFTWARE.
    #
    
    # Cursor movement:
    BOL = ''             #: Move the cursor to the beginning of the line
    UP = ''              #: Move the cursor up one line
    DOWN = ''            #: Move the cursor down one line
    LEFT = ''            #: Move the cursor left one char
    RIGHT = ''           #: Move the cursor right one char

    # Deletion:
    CLEAR_SCREEN = ''    #: Clear the screen and move to home position
    CLEAR_EOL = ''       #: Clear to the end of the line.
    CLEAR_BOL = ''       #: Clear to the beginning of the line.
    CLEAR_EOS = ''       #: Clear to the end of the screen

    # Output modes:
    BOLD = ''            #: Turn on bold mode
    BLINK = ''           #: Turn on blink mode
    DIM = ''             #: Turn on half-bright mode
    REVERSE = ''         #: Turn on reverse-video mode
    NORMAL = ''          #: Turn off all modes

    # Cursor display:
    HIDE_CURSOR = ''     #: Make the cursor invisible
    SHOW_CURSOR = ''     #: Make the cursor visible

    # Terminal size:
    COLS = None          #: Width of the terminal (None for unknown)
    LINES = None         #: Height of the terminal (None for unknown)

    # Foreground colors:
    BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
    
    # Background colors:
    BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
    BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
    
    _STRING_CAPABILITIES = """
    BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
    CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
    BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
    HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
    _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
    _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()

    def __init__(self, term_stream=sys.stdout):
        """
        Create a `TerminalController` and initialize its attributes
        with appropriate values for the current terminal.
        `term_stream` is the stream that will be used for terminal
        output; if this stream is not a tty, then the terminal is
        assumed to be a dumb terminal (i.e., have no capabilities).
        """
        # Curses isn't available on all platforms
        try: import curses
        except: return

        # If the stream isn't a tty, then assume it has no capabilities.
        if not term_stream.isatty(): return

        # Check the terminal type.  If we fail, then assume that the
        # terminal has no capabilities.
        try: curses.setupterm()
        except: return

        # Look up numeric capabilities.
        self.COLS = curses.tigetnum('cols')
        self.LINES = curses.tigetnum('lines')
        
        # Look up string capabilities.
        for capability in self._STRING_CAPABILITIES:
            (attrib, cap_name) = capability.split('=')
            setattr(self, attrib, self._tigetstr(cap_name) or '')

        # Colors
        set_fg = self._tigetstr('setf')
        if set_fg:
            for i,color in zip(range(len(self._COLORS)), self._COLORS):
                setattr(self, color, curses.tparm(set_fg, i) or '')
        set_fg_ansi = self._tigetstr('setaf')
        if set_fg_ansi:
            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
                setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
        set_bg = self._tigetstr('setb')
        if set_bg:
            for i,color in zip(range(len(self._COLORS)), self._COLORS):
                setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
        set_bg_ansi = self._tigetstr('setab')
        if set_bg_ansi:
            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
                setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')

    def _tigetstr(self, cap_name):
        # String capabilities can include "delays" of the form "$<2>".
        # For any modern terminal, we should be able to just ignore
        # these, so strip them out.
        import curses
        cap = curses.tigetstr(cap_name) or ''
        return re.sub(r'\$<\d+>[/*]?', '', cap)

    def render(self, template):
        """
        Replace each $-substitutions in the given template string with
        the corresponding terminal control string (if it's defined) or
        '' (if it's not).
        """
        return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
        
    def _render_sub(self, match):
        s = match.group()
        if s == '$$': return s
        else:
            #We try to handle strings of the form ${...} that
            #might occur in the string (especially if it's
            #a port description) and will be interpreted as
            #color variables, while they aren't.
            try:
                return getattr(self, s[2:-1])
            except AttributeError:
                return s
#}}}

#####################################################################################

def print_ports(port_list, long_descriptions):  #{{{
    """
    Pretty-prints each given port metadata.
    """
    global INSTALLED, PKGNAMES
    for port in port_list:
        print term.render('${MAGENTA}* ${GREEN}%s/${BOLD}${YELLOW}%s${NORMAL}' % ('/'.join(port['path'].split('/')[:-1]),port['path'].split('/')[-1]))
#        print term.render('${MAGENTA}* ${GREEN}%s/${BOLD}${YELLOW}%s${NORMAL}' % ('/'.join(port['path'].split('/')[:-1]),port['pkgname']))
        print term.render('Name       : ${GREEN}%s${NORMAL}'%port['portname'])
        print term.render('Version    : ${GREEN}%s${NORMAL}' % port['portversion'])
        print term.render('Revision   : ${GREEN}%s${NORMAL}' % port['portrevision'])
        print term.render('Maintainer : ${GREEN}%s${NORMAL}' % port['maintainer'])
        print term.render("Description: ${CYAN}%s${NORMAL}" % port['short_descr'])
        print term.render('Categories : ${GREEN}%s${NORMAL}' % ' '.join(port['categories']))
        if port['installed']:
            pkgname=PKGNAMES[port['pkgname']]['installedpkgname']
            portversion=INSTALLED[pkgname]['portversion']
            if INSTALLED[pkgname]['portrevision']:
                portversion+='_'+INSTALLED[pkgname]['portrevision']
            if INSTALLED[pkgname]['portepoch']:
                portversion+=','+INSTALLED[pkgname]['portepoch']
            print term.render('Installed  : ${YELLOW}Yes (%s)${NORMAL}' % portversion)
        else:
            print term.render('Installed  : ${GREEN}No${NORMAL}')
        if long_descriptions:
            print term.render("Verbose description:\n${CYAN}%s${NORMAL}" % get_verbose_descr(port))
        print
#}}}
def compact_print_ports(term, port_list):   #{{{
    """
    Pretty-compact-prints a port's data.
    """
    global INSTALLED
    max_len = 0
    if term.COLS:
        len_status=len("${GREEN}[x] ")
        for port in port_list:
            #caculate max_len
            length=len_status+len(port['pkgname'])
            if max_len < length:
                    max_len = length
        for port in port_list:
            tempstring = "${GREEN}["
            if port['installed']:
                if port['updateavail']:
                        tempstring += "u]"
                else:
                        tempstring += "i]"
            else:
                tempstring += " ]"
            tempstring += " " + port['pkgname']
            portname = tempstring + ' ' * (max_len - len(tempstring))
            #max_len is the max length of the port's status plus port's pkgname.
            #however, when calculated it contained the '${COLOR}' variables,
            #so we subtract their length.
            portdescr = port['short_descr'][0:(term.COLS - max_len)+7]
            print term.render('%s ${CYAN}%s${NORMAL}' % (portname, portdescr))
    else:
        for port in port_list:
            tempstring = "${GREEN}["
            if port['installed']:
                if port['updateavail']:
                        tempstring += "u]"
                else:
                        tempstring += "i]"
            else:
                tempstring += " ]"
            tempstring += " " + port['portname'] + "-" + port['portversion']
            portname = tempstring
            portdescr = port['short_descr']
            print term.render('%s ${CYAN}%s${NORMAL}' % (portname, portdescr))
#}}}
def get_verbose_descr(port):    #{{{
    """
    Reads and returns the port's verbose
    description from the .descr file.
    """
    try:
        return open(port['path'] + '/pkg-descr', 'r').read()
    except:
        return None
#}}}
def read_installed_packages():  #{{{
    """
    Reads the installed PORTS from /var/db/pkg.
    """
    global INSTALLED
    #INSTALLED is a dictionary with PKGNAMES as keys
    #and package dictionaries as values.
    INSTALLED = {}
    
    # list of port directories
    # ex. /var/db/pkg/xvid-1.1.3_1,1
    portdirs = [dir for dir in glob.glob('/var/db/pkg/*') if os.path.isdir(dir)]
    # package names only, ex.
    # xvid-1.1.3_1,1
    # make sure we select only directories, not files.
    pkgnames = [portdir[12:] for portdir in portdirs]
    re_origin=re.compile('@comment ORIGIN:(.*)')
    re_pkgname=re.compile('(.*)-([a-zA-Z\d\.]+)(?:_(\w+))?(?:,(\w+))?')
    for pkgname in pkgnames:
        data=re.match(re_pkgname, pkgname)
        #the pkgname should always contain a dash that
        #separates the port name from the port version.
        #this will always be the last dash in the pkgname string.
        #The port naming specification is specified in
        #http://www.freebsd.org/doc/en_US.ISO8859-1/books/porters-handbook/makefile-naming.html
        contents = open(portdirs[pkgnames.index(pkgname)] + '/+CONTENTS').read()
        match=re.search(re_origin, contents)
        if match is not None:
            origin=re.search(re_origin, contents).group(1)
        else:
            origin=None
        INSTALLED[pkgname] = {
                        'pkgname':pkgname,
                        'portname':data.group(1),
                        'portversion':data.group(2),
                        'portrevision':data.group(3),
                        'portepoch':data.group(4),
                        'origin':origin,
                        }
#}}}
def compare_versions(p1, p2):   #{{{
    """
    Compare versions of ports p1 and p2
    Returns -1 if p1 is older
             1 if p1 is newer
             0 if p1 and p2 are the same
    """

    #first we check portepoch
    if p1['portepoch']!=p2['portepoch']:
        if p1['portepoch']>p2['portepoch']:
            return 1
        else:
            return -1

    #if portepoch are the same, check port versions
    #port version is built from components separated with '.'
    #we compare each component independently
    v1 = p1['portversion'].split('.')
    v2 = p2['portversion'].split('.')

    v1len=len(v1)
    v2len=len(v2)
    #both port versions should consist of the same number of components
    #if they don't, we check only the common part
    length=min(v1len, v2len)
    #each component is digits prefixed/suffixed with letters
    re_vecomponent=re.compile('([a-z]*)(\\d*)([a-z]*)', re.I)
    while length>0:
        v1c=v1.pop(0)
        v2c=v2.pop(0)
        m1=re.match(re_vecomponent, v1c);
        m2=re.match(re_vecomponent, v2c);
        #compare prefix part
        if len(m1.group(1)) and len(m2.group(1)):
            r=cmp(m1.group(1), m2.group(1))
            if r!=0: return r
        #compare digits part
        if len(m1.group(2)) and len(m2.group(2)):
            r=cmp(int(m1.group(2)), int(m2.group(2)))
            if r!=0: return r
        #compare suffix part
        if len(m1.group(3)) and len(m2.group(3)):
            r=cmp(m1.group(3), m2.group(3))
            if r!=0: return r
        length-=1

    if v1len!=v2len:
        #We checked that port versions components are equal
        #if one of the components is longer, we assume it's newer
        if v1len>v2len:
            return 1
        else:   #v1len<v2len
            return -1

    #finally check port revisions
    #we convert it to int, because it cannot contain letters
    r1=0;   r2=0
    if p1['portrevision'] is not None:
        r1=int(p1['portrevision'])
    if p2['portrevision'] is not None:
        r2=int(p2['portrevision'])
    if r1!=r2:
        if r1>r2:
            return 1
        else:
            return -1

    return 0    #v1 and v2 are the same
#}}}
def check_installed(): #{{{
    global PKGNAMES, INSTALLED, PORTNAMES, PORTORIGINS
    installs=0  #number of installed packages
    update_values=[None, True, False]
    for pkgname in INSTALLED.keys():
        #Check the easy ones first.
        #If a port exists exactly with the same name, version etc.
        #as a package, marked it installed.
        if pkgname in PKGNAMES:
            PKGNAMES[pkgname]['installed']=True
            PKGNAMES[pkgname]['installedpkgname']=pkgname
            v=compare_versions(PKGNAMES[pkgname], INSTALLED[pkgname])
            PKGNAMES[pkgname]['updateavail']=update_values[v]
            installs+=1
        else:
            #if pkgname cannot be found, we search for port based on origin
            #(i.e. category/portname)
            origin=INSTALLED[pkgname]['origin']
            if origin in PORTORIGINS:
                pkg_name=PORTORIGINS[origin]['pkgname']
                if INSTALLED[pkgname]['portname']==PKGNAMES[pkg_name]['portname']:
                    #make sure the portname is the same
                    PKGNAMES[pkg_name]['installed']=True
                    PKGNAMES[pkg_name]['installedpkgname']=pkgname
                    v=compare_versions(PKGNAMES[pkg_name], INSTALLED[pkgname])
                    PKGNAMES[pkg_name]['updateavail']=update_values[v]
                    installs+=1
                #else: #origin matches, but portname doesn't, probably port was moved
                #TODO: is it safe to ignore this port?
            #else: #no such origin in ports tree
#}}}
def read_ports():   #{{{
    """
    Reads all PORTS from the INDEX
    file and returns a list of them.
    """
    global PKGNAMES
    global CATEGORIES
    global PORTNAMES
    global PORTORIGINS
    #PKGNAMES contains pkgnames (full port name+versions etc ex. gtkmumble-0.3_2,1)
    #as keys and port dictionaries as values.
    #PORTNAMES contains portnames (simple names, ex. 'gtkmumble') as keys
    #and lists of port dictionaries as values, because many ports might have
    #the same ${PORTNAME}.
    #PORTORIGINS contain port origin as keys and port dictionaries as values
    #PORT is a dictionary that defines a single port.
    #The FreeBSD porter's handbook defines the following
    #variables regarding each port's name:
    #PORTNAME=       gtkmumble
    #PORTVERSION=    0.3
    #PORTEPOCH=      1
    #PORTREVISION=   2
    #PKGNAME= gtkmumble-0.3_2,1
    PKGNAMES = {}
    PORTNAMES={}
    CATEGORIES = {}
    PORTORIGINS = {}
    
    indexes=['INDEX','INDEX-9','INDEX-8','INDEX-7','INDEX-6']
    indexfile=None
    for filename in indexes:
        if os.path.isfile("/usr/ports/"+filename):
            indexfile=filename
            break
    if not indexfile:
        PORTNAMES={}
        return
    fin = open("/usr/ports/"+indexfile, 'r')
    print_info("Opened %s"%indexfile)
    lines = fin.readlines()
    fin.close()
    
    re_pkgname=re.compile('(.*)-([a-zA-Z\d\.]+)(?:_(\w+))?(?:,(\w+))?')
    re_origin=re.compile('/usr/ports/(.*)')
    for line in lines:
        values = line.split('|')
        port = {}
        port['pkgname'] = values[0]
        data=re.match(re_pkgname, values[0])
        port['portname'] = data.group(1)
        port['portepoch']=data.group(4)
        port['portversion']=data.group(2)
        port['portrevision']=data.group(3)
        port['path'] = values[1]
        port['short_descr'] = values[3]
        #store lowered maintainer string to ease search.
        port['maintainer'] = values[5].lower()
        port['categories'] = values[6].split()
        #we also store categories separately
        for cat in port['categories']:
            if cat not in CATEGORIES:
                CATEGORIES[cat] = [port]
            else:
                CATEGORIES[cat].append(port)
        #Long descriptions are not in the INDEX file,
        #which means we must explicitely open each port's
        #pkg-descr file. This would take a long time if we
        #did it for each port, so we set them as None now
        #and if needed later, we update requested data.
        port['long_descr'] = None
        #We also check later if a port is installed.
        port['installed'] = False
        #is newer version avaiable; we check it later
        #True: port is installed and can be updated; False: port is installed and cannot be updated; None: port is not installed
        port['updateavail'] = None
        
        match=re.match(re_origin, port['path']);
        PORTORIGINS[match.group(1)] = port
        PKGNAMES[port['pkgname']] = port
        if port['portname'] in PORTNAMES:
            PORTNAMES[port['portname']].append(port)
        else:
            PORTNAMES[port['portname']]=[port]
#}}}
def find_ports(args):   #{{{
    """
    Returns the port entry
    that has a name equal (-e) to the given one,
    or a name that contains the given terms.
    """
    global PKGNAMES
    global PORTNAMES
    if args['exact_search']:
        found_ports = []
        for portname in PORTNAMES:
            for term in args['search_terms']:
                if term == portname.lower():
                    found_ports+=PORTNAMES[portname]
        return found_ports
    elif args['regex_search'] and args['short_descr_search']:
        ports_found = []
        for term in args['search_terms']:
            for found_port in search_port_short_descr_regex(term):
                ports_found.append(found_port)
        return ports_found
    elif args['regex_search']:
        ports_found = []
        for term in args['search_terms']:
            for found_port in search_port_regex(term):
                ports_found.append(found_port)
        return ports_found
    elif args['short_descr_search']:
        ports_found = []
        for term in args['search_terms']:
            for found_port in search_port_short_descr(term):
                ports_found.append(found_port)
        return ports_found
    else:
        ports_found = []
        for term in args['search_terms']:
            for found_port in search_port_name(term):
                ports_found.append(found_port)
        return ports_found
#}}}
def search_port_name(term): #{{{
    """
    Searches the PORTS for PORTS
    with name similar to the given one.
    Returns: a list of ports (dictionaries)
    """
    global PKGNAMES
    portnames = PKGNAMES.keys()
    found_ports = []
    for portname in portnames:
        if term in portname.lower():
            found_ports.append(PKGNAMES[portname])
    return found_ports
#}}}
def search_port_regex(term):    #{{{
    """
    Searches the PORTS for PORTS
    with name matching given regex.
    Returns: a list of ports (dictionaries)
    """
    global PKGNAMES
    portnames = PKGNAMES.keys()
    found_ports = []
    re_stmt=re.compile(term)
    for portname in portnames:
        if re.search(re_stmt, portname.lower()):
            found_ports.append(PKGNAMES[portname])
    return found_ports
#}}}
def search_port_short_descr(term):  #{{{
    """
    Searches the PORTS for PORTS
    with short_descr matching given one.
    Returns: a list of ports (dictionaries)
    """
    global PKGNAMES
    portnames = PKGNAMES.keys()
    found_ports = []
    for portname in portnames:
        if term in PKGNAMES[portname]['short_descr'].lower():
            found_ports.append(PKGNAMES[portname])
    return found_ports
#}}}
def search_port_short_descr_regex(term):    #{{{
    """
    Searches the PORTS for PORTS
    with short_descr matching given regex.
    Returns: a list of ports (dictionaries)
    """
    global PKGNAMES
    portnames = PKGNAMES.keys()
    found_ports = []
    re_stmt=re.compile(term)
    for portname in portnames:
        if re.search(re_stmt, PKGNAMES[portname]['short_descr'].lower()):
            found_ports.append(PKGNAMES[portname])
    return found_ports
#}}}
def filter_ports(ports_list, args): #{{{
    """
    Filters ports according to the
    given categories,installed status
    and maintainer.
    """
    remaining_ports = []
    for port in ports_list:
        port_passed = True
        if args['installed'] is not None:
            if port['installed'] != args['installed']:
                port_passed = False
        if args['updateavail'] is not None:
            if port['updateavail'] != args['updateavail']:
                port_passed = False
        if port_passed and args['maintainers']:
            found_maintainer=False
            for maintainer in args['maintainers']:
                if maintainer in port['maintainer']:
                    found_maintainer=True
            port_passed=found_maintainer
        if port_passed and args['categories']:
            port_passed = False
            for category in port['categories']:
                if category in args['categories']:
                    port_passed = True
                    break;
        if port_passed:
            remaining_ports.append(port)
    return remaining_ports
#}}}
def get_arguments():    #{{{
    """
    Parses the command line arguments
    and sets global variables accordingly.
    """
    global term
    #dictionary that will hold data given by the user
    args = {}
    args['categories'] = []
    args['maintainers'] = []
    args['long_descriptions'] = False
    args['compact_print'] = True
    args['exact_search'] = False
    args['installed'] = None
    args['search_terms'] = []
    args['statistics_only'] = False
    args['regex_search'] = False    #search port names by regex
    args['short_descr_search'] = False #search by short description
    args['updateavail'] = None

    try:
        optlist, arguments = getopt.getopt(sys.argv[1:], 'vsec:m:i:EDu:',["help","version"])
        #force lowercase search terms, in order to ease searching.
        args['search_terms'] = [argument.lower() for argument in arguments]
        for o, a in optlist:
            if o == "--version":
                print VERSION
                sys.exit(0)
            elif o == "--help":
                show_syntax()
            elif o == '-i':
                if a.lower() == 'yes':
                    args['installed'] = True
                elif a.lower() == 'no':
                    args['installed'] = False
                else:
                    show_syntax()
            elif o == '-u':
                if a.lower() == 'yes':
                    args['updateavail'] = True
                elif a.lower() == 'no':
                    args['updateavail'] = False
                else:
                    show_syntax()
            elif o == '-c':
                #flag_search_categories is set later
                args['categories'].append(a)
            elif o == '-e':
                args['exact_search'] = True
                if len(arguments) == 0:
                    print_warning("Need at least one argument if -e is specified")
                    show_syntax()
                print_info("Searching for ports mathing exact names '%s'." % ','.join(args['search_terms']))
            elif o == '-E':
                args['regex_search'] = True
                if len(arguments) == 0:
                    print_warning("Need at least one argument if -E is specified")
                    show_syntax()
                print_info("Searching for ports mathing regexes '%s'." % ','.join(args['search_terms']))
            elif o == '-D':
                args['short_descr_search'] = True
                if len(arguments) == 0:
                    print_warning("Need at least one argument if -D is specified")
                    show_syntax()
                print_info("Searching for ports mathing short descriptions '%s'." % ','.join(args['search_terms']))
            elif o == '-p':
                args['compact_print'] = True
            elif o == '-s':
                args['statistics_only'] = True
            elif o == '-v':
                args['long_descriptions'] = True
                print_info("Showing verbose descriptions.")
                args['compact_print'] = False
            elif o == '-m':
                #flag_search_categories is set later
                #store lowered maintainer strings to ease searching.
                args['maintainers'].append(a.lower())

        #check for option dependencies and compability
        if (args['regex_search']==True or args['short_descr_search']==True) and args['exact_search']==True:
            print_warning('Options -D and -E cannot be mixed with -e')
            sys.exit(1)
        if args['updateavail']==True and args['installed']==False:
            print_warning('Option -u cannot be mixed with -i no')
            sys.exit(1)
        if args['updateavail'] is not None:
            args['installed'] = True

        if len(arguments) == 0:
            print_info("No search term given, searching all ports.")
        if args['installed'] is not None:
            if args['installed']:
                print_info("Searching only for ports that are installed.")
            else:
                print_info("Searching only for non-installed ports.")
        if args['updateavail'] is not None:
            if args['updateavail']:
                print_info("Searching only for ports that can be updated")
            else:
                print_info("Searching only for ports with no updates")
        if len(args['categories']) > 0:
            print_info("Searching only in categories '%s'" % ','.join(args['categories']))
        if len(args['maintainers']) > 0:
            print_info("Searching only for maintainers '%s'" % ','.join(args['maintainers']))
        return args
    except getopt.GetoptError:
        print "Invalid Syntax. Try --help"
        sys.exit(1)
#}}}
def print_info(s): #{{{
    """
    Prints a string with the standard message colors.
    """
    global term
    print term.render('${GREEN}* ${BLUE}%s${NORMAL}' % s)
#}}}
def print_warning(s): #{{{
    """
    Prints a string with the standard warning color.
    """
    global term
    print term.render('${GREEN}* ${RED}%s${NORMAL}' % s)
#}}}
def show_syntax(): #{{{
    print "Syntax: %s [-e] [-E] [-D] [-m maintainer] [-c category] [-i yes/no] [-u yes/no] [term1 [term2]...]"%sys.argv[0]
    print "-e Exact port name match"
    print "-E search by regex"
    print "-D search by short description"
    print "-v Show verbose (large) port descriptions"
    print "-c <category> Search for PORTS only in category"
    print "-m <maintainer> Search for PORTS maintained only by maintainer"
    print "-i yes/no Search only for installed/non installed ports"
    print "-u yes/no Search only for installed ports that can/cannot be updated"
    print("-s Print port statistics only.")
    sys.exit(1)
#}}}
#############################################################################

def search(): #{{{
    global PKGNAMES
    global INSTALLED
    global CATEGORIES
    global args
    global term

    term = TerminalController()
    args = get_arguments()
    read_ports()
    read_installed_packages()
    check_installed()

    print_info("Found %d installed packages." % len(INSTALLED))
    print_info('Found %d indexed ports in %d categories.' % (len(PKGNAMES), len(CATEGORIES)))
    
    if args['statistics_only']:
        sys.exit(0)
    if not args['search_terms']:
        ports = [PKGNAMES[portname] for portname in PKGNAMES.keys()]
    else:
        ports = find_ports(args)
        
    ports = filter_ports(ports, args)

    if ports != []:
        print_info("%d matches found:" % len(ports))
        sort_proc=lambda p1, p2: cmp(p1['pkgname'], p2['pkgname'])
        ports.sort(sort_proc)
        if args['compact_print']:
            compact_print_ports(term, ports)
        else:
            print_ports(ports, args['long_descriptions'])
    else:
        if args['exact_search']:
            print_warning("No exact match found. You can try without '-e'.")
        else:
            print_warning("Cannot find a relevant port.")
#}}}

if __name__ == '__main__':
    search()
