Module::Metadata::Changes - Manage machine-readable Changes/CHANGES/Changelog.ini files



These examples use Changes/CHANGES and Changelog.ini in the 'current' directory.

The command line options (except for -h) correspond to the options documented under "Constructor and initialization", below.

        shell> -h
        shell> -c
        shell> -r 1.23
        shell>sudo -w > /var/www/Changelog.html
        shell>perl -MModule::Metadata::Changes -e 'Module::Metadata::Changes->new(convert => 1)->run'
        shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new->read->get_latest_version'
        shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new->read->report'
        shell>perl -MModule::Metadata::Changes -e 'print Module::Metadata::Changes->new(release=>"2.00")->read->report'

Module::Metadata::Changes ships with in the bin/ directory. It is installed along with the module.

Also, Module::Metadata::Changes uses Config::IniFiles to read and write Changelog.ini files.


With a script like this:

        #!/usr/bin/env perl

        use feature 'say';
        use strict;
        use warnings;

        use File::chdir; # For magic $CWD.

        use Module::Metadata::Changes;

        # ------------------------------------------------

        my($work) = "$ENV{HOME}/perl.modules";
        my($m)    = Module::Metadata::Changes -> new;

        opendir(INX, $work) || die "Can't opendir($work)";
        my(@name) = sort grep{! /^\.\.?$/} readdir INX;
        closedir INX;


        for my $name (@name)
                $CWD     = "$work/$name"; # Does a chdir.
                $version = $m -> read -> get_latest_version;
                $config  = $m -> config; # Must call read() before config().

                say $config -> val('Module', 'Name'), " V $version ", $config -> val("V $version", 'Date');

you can get a report of the latest version number, from Changelog.ini, for each module in your vast library.


Module::Metadata::Changes is a pure Perl module.

It allows you to convert old-style Changes/CHANGES files, and to read and write Changelog.ini files.

=head1 Distributions

This module is available as a Unix-style distro (*.tgz).

See for details.

See for help on unpacking and installing.

Constructor and initialization

new(...) returns an object of type Module::Metadata::Changes.

This is the class contructor.

Usage: Module::Metadata::Changes -> new().

This method takes a hash of options. There are no mandatory options.

Call new() as new(option_1 => value_1, option_2 => value_2, ...).

Available options:

o convert

This takes the value 0 or 1.

The default is 0.

If the value is 0, calling run() calls read() and report().

If the value is 1, calling run() calls writer(reader() ).

o inFileName

The default is 'Changes' (or, if absent, 'CHANGES') when calling reader(), and 'Changelog.ini' when calling read().

o outFileName

The default is 'Changelog.ini'.

o pathForHTML

This is path to the HTML::Template-style templates used by the 'table' and 'webPage' options.

The default is '/dev/shm/html/assets/templates/module/metadata/changes'.

o release

The default is ''.

If this option has a non-empty value, the value is assumed to be a release/version number.

In that case, reports (text, HTML) are restricted to only the given version.

The default ('') means reports contain all versions.

