diff -r 22486c9c7b15 -r 378360dbbdba releasing/cbrtools/perl/NotesCompiler.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/releasing/cbrtools/perl/NotesCompiler.pm Wed Jun 30 11:35:58 2010 +0800 @@ -0,0 +1,712 @@ +# Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Nokia Corporation - initial contribution. +# +# Contributors: +# +# Description: +# +# + +package NotesCompiler; + +use strict; +use CGI qw(-no_debug :standard start_ul); +use IniData; +use RelData; +use EnvDb; +use MrpData; +use IO::File; +use File::Basename; + + +# +# Constants. +# + +use constant NOTES_DIREXTENSION => '.RelNotes'; + +# +# Public. +# + +sub New { + my $pkg = shift; + my $self = {}; + bless $self, $pkg; + $self->{iniData} = shift; + $self->{comp} = shift; + $self->{ver} = shift; + $self->{verbose} = shift; + $self->{htmlMainFile} = shift; + $self->{outputSTDOUTonly} = shift; + $self->{htmlNotes} = shift; # flag to render old notes as html or plain text + $self->{fh} = undef; # filehandle to write to + $self->{envDb} = EnvDb->Open($self->{iniData}, $self->{verbose}); + # Not using 'use constant' because that requires Utils::PrependEpocRoot to be called at compile-time + $self->{notes_store} = Utils::PrependEpocRoot('\\epoc32\\relinfo\\notes'); # constant + return $self; +} + +sub DoStandardNotes { + my $self = shift; + if( !defined ($self->{htmlMainFile} )) { + my $filename = $self->{comp}.".".$self->{ver}; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + $self->{htmlName} = $self->{notes_store} . "\\$filename.html"; + } + else { + $self->{htmlName} = $self->{htmlMainFile}; + } + + if(!defined ($self->{outputSTDOUTonly})){ + $self->WriteUnlessAlreadyCompiled(\&PrepareStandardNotes, undef, 1); # sub, filename, cache + } + else{ + $self->WriteUnlessAlreadyCompiled(\&PrepareStandardNotes, undef, 0); # sub, filename, cache + } +} + +sub DoCompSummary { + my $self = shift; + if( !defined ($self->{htmlMainFile} )) { + $self->{htmlName} = $self->{notes_store} . "\\$self->{comp}.summary.html"; + } + else { + $self->{htmlName} = $self->{htmlMainFile}; + } + my $relDataObjects = RelData->OpenSet($self->{iniData}, $self->{comp}, $self->{verbose}); + @$relDataObjects = grep { $self->PassesFilter($_) } @$relDataObjects; + foreach my $thisRelData (@$relDataObjects) { + my $ver = $thisRelData->Version(); + my $htmlName; + my $filename = $self->{comp}.".".$ver; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + if( !defined ($self->{htmlMainFile}) ) { + $htmlName = $self->{notes_store} . "\\$filename.html"; + } + else { + $htmlName = $self->{htmlMainFile} . NOTES_DIREXTENSION . "\\$filename.html"; + } + $self->WriteUnlessAlreadyCompiled(\&PrepareStandardNotes, $htmlName, 1, $thisRelData); # sub, filename, cache, @args + } + $self->WriteUnlessAlreadyCompiled(\&PrepareSummary, undef, 0, $relDataObjects, 1); # sub, filename, cache, @args + return $self; +} + +sub DoEnvSummary { + my $self = shift; + if( !defined ($self->{htmlMainFile} )) { + if ($self->{comp} and $self->{ver}) { + $self->{htmlName} = $self->{notes_store} . "\\$self->{comp}.$self->{ver}.summary.html"; + } + else { + $self->{htmlName} = $self->{notes_store} . "\\current_env_summary.html"; + } + } + else { + $self->{htmlName} = $self->{htmlMainFile}; + } + + my $versionInfo; + if ($self->{comp} and $self->{ver}) { + my $relData = RelData->Open($self->{iniData}, $self->{comp}, $self->{ver}, $self->{verbose}); + $versionInfo = $relData->Environment(); + } + else { + $versionInfo = $self->{envDb}->VersionInfo(); + } + + my @relData; + foreach my $thisComp (sort keys %$versionInfo) { + my $thisVer = $versionInfo->{$thisComp}; + (my $relData, my $preview) = $self->CreateRelData($thisComp, $thisVer); + next unless $self->PassesFilter($relData); + push (@relData, $relData); + my $htmlName; + my $filename = $thisComp.".".$thisVer; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + if( !defined ($self->{htmlMainFile} )) { + $htmlName = $self->{notes_store} . "\\$filename.html"; + } + else { + $htmlName = $self->{htmlMainFile} . NOTES_DIREXTENSION . "\\$filename.html"; + } + $self->WriteUnlessAlreadyCompiled(\&PrepareStandardNotes, $htmlName, 1, $relData, $preview); # sub, filename, cache, @args + } + $self->WriteUnlessAlreadyCompiled(\&PrepareSummary, undef, 0, \@relData); # sub, filename, cache, @args + return $self; +} + +sub DoDiffEnvSummary { + my $self = shift; + my $comp2 = shift; + my $ver2 = shift; + + require EnvDifferencer; + + my $comp1 = $self->{comp}; + my $ver1 = $self->{ver}; + $comp2 ||= $comp1; + $ver2 ||= $self->{envDb}->VersionInfo()->{$comp2}; + unless ($ver2) { + die "Error: $comp2 not installed in current environment\n"; + } + if( !defined ($self->{htmlMainFile} )) { + my $filename = $comp1.".".$ver1.".".$comp2.".".$ver2."-full"; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + $self->{htmlName} = $self->{notes_store} . "\\$filename.html"; + } + else { + $self->{htmlName} = $self->{htmlMainFile}; + } + if(!defined ($self->{outputSTDOUTonly})){ + $self->WriteUnlessAlreadyCompiled(\&PrepareDiffEnvReport, undef, 1, $comp2, $ver2); # sub, filename, cache, @args + } + else{ + $self->WriteUnlessAlreadyCompiled(\&PrepareDiffEnvReport, undef, 0, $comp2, $ver2); # sub, filename, cache, @args + } +} + +sub HtmlFileName { + my $self = shift; + return $self->{htmlName}; +} + +sub HtmlMainFile { + my $self = shift; + return $self->{htmlMainFile}; +} + +sub SetProjectFilter { + my $self = shift; + $self->{filter}->{project} = shift; +} + +sub SetVersionNumberFilter { + my $self = shift; + $self->{filter}->{versionregex} = shift; +} + +# +# Private. +# + +sub WriteUnlessAlreadyCompiled { + my $self = shift; + my $sub = shift; + my $filename = shift || $self->{htmlName}; + my $cache = shift; + my @args = @_; + + if ($cache) { + return if $self->NotesFileAlreadyCompiled($filename, $self->{comp}, $self->{ver}); + } + + my $output = $sub->($self, @args); + + if (!defined ($self->{outputSTDOUTonly})) { + my $fh = $self->OpenFileForWriting($filename); + print "FILE LOCATION: $filename\n" if ($self->{verbose}); + print $fh $output; + $fh = undef; # close file + } + else { + print $output; + } +} + +sub OpenFileForWriting { + my $self = shift; + my $filename = shift; + Utils::MakeDir(dirname($filename)); + return new IO::File($filename, "w") or die "Couldn't open file \"$filename\" for writing: $!"; +} + +sub PrepareDiffEnvReport { + my $self = shift; + my $endcomp = shift; + my $endver = shift; + my $startcomp = $self->{comp}; + my $startver = $self->{ver}; + + $self->{envDb} = EnvDb->Open($self->{iniData}, $self->{verbose}); + + my $envDifferencer = EnvDifferencer->New($self->{iniData}, $self->{verbose}); + $envDifferencer->SetStartCompVer($startcomp, $startver); + $envDifferencer->SetEndCompVer($endcomp, $endver); + + my @contentsrows; + my $bodies; + + my $changedcomps = $envDifferencer->ChangedComps(); + my $i=0; + foreach my $comp (sort @$changedcomps) { + $i++; # counter for debug output only + my $endReldata = $envDifferencer->EndReldata($comp); + my $intermediateReldatas = $envDifferencer->IntermediateReldatas($comp); + + my @allreldatas = (@$intermediateReldatas, $endReldata); + @allreldatas = grep { $self->PassesFilter($_) } @allreldatas; + print "Processing $comp ($i/".(scalar @$changedcomps)."): ".(scalar @allreldatas)." releases to process\n" if $self->{verbose}; + next unless @allreldatas; + + my @versions; + + $bodies .= hr . h2(a({name=>$comp},$comp)); + + my $firstver; + foreach my $reldata (sort { $b->ReleaseTime() <=> $a->ReleaseTime() } @allreldatas) { + my $ver = $reldata->Version(); + my $link = "$comp$ver"; + if(defined $self->{htmlMainFile}) + { + $link = $self->{htmlMainFile} . NOTES_DIREXTENSION . "/" . $link; + } + # First add an entry to our contents table + push @versions, td(a{href=>"#$link"}, $ver); + + # Now prepare the body itself + $bodies .= a({name=>$link}, h3($ver)); + $bodies .= ul($self->MainBody($reldata, 1)); # 1 = concise + } + push @contentsrows, Tr(th(a({href=>"#$comp"},$comp)), @versions); + } + + my $output = ""; + $output .= h1("Differences between $startcomp $startver and $endcomp $endver"); + $output .= h1("Contents"); + $output .= p("Newer releases are on the left."); + $output .= table({border=>1},@contentsrows); + + if(defined $bodies){ + $output .= $bodies; + } + + return $output; +} + +sub PrepareStandardNotes { + my $self = shift; + my $relData = shift; + my $preview; + my $comp; + my $ver; + if ($relData) { + $comp = $relData->Component(); + $ver = $relData->Version(); + } else { + $comp = $self->{comp}; + $ver = $self->{ver}; + ($relData, $preview) = $self->CreateRelData($comp, $ver); + } + my $output = ""; + + if ($self->{verbose}) { print "Compiling release notes for $comp $ver...\n"; } + + if ($preview) { + $output .= start_html({-title => "$comp $ver release notes PREVIEW"}) + .h1({-style=>'Color: red;'}, 'Release Notes Preview'). hr + .h1("$comp") + .hr; + } + else { + $output .= start_html({-title => "$comp $ver release notes"}) + .h1("$comp") + .hr; + } + + $output .= $self->MainBody($relData); + $output .= $self->EnvDetails($relData, $preview); + $output .= $self->SrcFilterErrors($relData, $preview); + + $output .= end_html(); + + return $output; +} + +sub PrepareSummary { + my $self = shift; + my $relDataObjects = shift; + my $compSummary = shift; + + my $output = ""; + + if ($compSummary) { + if ($self->{verbose}) { print "Writing component summary for $self->{comp}...\n"; } + $output .= (start_html({-title => "Release note summary for component $self->{comp}"}) + .h1("Release note summary for component $self->{comp}") + .hr); + } + else { + if ($self->{comp} and $self->{ver}) { + if ($self->{verbose}) { print "Writing environment summary for $self->{comp} $self->{ver}...\n"; } + $output .= (start_html({-title => "Release note summary for environment $self->{comp} $self->{ver}"}) + .h1("Release note summary for environment $self->{comp} $self->{ver}") + .hr); + } + else { + if ($self->{verbose}) { print "Writing environment summary for current environment...\n"; } + $output .= (start_html({-title => "Release note summary for the current environment"}) + .h1("Release note summary for the current environment") + .hr); + } + } + + foreach my $thisRelData (@$relDataObjects) { + my $thisVer = $thisRelData->Version(); + my $thisComp = $thisRelData->Component(); + my $thisIntVer = $thisRelData->InternalVersion(); + + my $link; + if ($compSummary) { + my $filename = $self->{comp}.".".$thisVer; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + $link = "$filename.html"; + } + else { + my $filename = $thisComp.".".$thisVer; + if ($self->{htmlNotes}) { + $filename.=".htmlnotes"; + } else { + $filename.=".textnotes"; + } + $link = "$filename.html"; + } + + if(defined $self->{htmlMainFile}) + { + $link = $self->{htmlMainFile} . NOTES_DIREXTENSION . "/" . $link; + } + + my $caption = $thisVer; + if ($thisIntVer) { + $caption .= " [$thisIntVer]"; + } + unless ($compSummary) { + $caption = "$thisComp $caption"; + } + my $notesSrc = $thisRelData->NotesSource(); + $output .= a({ -href => $link }, $caption). ' - '; + $output .= ("Made by $notesSrc->{releaser} on $notesSrc->{date}"); + $output .= (p("")); + } + + $output .= (end_html()); + return $output; +} + +sub CreateRelData { + my $self = shift; + my $comp = shift; + my $ver = shift; + + my $installedVer = $self->{envDb}->Version($comp); + my $preview = 0; + my $relData; + if (defined $installedVer) { + if ($ver eq $installedVer) { + if ($self->{envDb}->Status($comp) == EnvDb::STATUS_PENDING_RELEASE) { + # This release has not yet been made, so preview the notes. + $preview = 1; + my $intVer = $self->{envDb}->InternalVersion($comp); + unless (defined $intVer) { + $intVer = ' '; + } + my $mrpData = MrpData->New($self->{envDb}->MrpName($comp), + $ver, $intVer, $self->{iniData}, $self->{verbose}); + $relData = RelData->New($self->{iniData}, $mrpData, + Utils::PrependSourceRoot($mrpData->NotesSource()), $self->{envDb}->VersionInfo(), 'viewnotes', $self->{verbose}, 1); + } + } + } + unless (defined $relData) { + # This release has already been made, so we can read it's reldata. + $relData = RelData->Open($self->{iniData}, $comp, $ver, $self->{verbose}); + } + + return ($relData, $preview); +} + +sub MainBody { + my $self = shift; + my $relData = shift; + my $concise = shift; + + my $output = ""; + + if ($self->{verbose} > 1) { + print "Compiling release notes main body for ".$relData->Component()." ".$relData->Version()."...\n"; + } + + my $notesSrc = $relData->NotesSource(); + + my $release_version = $relData->MadeWithVersion(); + + foreach my $key (keys %{$notesSrc}) { + my $html_markers; + my $note = $notesSrc->{$key}; + if (ref $note eq 'ARRAY') { + $html_markers = $self->CheckHtmlMarkers(join("",@$note)); + } else { + $html_markers = $self->CheckHtmlMarkers($note); + } + if (!$html_markers) { + if (Utils::CompareVers($release_version, "2.83.1013") > 0) { + # 'Recent' release: escape html chars + $notesSrc->{$key} = $self->EscapeHtmlChars($note); + } else { + # Old release + if (!($self->{htmlNotes})) { + # User hasn't set html_notes: escape html chars + $notesSrc->{$key} = $self->EscapeHtmlChars($note); + } + } + } + } + + unless ($concise) { + my $comp = $relData->Component(); + my $ver = $relData->Version(); + my $intVer = $relData->InternalVersion(); + my $toolsver = $relData->MadeWith(); + my $sourcecode = $relData->SourceIncluded(); + my $mrpName = $relData->MrpName(); + my $project = $self->ComponentProject($comp, $ver); + my $envUserName = $relData->EnvUserName() || ""; + my $firstCompatibleVersion = $relData->FirstCompatibleVersion() || "<unknown>"; + my $zipsize; + eval { + $zipsize = $self->{envDb}->GetReleaseSize($relData->Component(), $relData->Version()); + }; + $zipsize ||= "-"; # for example, if we're pending release... + + $output .= table({-border=>0}, Tr({-align =>'left'}, + [td([b('Version'), $ver]), + td([b('Internal version'), ($intVer || "<none>")]), + td([b('Made by'), $notesSrc->{releaser}]), + td([b('Date'), $notesSrc->{date}]), + td([b('Made with'), $toolsver]), + td([b('Earliest compatible tools'), $firstCompatibleVersion]), + td([b('Source included'), tt($sourcecode)]), + td([b('Size of release zips'), tt($zipsize)]), + td([b('Project storage archive'), $project]), + td([b('MRP file used'), tt($mrpName)]), + td([b('Environment username'), $envUserName]) + ])); + } + + $output .= hr; + $output .= h2("Release Summary"); + $output .= h3("Reason for release"); + foreach my $line (@{$notesSrc->{releaseReason}}) { + $output .= tt($line). br; + } + $output .= h3("General release comments"); + foreach my $line (@{$notesSrc->{generalComments}}) { + $output .= tt($line). br; + } + $output .= h3("Known omissions, deviations and discrepancies"); + foreach my $line (@{$notesSrc->{knownDeviations}}) { + $output .= tt($line). br; + } + + @{$notesSrc->{bugsFixed}} = map tt($_), @{$notesSrc->{bugsFixed}}; + @{$notesSrc->{bugsRemaining}} = map tt($_), @{$notesSrc->{bugsRemaining}}; + @{$notesSrc->{otherChanges}} = map tt($_), @{$notesSrc->{otherChanges}}; + + $output .= hr; + $output .= h2("Bugs fixed"); + $output .= ul(li($notesSrc->{bugsFixed})); + $output .= hr; + $output .= h2("Known bugs remaining"); + $output .= ul(li($notesSrc->{bugsRemaining})); + $output .= hr; + $output .= h2("Other changes"); + $output .= ul(li($notesSrc->{otherChanges})); + $output .= hr; + + return $output; +} + +sub EscapeHtmlChars { + my $self = shift; + my $note = shift; + + my $newnote; + if (ref $note eq 'ARRAY') { + $newnote = []; + foreach my $line (@$note) { + my $newline = $line; + $newline =~ s/&/&/g; + $newline =~ s//>/g; + push @$newnote, $newline; + } + } else { + $newnote = $note; + $newnote =~ s/&/&/g; + $newnote =~ s//>/g; + } + return $newnote; +} + +sub CheckHtmlMarkers { + my $self = shift; + my $note = shift; + if ($note =~ /^\s*<\s*html\s*>.*<\s*[\/\\]\s*html\s*>\s*$/i) { + # Note begins with and ends with or something along those lines + return 1; + } else { + return 0; + } +} + +sub EnvDetails { + my $self = shift; + my $relData = shift; + my $preview = shift; + + my $contents = ""; + + if ($preview) { + $contents .= h2("Release environment"); + $contents .= span({-style=>'Color: red;'}, '[not yet known]'); + $contents .= br(); + } + else { + my $env = $relData->Environment(); + if (defined $env) { + $contents .= h2("Release environment"); + my $tableData; + $contents .= p("Number of components: ".(scalar keys %$env)); + foreach my $comp (sort keys %{$env}) { + push (@$tableData, td([b($comp), $env->{$comp}])); + } + $contents .= table({-border=>0}, Tr({-align =>'left'}, $tableData)); + } + } + return $contents; +} + +sub SrcFilterErrors { + my $self = shift; + my $relData = shift; + my $preview = shift; + my $notesSrc = $relData->NotesSource(); + + my $contents = ""; + + if (defined $notesSrc->{srcFilterErrors} and scalar(@{$notesSrc->{srcFilterErrors}}) > 0) { + $contents .= hr, h2("Source filter errors"); + foreach my $errorLine (@{$notesSrc->{srcFilterErrors}}) { + $contents .= $errorLine, br(); + } + } + return $contents; +} + +sub NotesFileAlreadyCompiled { + my $self = shift; + my $fileName = shift; + my $comp = shift; + my $ver = shift; + my $alreadyCompiled = 0; + + if(!(defined $self->{htmlMainFile})) { + if (-e $fileName && $comp && $ver) { + my $reldata = $self->{iniData}->PathData->LocalArchivePathForExistingOrNewComponent($comp, $ver) . '\\reldata'; + if (-e $reldata and Utils::FileModifiedTime($reldata) < Utils::FileModifiedTime($fileName)) { + $alreadyCompiled = 1; + } + } + } + return $alreadyCompiled; +} + +sub PassesFilter { + my $self = shift; + my $rd = shift; + my $comp = $rd->Component(); + my $ver = $rd->Version(); + if ($self->{filter}->{project}) { + return 0 unless $self->ComponentProject($comp, $ver) eq $self->{filter}->{project}; + } + if ($self->{filter}->{versionregex}) { + my $re = $self->{filter}->{versionregex}; + return 0 unless $ver =~ m/$re/i; + } + return 1; +} + +sub ComponentProject { + my $self = shift; + my $comp = shift; + my $ver = shift; + + return $self->{iniData}->PathData->ComponentProject($comp, $ver); +} + +1; + +__END__ + +=head1 NAME + +NotesCompiler.pm - Compiles a set of release notes into HTML. + +=head1 INTERFACE + +=head2 New + +Expects to be passed an C reference, a component name, a version, a verbosity level, an output HTML file name and an output STDOUT only flag. Creates a C object for the component release and uses the information contained within it to compile the output HTML file. + +=head1 KNOWN BUGS + +None. + +=head1 COPYRIGHT + + Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies). + All rights reserved. + This component and the accompanying materials are made available + under the terms of the License "Eclipse Public License v1.0" + which accompanies this distribution, and is available + at the URL "http://www.eclipse.org/legal/epl-v10.html". + + Initial Contributors: + Nokia Corporation - initial contribution. + + Contributors: + + Description: + + +=cut