#!/usr/bin/env python
#coding: iso-8859-15
#
# uptodate helps you to keep your system uptodate
#
# Copyright (C) 2005  Gatan Lehmann <gaetan.lehmann@jouy.inra.fr>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

VERSION = "0.1"


import ConfigParser
import urllib
import os.path
import sys
import re
import __builtin__
from optparse import OptionParser



# following functions are commands

def commandAdd(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] add [options] name url regexp [comment]", description="")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display added module.")
	opts, args = parser.parse_args(options.argv)

	if len(args) < 3 :
		parser.print_usage(sys.stderr)
		return

	module, url, regexp = args[0]
	url = args[1]
	regexp = args[2]
	comment = " ".join(args[3:])
	current = getVersions(module, url, regexp)

	# test if a version can be found
	if len(current) != 0 :
		conf.add_section(module)
		conf.set(module, 'url', url)
		conf.set(module, 'regexp', regexp)
		conf.set(module, 'comment', comment)
		conf.set(module, 'current', repr(current))
		
		if opts.verbose :
			printModule(conf, module, out, True)
	else :
		# no version found... exit
		print >> sys.stderr, "Error: no version found."
		return


def commandCheck(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] check [options] [name] ...", description="")
	parser.add_option("-a", "--all", action="store_true", dest="all", help="check all modules.")
	parser.add_option("-r", "--removed", action="store_true", dest="removed", help="display removed versions.")
	parser.add_option("-A", "--added", action="store_true", dest="added", help="display added versions.")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display what is done.")
	opts, args = parser.parse_args(options.argv)

	if not opts.added and not opts.removed :
		# by default, both removed and added are displayed
		opts.added = True
		opts.removed = True
	
	modules = args
	if opts.all :
		modules = conf.sections()

	if len(modules) == 0 :
		# no module to check... print usage and exit
		parser.print_usage(sys.stderr)
		return
	
	for module in modules :
		if opts.verbose :
			print >> sys.stderr, "Checking %s" % module
		# get module properties
		url = conf.get(module, 'url')
		regexp = conf.get(module, 'regexp').replace('\\\\', '\\')
		currentVersions = eval(conf.get(module, 'current'))
		# get new versions
		newVersions = getVersions(module, url, regexp)
		# test if a version can be found
		if len(newVersions) != 0 :
			conf.set(module, 'current', repr(newVersions))
			
			added = set(newVersions) - set(currentVersions)
			removed = set(currentVersions) - set(newVersions)
			if added and opts.added :
				print >> out, "%s: %s added." % (module, ", ".join(map(repr, added)))
			if removed and opts.removed :
				print >> out, "%s: %s removed." % (module, ", ".join(map(repr, removed)))
		else :
			# no version found ... exit
			print >> sys.stderr, "Warning, no version found for %s. Nothing will be stored." % module
			return
		

def commandEdit(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] edit [options] name property value", description="")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display modified module.")
	opts, args = parser.parse_args(options.argv)

	if len(args) != 3 :
		parser.print_usage(sys.stderr)
		return

	module, prop, value = args
	if conf.has_option(module, prop) :
		conf.set(module, prop, value)
		if opts.verbose :
			printModule(conf, module, out, True)
	else :
		print >> sys.stderr, "Unknown module or property."

def commandRemove(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] remove [options] [name] ...", description="")
	parser.add_option("-a", "--all", action="store_true", dest="all", help="remove all modules.")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display what is done.")
	opts, args = parser.parse_args(options.argv)

	modules = args
	if opts.all :
		modules = conf.sections()

	if len(modules) == 0 :
		parser.print_usage(sys.stderr)
		return
	
	for module in modules :
		if opts.verbose :
			print >> out, "Removing %s" % module
		conf.remove_section(module)
	

def commandDisplay(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] display [options] [name] ...", description="display configured modules")
	parser.add_option("-a", "--all", action="store_true", dest="all", help="display all modules.")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display more info.")
	opts, args = parser.parse_args(options.argv)

	modules = args
	if opts.all :
		modules = conf.sections()

	if len(modules) == 0 :
		parser.print_usage(sys.stderr)
		return
	
	for module in modules :
		printModule(conf, module, out, opts.verbose)


