[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