[csw-devel] SF.net SVN: gar:[12202] csw/mgar/gar/v2-uwatch2/bin/upstream_watch

wbonnet at users.sourceforge.net wbonnet at users.sourceforge.net
Wed Jan 5 22:30:29 CET 2011


Revision: 12202
          http://gar.svn.sourceforge.net/gar/?rev=12202&view=rev
Author:   wbonnet
Date:     2011-01-05 21:30:29 +0000 (Wed, 05 Jan 2011)

Log Message:
-----------
First python version. Does not integrate yet with GAR makefile

Modified Paths:
--------------
    csw/mgar/gar/v2-uwatch2/bin/upstream_watch

Modified: csw/mgar/gar/v2-uwatch2/bin/upstream_watch
===================================================================
--- csw/mgar/gar/v2-uwatch2/bin/upstream_watch	2011-01-05 19:26:16 UTC (rev 12201)
+++ csw/mgar/gar/v2-uwatch2/bin/upstream_watch	2011-01-05 21:30:29 UTC (rev 12202)
@@ -1,208 +1,478 @@
-#!/usr/bin/perl -lw
+#!/usr/bin/env python
+
 #
-# Copyright 2006 Yann Rouillard <yann at blastwave.org>
-# All rights reserved.  Use is subject to license terms.
+# The contents of this file are subject to the COMMON DEVELOPMENT AND 
+# DISTRIBUTION LICENSE (CDDL) (the "License"); you may not use this 
+# file except in compliance with the License. 
 #
-# Redistribution and/or use, with or without modification, is
-# permitted.  This software is without warranty of any kind.  The
-# author(s) shall not be liable in the event that use of the
-# software causes damage.
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
 #
-# upstream_watch - search for new upstream files 
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 3 or later (the "GPL"),
+# in which case the provisions of the GPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL, and not to allow others to
+# use your version of this file under the terms of the CDDL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the CDDL, or the GPL.
 #
+# Copyright 2010 OpenCSW (http://www.opencsw.org).  All rights reserved.
+# Use is subject to license terms.
+#
+#
+# Contributors list :
+#
+#    William Bonnet wbonnet at opencsw.org
+#
+#
 
-use Env;
-use Getopt::Long;
+# Import all the needed modules
+import sys
+import string
+import re
 
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Function : compare_versions
-# Purpose  : Compare version number of two software
-# Arguments: $version1 - version number of the first software
-#            $version2 - version number of the first software
-# Returns  : 1 if $version1 > $version2
-#            0 if $version1 == $version2
-#            -1 if $version1 < $verson2
+# import httplib
+
+from urllib2 import Request, urlopen, URLError
+from optparse import OptionParser
+
+# import subprocess
+# import os
+# import shutil
+
+# ---------------------------------------------------------------------------------------------------------------------
 #
-sub compare_versions
-{
-    my $version1 = shift;
-    my $version2 = shift;
-   
-    # we consider the version to be composed of several elements separated by '.' ',' or '_'
-    # an elements can be a string or a number
-    # at each step we extract the next elements of the two version strings and compare them
-    while (my ($number1, $string1, $rem1) = ($version1 =~ /^(?:([0-9]+)|([^0-9\.,_]+))[\.,_]?(.*)?$/)) {
+#
+class NoUpstreamProtocolException(Exception):
+    """Exception raised when no protocol is specified in the upstream url 
+    """
 
-	my ($number2, $string2, $rem2) = ($version2 =~ /^(?:([0-9]+)|([^0-9\.,_]+))[\.,_]?(.*)?$/) or 
-	    # $versions1 if the same as $versions with additional characters so it must be more recent
-	    # (i.e. 1.2foo is usually more recent than 1.2)
-	    return 1;
+    # -----------------------------------------------------------------------------------------------------------------
+    # Just an empty class...
 
-	if (defined $number1 and defined $number2) {
-	    my $ret = ($number1 <=> $number2);
-	    return $ret if $ret != 0;
-	} elsif (defined $string1 and defined $string2) {
-	    # string comparisons is relevevant for comparing 
-	    # version strings like 2.a and 2.b
-	    my $ret = ($string1 cmp $string2);
-	    return $ret if $ret != 0;
-	} elsif (defined $number1 and defined $string2) {
-	    # we suppose that numbers are always greater that string
-	    return 1;
-	} else {
-	    return -1;
-	}
-	
-	$version1 = $rem1;
-	$version2 = $rem2;
-    }
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class UpstreamUrlRetrievalFailedException(Exception):
+    """Exception raised when an unsuported protocol is specified in the upstream url 
+    """
 
-    if ($version2 ne "") {
-	# $version2 if the same as $version1 with additionnal characters so it must be more recent
-	# (i.e. 1.2foo is usually more recent than 1.2)
-	return -1;
-    } 
+    # -----------------------------------------------------------------------------------------------------------------
 
-    return 0;
-}
+    def __init__(self, url):
+        self.url  = url
 
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class NoUpstreamVersionFoundException(Exception):
+    """Exception raised when searching the upstream page content does not match the regexp
+    """
 
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Function : get_file_list
-# Purpose  : Return the list of files available at a given url
-# Arguments: $url - an url
-# Returns  : the list of files availables at the given url
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def __init__(self, url, regexp):
+        self.url     = url
+        self.regexp  = regexp
+
+
+# ---------------------------------------------------------------------------------------------------------------------
 #
