All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.zz.diff.Main Maven / Gradle / Ivy

The newest version!

/*
 * de.unkrig.diff - An advanced version of the UNIX DIFF utility
 *
 * Copyright (c) 2011, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.zz.diff;

import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.regex.Pattern;

import org.apache.commons.compress.utils.Charsets;

import de.unkrig.commons.file.ExceptionHandler;
import de.unkrig.commons.file.org.apache.commons.compress.archivers.ArchiveFormatFactory;
import de.unkrig.commons.file.org.apache.commons.compress.archivers.sevenz.SevenZArchiveFormat;
import de.unkrig.commons.file.org.apache.commons.compress.compressors.CompressionFormatFactory;
import de.unkrig.commons.file.resourceprocessing.ResourceProcessings;
import de.unkrig.commons.lang.AssertionUtil;
import de.unkrig.commons.lang.protocol.RunnableWhichThrows;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.text.AbstractPrinter.Level;
import de.unkrig.commons.text.LevelFilteredPrinter;
import de.unkrig.commons.text.Printers;
import de.unkrig.commons.text.pattern.Glob;
import de.unkrig.commons.text.pattern.Pattern2;
import de.unkrig.commons.util.CommandLineOptions;
import de.unkrig.commons.util.annotation.CommandLineOption;
import de.unkrig.commons.util.annotation.CommandLineOptionGroup;
import de.unkrig.commons.util.annotation.RegexFlags;
import de.unkrig.commons.util.logging.SimpleLogging;
import de.unkrig.zip4jadapter.archivers.zip.ZipArchiveFormat;
import de.unkrig.zz.diff.Diff.AbsentFileMode;
import de.unkrig.zz.diff.Diff.DiffMode;
import de.unkrig.zz.diff.DocumentDiff.LineEquivalence;
import de.unkrig.zz.diff.DocumentDiff.Tokenization;

/**
 * A DIFF utility that processes not only files and directories, but also web resources, compressed contents and
 * archives.
 */
public
class Main {

    static { AssertionUtil.enableAssertionsForThisClass(); }