def commandCopy(options, conf, out) :
	parser = OptionParser(usage="uptodate [options] copy name name", description="")
	parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="display copied module.")
	opts, args = parser.parse_args(options.argv)

	if len(args) != 2 :
		parser.print_usage(sys.stderr)
		return

	module, new = args
	if conf.has_section(module) and not conf.has_section(new) :
		conf.add_section(new)
		for prop in conf.options(module) :
			conf.set(new, prop, conf.get(module, prop))
		if opts.verbose :
			printModule(conf, new, out, True)
	else :
		print >> sys.stderr, "Unknown or already present module."

	
	
def printModule(conf, module, out, verbose=False) :
	"""
	printModule(conf, module, out, verbose)

	print module info to out file. Infos comes from conf object.
	"""
	url = conf.get(module, 'url')
	regexp = conf.get(module, 'regexp').replace('\\\\', '\\')
	current = conf.get(module, 'current')
	comment = conf.get(module, 'comment')
	
	if verbose :
		print >> out, module
		print >> out, "\tcomment= "+comment
		print >> out, "\turl= "+url
		print >> out, "\tregexp= "+regexp
		print >> out, "\tcurrent= "+current
	else :
		s = module
		if comment :
			s += " (%s)" % comment
		print >> out, s
		


USAGE="uptodate [options] command [options] [arguments]"

COMMANDS = {'add': commandAdd,
	    'check': commandCheck,
	    'edit': commandEdit,
	    'remove': commandRemove,
	    'display': commandDisplay,
	    'copy' : commandCopy
	    }

DESCRIPTION="Commands :"
for command in COMMANDS.keys() :
	DESCRIPTION += "\n\t"+command
DESCRIPTION += 'Run "uptodate command --help" for more information.'


def parse_options(argv):
    parser = OptionParser(usage=USAGE,
                          description=DESCRIPTION,
                          version="uptodate %s" % VERSION)
    parser.disable_interspersed_args()
    parser.add_option("-o", "--output", dest="outputPath", default="-", metavar="FILE", help="write output to FILE")
    parser.add_option("-c", "--config-file", dest="configPath", default=os.path.expanduser("~/.uptodate"), metavar="FILE", help="configuration file ")
    parser.add_option("-d", "--dry-run", action="store_true", dest="dryRun", help="do not modify config file.")
    opts, args = parser.parse_args()
    if args and args[0] in COMMANDS.keys() :
        opts.command = args[0]
        opts.argv = args[1:]
    else:
        opts.command = None
        opts.argv = []
    if not opts.command:
        parser.print_help()
        sys.exit(1)
    return opts



def getVersions(module, url, regexp) :
    urlFile = urllib.urlopen(url)
    urlData = urlFile.read()
    urlFile.close()
    new = re.findall(regexp, urlData)
    return new



def file(fName, output=False, notNone=False, append=False) :
        """Renvoi un fichier en fonction du nom de fichier pass en paramtre.
        
        fName est le chemin d'acces complet a un fichier.
        fName = "-" est interprt comme l'entre (ou la sortie, si output=True) standard.
        output = True indique que le ficher doit etre ouvert en ecriture
        notNone = True provoque le renvoi d'un fichier ouvert sur /dev/null plutot que None quand fName = ""
        append provoque l'ouverture du ficheir en mode append. Doit etre utilis avec output = True
        """
        if append :
                wMode = "a"
        else :
                wMode = "w"
        
        #le nom de fichier est vide...
        if fName == "" :
                if notNone :
                        if output :
                                return file("/dev/null", wMode)
                        else :
                                return file("/dev/null")
                else :
                        return None
        
        elif fName == "-" :
                #la sortie ou l'entree standard
                if output :
                        return sys.stdout
                else :
                        return sys.stdin
        else :
                # un fichier normal
                if output :
                        return __builtin__.file(fName, wMode)
                else :
                        return __builtin__.file(fName)




def main(argv) :
	options = parse_options(argv)

	# load config in a file to be able to load it from stdin
	conf = ConfigParser.SafeConfigParser()
	try :
		# try to load config
		confFile = file(options.configPath)
	except :
		# load fail... try to create it
		file(options.configPath, True).close()
		confFile = file(options.configPath)
	conf.readfp(confFile)
	confFile.close()

	# set output file
	out = file(options.outputPath, True)
	
	# execute command
	COMMANDS[options.command](options, conf, out)
    
	# save config file, if needed
	if not options.dryRun :
		confFile = file(options.configPath, 'w')
		conf.write(confFile)
		confFile.close()







if __name__ == '__main__':
        import sys
	sys.exit(main(sys.argv[1:]))