-# TODO: find a better way than lftp to do this
 #
-sub get_file_list_lftp 
-{
-	my $url = shift;
-	my @file_list;
+class UpstreamProtocolNotSupportedException(Exception):
+    """Exception raised when an unsuported protocol is specified in the upstream url 
+    """
 
-	my $ftp_proxy_cmd = "";
-	my $http_proxy_cmd = "";
-	
-	# lftp doesn't seem to obey the _PROXY env variable
-	# we must manually set them
-	$ftp_proxy_cmd = "set ftp:proxy $ENV{FTP_PROXY};" if exists $ENV{FTP_PROXY};
-	$ftp_proxy_cmd = "set ftp:proxy $ENV{ftp_proxy};" if exists $ENV{ftp_proxy};
-	$http_proxy_cmd = "set http:proxy $ENV{HTTP_PROXY};" if exists $ENV{HTTP_PROXY};
-	$http_proxy_cmd = "set http:proxy $ENV{http_proxy};" if exists $ENV{http_proxy};
-	$https_proxy_cmd = "set https:proxy $ENV{HTTPS_PROXY};" if exists $ENV{HTTPS_PROXY};
-	$https_proxy_cmd = "set https:proxy $ENV{https_proxy};" if exists $ENV{https_proxy};
+    # -----------------------------------------------------------------------------------------------------------------
 
-	open (FH, "lftp -q -c \"set net:timeout 30; set net:max-retries 16; $ftp_proxy_cmd $http_proxy_cmd $https_proxy_cmd open $url/ && ls\" 2>/dev/null |");
+    def __init__(self, value):
+        self.parameter = value
 
-	while (my $line = <FH>) {
-		my @cols = split (/\s+/, $line);
-		my $filename = $cols[$#cols];
-		chomp ($filename);
-		my $result = rindex($filename, '/');
-		if ($result != -1) {
-			$filename = substr $filename , $result +1 ;
-		}
-		push (@file_list, $filename);
-	}
+    def __str__(self):
+        return repr(self.parameter)
 
-	close (FH);
 
-	return \@file_list;
-}
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class MissingArgumentException(Exception):
+    """Exception raised when a command line argument is missing
+    """
 
-sub get_file_list_wget_sourceforge
-{
-	my $url = shift;
-	my @file_list;
+    # -----------------------------------------------------------------------------------------------------------------
 
-	my $http_proxy_cmd = "";
-	my $wget_command ="wget";
-	my $filename = "";
+    def __init__(self, value):
+        self.parameter = value
 
-	# lftp doesn't seem to obey the _PROXY env variable
-	# we must manually set them
-	$http_proxy_cmd = "set http:proxy $ENV{HTTP_PROXY};" if exists $ENV{HTTP_PROXY};
-	$http_proxy_cmd = "set http:proxy $ENV{http_proxy};" if exists $ENV{http_proxy};
-	$wget_command   = "$http_proxy_cmd ; wget --proxy=on" if exists $ENV{http_proxy_cmd};
+    def __str__(self):
+        return repr(self.parameter)
 
-	open (FH, "$wget_command -qO- $url 2>/dev/null | grep class | grep selected | grep li | grep Download | ");
 
-	if (my $line = <FH>) {
-		my @cols = split (/"/, $line);
-		$filename = $cols[3];
-		chomp ($filename);
-	}
-	else {
-		close (FH);
-		return \@file_list;
-	}
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class CommandLineParser(object):
+    """This class is used to parse command line. It process only options. Command verb is parsed by the main procedure
+    """
 
-	close (FH);
+    # -----------------------------------------------------------------------------------------------------------------
 
-	$url = "http://downloads.sourceforge.net" . $filename;
-	open (FH, "$wget_command -qO- $url 2>/dev/null | grep $filename | grep package_id= | grep release_id | ");
+    def __init__(self):
+        # Create the parser object
+        self.parser = OptionParser()
 
-	while (my $line = <FH>) {
-		my @cols = split (/>/, $line);
-		my $filename = $cols[2];
-		chomp ($filename);
-		@cols = split (/</, $filename);
-		$filename = $cols[0];
-		chomp ($filename);
-		push (@file_list, $filename);
-	}
+        # Add options to parser
+        self.parser.add_option("-V", "--verbose", help="Activate verbose mode", action="store_true", dest="verbose") 
+        self.parser.add_option("-c", "--current-version", help="Current package version", action="store", dest="current_version") 
+        self.parser.add_option("-r", "--regexp", help="Version matching regular expression", action="store", dest="regexp") 
+        self.parser.add_option("-u", "--upstream-url", help="Upstream version page url", action="store", dest="upstream_url") 
 
-	close (FH);
+    # -----------------------------------------------------------------------------------------------------------------
 
-	return \@file_list;
-}
+    def parse(self):
+        (self.options, self.args) = self.parser.parse_args()
+        return self.options, self.args
 
-my $help;
-my @urls;
-my $use_sf = 0;
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class ConfigurationParser(object):
+    """This handles parameters retrieved either from command line or configuration file. 
+    """
 
-Getopt::Long::Configure ("no_ignore_case");  
-GetOptions("h|help" => \$help,
-	   "s|use_sf=i" => \$use_sf,
-	   "u|url=s@" => \@urls);
+    # -----------------------------------------------------------------------------------------------------------------
 
-if ($help) {
-	print <<EOF
-Usage: upstream_watch -u URL file_regex...
-Display the latest upstream files.
-EOF
-}
+    def initialize(self, args):
+        """Initialize configuration object. If configurations contains files reference, values from the files are
+        read and copied into this object. Next attempts to retrieve files stored information will read values from 
+        this object.
+        """
 
-foreach my $file_pattern (@ARGV) {
+        # This member variable is a flag which defines the status of the verbose mode (True : activated)
+        if args.verbose != None:
+            self._verbose = args.verbose
+        else:
+            self._verbose = False
 
-	my $newest_version = 0;
-	my $newest_file = "";
-	my $file_list ;
+        # This member variable defines the value of the version of the package
+        if args.current_version != None:
+            self._current_version = args.current_version
 
-	foreach my $url (@urls) {
-		if ($use_sf != 0) {
-			$file_list = get_file_list_wget_sourceforge ($url);
-		} else {	
-			$file_list = get_file_list_lftp ($url);
-		}
+        # This member variable defines the value of the regexp used to match the upstream web page
+        if args.regexp != None:
+            self._regexp = args.regexp
 
-		foreach my $file (@{$file_list}) {			
-			if ($file =~ /^$file_pattern$/) {
-				my $char = '/';
-				my $result = rindex($file, $char);
-				if ($result != -1) {
-					$file = substr $file , $result +1 ;
-				}
+        # This member variable defines the url of the upstream web page used to check for new version
+        if args.upstream_url != None:
+            self._upstream_url = args.upstream_url
 
-				# we only keep the newest file for a given $file_pattern
-				if (compare_versions ($1, $newest_version) > 0) {
-					$newest_version = $1;
-					$newest_file = $file;
-				}
-			}
-		}
-	}
-	printf "$newest_file " if $newest_version;
-}
-printf "\n";
 
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def __init__(self):
+        
+        # Initialize all variables to None. Even if useless, the purpose of this to keep up to date the list
+        self._verbose = None
+        self._current_version = None
+        self._regexp = None
+        self._upstream_url = None
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def getCurrentVersion(self):
+        return self._current_version
+
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def getRegexp(self):
+        return self._regexp
+
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def getUpstreamURL(self):
+        return self._upstream_url
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def getVerbose(self):
+        return self._verbose
+
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class AbstractCommand(object):
+    """Base class for command implementation. You should create a derived class per command to implemente.
+       A "command" represent a concrete action verb given to the program.
+    """
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def __init__(self, name):
+        self.configParser = ConfigurationParser()
+        self.name = name
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def getName(self):
+        return self.name
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def execute(self, opts, arguments):
+        print "TODO Generate and error instead of this message\n"
+        print self.name, "\n"
+
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class CheckUpstreamCommand(AbstractCommand):
+    """CheckUpstream command. This command retrieve the upstream web page and search for a new version. Version check is 
+    done by matching the regexp from the makefile on the page. Results are sorted to get the highest available and 
+    compared to current version
+    """
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def __init__(self, name):
+        super(CheckUpstreamCommand, self).__init__(name)    
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def checkArgument(self):
+
+        # Variable used to flag that we have a missing argument
+        argsValid = True
+
+        # Current version is mandatory
+        if self.configParser.getCurrentVersion() == None:
+            print "Error : Current version is not defined. Please use --current-version flag, or --help to display help"
+            argsValid = False
+
+        # Regexp is mandatory
+        if self.configParser.getRegexp() == None:
+            print "Error : Regexp is not defined. Please use --regexp flag, or --help to display help"
+            argsValid = False
+
+        # UpstreamURL is mandatory
+        if self.configParser.getUpstreamURL() == None:
+            print "Error : Upstream version page URL is not defined. Please use --upstream-url flag, or --help to display help"
+            argsValid = False
+        
+        # If arguments are not valid raise an exception
+        if argsValid == False:
+            raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def UrlContentRetrieve(self, url):
+
+        try:
+            # Request the upstream URL and open it
+            req = Request(url)
+            response = urlopen(req)
+
+        except URLError, e:
+            if hasattr(e, 'reason'):
+                print 'We failed to reach a server during retrieval of : ' + url
+                print 'Reason: ', e.reason
+            elif hasattr(e, 'code'):
+                print 'The server couldn\'t fulfill the request during retrieval of : ' + url
+                print 'Error code: ', e.code
+       
+            # Check for response code value. We should get a 200
+            raise UpstreamUrlRetrievalFailedException(url)
+
+        else:
+            # everything is fine, retrieve the page content
+            return response.read()
+
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def GetNewestVersion(self, version1, version2):
+
+        # we consider the version to be composed of several elements separated by '.' ',' '-' or '_'
+        # an elements can be a string or a number
+        # at each step we extract the next elements of the two version strings and compare them
+
+        # Retrieve the tokens from both version. Use . - , and _ as splitters
+	splittingRegExp = "(?:[\.,_-]?)"
+        tokens1 = re.split(splittingRegExp, version1)
+        tokens2 = re.split(splittingRegExp, version2)
+
+        if self.configParser.getVerbose():
+            print "Comparing " + version1 + " and " + version2 
+
+        # Iterates the toeksn of version 1, pop tokens one by one and compare to the token at same
+        # in version 2
+        while len(tokens1) > 0:
+            # If we still have tokens in version 1 and version 2 is empty, then version 1 is newer
+            # TODO: may have to deal with beta suffixes...
+            if len(tokens2) == 0:
+                return version1
+
+            # Convert both elements to integer
+            # TODO : handles chars in versions
+            elem1 = tokens1.pop(0)
+            elem2 = tokens2.pop(0)
+
+            # If both elements are integer, then convert to int before comparison, otherwise compare strings
+            try:
+                elem1 = int(elem1)
+                elem2 = int(elem2)
+            except:
+                elem1 = str(elem1)
+                elem2 = str(elem2)
+                # print "Doing string comparison"
+                
+            # print "Comparing %(elem1)s and %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }               
+
+            # if elements are the same continue the loop
+            if elem1 == elem2:
+                continue
+
+            # Test if elem1 is more recent
+            if elem1 > elem2:
+                # print "%(elem1)s > %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }
+                return version1
+            else:
+                # print "%(elem1)s < %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }
+                return version2
+    
+        return version1
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def execute(self, opts, args):
+    
+        try:
+
+            # Initialize configuration
+            self.configParser.initialize(opts)
+        
+            # Need a way to check that all options needed are available
+            self.checkArgument()
+       
+            # Call the method in charge of retrieving upstream content
+            content = self.UrlContentRetrieve(self.configParser.getUpstreamURL())
+
+            # Search the strings matching the regexp passed through command line arguments
+            p = re.compile(self.configParser.getRegexp())
+            matches = p.findall(content)
+           
+            # Check if we have found some results
+            if len(matches) == 0:
+                raise NoUpstreamVersionFoundException(self.configParser.getUpstreamURL(), self.configParser.getRegexp())
+                print "No match found, we should trigger some error since even current version has not been found"
+                return False
+            else:
+                newestVersion = self.configParser.getCurrentVersion()
+                while len(matches) > 0:
+                    newestVersion = self.GetNewestVersion(newestVersion, matches.pop(0))
+
+            # At the end of the processing loop, test if we have a newer version avail, if yes output it
+            if newestVersion <>  self.configParser.getCurrentVersion():
+                print newestVersion
+    
+            # Exit after processing, eveythin gis ok, return true to the command processor
+            return True
+
+        except MissingArgumentException, (instance):
+
+            # Display a cool error message :)
+            print instance.parameter
+                
+            # Exits through exception handling, thus return false to the command processor
+            return False
+
+        except UpstreamProtocolNotSupportedException, (instance):
+
+            # Display a cool error message :)
+            print "Protocol " + instance.parameter + " is not supportd by upstream_watch"
+                
+            # Exits through exception handling, thus return false to the command processor
+            return False
+
+        except UpstreamUrlRetrievalFailedException, (instance):
+
+            # Exits through exception handling, thus return false to the command processor
+            return False
+
+        except NoUpstreamVersionFoundException, (instance):
+
+            # Exits through exception handling, thus return false to the command processor
+            return False
+
+
+# ---------------------------------------------------------------------------------------------------------------------
+#
+#
+class CommandProcessor(object):
+    """This class receive commands from the main loop and forward call to concrete command.
+    """
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def __init__(self):
+        """Initialize the objects in charge of concrete command processing. Each object instance are stored in a 
+        map using a key which is the action verb used on the command line.
+        """
+
+        # Defines the map storing the concrete commands
+        self.commandArray = {}
+
+        # Creates all the concrete commands
+        cmd = CheckUpstreamCommand("check-upstream")
+        self.commandArray[cmd.getName()] = cmd
+
+    # -----------------------------------------------------------------------------------------------------------------
+
+    def execute(self, opts, arguments):
+        """Cette methode teste les differents arguments et appelle la methode execute sur l'objet en charge du traitement
+        L'ordre de declaration des tests est important. En effet certains flags declanchent des actions, d'autres des options.
+        """
+
+        # Check that an action verb is supplied. If none an error is returned
+        if len(arguments) == 0:
+            print "Error : no action supplied"
+            return 1
+    
+        # The first element in the arguments array is the action verb. Retrieve the command
+        # using action verb as key
+        if self.commandArray.has_key(arguments[0]):
+            return self.commandArray[arguments[0]].execute(opts, arguments)
+        else:
+            print "Error : %(action)s action is not supported" % { 'action' : arguments[0] }
+            return 2
+          
+# ---------------------------------------------------------------------------------------------------------------------
+#
+# Fonction principale
+#    
+def main():
+    """Program main loop. Process args and call concrete command action.
+    """
+
+    # Create the command processor object
+    commandProcessor = CommandProcessor()
+
+    # Parse command line arguments 
+    cliParser  = CommandLineParser()
+
+    # Call the command line parser
+    (opts, args) = cliParser.parse()
+ 
+    # Call the execute method on the command processor. This method is in charge to find the concrete command  
+    return commandProcessor.execute(opts, args)
+
+# On sort en rendant le code de retour de main
+if __name__ == '__main__':
+    sys.exit(main())
+


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.


More information about the devel mailing list