'release' was chosen, rather than 'version', in order to avoid a clash with 'verbose', since all options could then be abbreviated to 1 letter (when running

Also, a lot of other software uses -r to refer to release/version.

o table

This takes the value 0 or 1.

The default is 0.

This option is only used when report() is called.

If the value is 0, calling report() outputs a text report.

If the value is 1, calling report() outputs a HTML report.

By default, the HTML report will just be a HTML table.

However, if the 'webPage' option is 1, the HTML will be a complete web page.

o urlForCSS

The default is '/assets/css/module/metadata/changes/ini.css'.

This is only used if the 'webPage' option is 1.

o verbose

This takes the value 0 or 1.

The default is 0.

If the value is 1, write progress reports to STDERR.

o webPage

This takes the value 0 or 1.

The default is 0.

A value of 1 automatically sets 'table' to 1.

If the value is 0, the 'table' option outputs just a HTML table.

If the value is 1, the 'table' option outputs a complete web page.


o config()

Returns the Config::IniFiles object, from which you can extract all the data.

This method must be called after calling read().

See scripts/ for sample code.

The names of the sections, [Module] and [V 1.23], and the keys under each, are documented in the FAQ.

o errstr()

Returns the last error message, or ''.

o get_latest_release()

Returns an hash ref of details for the latest release.

Returns {} if there is no such release.

The hash keys are (most of) the reserved tokens, as discussed below in the FAQ.

Some reserved tokens, such as EOT, make no sense as hash keys.

o get_latest_version()

Returns the version number of the latest version.

Returns '' if there is no such version.

o parse_datetime()

Used by transform().

o parse_datetime_1()

Used by transform().

o parse_datetime_2()

Used by transform().

o read([$input_file_name])

This method reads the given file, using Config::IniFiles.

The $input_file_name is optional. It defaults to 'Changelog.ini'.

See config().

Return value: The object, for method chaining.

o reader([$input_file_name])

This method parses the given file, assuming it is format is the common-or-garden Changes/CHANGES style.

The $input_file_name is optional. It defaults to 'Changes' (or, if absent, 'CHANGES').

reader() calls module_name() to save the module name for use by other methods.

reader() calls transform().

Return value: An arrayref of hashrefs, i.e. the return value of transform().

This value is suitable for passing to writer().

o report()

Displays various items for one or all releases.

If the 'release' option to new() was not used, displays items for all releases.

If 'release' was used, restrict the report to just that release/version.

If either the 'table' or 'webPage' options to new() were used, output HTML by calling report_as_html().

If these latter 2 options were not used, output text.

HTML is escaped using HTML::Entities::Interpolate.

Output is to STDOUT.

Clearly, you should not use -v to get logging output when using text or HTML output.

o report_as_html()

Displays various items as HTML for one or all releases.

If the 'release' option to new() was not used, displays items for all releases.

If 'release' was used, restrict the report to just that release/version.

Warning: This method must be called via the report() method.

Output is to STDOUT.

o run()

Use the options passed to new() to determine what to do.

Calling new(convert => 1) and then run() will cause writer(reader() ) to be called.

If you do not set 'convert' to 1 (i.e. use 0 - the default), run() will call read() and report().

Return value: 0.

o transform(@line)

Transform the memory-based version of Changes/CHANGES into an arrayref of hashrefs, where each array element holds data for 1 version.

Must be called by reader().

The array is the text read in from Changes/CHANGES.

transform() stores the arrayref of hashrefs in $obj -> changes(), for use by writer().

Return value: The object, for method chaining.

o validate($file_name)

This method is used by read() to validate the contents of the file read in.

validate() does not read the file.

validate() calls die when a validation test fails.

The file name is just used for reporting.

Return value: The object, for method chaining.

o writer([$output_file_name])

This method writes the arrayref stored in $obj -> changes(), using Config::IniFiles, to the given file.

See transform().

The $output_file_name is optional. It defaults to 'Changelog.ini'.

Return value: The object, for method chaining.


o Are there any things I should look out for?
o Invalid dates

Invalid dates in Changes/CHANGES cannot be distinguished from comments. That means that if the output file is missing one or more versions, it is because of those invalid dates.

o Invalid day-of-week (dow)

If Changes/CHANGES includes the dow, it is not cross-checked with the date, so if the dow is wrong, you will not get an error generated.

o How do I display Changelog.ini?

See bin/ It outputs text or HTML.

o What is the format of Changelog.ini?

See also the next question.

See scripts/ for sample code.

Here is a sample:

        Changelog.Creator=Module::Metadata::Changes V 1.00
        Changelog.Parser=Config::IniFiles V 2.39

        [V 4.30]
        Comments= <<EOT
        * FIX: Patch POD for CGI::Session in various places, to emphasize even more that auto-flushing is
        unreliable, and that flush() should always be called explicitly before the program exits.
        The changes are a new section just after SYNOPSIS and DESCRIPTION, and the PODs for flush(),
        and delete(). See RT#17299 and RT#34668
        * NEW: Add t/new_with_undef.t and t/load_with_undef.t to explicitly demonstrate the effects of
        calling new() and load() with various types of undefined or fake parameters. See RT#34668

        [V 4.10]
        Comments= <<EOT
        * SECURITY: Hopefully this settles all of the problems with symlinks. Both the file
        and db_file drivers now use O_NOFOLLOW with open when the file should exist and
        O_EXCL|O_CREAT when creating the file. Tests added for symlinks. (Matt LeBlanc)
        * SECURITY: sqlite driver no longer attempts to use /tmp/sessions.sqlt when no
        Handle or DataSource is specified. This was a mistake from a security standpoint
        as anyone on the machine would then be able to create and therefore insert data
        into your sessions. (Matt LeBlanc)
        * NEW: name is now an instance method (RT#17979) (Matt LeBlanc)
o What are the reserved tokens in this format?

I am using tokens to refer to both things in [] such as Module, and things on the left hand side of the = signs, such as Date.

And yes, these tokens are case-sensitive.

Under the [Module] section, the tokens are:

o Changelog.Creator

sample: Changelog.Creator=Module::Metadata::Changes V 2.00

o Changelog.Parser

Sample: Changelog.Parser=Config::IniFiles V 2.66

o Name

Sample: Name=Manage::Module::Changes

Under each version (section), whose name is like [V 1.23], the token are as follows.

Config::IniFiles calls the V in [V 1.23] a Group Name.

o Comments

Sample: Comments=- Original version

o Date

The datetime of the release, in W3CDTF format.

Sample: Date=2008-05-02T15:15:45

I know the embedded 'T' makes this format a bit harder to read, but the idea is that such files will normally be processed by a program.

o Deploy.Action

The module author makes this recommendation to the end user.

This enables the end user to quickly grep the Changelog.ini, or the output of, for things like security fixes and API changes.

Run 'bin/ -h' for help.


        Deploy.Reason=(Security|Major bug fix)

        Deploy.Action=Upgrade with caution
        Deploy.Reason=(Major|Minor) API change/Development version

Alternately, the classic syslog tokens could perhaps be used:


I think the values for these 2 tokens (Deploy.*) should be kept terse, and the Comments section used for an expanded explanation, if necessary.

Omitting Deploy.Action simply means the module author leaves it up to the end user to read the comments and make up their own mind.

reader() called directly, or via -c (i.e. old format to ini format converter), inserts these 2 tokens if it sees the word /Security/i in the Comments. It is a crude but automatic warning to end users. The HTML output options (-t and -w) use red text via CSS to highlight these 2 tokens.

Of course security is best handled by the module author explicitly inserting a suitable note.

And, lastly, any such note is purely up to the judgement of the author, which means differences in opinion are inevitable.

o Deploy.Reason

The module author gives this reason for their recommended action.


Config::IniFiles uses EOT to terminate multi-line comments.

If transform() finds a line beginning with EOT, it jams a '-' in front of it.

o Why are there not more reserved tokens?

Various reasons:

o Any one person, or any group, can standardize on their own tokens

Obviously, it would help if they advertised their choice, firstly so as to get as many people as possible using the same tokens, and secondly to get agreement on the interpretation of those choices.

Truely, there is no point in any particular token if it is not given a consistent meaning.

o You can simply add your own to your Changelog.ini file

They will then live on as part of the file.

Special processing is normally only relevant when converting an old-style Changes/CHANGES file to a new-style Changelog.ini file.

However, if you think the new tokens are important enough to be displayed as part of the text and HTML format reports, let me know.

I have deliberately not included the Comments in reports since you can always just examine the Changelog.ini file itself for such items. But that too could be changed.

o Are single-line comments acceptable?

Sure. Here is one:

        Comments=* INTERNAL: No Changes since 4.20_1. Declaring stable.

The '*' is not special, it is just part of the comment.

o What is with the datetime format?

It is called W3CDTF format. See:

See also ISO8601 format:

o Why this file format?

Various reasons:

o [Module] allows for [Script], [Library], and so on.
o *.ini files are easy for beginners to comprehend
o Other formats were considered. I made a decision

There is no perfect format which will please everyone.

Various references, in no particular order:

o The module Config::IniFiles already existed, for reading and writing this format

Specifically, Config::IniFiles allows for here documents, which I use to hold the comments authors produce for most of their releases.

o What is the difference between release and version?

I am using release to refer not just to the version number, but also to all the notes relating to that version.

And by notes I mean everything in one section under the name [V $version].

o Will you switch to YAML or XML format?

YAML? No, never. It is targetted at other situations, and while it can be used for simple applications like this, it can't be hand-written by beginners.

And it's unreasonable to force people to write a simple program to write a simple YAML file.

XML? Nope. It is great in some situations, but too visually dense and slow to write for this one.

o What about adding Changed Requirements to the file?

No. That info will be in the changed Build.PL or Makefile.PL files.

It is a pointless burden to make the module author also add that to Changelog.ini.

o Who said you had the power to decide on this format?

No-one. But I do have the time and the inclination to maintain Module::Metadata::Changes indefinitely.

Also, I had a pressing need for a better way to manage metadata pertaining my own modules, for use in my database of modules.

One of the reports I produce from this database is visible here:

Ideally, there will come a time when all of your modules, if not the whole of CPAN, will have Changelog.ini files, so producing such a report will be easy, and hence will be that much more likely to happen.

o Why not use, say, Config::Tiny to process Changelog.ini files?

Because Config::Tiny contains this line, 's/\s\;\s.+$//g;', so it will mangle text containing English semi-colons.

Also, authors add comments per release, and most Config::* modules only handle lines of the type X=Y.

o How are the old Changes/CHANGES files parsed?

The first line is scanned looking for /X::Y/ or /X\.$/. And yes, it fails for modules which identify themselves like Fuse-PDF not at the end of the line.

Then lines looking something like /$a_version_number ... $a_datetime/ are searched for. This is deemed to be the start of information pertaining to a specific release.

Everything up to the next release, or EOF, is deemed to belong to the release just identified.

This means a line containing a version number without a date is not recognized as a new release, so that that line and the following comments are added to the 'current' release info.

For an example of this, process the Changes file from CGI::Session (t/Changes), and scan the output for '[4.00_01]', which you will see contains stuff for V 3.12, 3.8 and 3.x.

See above, under the list of reserved tokens, for how security advisories are inserted in the output stream.

o Is this conversion process perfect?

Well, no, actually, but it will be as good as I can make it.

For example, version numbers like '3.x' are turned into '3.'.

You will simply have to scrutinize (which means 'read carefully') the output of this conversion process.

If a Changes/CHANGES file is not handled by the current version, log a bug report on Request Tracker:

o How are datetimes in old-style files parsed?

Firstly try DateTime::Format::HTTP, and if that fails, try these steps:

o Strip 'st' from 1st, 'nd' from 2nd, etc
o Try DateTime::Format::Strptime
o If that fails, strip Monday, etc, and retry DateTime::Format::Strptime

I noticed some dates were invalid because the day of the week did not match the day of the month. So, I arbitrarily chop the day of the week, and retry.

Other date parsing modules are Date::Manip, Date::Parse and Regexp::Common::time.

o Why did you choose these 2 modules?

I had a look at a few Changes/CHANGES files, and these made sense.

If appropriate, other modules can be added to the algorithm.

See the discussion on this page (search for 'parse multiple formats'):

If things get more complicated, I will reconsider using DateTime::Format::Builder.

o What happens for 2 releases on the same day?

It depends whether or not the version numbers are different.

The Changes file for CGI::Session contains 2 references to version 4.06 :-(.

As long as the version numbers are different, the date does not actually matter.

o Will a new file format mean more work for those who maintain CPAN?

Yes, I am afraid so, unless they completely ignore me!

But I am hopeful this will lead to less work overall.

o Why did you not use the Template Toolkit for the HTML?

It is too complex for this tiny project.

o Where do I go for support?

Log a bug report on Request Tracker:

If it concerns failure to convert a specific Changes/CHANGES file, just provide the name of the module and the version number.

It would help - if the problem is failure to parse a specific datetime format - if you could advise me on a suitable DateTime::Format::* module to use.

Machine-Readable Change Log

The file Changes was converted into Changelog.ini by Module::Metadata::Changes.

Version Numbers

Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.



Email the author, or log a bug on RT:

See Also





Module::Metadata::Changes was written by Ron Savage <> in 2008.

Home page:


Australian copyright (c) 2008, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Perl License, a copy of which is available at: