From ea33b6b4691ce6030349ce998517658582ec6c01 Mon Sep 17 00:00:00 2001 From: Peter Donald Date: Sun, 24 Mar 2002 02:04:23 +0000 Subject: [PATCH] Integrate a changelog task that generates an XML changelog (useful for doing reports on codebase) git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@272005 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/antlib/cvslib/ChangeLog.java | 480 ++++++++++++++++++ .../apache/antlib/cvslib/Resources.properties | 5 + proposal/myrmidon/src/make/sample.ant | 6 + 3 files changed, 491 insertions(+) create mode 100644 proposal/myrmidon/src/java/org/apache/antlib/cvslib/ChangeLog.java diff --git a/proposal/myrmidon/src/java/org/apache/antlib/cvslib/ChangeLog.java b/proposal/myrmidon/src/java/org/apache/antlib/cvslib/ChangeLog.java new file mode 100644 index 000000000..c1d945348 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/antlib/cvslib/ChangeLog.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.antlib.cvslib; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Properties; +import java.util.Vector; +import org.apache.aut.nativelib.ExecOutputHandler; +import org.apache.avalon.excalibur.i18n.ResourceManager; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.io.IOUtil; +import org.apache.myrmidon.api.AbstractTask; +import org.apache.myrmidon.api.TaskException; +import org.apache.myrmidon.framework.Execute; +import org.apache.tools.todo.types.Commandline; + +/** + * Change log task. + * The task will examine the output of cvs log and group related changes together. + * It produces an XML output representing the list of changes. + *
+ * <!-- Root element -->
+ * <!ELEMENT changelog (entry+)>
+ * <!-- CVS Entry -->
+ * <!ELEMENT entry (date,author,file+,msg)>
+ * <!-- Date of cvs entry -->
+ * <!ELEMENT date (#PCDATA)>
+ * <!-- Author of change -->
+ * <!ELEMENT author (#PCDATA)>
+ * <!-- List of files affected -->
+ * <!ELEMENT msg (#PCDATA)>
+ * <!-- File changed -->
+ * <!ELEMENT file (name,revision,prevrevision?)>
+ * <!-- Name of the file -->
+ * <!ELEMENT name (#PCDATA)>
+ * <!-- Revision number -->
+ * <!ELEMENT revision (#PCDATA)>
+ * <!-- Previous revision number -->
+ * <!ELEMENT prevrevision (#PCDATA)>
+ * 
+ * + * @author Jeff Martin + * @author Peter Donald + * @version $Revision$ $Date$ + * @ant.task name="changelog" + */ +public class ChangeLog + extends AbstractTask + implements ExecOutputHandler +{ + private final static Resources REZ = + ResourceManager.getPackageResources( ChangeLog.class ); + + //private static final int GET_ENTRY = 0; + private static final int GET_FILE = 1; + private static final int GET_DATE = 2; + private static final int GET_COMMENT = 3; + private static final int GET_REVISION = 4; + private static final int GET_PREVIOUS_REV = 5; + + /** input format for dates read in from cvs log */ + private static final SimpleDateFormat c_inputDate = new SimpleDateFormat( "yyyy/MM/dd" ); + /** output format for dates writtn to xml file */ + private static final SimpleDateFormat c_outputDate = new SimpleDateFormat( "yyyy-MM-dd" ); + /** output format for times writtn to xml file */ + private static final SimpleDateFormat c_outputTime = new SimpleDateFormat( "hh:mm" ); + + /** User list */ + private File m_users; + + /** User list */ + private final Properties m_userList = new Properties(); + + /** User list */ + private Vector m_ulist = new Vector(); + + /** Input dir */ + private File m_basedir; + + /** Output file */ + private File m_destfile; + + private int m_status = GET_FILE; + + /** rcs entries */ + private final Hashtable m_entries = new Hashtable(); + private String m_workingFile; + private String m_workingDate; + private String m_workingAuthor; + private String m_workingComment; + private String m_workingRevision; + private String m_workingPreviousRevision; + + /** + * Set the base dir for cvs. + */ + public void setBasedir( final File basedir ) + { + m_basedir = basedir; + } + + /** + * Set the output file for the log. + */ + public void setDestfile( final File destfile ) + { + m_destfile = destfile; + } + + /** + * Set a lookup list of user names & addresses + */ + public void setUserlist( final File users ) + { + m_users = users; + } + + /** + * Add a user to list changelog knows about. + * + * @param user the user + */ + public void addUser( final CvsUser user ) + { + m_ulist.addElement( user ); + } + + /** + * Execute task + */ + public void execute() throws TaskException + { + validate(); + + loadUserlist(); + + for( Enumeration e = m_ulist.elements(); e.hasMoreElements(); ) + { + final CvsUser user = (CvsUser)e.nextElement(); + user.validate(); + m_userList.put( user.getUserID(), user.getDisplayname() ); + } + + final Commandline command = new Commandline(); + command.setExecutable( "cvs" ); + command.addArgument( "log" ); + + final Execute exe = new Execute(); + exe.setWorkingDirectory( m_basedir ); + exe.setCommandline( command ); + exe.setExecOutputHandler( this ); + exe.execute( getContext() ); + + writeChangeLog(); + } + + /** + * Validate the parameters specified for task. + * + * @throws TaskException if fails validation checks + */ + private void validate() + throws TaskException + { + if( null == m_basedir ) + { + final String message = REZ.getString( "changelog.missing-basedir.error" ); + throw new TaskException( message ); + } + if( null == m_destfile ) + { + final String message = REZ.getString( "changelog.missing-destfile.error" ); + throw new TaskException( message ); + } + if( !m_basedir.exists() ) + { + final String message = + REZ.getString( "changelog.bad-basedir.error", m_basedir.getAbsolutePath() ); + throw new TaskException( message ); + } + if( null != m_users && !m_users.exists() ) + { + final String message = + REZ.getString( "changelog.bad-userlist.error", m_users.getAbsolutePath() ); + throw new TaskException( message ); + } + } + + /** + * Load the userlist from the userList file (if specified) and + * add to list of users. + * + * @throws TaskException if file can not be loaded for some reason + */ + private void loadUserlist() + throws TaskException + { + if( null != m_users ) + { + try + { + m_userList.load( new FileInputStream( m_users ) ); + } + catch( final IOException ioe ) + { + throw new TaskException( ioe.toString(), ioe ); + } + } + } + + /** + * Receive notification about the process writing + * to standard output. + */ + public void stdout( final String line ) + { + switch( m_status ) + { + case GET_FILE: + processFile( line ); + break; + case GET_REVISION: + processRevision( line ); + //Was a fall through .... + //break; + case GET_DATE: + processDate( line ); + break; + + case GET_COMMENT: + processComment( line ); + break; + + case GET_PREVIOUS_REV: + processGetPreviousRevision( line ); + break; + } + } + + /** + * Process a line while in "GET_COMMENT" state. + * + * @param line the line + */ + private void processComment( final String line ) + { + final String lineSeparator = System.getProperty( "line.separator" ); + if( line.startsWith( "======" ) || line.startsWith( "------" ) ) + { + final int end = m_workingComment.length() - lineSeparator.length(); //was -1 + m_workingComment = m_workingComment.substring( 0, end ); + m_workingComment = ""; + m_status = GET_PREVIOUS_REV; + } + else + { + m_workingComment += line + lineSeparator; + } + } + + /** + * Process a line while in "GET_FILE" state. + * + * @param line the line + */ + private void processFile( final String line ) + { + if( line.startsWith( "Working file:" ) ) + { + m_workingFile = line.substring( 14, line.length() ); + m_status = GET_REVISION; + } + } + + /** + * Process a line while in "REVISION" state. + * + * @param line the line + */ + private void processRevision( final String line ) + { + if( line.startsWith( "revision" ) ) + { + m_workingRevision = line.substring( 9 ); + m_status = GET_DATE; + } + } + + /** + * Process a line while in "DATE" state. + * + * @param line the line + */ + private void processDate( final String line ) + { + if( line.startsWith( "date:" ) ) + { + m_workingDate = line.substring( 6, 16 ); + String lineData = line.substring( line.indexOf( ";" ) + 1 ); + m_workingAuthor = lineData.substring( 10, lineData.indexOf( ";" ) ); + + if( m_userList.containsKey( m_workingAuthor ) ) + { + m_workingAuthor = ""; + } + + m_status = GET_COMMENT; + + //Reset comment to empty here as we can accumulate multiple lines + //in the processComment method + m_workingComment = ""; + } + } + + /** + * Process a line while in "GET_PREVIOUS_REVISION" state. + * + * @param line the line + */ + private void processGetPreviousRevision( final String line ) + { + final String entryKey = m_workingDate + m_workingAuthor + m_workingComment; + if( line.startsWith( "revision" ) ) + { + m_workingPreviousRevision = line.substring( 9 ); + m_status = GET_FILE; + + CVSEntry entry; + if( !m_entries.containsKey( entryKey ) ) + { + entry = new CVSEntry( parseDate( m_workingDate ), m_workingAuthor, m_workingComment ); + m_entries.put( entryKey, entry ); + } + else + { + entry = (CVSEntry)m_entries.get( entryKey ); + } + entry.addFile( m_workingFile, m_workingRevision, m_workingPreviousRevision ); + } + else if( line.startsWith( "======" ) ) + { + m_status = GET_FILE; + CVSEntry entry; + if( !m_entries.containsKey( entryKey ) ) + { + entry = new CVSEntry( parseDate( m_workingDate ), m_workingAuthor, m_workingComment ); + m_entries.put( entryKey, entry ); + } + else + { + entry = (CVSEntry)m_entries.get( entryKey ); + } + entry.addFile( m_workingFile, m_workingRevision ); + } + } + + /** + * Receive notification about the process writing + * to standard error. + */ + public void stderr( String line ) + { + //ignored + } + + /** + * Parse date out from expected format. + * + * @param date the string holding dat + * @return the date object or null if unknown date format + */ + private Date parseDate( final String date ) + { + try + { + return c_inputDate.parse( date ); + } + catch( ParseException e ) + { + final String message = REZ.getString( "changelog.bat-date.error", date ); + getContext().error( message ); + return null; + } + } + + /** + * Print changelog to file specified in task. + * + * @throws TaskException if theres an error writing changelog + */ + private void writeChangeLog() + throws TaskException + { + FileOutputStream output = null; + try + { + output = new FileOutputStream( m_destfile ); + final PrintWriter writer = + new PrintWriter( new OutputStreamWriter( output, "UTF-8" ) ); + printChangeLog( writer ); + } + catch( final UnsupportedEncodingException uee ) + { + getContext().error( uee.toString(), uee ); + } + catch( final IOException ioe ) + { + throw new TaskException( ioe.toString(), ioe ); + } + finally + { + IOUtil.shutdownStream( output ); + } + } + + /** + * Print out the full changelog. + */ + private void printChangeLog( final PrintWriter output ) + { + output.println( "" ); + for( Enumeration en = m_entries.elements(); en.hasMoreElements(); ) + { + final CVSEntry entry = (CVSEntry)en.nextElement(); + printEntry( output, entry ); + } + output.println( "" ); + output.flush(); + output.close(); + } + + /** + * Print out an individual entry in changelog. + * + * @param entry the entry to print + */ + private void printEntry( final PrintWriter output, final CVSEntry entry ) + { + output.println( "\t" ); + output.println( "\t\t" + c_outputDate.format( entry.getDate() ) + "" ); + output.println( "\t\t" ); + output.println( "\t\t" + entry.getAuthor() + "" ); + + final Iterator iterator = entry.getFiles().iterator(); + while( iterator.hasNext() ) + { + final RCSFile file = (RCSFile)iterator.next(); + output.println( "\t\t" ); + output.println( "\t\t\t" + file.getName() + "" ); + output.println( "\t\t\t" + file.getRevision() + "" ); + + final String previousRevision = file.getPreviousRevision(); + if( previousRevision != null ) + { + output.println( "\t\t\t" + previousRevision + "" ); + } + + output.println( "\t\t" ); + } + output.println( "\t\t" + entry.getComment() + "" ); + output.println( "\t" ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/antlib/cvslib/Resources.properties b/proposal/myrmidon/src/java/org/apache/antlib/cvslib/Resources.properties index ebe3267ff..ad60f0533 100644 --- a/proposal/myrmidon/src/java/org/apache/antlib/cvslib/Resources.properties +++ b/proposal/myrmidon/src/java/org/apache/antlib/cvslib/Resources.properties @@ -1,5 +1,10 @@ changelog.nodisplayname.error=Displayname attribute must be set for userID "{0}". changelog.nouserid.error=Username attribute must be set. +changelog.missing-basedir.error=Basedir must be set. +changelog.missing-destfile.error=Destfile must be set. +changelog.bad-basedir.error=Cannot find base dir {0} +changelog.bad-userlist.error=Cannot find user lookup list {0}. +changelog.bat-date.error=I don't understand this date -> {0} cvspass.nopassword.error=Password is required. cvspass.noroot.error=Cvsroot is required. diff --git a/proposal/myrmidon/src/make/sample.ant b/proposal/myrmidon/src/make/sample.ant index c122b11de..1b982d84b 100644 --- a/proposal/myrmidon/src/make/sample.ant +++ b/proposal/myrmidon/src/make/sample.ant @@ -283,4 +283,10 @@ Legal: + + + + + + \ No newline at end of file