/* * [Replacer.java] * * Summary: Expands HTML boilerplate refresher macros. * * Copyright: (c) 2002-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.3 2009-01-01 rewrite to use StringBuilder and fell-swoop I/O for both in and out, and also avoid writing if files * are the same. * 1.4 2013-01-13 permit missing spaces in */ package com.mindprod.htmlmacros; import com.mindprod.commandline.CommandLine; import com.mindprod.common18.EIO; import com.mindprod.common18.Misc; import com.mindprod.common18.ST; import com.mindprod.common18.Twirler; import com.mindprod.compactor.Compactor; import com.mindprod.fastcat.FastCat; import com.mindprod.filter.AllButFootDirectoriesFilter; import com.mindprod.filter.ExtensionListFilter; import com.mindprod.htmlmacros.macro.Global; import com.mindprod.htmlmacros.macro.Macro; import com.mindprod.htmlmacros.support.Embellishment; import com.mindprod.htmlmacros.support.Fireup; import com.mindprod.htmlmacros.support.InlineFooter; import com.mindprod.htmlmacros.support.Lazy; import com.mindprod.htmlmacros.support.LoadCodeToProcessMacro; import com.mindprod.htmlmacros.support.ParmParser; import com.mindprod.htmlmacros.support.Shutdown; import com.mindprod.htmlmacros.support.Tools; import com.mindprod.hunkio.HunkIO; import java.awt.Toolkit; import java.io.File; import java.io.IOException; import java.util.regex.Pattern; import static com.mindprod.htmlmacros.macro.Global.configuration; import static java.lang.System.*; /* * TODO: * Futures: */ /** * Expands HTML boilerplate refresher macros. *

* e.g. expands [!-- macro XXXXX parms -] with Allows arbitrary expander macro code. *

* WARNING: Replacer will remove all previous [!-- generated ... [/generated] even ones without matching macro commands. *

* We read the entire file into RAM at once. We accumulate the expansion is a StringBuilder then write the whole thing * to a temporary file, if there were any changes. Then we delete the original and rename the temporary. ParmParser does * the tricky work of extracting the parms from the macro with the various kinds of delimiter. *

* main calls expandMacrosInFile calls parseAndExpandOneMacro calls processorDelegate.expandNoRef
* expandMacrosInFile catches * the exceptions and does error reporting. * * @author Roedy Green, Canadian Mind Products * @version 1.4 2013-01-13 permit missing spaces in * @since 2002-01-01 */ public class Replacer { // declarations /** * do we want extra debugging output, assigned in main. Assigned mby configuration. Not known at compile time. */ private static final boolean DEBUGGING = false; /** * we may bypass processing using caching if there are at least this many files. */ private static final int BYPASS_LIMIT = 10; /** * minimum size we expect the file we are expanded to me. Smallest will be a Moved macro that has been * StripGenerated */ private static final int MIN_FILE_SIZE = 30; /** * easily visible marker for end of error message group */ private static final String END_OF_ERROR_MARKER = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"; /** * easily visible marker for start of error message group */ private static final String START_OF_ERROR_MARKER = "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"; /** *
pattern includes initial */ private static final Pattern startGenerated = Pattern .compile( "" ); private static final Toolkit toolkit = Toolkit.getDefaultToolkit(); /** * rotation state of the twirling animation */ private static final Twirler twirler = new Twirler(); /** * true we are copying the input to the output . false we deleting the input from the output stream, e.g. between * generate-/generate */ private static boolean copy = true; /** * true if expanding masters in /master/ subdir to distributable, compacting out macros and spaces. */ private static boolean separateMasters; /** * offset in chars we have so far processed the file image. */ private static int offset; /** * length of the original input string before any processing. */ private static int originalLength; /** * files older than this are not expected to change expansions. * They might change because of coding change or because of include changes. * Differences are reported in case they are caused by bugs. * Most changes are caused by recent edits or bulk search/replace changes. */ private static long ageOfUnexpectedChanges; /** * String representing to text just prior to the marker scanned for. */ private static String chunk; /** * contents expaneded macros. */ private static String contents; /** * what this particular document uses for a line separator */ private static String documentLineSeparator; /** * name of the macro we are about to execute e.g. Head Include Image */ private static String macroName; /** * The string representing the original file image we are processing. */ private static String original; /** * array of parms for latest macro */ private static String[] parms; /** * previous contents of the distributed file. * For old style in the original. * For separateMasters in the previous expansion without macros. */ private static String prevContents; /** * where we emit the expanded file. We generate a new StringBuilder for each file. Otherwise one giant file could * pump it up big and leave it there needlessly. */ private static FastCat emit; /** * track which files we can bypass processing */ private static Lazy trackAlreadyDone; // /declarations // methods /** * file where we write the expanded macros, old style back on fileBeingProcessed, * new style is separate file one level up. */ private static File fileBeingDistributed; /** * file where the macros are */ private static File fileBeingProcessed; /** * Adjust line separator \n to document convention * * @param text line of text to have embedded new lines fixed. * * @return text with \n replaced with \r\n or whatever. */ private static String adjustLineSeparator( String text ) { if ( documentLineSeparator.equals( "\n" ) ) { return text; } else { // macros use \n. Expand to something else. return endOfLine.matcher( text ).replaceAll( documentLineSeparator ); } }// /method /** * announce we are starting to expand the macros in a file. * * @param quiet true if just twirler. false if want name of file displayed. * * @see com.mindprod.htmlmacros.macro.Include# */ private static void announceStartOfExpandingOneFile( final boolean quiet ) { if ( !quiet ) // don't println till finished whole file. Macros can print single letters to // status line { out.print( " expanding " + EIO.getCanOrAbsPath( fileBeingProcessed ) + " " ); } }// /method /** * beep a to alert to an error.
This is a polite little DOS speaker beep, not the full system error tone.
It * sounds quite different from ordinary sound-card sounds,
so it catches the user's attention.
It also can't * be shut off with the volume control. */ private static void beep() { toolkit.beep(); }// /method /** * calculate fileBeingDistributed and fileBeingProcessed. */ private static void calcFileBeingDistributed() { if ( separateMasters ) { if ( Tools.isFileAMaster( fileBeingProcessed ) ) { fileBeingDistributed = Tools.correspondingDistributable( fileBeingProcessed ); } else { // swap roles. fileBeingDistributed = fileBeingProcessed; fileBeingProcessed = Tools.correspondingMaster( fileBeingDistributed ); } } else { // old style, write expanded back on original. fileBeingDistributed = fileBeingProcessed; } }// /method /** * display start and end of a bit of offending text. * Pruned to one line. * * @param text raw HTML text to display. */ private static void displayOffendingText( String text ) { if ( text.length() <= 100 ) { // display whole thing err.println( "\n " + text ); } else { // chop out the middle err.println( "\n " + text.substring( 0, 45 ) + " ... " + text.substring( text.length() - 45 ) ); } }// /method /** * show what changed by he macro expansions */ private static void displayWhatChanged() { // prev in old expanion if this page. if ( prevContents == null ) { try { prevContents = HunkIO.readEntireFile( fileBeingDistributed, HunkIO.UTF8 ); } catch ( IOException e ) { // if had trouble reading, that is fine. It may not exist yet. } } if ( prevContents == null ) { return; } // dump out the change. out.println( "\n previous expansion length: " + prevContents.length() + " new expansion length: " + contents.length() ); final int length = Math.min( prevContents.length(), contents.length() ); for ( int i = 0; i < length; i++ ) { if ( contents.charAt( i ) != prevContents.charAt( i ) ) { final int start = Math.max( 0, i - 30 ); final int end = Math.min( i + 30, length ); final FastCat sb = new FastCat( 10 ); sb.append( " info: file " ); sb.append( fileBeingDistributed.toString() ); sb.append( " changed at offset " ); sb.append( i ); sb.append( "\n old[" ); sb.append( makeVisible( prevContents.substring( start, end ) ) ); sb.append( "]\n new[" ); sb.append( makeVisible( contents.substring( start, end ) ) ); sb.append( "]\n" ); out.print( sb.toString() ); // suppress blank line // name of file changed will follow. break; } } }// /method /** * parse the big file, replacing boiler plate, and copying ordinary text. * Handles and // balance properly. will throw IllegalArgumentException if problems. // We don't remove them! We later just leave them behind as we copy over other stuff to emit buffer. copy = true; // loop for each fragment while ( true ) { // scan for beginning of next comment if ( scan( " generates: ", true ) ) { copy = false; } else { throw new IllegalArgumentException( "syntax error " ); } } else if ( command.equals( "/generated" ) ) { if ( scan( "-->", true ) ) { copy = true; } else { throw new IllegalArgumentException( "syntax error " ); } } else { // just ordinary comment, treat it like other HTML text. if ( scan( "-->", true ) ) { if ( copy ) { // comment might have been inside } } else { throw new IllegalArgumentException( "syntax error missing -->" ); } } } else { // no more comments, just copy tail and we are done. if ( copy ) { // tail end assert 0 <= offset && offset <= originalLength; emit.append( original.substring( offset ) ); } break; } } // end while // all done } catch ( IllegalArgumentException e ) { // garden variety error, e.g. wrong number of parms or wrong types. err.println(); err.println(); err.println( e.getMessage() ); err.println( START_OF_ERROR_MARKER ); e.printStackTrace( err ); err.println( e.getClass() + " " + e.getMessage() + where() ); showParms(); beep(); err.println(); abort = true; } catch ( NoClassDefFoundError e ) { err.println(); err.println(); err.println( START_OF_ERROR_MARKER ); err.println( "Unrecognised macro command " + macroName + where() ); showParms(); beep(); err.println(); abort = true; } catch ( IndexOutOfBoundsException e ) { err.println(); err.println(); err.println( START_OF_ERROR_MARKER ); e.printStackTrace( err ); err.println( "Index out of bounds, probable missing parm or FastCat overflow" + "\n" + e.getMessage() + where() ); showParms(); beep(); err.println(); abort = true; } catch ( Throwable e ) { err.println(); err.println(); err.println( START_OF_ERROR_MARKER ); e.printStackTrace( err ); err.println( "Unusual Error: " + e.getClass() + " " + e.getMessage() + where() ); showParms(); beep(); err.println(); abort = true; } if ( abort ) { // mark it bad. Even though unchanged, even though may have passed previously, // is not acceptable to leave this untouched in future. trackAlreadyDone.markStatus( fileBeingProcessed, false ); err.println( "file " + EIO.getCanOrAbsPath( fileBeingProcessed ) + " left unchanged." ); err.println( END_OF_ERROR_MARKER ); err.println(); err.println(); } else { // we must avoid saving file if was error in expansion, we have only a partial file. // generate possibly compacted output for new file as a String // We need to compact file even if nothing changed. contents = emit.toString(); contents = Compactor.compactStringAsNeeded( contents, fileBeingProcessed.getPath(), configuration.getCompacting() ); if ( DEBUGGING ) { emit.dump(); } emit.checkEstimate(); emit = null; // whether it changed or not, it is good. trackAlreadyDone.markStatus( fileBeingProcessed, true ); final boolean different = isExpansionDifferentFromBefore(); if ( different ) { // It is unusual for expansions to change if it has not been recently edited. // it can happen if new code is being generated for the same macros. // We suppress change display for edits done in the last 45 minutes. if ( verbose || DEBUGGING || !forceRegenerate && fileBeingProcessed.lastModified() < System.currentTimeMillis() - ageOfUnexpectedChanges ) { // don't bother showing change if expected displayWhatChanged(); } // really changed, save it back try { saveProcessedContents(); } catch ( IOException e ) { err.println( e.getMessage() ); System.exit( 2 ); } } // notify the user of the change. showPostChangeProgress( quiet, different ); } return false; } // end processFile// /method /** * Guess what line separator is being used in the current file. * * @return guessed line separator. */ private static String guessLineSeparator() { String test = ( originalLength < 400 ) ? original : original.substring( 80, 400 ); if ( test.contains( "\r\n" ) ) { return "\r\n"; } if ( test.indexOf( '\r' ) >= 0 ) { return "\r"; } if ( test.indexOf( '\n' ) >= 0 ) { return "\n"; } return configuration.getPreferredLineSeparator(); }// /method /** * have the expanded macros actually changed so we need to write the out? * * @return true if we need to write * @throws java.io.IOException */ private static boolean isExpansionDifferentFromBefore() throws IOException { // prevConents is previouzs expansion of this page, i.e. old distributed or orginial if ( separateMasters ) { // this looks like useless extra work it saves on untouch later. if ( fileBeingDistributed.exists() && contents.length() == fileBeingDistributed.length() ) { // prev contents look like a match to what we just generated. Check to be sure. prevContents = HunkIO.readEntireFile( fileBeingDistributed, HunkIO.UTF8 ); return prevContents == null || !prevContents.equals( contents ); } else { // fileBeingBeingDistributed is not the same as what we just generated. // we might need to read it later if want verbose display of what changed. prevContents = null; return true; } } else { // about to write on top of original. See if can avoid the work. prevContents = original; return !contents.equals( original ); } }// /method /** * make the \r and \n in a string visible * * @param s string to make more visiblu * * @return visible string. */ private static String makeVisible( final String s ) { return s.replace( '\n', '|' ).replace( '\r', '~' ); }// /method /** * mark the HTMLMacros data files as busy. */ private static void markAsBusy() { final File webrootDir = new File( configuration.getLocalWebrootWithSlashes() ); // There are three different mechanisms. // 1. running.dat managed by the htmlmacros program // 2. busy.dat managed by btm files // 3. stop.dat managed by user when wants a graceful stop. final File busy = new File( webrootDir, "busy/running.dat" ); if ( busy.exists() ) { err.println( "Another copy of HTMLMacros already running. Aborting. Clear manually with reset.btm." ); System.exit( 2 ); } // create the busy marker file with dummy contents. try { HunkIO.writeEntireFile( busy, "HTMLMacros busy", EIO.UTF8 ); } catch ( IOException e ) { err.println( "Unable to write busy marker file: busy/running.dat" ); err.println( e.getMessage() ); System.exit( 2 ); } }// /method /** * mark the HTMLMacros data files as no longer busy. */ private static void markAsUnBusy() { final File webrootDir = new File( configuration.getLocalWebrootWithSlashes() ); final File busy = new File( webrootDir, "busy/running.dat" ); busy.delete(); }// /method /** * Scan from the current offset looking for the next word (delimited by white space) It does not modify chunk or * offset. * * @return next word in original starting at offset. */ private static String nextWord() { // we want to avoid creating needless substrings that could // be very long, so we process char by char. boolean inLeading = true; int start = offset; for ( int i = offset; i < originalLength; i++ ) { if ( Character.isWhitespace( original.charAt( i ) ) ) { if ( inLeading ) { start = i + 1; } else { return original.substring( start, i/* one past end */ ); } } else { inLeading = false; } } // end for return ( original.substring( start ) ); }// /method /** * process a macro of the form: * * @param quiet true if want progress messages suppressed * @param verbose true if want extra output */ private static String parseAndExpandOneMacro( final boolean quiet, final boolean verbose ) { // we found something of the form: // // we have parsed up to but not including the word macro // parse out the various sections. // has to be ok, since we already found it. // We don't yet have a macroName or parms, set null it case get // exception. // FastCat slots: macro lead, macroname, parmarea, macro tail, // generated, expansion, /generated. final FastCat emit = new FastCat( 10 ); macroName = null; parms = null; scan( "macro", true ); // name of macro to invoke. macroName = nextWord(); // at worst would be empty name scan( macroName, true ); if ( !scan( "-->", false ) ) { throw new IllegalArgumentException( "syntax error missing --> to close macro" + where() ); } final String parmArea = chunk.trim(); /* get array of parms */ parms = ParmParser.parseParms( parmArea ); // has to be ok, since already found it earlier scan( "-->", true ); // leave the original macro in place, slightly tidied. // don't println or append an extra \n. We have not parsed that yet. emit.append( "" ); // don't add nl before or after or they will accumulate each time macro // is run. // we replace only stuff strictly inside. // No extra \n before or after to avoid introducing spurious white space. emit.append( "" ); // invoke the specific macro processor, getting it to expandNoRef // tell it the filename, no so that it can read it, but so that it // can use its names // in the generation of the macro. try { // get the Macro-implementing class instance that will process the macro. // It implement the Macro interface with the expandNoRef method. // Hopefully already loaded from previous use. Will throw exception on trouble. Should not return null. final Macro macroDelegate = LoadCodeToProcessMacro.getMacroProcessorInstance( macroName ); assert macroDelegate != null : "null delegate to process macro " + macroName; // Tell the macro the source and target files. It does not need to read/write them, // but it may need to know them. macroDelegate.setFileBeingProcessedAndFileBeingDistributed( fileBeingProcessed, fileBeingDistributed ); // F I N A L L Y ! _ E X P A N D _ T H E _ M A C R O ! String expansion = macroDelegate.expandMacro( parms, quiet, verbose ); // compacting individual macro expansions // This saves RAM since we get rid of fat early // Compactor might be able to avoid work at the end if all is already compact. expansion = Compactor.compactStringAsNeeded( expansion, fileBeingProcessed.getPath(), configuration.getCompacting() ); expansion = adjustLineSeparator( expansion ); // tack the macro expansion just after the macro command emit.append( expansion ); } catch ( ClassNotFoundException e ) { throw new IllegalArgumentException( "No such macro as " + macroName + " or possibly missing code to " + "handle it." ); } catch ( InstantiationException e ) { throw new IllegalArgumentException( "No such macro as " + macroName + " or possibly missing code to " + "handle it." ); } catch ( IllegalAccessException e ) { throw new IllegalArgumentException( "No such macro as " + macroName + " or possibly missing code to " + "handle it." ); } // we don't catch other exceptions here. // don't add \n before or after or they will accumulate each elapsedTime macro is // run. Further we want to avoid adding spurious white space. emit.append( "" ); copy = true; return emit.toString(); }// /method /** * save expanded/compacted contents of the file. * * @throws IOException if trouble doing the io. */ private static void saveProcessedContents() throws IOException { // we REALLY changed. // If we were to crash in the middle of writing to the original, // it would be corrupted. So instead we write to temp, then // after it is safely written, we delete the original and // rename. final File tempFile = HunkIO.createTempFile( "temp_", ".tmp", fileBeingProcessed ); HunkIO.writeEntireFile( tempFile, contents, HunkIO.UTF8 ); HunkIO.deleteAndRename( tempFile, fileBeingProcessed ); }// /method /** * Scan original looking for the lookFor string, starting at offset.
It leaves the material prior to what was * sought in chunk. It updates the offset to point either before or after the lookFor string. * * @param lookFor String to search for. Not a regex. * @param after true if scan to char after the lookFor String
false, leave offset pointing to beginning of * the lookFor string. * * @return true if successful. */ @SuppressWarnings( { "BooleanMethodNameMustStartWithQuestion" } ) private static boolean scan( String lookFor, boolean after ) { int where = original.indexOf( lookFor, offset ); if ( where < 0 ) { // did not find it chunk = null; return false; } if ( after ) { where += lookFor.length(); } chunk = original.substring( offset, where ); offset = where; return true; } // end scan// /method /** * Display macro name and parms on error */ private static void showParms() { // macroName and parms may be null. if ( macroName == null ) { macroName = "?"; } err.println( "macro where problem was detected:" ); err.println( " " ); }// /method /** * show progress of how we are processing files. * * @param quiet true to suppress progress messages * @param different true if this file has changed */ private static void showPostChangeProgress( boolean quiet, boolean different ) { if ( different ) { if ( quiet ) { // quiet // only print names of files that actually changed out.println( "* " + EIO.getCanOrAbsPath( fileBeingProcessed ) ); } else { // verbose // append * onto list of all the macros expanded in that // file. out.println( "*" ); } } else { // leave original file untouched, same date and elapsedTime. // Notify user we left the file unchanged. if ( quiet ) { // quiet, say nothing at all // out.print( "-" ); } else { // verbose, tack onto end of letters for macro expansion. out.println( "-" ); } } }// /method /** * warn user of problem. * * @param message text of error message. */ private static void warn( String message ) { err.println(); err.println( "------------------------------------------------------" ); err.println( "W A R N I N G: " + message ); // This is a polite little DOS speaker beep, not the full system error // tone. It sounds quite different from ordinary sounds, so catches // attention. // It also can't be shut off with the volume control. beep(); }// /method /** * display text near current given offset * * @return text near that offset, line number etc to help user find the error * @see com.mindprod.compactor.HTMLState#where */ private static String where() { final FastCat sb = new FastCat( 11 ); sb.append( "\n>>> in file: " ); sb.append( EIO.getCanOrAbsPath( fileBeingProcessed ) ); sb.append( " near offset: " ); sb.append( offset ); sb.append( " near line: " ); sb.append( ST.countInstances( original.substring( 0, offset ), '\n' ) + 1 /* one based */ ); sb.append( "\n [" ); // get at least 100 chars before and after, break at even line. int start = Math.max( 0, offset - 100 ); // cannot compute in one line int startBreak = original.lastIndexOf( '\n', start ); if ( startBreak >= 0 ) { start = startBreak; } int end = Math.min( offset + 100, original.length() ); // cannot compute in one line final int endBreak = original.indexOf( '\n', end ); if ( endBreak >= 0 ) { end = endBreak; } sb.append( original.substring( start, offset ).trim() ); sb.append( " <><>||||<><> " ); sb.append( original.substring( offset, end ).trim() ); sb.append( "]\n\n" ); return sb.toString(); }// /method /** * estimate how many FastCat slots will be needed to process this page * * @param original contents of the page * * @return number of slots needed * @see com.mindprod.htmlmacros.macro.Include * @see com.mindprod.htmlmacros.macro.Insert */ public static int estimateFastCatSlots( String original ) { // macros may been bang up against each other with text // count 1 for each block of text length // 2 for each comment // 1 for each macro // 0 for each might start a line so cannot search for space--> // <-- might end a line, so cannot search for <--space int slots = 0; int start = 0; if ( DEBUGGING ) { out.println( "estimateFastCatSlots" ); } loop: while ( start < original.length() ) { int place = original.indexOf( "", place + "".length(); break; case "generated": /* skip over all generated text */ start = place + "", place + "".length(); break; case "/generated": start = original.indexOf( "-->", place + "".length(); break; default: // ordinary comment slots++; /* for ", place + "".length(); } // /switch } // /loop slots++; /* why? don't know */ if ( DEBUGGING ) { out.println( "final slots: " + slots ); } return slots; } /** * what do do when replacer starts up */ public static void fireup() { markAsBusy(); trackAlreadyDone = new Lazy(); trackAlreadyDone.open( new File( "E:\\mindprod\\busy\\cache.bin" ), "E:\\mindprod\\", ".html", 10_000 /* estimated files */ ); }// /method /** * Expands macros written in Java in HTML files files. * * @param args name of configuration class, e.g. ConfigurationForMindprod.com then names of files to process, * no wildcards. */ public static void main( String[] args ) { if ( args.length < 2 ) { throw new IllegalArgumentException( "Must have Configuration and at least one directory/file on the command line." ); } Global.installConfiguration( args ); final String absParm = EIO.getCanOrAbsPath( new File( args[ args.length - 1 ] ) ).toLowerCase(); if ( !absParm.startsWith( Global.configuration.getLocalWebrootWithBackslashes().toLowerCase() ) ) { throw new IllegalArgumentException( Global.configuration.getClass().toString() + " incompatible with " + absParm ); } // When old files chuange, we display the change details ageOfUnexpectedChanges = Global.configuration.getAgeOfUnexpectedChanges(); Fireup.fireup(); // gather all the files mentioned on the command line. // either directories, files, *.*, with -s and subdirs option. out.println( "Gathering html files to expand macros..." ); // command line, estimated files, dir filter, file filter. // These are fully qualified names. final CommandLine commandLine = new CommandLine( args, new AllButFootDirectoriesFilter(), new ExtensionListFilter( ExtensionListFilter.COMMON_HTML_EXTENSIONS ) ); final boolean quiet = commandLine.isQuiet(); if ( quiet ) { out.println( "-q quiet" ); } final boolean verbose = commandLine.isVerbose(); // command line displays it automatically // this mechanism does not modify the cache. // if just expanding a few files, obviously want them regenerated. final boolean smallSet = commandLine.size() < BYPASS_LIMIT; for ( File file : commandLine ) { try { // replace any non-canonical files. // ensure added files are canonical. try { // don't use getCanOrAbsPath final File fileBeingProcessed = new File( file.getCanonicalPath() ); final boolean alwaysRegenerate = Global.configuration.alwaysRegenerate( fileBeingProcessed ); final boolean containsRandomQuotation = Embellishment.QUOTATION.isFileEmbellishedWithThisLetter( fileBeingProcessed ); final boolean forceRegenerate = smallSet || alwaysRegenerate || containsRandomQuotation; if ( expandMacrosInFile( fileBeingProcessed, quiet, verbose, forceRegenerate ) ) { break; } } catch ( IOException e ) { err.println( e.getMessage() ); // usually problem is can't make file canonical. System.exit( 1 ); } } catch ( Exception e ) { err.println(); err.println( e.getMessage() + "\n" + "in file " + EIO.getCanOrAbsPath( file ) ); err.println(); } } Shutdown.shutdown(); Misc.trackLastThread(); out.println( "done" ); System.exit( 0 ); } // end main// /method /** * what do do when replacer quits */ public static void shutdown() { trackAlreadyDone.close(); trackAlreadyDone = null; markAsUnBusy(); }// /method /** * Ensures and and tags balance, and do not nest.
Also * used by Include.java * * @param fileBeingProcessed file being processed. Just needed for error messages. * * @param segment html text in which to check the and regex to recognize // int length = segment.length(); boolean insideGenerated = false; // indexes which character we are working on int i = 0; while ( i < length ) { // search for start of comment int startCommentPlace = segment.indexOf( "", startCommentPlace + " on a ".length(); // comment includes String comment = segment.substring( startCommentPlace, endCommentPlace ); // make sure the comments not malformed. Should be no embedded start // comment marker String commentGuts = comment.substring( "".length() ); if ( commentGuts.contains( " not balanced" ); } // three special kinds of comment // treat like ordinary comments here. // start stripping // stop stripping // we treat macro like ordinary comments. We don't try to enforce // macro generated /generated or similar order. The whole point of // this is // to clean up mess where you may have got dups etc. if ( startGenerated.matcher( comment ).matches() ) { // was without previous " ); } insideGenerated = true; } else if ( endGenerated.matcher( comment ).matches() ) { // was without previous " ); } insideGenerated = false; } else { // was SSI comment which does not need whitespace, e.g. ordinary comment or macro // not // not if ( !( charAfterCommentStart == ' ' || Character.isWhitespace( charAfterCommentStart ) ) ) { warn( "Missing whitespace after ".length() - 1 ); if ( !( charBeforeCommentEnd == ' ' || Character.isWhitespace( charBeforeCommentEnd ) ) ) { warn( "Missing whitespace before --> at offset " + ( endCommentPlace - "-->".length() - 1 ) + " in file " + fileBeingProcessed.toString() ); displayOffendingText( segment.substring( startCommentPlace, endCommentPlace ) ); } } } i = endCommentPlace; } // end while // make sure last at end of file." ); } }// /method // /methods } // end Replacer