1#! perl
2
3# Copyright (C) 2012, Parrot Foundation.
4
5=head1 NAME
6
7tools/release/parrot_github_release.pl - automates the 'parrot.github.com'
8release process
9
10=head1 SYNOPSIS
11
12    $ perl tools/release/parrot_github_release.pl [OPTIONS]
13
14=head1 DESCRIPTION
15
16This script automates the packaging of the 'parrot.github.com' repository and
17the archiving of the previous documentation release into the appropriate
18'parrot-docsx' repository.
19
20In short, this script automates Section X of the Release Manager Guide
21(F<docs/project/release_manager_guide.pod>) as outlined in the Release Parrot
22Github Guide (F<docs/project/release_parrot_github_guide.pod>).
23
24=head1 OPTIONS
25
26=over 4
27
28=item B<--docs>=[/path_to/previous/docs/]
29
30The path to the directory which contains the previous documentation release.
31Specifically, the 'docs/' directory of the previous release of parrot.
32
33=item B<-h>, B<--help>
34
35Displays this help message and exits.
36
37=item B<-v>, B<--version>
38
39Displays the version and copyright information and exits.
40
41=back
42
43=head1 LIMITATIONS
44
451. As written, this script will execute only on *nix (and related) systems.
46
472. You must execute this script from the parrot root directory, I<i.e.>,
48'./parrot'; otherwise, it will fail.
49
50=head1 NOTES
51
521. This script assumes you are the Release Manager, working on Section X of
53the Release Manger Guide, and have, therefore, already cut the new Parrot
54release.
55
562. You must use a fully qualified path for the '--docs' option.
57
58For example, if the path to the previous version of the documentation is
59contained in F</home/user/git-work/parrot/docs/'>, you I<must> specify the
60complete path to the 'docs/' directory and may not use "shell expansion" as
61the name of your home directory, I<i.e.,> you may not use
62C<~/git-work/parrot/docs/>. To do otherwise means the script will fail.
63
64=head1 HISTORY
65
66* [2012-03-21] Initial version written by Alvis Yardley <ac.yardley@gmail.com>
67
68* [2012-07-13] Made the script more robust Alvis Yardley <ac.yardley@gmail.com>
69
70=head1 SEE ALSO
71
72F<docs/project/release_manager_guide.pod>
73
74F<docs/project/release_parrot_github_guide.pod>
75
76=cut
77
78use strict;
79use warnings;
80
81use Getopt::Long;
82use Pod::Usage;
83use System::Command;
84use lib qw( ./lib );
85use Parrot::Config;
86use Cwd;
87
88# Switches
89my $docs;             # Path to the previous docs release
90my $help;             # Displays help message
91my $version;          # Displays version and copyright information
92
93my $result = GetOptions('docs=s'    => \$docs,
94                        'h|help'    => \$help,
95                        'v|version' => \$version);
96
97my $repos;            # Path to where to store, temporarily, the repositories
98
99# Catch unrecognized switches
100pod2usage() unless $result;
101
102# Display help message if '-h' was given
103pod2usage(0) if $help;
104
105# Display version and copyright information if '-v' was given
106version() && exit(0) if $version;
107
108# Get temporary directory defined in 'Parrot::Config::Generated.pm'
109get_repo_directory();
110
111# Get 'docs/' directory if not supplied
112get_docs_directory() unless $docs;
113
114# Test 'docs/' directory to ensure it's a valid 'docs/' directory.
115tst_docs_directory();
116
117# Get VERSION
118open my $FH, '<', 'VERSION' or stop("Unable to open 'VERSION' file");
119chomp($version = <$FH>);
120close $FH;
121
122# Parse version number
123my ($major, $minor, $patch); # Quiet perlcritic
124($major, $minor, $patch) = ($1, $2, $3) if $version =~ /^(\d+)\.(\d+)\.(\d+)$/;
125stop("There is some problem with the major or the minor release numbers")
126  unless $major and $minor;
127
128# Set to the previous release version
129if ($minor == 0) {
130    $minor = 11;
131    $major -= 1;
132}
133else {
134    $minor -= 1;
135}
136
137# Get the current working directory
138my $parrot_dir = getcwd();
139
140# Release process
141get_parrot_github();
142get_parrot_docsx();
143archive_parrot_docsx();
144update_parrot_github();
145delete_repos();
146exit(0);
147
148##########################
149# Subroutine definitions #
150##########################
151
152# Get the temporary directory, contained in '%PConfig', in which to clone
153# the repos
154sub get_repo_directory {
155    $repos = $PConfig{tempdir};
156    if (!defined $repos) {
157        print "\'\$PConfig{\'tempdir\'}\' is undefined. This variable must ",
158          "be defined and defined with a readable and a writeable directory ",
159            "to execute, successfuly, this script.\n";
160        print "Did you, perhaps, fail to configure parrot?\n";
161        exit(1);
162    }
163
164    $repos .= '/';
165
166    # Test '$repo' directory to ensure we can read and write to it.
167    my $tstfile   = $repos . 'parrot_github_release.out';
168    my $outstring = "A simple test string: parrot_github_release.out";
169    open my $OUT, '+>', $tstfile or
170      stop("Unable to open file for output in $repos directory.");
171    print $OUT $outstring;
172    close $OUT or stop("Unable to close file in $repos directory");
173
174    open my $IN, '<', $tstfile or
175      stop("Unable to open file for input in $repos directory.");
176    my $instring = <$IN>;
177    close $IN or stop("Unable to close $tstfile");
178    stop("Unable to read and to write to $repos directory")
179      unless $instring eq $outstring;
180
181    unlink $tstfile or warn "Unable to delete $tstfile: $!";
182}
183
184# Get 'docs/' directory
185sub get_docs_directory {
186    while (1) {
187        print "Please specify the path to the previous documentation release? ";
188        $docs = <>;
189        chomp $docs;
190
191        last if -d $docs;
192    }
193
194    $docs .= '/' if $docs =~ /[a-zA-Z0-9]$/;
195}
196
197# Test whether or not we actually have a valid 'docs/' directory.
198sub tst_docs_directory {
199    my $parrot_dir = getcwd();
200    my $filename   = 'parrothist.pod'; # This one's likely to stick around.
201
202    $docs .= '/' if $docs =~ /[a-zA-Z0-9]$/;
203    chdir $docs;
204
205    stop("Unable to access the $docs directory")
206      unless (-f $filename && -s $filename);
207
208    chdir $parrot_dir;
209}
210
211# Clone a local copy of 'parrot.github.com'
212sub get_parrot_github {
213    chdir $repos;
214
215    print "\n== CLONING 'PARROT.GITHUB.COM' ==\n";
216    system('git', 'clone', 'git@github.com:parrot/parrot.github.com.git') == 0
217      or stop("Unable to clone 'parrot.github.com'");
218
219    chdir $parrot_dir;
220}
221
222# Clone a local copy of 'parrot-docsx'
223sub get_parrot_docsx {
224    my $parrot_docsx = 'git@github.com:parrot/parrot-docs' . $major . '.git';
225    chdir $repos;
226
227    print "\n== CLONING 'PARROT-DOCSX' ==\n";
228    system('git', 'clone', $parrot_docsx) == 0 or
229      stop("Unable to clone the appropriate 'parrot-docsx' repo.");
230
231    chdir $parrot_dir;
232}
233
234# Archive the previous documentation release to the 'parrot-docsx' repository
235sub archive_parrot_docsx {
236    my $parrot_docsx = $repos . 'parrot-docs' . $major . '/';
237    chdir $parrot_docsx;
238
239    print "\n== CHECKING OUT GH-PAGES BRANCH ==\n";
240    system('git', 'checkout', 'gh-pages') == 0 or
241      stop("Unable to switch to the 'gh-pages' branch");
242
243    my $previous  = $major . '.' . $minor . '.' . $patch;
244    my $copy_to   = $parrot_docsx . $previous . '/';
245    my $copy_from = $docs . '*';
246
247    print "\n== MAKING NEW DIRECTORY IN 'PARROT-DOCSX'  ==\n";
248    mkdir($copy_to) or stop("Unable to make new directory in 'parrot-docsx'");
249
250    print "\n== COPYING DOCS TO 'PARROT-DOCSX' ==\n";
251    # Use shell globbing, for convenience.  (Should I rewrite this?)
252    system("cp -r --target-directory=$copy_to $copy_from") == 0 or
253      stop("Unable to copy 'docs/' to 'parrot-docsx'");
254
255    print "\n== GIT ADD ('PARROT-DOCSX') ==\n";
256    system('git', 'add', '.') == 0 or
257      stop("Unable to add to 'parrot-docsx'");
258
259    print "\n== GIT COMMIT ('PARROT-DOCSX') ==\n";
260    system('git', 'commit', '-m', "'Archiving documentation release'") == 0 or
261      stop("Unable to commit to 'parrot-docsx'");
262
263    print "\n== CHECKING OUT MASTER ==\n";
264    system('git', 'checkout', 'master') == 0 or
265      stop("Unable to switch to 'master'");
266
267    print "\n== PUSHING 'PARROT-DOCSX' ==\n";
268    system('git', 'push', 'origin', 'gh-pages') == 0 or
269      stop("Unable to push updates to 'parrot-docsx'");
270
271    chdir $parrot_dir;
272}
273
274# Update parrot.github.com with present release docs
275sub update_parrot_github {
276    my $parrot_github = $repos . 'parrot.github.com' . '/';
277    chdir $parrot_github;
278
279    my $tmp = $PConfig{tempdir};
280
281    print "\n== SAVING KEY 'PARROT.GITHUB.COM' FILES ==\n";
282    system('cp', "--target-directory=$tmp", 'README.md') == 0 or
283      stop("Unable to save 'README.md'");
284    system('cp', "--target-directory=$tmp", 'index.html') == 0 or
285      stop("Unable to save 'index.html'");
286    system('cp', "--target-directory=$tmp", 'releases.html') == 0 or
287      stop("Unable to save 'releases.html'");
288
289    print "\n== GIT RM ('PARROT.GITHUB.COM') ==\n";
290    system('git', 'rm', '-rf', '*') == 0 or
291      stop("Unable to remove files from 'parrot.github.com'");
292
293    print "\n== GIT ADD ('PARROT.GITHUB.COM') ==\n";
294    system('git', 'add', '-A') == 0 or
295      stop("Unable to add to 'parrot.github.com'");
296
297    print "\n== GIT COMMIT ('PARROT.GITHUB.COM') ==\n";
298    system('git', 'commit', '-m', "'Removed files from 'parrot.github.com'") == 0 or
299      stop("Unable to commit to 'parrot.github.com'");
300
301    print "\n== RESTORING KEY 'PARROT.GITHUB.COM' FILES ==\n";
302    system('cp', "$tmp/README.md", '.') == 0 or
303      stop("Unable to restore 'README.md'");
304    system('cp', "$tmp/index.html", '.') == 0 or
305      stop("Unable to restore 'index.html'");
306     system('cp', "$tmp/releases.html", '.') == 0 or
307      stop("Unable to restore 'releases.html'");
308
309    update_index_html();
310    update_releases_html();
311
312    my $parrot_docs = $parrot_dir . '/' . 'docs/*';
313    print "\n== COPYING 'DOCS/' TO 'PARROT.GITHUB.COM' ==\n";
314    # Here I am, relying on the shell, again. <sigh>
315    system("cp -r $parrot_docs .") == 0 or
316      stop("Unable to copy 'docs/' to 'parrot.github.com'");
317
318    print "\n== GIT ADD ('PARROT.GITHUB.COM') ==\n";
319    system('git', 'add', '.') == 0 or
320      stop("Unable to add to 'parrot.github.com'");
321
322    print "\n== GIT COMMIT ('PARROT.GITHUB.COM') ==\n";
323    system('git', 'commit', '-m', "'Updated 'parrot.github.com'") == 0 or
324      stop("Unable to commit to 'parrot.github.com'");
325
326    print "\n== PUSHING ('PARROT.GITHUB.COM') ==\n";
327    system('git', 'push', 'origin', 'master') == 0 or
328      stop("Unable to push updates to 'parrot.github.com' master");
329
330    chdir $parrot_dir;
331}
332
333# Update the link to 'Previous Parrot Documentation Releases' in 'index.html'
334sub update_index_html {
335    my $buffer = '';
336    open my $FH, '+<', 'index.html' or stop("Unable to open 'index.html'");
337    while (<$FH>) {
338        s/$1/$version/ if /Parrot ($major\.$minor\.$patch)-devel - Home/;
339        s/$1/$version/ if /Parrot version ($major\.$minor\.$patch)-devel/;
340        s/$1/$major\.$minor\.$patch/
341          if /Previous Parrot Documentation Releases \((\d.\d.\d) - 0.1.1\)/;
342        $buffer .= $_;
343    }
344    seek($FH, 0, 0)          or stop("Unable to seek start of 'index.html'");
345    print $FH $buffer        or stop("Unable to print out 'index.html'");
346    truncate($FH, tell($FH)) or stop("Unable to truncate 'index.html'");
347    close $FH                or stop("Unable to close 'index.html'");
348}
349
350# Update 'releases.html' to point to the newly archived documents in
351# 'parrot-docsx'
352sub update_releases_html {
353    my $buffer = '';
354    my $ul = '<ul>';
355    my $li = '<li><a href="./parrot-docs' . $major . '/' . $major . '.' .
356      $minor . '.' . $patch . '/html/index.html">Release ' . $major . '.' .
357      $minor . '.' . $patch . '</a></li>' . "\n";
358
359    open my $FH, '+<', 'releases.html' or stop("Unable to open 'releases.html'");
360    while (<$FH>) {
361        s/$1/$version/ if /Parrot ($major\.$minor\.$patch)-devel - Home/;
362        s/$1/$version/ if /Parrot version ($major\.$minor\.$patch)-devel/;
363        if (/<ul>/) {
364            $buffer .= $_;
365            $buffer .= $li;
366            next; # So we don't duplicate the '<ul>' tag.
367        }
368        $buffer .= $_;
369    }
370    seek($FH, 0, 0)          or stop("Unable to seek to start of 'releases.html'");
371    print $FH $buffer        or stop("Unable to print out 'releases.html'");
372    truncate($FH, tell($FH)) or stop("Unable to truncate 'releases.html'");
373    close $FH                or stop("Unable to close 'releases.html'");
374}
375
376# Delete the downloaded repositories
377sub delete_repos {
378    my $parrot_docsx = $repos . 'parrot-docs' . $major . '/';
379    my $parrot_github = $repos . 'parrot.github.com' . '/';
380
381    print "\n== DELETING PARROT-DOCSX ==\n";
382    system('rm', '-rf', $parrot_docsx) == 0 or
383      stop("Unable to delete the 'parrot-docsx' repo");
384
385    print "\n== DELETING PARROT.GITHUB.COM ==\n";
386    system('rm', '-rf', $parrot_github) == 0 or
387      stop("Unable to delete the 'parrot.github.com' repo");
388}
389
390# Customized version of die() for more consistent diagnostics
391sub stop {
392    if (@_ == 0) {
393        print '[FATAL ERROR] ';
394
395        if ($!) {
396            print $!;
397        }
398        else {
399            print 'An unrecoverable error has occurred';
400        }
401    }
402    else {
403        my $msg = shift;
404        print "[ERROR] $msg";
405
406        if ($!) {
407            print ": $!";
408        }
409    }
410
411    print "!\n";
412
413    exit 1;
414}
415
416sub version {
417    (my $version = <<VERSION_END) =~ s/^\S+//gm;
418  This is parrot_github_release.pl, v1.0
419
420  Copyright (C) 2001-2012, Parrot Foundation.
421
422VERSION_END
423    print "\n$version";
424}
425
426###################
427# Signal handlers #
428###################
429
430$SIG{'INT'} = sub { stop('SIGINT received. Stopping') };
431
432# Local Variables:
433#   mode: cperl
434#   cperl-indent-level: 4
435#   fill-column: 100
436# End:
437# vim: expandtab shiftwidth=4:
438