SF.net SVN: gar:[23628] csw/mgar/gar/v2/bin/cswch
guengel at users.sourceforge.net
guengel at users.sourceforge.net
Sat May 17 14:34:45 CEST 2014
Revision: 23628
http://sourceforge.net/p/gar/code/23628
Author: guengel
Date: 2014-05-17 12:34:43 +0000 (Sat, 17 May 2014)
Log Message:
-----------
v2/bin/cswch:
* changelog specific exceptions now have a common base class
* added support for Dam style changelog files
* added new command 'refresh': update timestamps of latest changelog
entry
* complain if changelog file is not under version control (can be
disabled with --no-svn)
Modified Paths:
--------------
csw/mgar/gar/v2/bin/cswch
Modified: csw/mgar/gar/v2/bin/cswch
===================================================================
--- csw/mgar/gar/v2/bin/cswch 2014-05-17 10:50:08 UTC (rev 23627)
+++ csw/mgar/gar/v2/bin/cswch 2014-05-17 12:34:43 UTC (rev 23628)
@@ -50,16 +50,34 @@
class TestChangeLogHeader(unittest.TestCase):
"""Test ChangeLogHeader"""
- def test_changelogheader(self):
- """initialize changelog header with keywords"""
+ def test_deb_changelogheader(self):
+ """initialize debian changelog header with keywords"""
chlg = ChangeLogHeader(package='test', version='0.1', rev='1991.09.04')
+ self.assertEqual(ChangeLogHeader.STYLE_DEB, chlg.style)
self.assertEqual('test (0.1,REV=1991.09.04)', str(chlg))
- def test_parser(self):
- """initialize changelog header from line"""
+ def test_deb_parser(self):
+ """initialize debian changelog header from line"""
chlg = ChangeLogHeader(line='test (0.1,REV=1991.09.04)')
+ self.assertEqual(ChangeLogHeader.STYLE_DEB, chlg.style)
self.assertEqual('test (0.1,REV=1991.09.04)', str(chlg))
+ def test_dam_changelogheader(self):
+ """initialize dam changelog header with keywords"""
+ ts = '2014-01-01T00:01:02+0000'
+ package = 'testdam'
+ header_str = ChangeLogHeader.format_str_dam % {'package': package,
+ 'timestamp': ts}
+ chlg = ChangeLogHeader(package=package, timestamp=ts)
+ self.assertEqual(ChangeLogHeader.STYLE_DAM, chlg.style)
+ self.assertEqual(header_str, str(chlg))
+
+ def test_dam_parser(self):
+ """initialize dam changelog header from line"""
+ line = 'testdam (2014-01-01T00:01:02+0300)'
+ chlg = ChangeLogHeader(line=line)
+ self.assertEqual(ChangeLogHeader.STYLE_DAM, chlg.style)
+
def test_garbled(self):
"""test garbled changelog header"""
self.assertRaises(GarbledChangeLogHeader,
@@ -199,7 +217,13 @@
### C O D E ###
-class GarbledChangeLogHeader(Exception):
+class ChangeLogException(Exception):
+ """Exception Base class"""
+ def __init__(self):
+ super(ChangeLogException, self).__init__()
+
+
+class GarbledChangeLogHeader(ChangeLogException):
"""Garbled ChangeLog header
Raised if parsing of ChangeLog header fails.
@@ -216,7 +240,7 @@
return str(self)
-class GarbledChangeLogFooter(Exception):
+class GarbledChangeLogFooter(ChangeLogException):
"""Garbled ChangeLog footer
Raised if parsing of ChangeLog footer fails.
@@ -233,7 +257,7 @@
return str(self)
-class EmptyChangeLogFile(Exception):
+class EmptyChangeLogFile(ChangeLogException):
"""Empty ChangeLog File
Raised if operation expect existing entries in the ChangeLog File
@@ -251,6 +275,19 @@
return str(self)
+class OperationNotApplicable(ChangeLogException):
+ """Operation is not applicable to the given changelog file style"""
+ def __init__(self, style):
+ super(OperationNotApplicable, self).__init__()
+ self.style = style
+
+ def __str__(self):
+ return "Operation not applicable to %s style change logs" % (self.style,)
+
+ def __repr__(self):
+ return str(self)
+
+
class ChangeLogHeader(object):
"""Constitutes a changelog header.
@@ -277,21 +314,38 @@
self.parse_line(kwargs['line'])
return
- assert 'package' in kwargs and 'version' in kwargs
+ assert 'package' in kwargs
- self.package = kwargs['package']
- self.version = kwargs['version']
+ if 'version' in kwargs and kwargs['version'] is not None:
+ # Must be Debian style changelog
+ self.package = kwargs['package']
+ self.version = kwargs['version']
- if 'rev' not in kwargs:
- self.rev = ChangeLogHeader.opencsw_rev()
+ if 'rev' not in kwargs:
+ self.rev = ChangeLogHeader.opencsw_rev()
+ else:
+ self.rev = kwargs['rev']
+
+ self.style = ChangeLogHeader.STYLE_DEB
else:
- self.rev = kwargs['rev']
+ # dam style changelog
+ self.package = kwargs['package']
+ if 'timestamp' in kwargs:
+ self.timestamp = kwargs['timestamp']
+ else:
+ self.timestamp = ChangeLogHeader.get_rfc3339timestamp()
+ self.style = ChangeLogHeader.STYLE_DAM
+
+
def __str__(self):
- """Get the ChangeLog header"""
- return ChangeLogHeader.format_str % {'package': self.package,
- 'version': self.version,
- 'rev': self.rev}
+ """Get the ChangeLog header as string"""
+ if self.style == ChangeLogHeader.STYLE_DEB:
+ return ChangeLogHeader.format_str_deb % {'package': self.package,
+ 'version': self.version,
+ 'rev': self.rev}
+ return ChangeLogHeader.format_str_dam % {'package': self.package,
+ 'timestamp': self.timestamp}
def parse_line(self, line):
"""Parse ChangeLog header
@@ -299,16 +353,40 @@
Dissect a ChangeLog header into package, version, and
revision.
+ It will try to parse Debian style and Dam style header lines
+ and set self.style according to the style.
+
"""
- matches = ChangeLogHeader.compiled_re.match(line)
+ matches = ChangeLogHeader.compiled_re_deb.match(line)
if matches is None:
- raise GarbledChangeLogHeader(line)
+ # Now, try dam style
+ matches = ChangeLogHeader.compiled_re_dam.match(line)
+ if matches is None:
+ raise GarbledChangeLogHeader(line)
+ self.package = matches.group('package')
+ self.timestamp = matches.group('timestamp')
+ self.style = ChangeLogHeader.STYLE_DAM
+ ## Leave method!
+ return
+
+ # Debian style log
self.package = matches.group('package')
self.version = matches.group('version')
self.rev = matches.group('rev')
+ self.style = ChangeLogHeader.STYLE_DEB
+ def refresh_timestamp(self):
+ """Set timestamp to current date/time
+
+ Just a convenience method.
+ """
+ if self.style == ChangeLogHeader.STYLE_DEB:
+ self.rev = ChangeLogHeader.opencsw_rev()
+ else:
+ self.timestamp = ChangeLogHeader.get_rfc3339timestamp()
+
@classmethod
def opencsw_rev(cls, date=None):
"""Convert a date to as string suitable for REV=
@@ -321,11 +399,34 @@
return date.strftime('%Y.%m.%d')
- format_str = r'%(package)s (%(version)s,REV=%(rev)s)'
- parse_re = r'(?P<package>[\w-]+) \((?P<version>[\d\.]+),REV=(?P<rev>\d{4}\.\d{2}\.\d{2})\)'
- compiled_re = re.compile(parse_re)
+ @classmethod
+ def get_rfc3339timestamp(cls, date_obj=None):
+ """Return a timestamp in the format specified in RFC3339, section 5.6
+ :param date_obj: datetime object to be formatted. If None, the
+ current time will be taken.
+ :retval: datetime formatted as RFC3339 timestamp
+
+ """
+ if date_obj is None:
+ timezone = dateutil.tz.tzlocal()
+ date_obj = datetime.datetime.now(timezone)
+
+ return date_obj.strftime('%Y-%m-%dT%H:%M:%S%z')
+
+ format_str_deb = r'%(package)s (%(version)s,REV=%(rev)s)'
+ parse_re_deb = r'(?P<package>[\w-]+) \((?P<version>[\d\.]+),REV=(?P<rev>\d{4}\.\d{2}\.\d{2})\)'
+ compiled_re_deb = re.compile(parse_re_deb)
+
+ format_str_dam = r'%(package)s (%(timestamp)s)'
+ parse_re_dam = r'(?P<package>[\w-]+) \((?P<timestamp>[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}(?:\+|-)[\d]{4})\)'
+ compiled_re_dam = re.compile(parse_re_dam)
+
+ STYLE_DEB = 'debian'
+ STYLE_DAM = 'dam'
+
+
class ChangeLogFooter(object):
"""End a changelog entry.
@@ -357,7 +458,7 @@
self.timestamp = ChangeLogFooter.get_rfc2822timestamp()
def __str__(self):
- return ChangeLogFooter.format_str % {'maintainer': self.maintainer,
+ return ChangeLogFooter.format_str_deb % {'maintainer': self.maintainer,
'email': self.email,
'timestamp': self.timestamp}
@@ -368,7 +469,7 @@
timestamp.
"""
- matches = ChangeLogFooter.compiled_re.match(line)
+ matches = ChangeLogFooter.compiled_re_deb.match(line)
if matches is None:
raise GarbledChangeLogFooter(line)
@@ -377,6 +478,13 @@
self.email = matches.group('email')
self.timestamp = matches.group('timestamp')
+ def refresh_timestamp(self):
+ """Set timestamp to current date/time
+
+ Just a convenience method.
+ """
+ self.timestamp = ChangeLogFooter.get_rfc2822timestamp()
+
@classmethod
def get_rfc2822timestamp(cls, date_obj=None):
"""Return a timestamp in the proper format.
@@ -390,8 +498,7 @@
"""
if date_obj is None:
timezone = dateutil.tz.tzlocal()
- today = datetime.datetime.now(timezone)
- return email.utils.formatdate(time.mktime(today.timetuple()), True)
+ date_obj = datetime.datetime.now(timezone)
return email.utils.formatdate(time.mktime(date_obj.timetuple()), True)
@@ -411,9 +518,9 @@
return (fullname, m_email)
- format_str = r' -- %(maintainer)s <%(email)s> %(timestamp)s'
- parse_re = r' -- (?P<maintainer>[\w\d ]+) <(?P<email>[\w\d@ \.]+)> (?P<timestamp>\w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} (\+|-)\d{4})'
- compiled_re = re.compile(parse_re)
+ format_str_deb = r' -- %(maintainer)s <%(email)s> %(timestamp)s'
+ parse_re_deb = r' -- (?P<maintainer>[\w\d ]+) <(?P<email>[\w\d@ \.]+)> (?P<timestamp>\w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} (\+|-)\d{4})'
+ compiled_re_deb = re.compile(parse_re_deb)
class ChangeLogParagraph(object):
@@ -593,6 +700,12 @@
self.changelog_entries = list()
def read(self):
+ """Read the ChangeLogFile
+
+ Raises an exception 'ChangeLogFileEmpty()' if the file is
+ empty, or no ChangeLogEnties could be extracted.
+
+ """
changelog_entry = None
current_paragraph = None
with open(self.filename, "r") as fobj:
@@ -637,6 +750,9 @@
else:
current_paragraph.add_line(line)
+ if len(self.changelog_entries) < 1:
+ raise EmptyChangeLogFile(self.filename)
+
def save(self):
"""Save ChangeLog entries to file"""
with open(self.filename, 'w') as fobj:
@@ -644,11 +760,20 @@
self.changelog_entries]
def versions(self):
- """Return a list of all versions in the ChangeLog file"""
+ """Return a list of all versions in the ChangeLog file
- if (len(self.changelog_entries) < 1):
+ When called on a Dam style changelog file, it throws an
+ exception, since Dam style changelog files do not have a
+ version.
+
+ """
+
+ if len(self.changelog_entries) < 1:
return None
+ if self.changelog_entries[0].header.style == ChangeLogHeader.STYLE_DAM:
+ raise OperationNotApplicable(ChangeLogHeader.STYLE_DAM)
+
return [changelog_entry.header.version for changelog_entry in
self.changelog_entries]
@@ -662,9 +787,11 @@
Package name is returned as defined by the first entry of the
ChangeLog file.
+ None is returned if changelog file is empty.
+
"""
if len(self.changelog_entries) < 1:
- return ""
+ return None
return self.changelog_entries[0].header.package
@@ -718,12 +845,18 @@
else:
self.changelog_entries[0].add_paragraph(paragraph, True)
- latest_header = self.changelog_entries[0].header
- latest_header.rev = ChangeLogHeader.opencsw_rev()
- self.changelog_entries[0].add_header(latest_header)
- self.changelog_entries[0].add_footer(ChangeLogFooter())
+ self.refresh_timestamps()
+ def refresh_timestamps(self):
+ """Set the timestamps of the latest entry to the current date
+ Timestamps of ChangeLogHeader and ChangeLogFooter will updated.
+ """
+ assert len(self.changelog_entries) > 0
+
+ self.changelog_entries[0].header.refresh_timestamp()
+ self.changelog_entries[0].footer.refresh_timestamp()
+
def svn_add(self):
"""Add file to svn repository
@@ -731,8 +864,6 @@
directory. Else it does nothing.
"""
- assert len(self.changelog_entries) > 0
-
# First of all, make sure the file exists
if not os.path.exists(self.filename):
raise OSError((errno.ENOENT, 'No such file: %s' % self.filename))
@@ -756,19 +887,59 @@
svnclient.add([self.filename])
logging.info("Scheduled file '%s' for addition to SVN repository", self.filename)
+ def in_svn(self):
+ """Checks if ChangeLogFile is added to SVN working directory.
+ :returns: True if the file is added to the SVN working
+ directory, else False.
+
+ """
+ # First of all, make sure the file exists
+ if not os.path.exists(self.filename):
+ raise OSError((errno.ENOENT, 'No such file: %s' % self.filename))
+
+ svnclient = pysvn.Client()
+
+ try:
+ svnclient.info(self.filename)
+ except pysvn.ClientError:
+ return False
+
+ return True
+
+ def changelog_style(self):
+ """Get the type of the changelog
+
+ Determines the style of the changelog by reading the first line.
+ """
+ with open(self.filename, "r") as changelog_file:
+ # read and parse the first line
+ changelog_header = ChangeLogHeader(line=changelog_file.readline())
+ return changelog_header.style
+
+
def new_changelog_entry(filename, version, log):
+ """Create a new change log entry
+
+ :param version: has to be None for Dam style log files.
+ """
changelog_file = ChangeLogFile(filename)
+
+ style = changelog_file.changelog_style()
+ if style == ChangeLogHeader.STYLE_DAM and\
+ version is not None:
+ logging.error("Changelog '%s' is Dam style. Version does not apply.",
+ filename)
+ sys.exit(1)
+
changelog_file.read()
- if changelog_file.versions() is None:
- raise EmptyChangeLogFile(filename)
-
# get the package from the first entry
package = changelog_file.get_package()
header = ChangeLogHeader(package=package,
version=version)
+
paragraphs = [ChangeLogParagraph(log_para) for log_para in log]
footer = ChangeLogFooter()
@@ -776,19 +947,27 @@
changelog_file.save()
def update_changelog_entry(filename, log):
+
changelog_file = ChangeLogFile(filename)
changelog_file.read()
- if changelog_file.versions() is None:
- raise EmptyChangeLogFile(filename)
-
[changelog_file.update_latest(ChangeLogParagraph(l)) for l in
log]
changelog_file.save()
-def create_changelog_file(filename, overwrite, package, version, log, register_svn):
- header = ChangeLogHeader(package=package,
- version=version)
+def refresh_changelog(filename):
+ changelog_file = ChangeLogFile(filename)
+ changelog_file.read()
+
+ changelog_file.refresh_timestamps()
+
+ changelog_file.save()
+
+def create_changelog_file(filename, style, overwrite, package,
+ version, log, register_svn):
+ # For Dam style changelogs, version is expected to be None
+ header = ChangeLogHeader(package=package, version=version)
+
footer = ChangeLogFooter()
paragraphs = [ChangeLogParagraph(log_para) for log_para in log]
@@ -811,6 +990,33 @@
else:
print(changelog_file.versions()[0])
+def compile_log(logitem, logparagraph):
+ """Create a list of strings having the proper format to be fed to
+ ChangeLogParagraph
+
+ Returns a list of strings that is suitable for feeding to
+ ChangeLogParagraph, i.e. item style entries are prepended with ' *
+ ' and plain paragraphs are left alone.
+
+ """
+ log = list()
+ if logitem:
+ log = [' * ' + item for item in logitem]
+
+ if logparagraph:
+ log.extend(logparagraph)
+
+ return log
+
+def cond_complain_svn(filename, complain):
+ """Conditionally complain about changelog file not registered in SVN
+
+ :param complain: if True and file is not in SVN, complain. Else do nothin.
+ """
+ changelogfile = ChangeLogFile(filename)
+ if complain and not changelogfile.in_svn():
+ logging.warn("%s is not under version control", changelogfile.filename)
+
def cmdline_parse():
"""Parse the command line
@@ -821,15 +1027,28 @@
parser.add_argument('--logfile',
help='filename to use (default: %(default)s)',
default='files/changelog.CSW')
+ parser.add_argument('--no-svn', help="Do not add file to SVN repository or stop complaining about file not registerd with svn.",
+ default=False, action='store_const',
+ const=True)
subparser = parser.add_subparsers(help='sub-command help',
dest='command')
parser_create = subparser.add_parser('create',
help="create new changelog.CSW file. Existing file will not be overwritten.")
+ group = parser_create.add_mutually_exclusive_group()
+ group.add_argument('--deb-style',
+ help='Create a Debian style changelog',
+ action='store_const',
+ dest='style', const=ChangeLogHeader.STYLE_DEB)
+ group.add_argument('--dam-style',
+ help='Create a Dam style changelog',
+ action='store_const',
+ dest='style', const=ChangeLogHeader.STYLE_DAM)
parser_create.add_argument('package',
help="package name")
- parser_create.add_argument('version',
- help="package version")
+ parser_create.add_argument('--version',
+ help="package version (only required for Debian style changelog)",
+ required=False)
parser_create.add_argument('--log-item',
help="the log entry to be recorded as list item",
nargs='*')
@@ -839,14 +1058,12 @@
parser_create.add_argument('--force', help="force creation of file. Overwrite existing file.",
default=False, action='store_const',
const=True)
- parser_create.add_argument('--no-svn', help="Do not add file to SVN repository.",
- default=False, action='store_const',
- const=True)
parser_new = subparser.add_parser('new',
help="new changelog entry")
- parser_new.add_argument('version',
- help="new version")
+ parser_new.add_argument('--version',
+ help="new version (only required for Debian style changelog)",
+ required=False)
parser_new.add_argument('--log-item',
help="the log entry to be recorded as list item",
nargs='*')
@@ -864,6 +1081,9 @@
help="the log entry to be recorded as plain paragraph",
nargs='*')
+ parser_refresh = subparser.add_parser('refresh',
+ help="refresh header and footer of latest changelog entry")
+
parser_version = subparser.add_parser('version',
help="retrieve version from changelog.CSW")
parser_version.add_argument('--all-versions',
@@ -880,19 +1100,14 @@
arguments.log_item is None:
parser.error("'%s' requires either '--log-paragraph' or '--log-item'" % arguments.command)
+ if arguments.command == "create" and\
+ arguments.style == ChangeLogHeader.STYLE_DAM and\
+ arguments.version is not None:
+ parser.error("Dam style changelogs do not take version")
+
return arguments
-def compile_log(logitem, logparagraph):
- log = list()
- if logitem:
- log = [' * ' + item for item in logitem]
- if logparagraph:
- log.extend(logparagraph)
-
- return log
-
-
if __name__ == '__main__':
arguments = cmdline_parse()
@@ -902,24 +1117,34 @@
del sys.argv[1:]
unittest.main()
- if arguments.command == "create":
- log = compile_log(arguments.log_item, arguments.log_paragraph)
- create_changelog_file(arguments.logfile,
- arguments.force,
- arguments.package,
- arguments.version,
- log,
- not arguments.no_svn)
+ try:
+ if arguments.command == "create":
+ log = compile_log(arguments.log_item, arguments.log_paragraph)
+ create_changelog_file(arguments.logfile,
+ arguments.style,
+ arguments.force,
+ arguments.package,
+ arguments.version,
+ log,
+ not arguments.no_svn)
- if arguments.command == "new":
- log = compile_log(arguments.log_item, arguments.log_paragraph)
- new_changelog_entry(arguments.logfile,
- arguments.version,
- log)
+ if arguments.command == "new":
+ log = compile_log(arguments.log_item, arguments.log_paragraph)
+ new_changelog_entry(arguments.logfile,
+ arguments.version,
+ log)
+ cond_complain_svn(arguments.logfile, not arguments.no_svn)
- if arguments.command == "update":
- log = compile_log(arguments.log_item, arguments.log_paragraph)
- update_changelog_entry(arguments.logfile, log)
+ if arguments.command == "update":
+ log = compile_log(arguments.log_item, arguments.log_paragraph)
+ update_changelog_entry(arguments.logfile, log)
+ cond_complain_svn(arguments.logfile, not arguments.no_svn)
- if arguments.command == "version":
- get_version(arguments.logfile, arguments.all_versions)
+ if arguments.command == "refresh":
+ refresh_changelog(arguments.logfile)
+ cond_complain_svn(arguments.logfile, not arguments.no_svn)
+
+ if arguments.command == "version":
+ get_version(arguments.logfile, arguments.all_versions)
+ except ChangeLogException as ex:
+ logging.error(str(ex))
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