    /**
     * 

Usage:

* *
*
{@code zzdiff} [ option ] ... file-or-url1 file-or-url2
*
* Show contents differences between file-or-url1 and file-or-url2 in DIFF format. * The "path" (relevant, e.g., for the "--path" command line option, see below) is "" (the empty string). * One of the two inputs may be "-" (standard input). *
*
{@code zzdiff} [ option ] ... dir1 dir2
*
* Show which files were added (missing in dir1) or deleted (missing in dir2) and their * subdirectories, and any contents differences for the remaining files in DIFF format. The "path" (relevant, * e.g., for the "--path" command line option, see below) is relative to dir1, e.g. "file.txt" * or "subdir/file.zip!dir/file.txt" or "archive.tgz%!dir/file.txt" or "dir/file.Z%". *
*
* *

Description:

* *

* Compares files and web resources line by line; detects directories, compressed and archive files, and even * nested archives, and compares their entries instead of their raw contents. *

*

* The default output format is the "normal * format", also known as the "traditional format". Other output formats can be chosen through command * line options, see "Output generation", below. *

* *

Options:

* *

General

* *
* {@main.commandLineOptions} *
* *

File selection

* *
* {@main.commandLineOptions File-Selection} *
* *

Content processing

* *
* {@main.commandLineOptions Contents-Processing} *
* *

Output generation

* *
* {@main.commandLineOptions Output-Generation} *
* *

Globs

* *

* A glob can have the form *

*
     *   {@code *~*.c~*.h,foo.c}
     * 
*

* , which means "foo.c plus all that don't end with .c or .h". *

* *

Example globs:

* *
*
{@code dir/file}
*
* File "file" in directory "dir". *
*
{@code file.gz%}
*
* Compressed file "file.gz". *
*
{@code file.zip!dir/file}
*
* Entry "dir/file" in archive file "file.zip". *
*
{@code file.tar.gz%!dir/file}
*
* Entry "dir/file" in the compressed archive file "file.tar.gz". *
*
*/x
*
* File "x" in an immediate subdirectory. *
*
**/x
*
* File "x" in any subdirectory. *
*
***/x
*
* File "x" in any subdirectory, or any entry "**/x" in any archive file in any subdirectory. *
*
{@code a,dir/file.7z!dir/b}
*
* File "a" and entry "dir/b" in archive file "dir/file.7z". *
*
{@code ~*.c}
*
* Files that don't end with ".c". *
*
{@code ~*.c~*.h}
*
* Files that don't end with ".c" or ".h". *
*
{@code ~*.c~*.h,foo.c}
*
* "foo.c" plus all files that don't end with ".c" or ".h". *
*
*/ public static void main(final String[] args) { // Install the "de.unkrig.commons.net.authenticator.CustomAuthenticator", if it can be found on the // classpath. try { ClassLoader cl = ClassLoader.getSystemClassLoader(); Class cacheModeClass = cl.loadClass("de.unkrig.commons.net.authenticator.CustomAuthenticator$CacheMode"); Class storeModeClass = cl.loadClass("de.unkrig.commons.net.authenticator.CustomAuthenticator$StoreMode"); Authenticator customAuthenticator = (Authenticator) ( cl .loadClass("de.unkrig.commons.net.authenticator.CustomAuthenticator") .getConstructor(cacheModeClass, storeModeClass) .newInstance(cacheModeClass.getEnumConstants()[2], storeModeClass.getEnumConstants()[2]) ); Authenticator.setDefault(customAuthenticator); } catch (Exception e) { ; } final Main main = new Main(); main.levelFilteredPrinter.run(new Runnable() { @Override public void run() { main.main2(args); } }); } private final Diff diff = new Diff(); private final LevelFilteredPrinter levelFilteredPrinter = new LevelFilteredPrinter(); @Nullable private File outputFile; public Main() {} /** * Processes any thrown exceptions. */ private void main2(String[] args) { try { this.main3(args); } catch (Exception e) { Printers.error(null, e); System.exit(1); } } /** * Processes the command line options and arguments. */ private void main3(String[] args) throws Exception { args = CommandLineOptions.parse(args, this); if (args.length != 2) { Printers.error("Wrong number of file or directory names - try \"--help\"."); System.exit(2); } this.main4(ResourceProcessings.toUrl(args[0]), ResourceProcessings.toUrl(args[1])); } /** * Handles the {@link #outputFile}. */ private void main4(final URL resource1, final URL resource2) throws Exception { Printers.redirectToFile( Level.INFO, // level this.outputFile, // outputFile null, // charset new RunnableWhichThrows() { // runnable @Override public void run() throws Exception { Main.this.main5(resource1, resource2); } } ); } /** * Exits with status "1" iff there are one or more differences. */ private void main5(final URL resource1, final URL resource2) throws Exception { long differenceCount = Main.this.diff.execute(resource1, resource2); if (differenceCount > 0) System.exit(1); } /** * Print this text and terminate. */ @CommandLineOption public void help() throws IOException { System.setProperty("archive.formats", ArchiveFormatFactory.allFormats().toString()); System.setProperty("compression.formats", CompressionFormatFactory.allFormats().toString()); CommandLineOptions.printResource(Main.class, "main(String[]).txt", Charset.forName("UTF-8"), System.out); System.exit(0); } /** * Process only matching files/entries, e.g."{@code dir/file.zip!dir/file}" or "**/file". * See also "
Globs", below. * * @main.commandLineOptionGroup File-Selection */ @CommandLineOption public void setPath(@RegexFlags(Pattern2.WILDCARD | Glob.INCLUDES_EXCLUDES) Glob glob) { this.diff.setPathPredicate(glob); } /** * Look into compressed and archive contents if its format matches format-glob and its path matches the * path-glob. * The default is to look into any recognised archive or compressed contents. *
* Supported archive formats in this runtime configuration are: *
* {@code ${archive.formats}} *
* Supported compression formats in this runtime configuration are: *
* {@code ${compression.formats}} * * @main.commandLineOptionGroup File-Selection * @param discriminator format-glob:path-glob */ @CommandLineOption public void setLookInto(@RegexFlags(Glob.INCLUDES_EXCLUDES | Pattern2.WILDCARD) Glob discriminator) { this.diff.setLookInto(discriminator); } /** * Files with different names map iff their names match the path-regex and all capturing groups are * equal. May be given more than once. * * @main.commandLineOptionGroup File-Selection */ @CommandLineOption(name = { "path-equivalence", "pe" }, cardinality = CommandLineOption.Cardinality.ANY) public void addPathEquivalence(Pattern pathRegex) { this.diff.addEquivalentPath(pathRegex); } /** * @main.commandLineOptionGroup File-Selection * @deprecated Equivalent with "{@code --path-equivalence} path-regex". */ @Deprecated @CommandLineOption(name = "ne", cardinality = CommandLineOption.Cardinality.ANY) public void addNameEquivalence(Pattern pathRegex) { this.addPathEquivalence(pathRegex); } /** * Don't recurse through subdirectories; just compare the existence of subdirectories. * * @main.commandLineOptionGroup File-Selection */ @CommandLineOption public void setNoRecurseSubdirctories() { this.diff.setRecurseSubdirectories(false); } // CONTENTS PROCESSING OPTIONS /** * Disassemble .class files. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(name = { "disassemble", "da" }) public void setDisassemble() { this.diff.setDisassembleClassFiles(true); } /** * When disassembling .class files, include a constant pool dump, constant pool indexes, and hex dumps of all * attributes in the disassembly output. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setDaVerbose() { this.diff.setDisassembleClassFilesVerbose(true); } /** * When disassembling .class files, look for source files in this directory. Source file loading is disabled by * default. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setDaSourceDirectory(File directory) { this.diff.setDisassembleClassFilesSourceDirectory(directory); } /** * When disassembling .class files, don't display lines debug info. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setDaNoLines() { this.diff.setDisassembleClassFilesButHideLines(true); } /** * When disassembling .class files, don't display local variables debug info. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setDaNoVars() { this.diff.setDisassembleClassFilesButHideVars(true); } /** * When disassembling .class files, use symbolic labels /'L12') instead of numeric labels ('#123'). * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setDaSymbolicLabels() { this.diff.setDisassembleClassFilesSymbolicLabels(true); } /** * Lines in files path-pattern that contain line-regex and all capturing groups are equal are * regarded as equal. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(cardinality = CommandLineOption.Cardinality.ANY) public void addLineEquivalence( @RegexFlags(Pattern2.WILDCARD | Glob.INCLUDES_EXCLUDES) Glob pathPattern, Pattern lineRegex ) { this.diff.addEquivalentLine(new LineEquivalence(pathPattern, lineRegex)); } /** * Ignore differences in files path-pattern where all lines (deleted, changed or added) match the given * line-regex. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(name = { "ignore", "I" }, cardinality = CommandLineOption.Cardinality.ANY) public void addIgnore( @RegexFlags(Pattern2.WILDCARD | Glob.INCLUDES_EXCLUDES) Glob pathPattern, Pattern lineRegex ) { this.diff.addIgnore(new LineEquivalence(pathPattern, lineRegex)); } /** * Ignore whitespace differences. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(name = { "ignore-whitespace", "w" }) public void setIgnoreWhitespace() { this.diff.setIgnoreWhitespace(true); } @CommandLineOptionGroup interface AddedFileModeOptionGroup {} @CommandLineOptionGroup interface DeletedFileModeOptionGroup {} /** * How to deal with added resp. deleted files: *
*
REPORT (the default):
*
* Print "File added path", resp. "File deleted path" (or, with "--exist" or "--brief", * print "+ path" resp. "- path"). *
*
COMPARE_WITH_EMPTY:
*
* Generate a diff document by comparing with the empty document. *
*
IGNORE:
*
* Do nothing. *
*
* * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(group = AddedFileModeOptionGroup.class) public void setAddedFile(AbsentFileMode mode) { this.diff.setAddedFileMode(mode); } /** * @see #setAddedFile(Diff.AbsentFileMode) */ @CommandLineOption(group = DeletedFileModeOptionGroup.class) public void setDeletedFile(AbsentFileMode mode) { this.diff.setDeletedFileMode(mode); } /** * Treat absent files as empty. This is shorthand for "{@code --added-file COMPARE_WITH_EMPTY --deleted-file * COMPARE_WITH_EMPTY}". * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption( name = { "new-file", "N" }, group = { AddedFileModeOptionGroup.class, DeletedFileModeOptionGroup.class } ) public void setNewFile() { this.diff.setAddedFileMode(AbsentFileMode.COMPARE_WITH_EMPTY); this.diff.setDeletedFileMode(AbsentFileMode.COMPARE_WITH_EMPTY); } /** * Treat added files as (previously) empty; deleted files are only reported. This is shorthand for "{@code * --added-file COMPARE_WITH_EMPTY --deleted-file REPORT}". * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(group = { AddedFileModeOptionGroup.class, DeletedFileModeOptionGroup.class }) public void setUnidirectionalNewFile() { this.diff.setAddedFileMode(AbsentFileMode.COMPARE_WITH_EMPTY); this.diff.setDeletedFileMode(AbsentFileMode.REPORT); } /** * Also report unchanged files. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setUnchangedFiles() { this.diff.setReportUnchangedFiles(true); } /** * Encoding of the files being compared (defaults to the JVM default charset, "${file.encoding}"). * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setEncoding(Charset charsetName) { this.diff.setCharset(charsetName); } // OUTPUT GENERATION OPTIONS /** * Write DIFF to output-file instead of STDOUT. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setOut(File outputFile) { this.outputFile = outputFile; } @CommandLineOptionGroup interface DiffModeOptionGroup {} /** * Output "normal diff format"; this is the default. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(group = DiffModeOptionGroup.class) public void setNormal() { this.diff.setDiffMode(DiffMode.NORMAL); } /** * Report only which files were added or deleted (do not report changed content). * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(group = DiffModeOptionGroup.class) public void setExist() { this.diff.setDiffMode(DiffMode.EXIST); } /** * Report only which files were added, deleted or changed. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "brief", "q" }, group = DiffModeOptionGroup.class) public void setBrief() { this.diff.setDiffMode(DiffMode.BRIEF); } /** * Output "context diff format" with three * lines of context. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "context", "c" }, group = DiffModeOptionGroup.class) public void setContext() { this.diff.setDiffMode(DiffMode.CONTEXT); } /** * Output "context diff format" with * amount lines of context. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "Context", "C" }, group = DiffModeOptionGroup.class) public void setContext2(int amount) { this.diff.setDiffMode(DiffMode.CONTEXT); this.diff.setContextSize(amount); } /** * Output "unified diff format" with three * lines of context. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "unified", "u" }, group = DiffModeOptionGroup.class) public void setUnified() { this.diff.setDiffMode(DiffMode.UNIFIED); } /** * Output "unified diff format" with * amount lines of context. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = "Unified", group = DiffModeOptionGroup.class) public void setUnified2(int amount) { this.diff.setDiffMode(DiffMode.UNIFIED); this.diff.setContextSize(amount); } /** * Report errors and continue with next file. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setKeepGoing() { this.diff.setExceptionHandler(new ExceptionHandler() { @Override public void handle(String path, IOException ioe) { Printers.error(path, ioe.getMessage()); } @Override public void handle(String path, RuntimeException re) { Printers.error(path, re.getMessage()); } }); } /** * Scan directories strictly sequentially. The default is to parallelize the directory scan in multiple threads. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setSequential() { this.diff.setSequential(true); } /** * Regard documents as streams of Java tokens; the whitespace between tokens (including line breaks) is then * insignificant. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setJavaTokenization() { this.diff.setTokenization(Tokenization.JAVA); } /** * Don't regard C-style comments ("/* ... */") as relevant for comparison. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setIgnoreCStyleComments() { this.diff.setIgnoreCStyleComments(true); } /** * Don't regard C++-style comments ("// ...") as relevant for comparison. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = "ignore-c++-style-comments") public void setIgnoreCppStyleComments() { this.diff.setIgnoreCPlusPlusStyleComments(true); } /** * Don't regard doc comments ("/** ... */") as relevant for comparison. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setIgnoreDocComments() { this.diff.setIgnoreDocComments(true); } /** * Password to decrypt password-protected 7ZIP input files. */ @SuppressWarnings("static-method") @CommandLineOption public void set7zInputFilePassword(String value) { SevenZArchiveFormat.setPassword(value.getBytes(Charsets.UTF_16LE)); } /** * Password to decrypt password-protected zip archive entries. */ @SuppressWarnings("static-method") @CommandLineOption public void setZipInputFilePassword(String value) { ZipArchiveFormat.setInputFilePasswordChars(value.toCharArray()); } /** * All of the above. */ @CommandLineOption public void setPassword(String value) { this.set7zInputFilePassword(value); this.setZipInputFilePassword(value); } /** * Suppress all output except errors. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setNowarn() { this.levelFilteredPrinter.setNoWarn(); SimpleLogging.setNoWarn(); } /** * Suppress "normal" output; print only errors and warnings. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setQuiet() { this.levelFilteredPrinter.setQuiet(); SimpleLogging.setQuiet(); } /** * Print verbose messages. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setVerbose() { this.levelFilteredPrinter.setVerbose(); SimpleLogging.setVerbose(); } /** * Print verbose and debug messages. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setDebug() { this.levelFilteredPrinter.setDebug(); SimpleLogging.setDebug(); SimpleLogging.setDebug(); SimpleLogging.setDebug(); } /** * Add logging at level {@code FINE} on logger "{@code de.unkrig}" to STDERR using the {@code FormatFormatter} * and {@code SIMPLE} format, or the given arguments (which are all optional). * * @param spec level:logger:handler:formatter:format * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(cardinality = CommandLineOption.Cardinality.ANY) public static void addLog(String spec) { SimpleLogging.configureLoggers(spec); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy