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

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

The newest version!

/*
 * de.unkrig.grep - An advanced version of the UNIX GREP 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.grep;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

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

import de.unkrig.commons.file.ExceptionHandler;
import de.unkrig.commons.file.fileprocessing.FileProcessings;
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.lang.AssertionUtil;
import de.unkrig.commons.lang.protocol.ProducerWhichThrows;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.text.AbstractPrinter;
import de.unkrig.commons.text.LevelFilteredPrinter;
import de.unkrig.commons.text.Printers;
import de.unkrig.commons.text.RedirectablePrinter;
import de.unkrig.commons.text.pattern.Glob;
import de.unkrig.commons.text.pattern.IncludeExclude;
import de.unkrig.commons.text.pattern.Pattern2;
import de.unkrig.commons.util.CommandLineOptionException;
import de.unkrig.commons.util.CommandLineOptions;
import de.unkrig.commons.util.annotation.CommandLineOption;
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.grep.Grep.Operation;

/**
 * A GREP utility that looks recursively into directories, archive files and compressed files
 */
public final
class Main {

    static { AssertionUtil.enableAssertionsForThisClass(); }

    private Main() {}

    private final Grep grep = new Grep();
    {
        this.grep.setExceptionHandler(new ExceptionHandler() {
            @Override public void handle(String path, IOException ioe)     { Printers.error(path, ioe); }
            @Override public void handle(String path, RuntimeException re) { Printers.error(path, re);  }
        });
    }
    @Nullable private Boolean    withPath; // null = "default behavior"
    private boolean              caseSensitive  = true;
    private final IncludeExclude includeExclude = new IncludeExclude();

    private final List regexes = new ArrayList();

    private final RedirectablePrinter  redirectablePrinter  = new RedirectablePrinter();
    private final LevelFilteredPrinter levelFilteredPrinter = new LevelFilteredPrinter(this.redirectablePrinter);

    /**
     * 

Usage:

*
*
{@code zzgrep} [ option ... ] [ {@code -} ] regex file-or-dir ...
*
* Reads file or all files under dir and prints all lines which contain matches of the * regex. *
*
{@code zzgrep} [ option ... ] regex
*
* Reads STDIN and prints all lines which contain matches of the regex. *
*
* *

Options:

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

File Selection:

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

Contents Processing:

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

Output Generation:

*

* By default, print each matching line, prefixed with the filename/path (iff two or more files are * specified on the command line). *

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

Example globs:

*
*
{@code dir/file}
*
* File "{@code file}" in directory "{@code dir}" *
*
{@code file.gz%}
*
* Compressed file "{@code file.gz}" *
*
{@code file.zip!dir/file}
*
* Entry "{@code dir/file}" in archive file "{@code dir/file.zip}" *
*
{@code file.tar.gz%!dir/file}
*
* Entry "{@code dir/file}" in the compressed archive file "{@code file.tar.gz}" *
*
*/x
*
* File "{@code x}" in an immediate subdirectory *
*
**/x
*
* File "{@code x}" in any subdirectory *
*
***/x
*
* File "{@code x}" in any subdirectory, or any entry "**/x" in any archive file in any * subdirectory *
*
{@code a,dir/file.7z!dir/b}
*
* File "{@code a}" and entry "{@code dir/b}" in archive file "{@code dir/file.7z}" *
*
{@code ~*.c}
*
* Files that don't end with "{@code .c}" *
*
{@code ~*.c~*.h}
*
* Files that don't end with "{@code .c}" or "{@code .h}" *
*
{@code ~*.c~*.h,foo.c}
*
* "{@code foo.c}" plus all files that don't end with "{@code .c}" or "{@code .h}" *
*
*

* Exit status is 0 if any line was selected, 1 otherwise; if any error occurs, the exit status is 2. *

*/ public static void main(final String[] args) { final Main main = new Main(); main.levelFilteredPrinter.run(new Runnable() { @Override public void run() { main.main2(args); } }); } private void main2(String[] args) { try { this.main3(args); } catch (Exception e) { Printers.error(null, e); System.exit(2); } } private void main3(String[] args) throws IOException, InterruptedException { try { args = CommandLineOptions.parse(args, this); } catch (CommandLineOptionException cloe) { Printers.error(cloe.getMessage() + ", try \"--help\"."); System.exit(2); } int argi = 0; // Next command line argument is the regex (unless "-e" was used). if (this.regexes.isEmpty()) { if (argi >= args.length) { System.err.println("Regex missing, try \"--help\"."); System.exit(2); } this.regexes.add(args[argi++]); } // Configure search objects. for (String regex : this.regexes) { this.grep.addSearch(this.includeExclude, regex, this.caseSensitive); } // If neither "--with-path" nor "--no-path" was configured on the command line, compute the default behavior. this.grep.setWithPath(this.withPath != null ? this.withPath : args.length - argi >= 2); // Process files command line arguments. final List files = new ArrayList(); while (argi < args.length) files.add(new File(args[argi++])); if (files.isEmpty()) { this.grep.contentsProcessor().process( "(standard input)", // path System.in, // inputStream null, // lastModifiedDate -1L, // size -1L, // crc32 new ProducerWhichThrows() { // opener @Override @Nullable public InputStream produce() { throw new UnsupportedOperationException(); } } ); } else { FileProcessings.process(files, this.grep.fileProcessor(true)); } if (!this.grep.getLinesSelected()) System.exit(1); } /** * Print this text and terminate. */ @CommandLineOption public static 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); } /** * By default directory members are sorted lexicographically in order to achieve deterministic results. * * @main.commandLineOptionGroup File-Selection */ @CommandLineOption public void dontSortDirectoryMembers() { this.grep.setDirectoryMemberNameComparator(null); } /** * Look into compressed and archive contents if "format:path" matches the 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 */ @CommandLineOption public void lookInto(@RegexFlags(Pattern2.WILDCARD | Glob.INCLUDES_EXCLUDES) Glob glob) { this.grep.setLookIntoFormat(glob); } /** * Input contents encoding, default is "${file.encoding}". * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setInputEncoding(Charset charset) { this.grep.setCharset(charset); } /** * Contents encoding, default is "${file.encoding}". * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setOutputEncoding(Charset charset) { PrintWriter stdout = new PrintWriter(new OutputStreamWriter(System.out, charset), /*autoFlush*/ true); PrintWriter stderr = new PrintWriter(new OutputStreamWriter(System.err, charset), /*autoFlush*/ true); this.redirectablePrinter.setDelegate(new AbstractPrinter() { @Override public void error(@Nullable String message) { stderr.println(message); } @Override public void warn(@Nullable String message) { stderr.println(message); } @Override public void info(@Nullable String message) { stdout.println(message); } @Override public void verbose(@Nullable String message) { stdout.println(message); } @Override public void debug(@Nullable String message) { stdout.println(message); } }); } /** * Input and output contents encoding, default is "${file.encoding}". * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption public void setEncoding(Charset charset) { this.setInputEncoding(charset); this.setOutputEncoding(charset); } /** * Print only filename/path, colon, and match count. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-c", "--count" }) public void setCount() { this.grep.setOperation(Operation.COUNT); } /** * Print only filename/path of documents containing matches. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-l", "--list", "--files-with-matches" }) public void setFilesWithMatches() { this.grep.setOperation(Operation.FILES_WITH_MATCHES); } /** * Print only filename/path of documents that do not contain any matches. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-L", "--files-without-match" }) public void setFilesWithoutMatch() { this.grep.setOperation(Operation.FILES_WITHOUT_MATCH); } /** * Print this instead of filename/path * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = "--label") public void setLabel(String value) { this.grep.setLabel(value); } /** * Prefix each match with the document filename/path (default if two or more files are specified on the command * line) * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-H", "--with-path", "--with-filename" }) public void setWithPath() { this.withPath = true; } /** * Do not prefix each match with the document path (default if zero or one files are specified on the * command line) * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-h", "--no-path", "--no-filename" }) public void setNoPath() { this.withPath = false; } /** * Prefix each match with the line number * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-n", "--line-number" }) public void setLineNumber() { this.grep.setWithLineNumber(true); } /** * Prefix each match with the byte offset * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-b", "--byte-offset" }) public void setByteOffset() { this.grep.setWithByteOffset(true); } /** * Print n lines of context before matching lines * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-B", "--before-context" }) public void setBeforeContext(int n) { this.grep.setBeforeContext(n); } /** * Print n lines of context after matching lines * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-A", "--after-context" }) public void setAfterContext(int n) { this.grep.setAfterContext(n); } /** * Print n lines of context before and after matching lines * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-C", "--context" }) public void setContext(int n) { this.grep.setBeforeContext(n); this.grep.setAfterContext(n); } /** * Print only the matched parts; prints one line per match * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-o", "--only-matching" }) public void setOnlyMatching() { this.grep.setOperation(Operation.ONLY_MATCHING); } /** * Stop reading after n matching lines * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "-m", "--max-count" }) public void setMaxCount(int n) { this.grep.setMaxCount(n); } /** * Ignore case distinctions. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(name = { "i", "ignore-case" }) public void ignoreCase() { this.caseSensitive = false; } /** * Select non-matching lines. * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption(name = { "v", "inverted" }) public void inverted() { this.grep.setInverted(true); } /** * @main.commandLineOptionGroup Contents-Processing * @see Grep#setDisassembleClassFiles(boolean) */ @CommandLineOption(name = { "da", "disassemble-class-files" }) public void setDisassemble() { this.grep.setDisassembleClassFiles(true); } /** * @main.commandLineOptionGroup Contents-Processing * @see Grep#setDisassembleClassFilesVerbose(boolean) */ @CommandLineOption public void setDaVerbose() { this.grep.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.grep.setDisassembleClassFilesSourceDirectory(directory); } /** * @main.commandLineOptionGroup Contents-Processing * @see Grep#setDisassembleClassFilesButHideLines(boolean) */ @CommandLineOption(name = { "da-no-lines", "disassemble-class-files-but-hide-lines" }) public void setDisassembleClassFilesButHideLines() { this.grep.setDisassembleClassFilesButHideLines(true); } /** * @main.commandLineOptionGroup Contents-Processing * @see Grep#setDisassembleClassFilesButHideVars(boolean) */ @CommandLineOption(name = { "da-no-vars", "disassemble-class-files-but-hide-vars" }) public void setDisassembleClassFilesButHideVars() { this.grep.setDisassembleClassFilesButHideVars(true); } /** * @main.commandLineOptionGroup Contents-Processing * @see Grep#setDisassembleClassFilesSymbolicLabels(boolean) */ @CommandLineOption public void setDaSymbolicLabels() { this.grep.setDisassembleClassFilesSymbolicLabels(true); } /** * Excludes files and archive entries from the search iff the file's / archive entry's path matches the * regex. *

* Any number of {@code --exclude} and {@code --include} options can be given, and later take precedence over * the earlier. *

*

* The default is that all files and archive entries are included; thus you would always begin with a * {@code --exclude} option. *

* * @main.commandLineOptionGroup File-Selection */ @CommandLineOption(cardinality = CommandLineOption.Cardinality.ANY) public void addExclude(@RegexFlags(Pattern2.WILDCARD) Glob regex) { this.includeExclude.addExclude(regex, true); } /** * @see #addExclude(Glob) * * @main.commandLineOptionGroup File-Selection */ @CommandLineOption(cardinality = CommandLineOption.Cardinality.ANY) public void addInclude(@RegexFlags(Pattern2.WILDCARD) Glob regex) { this.includeExclude.addInclude(regex, true); } /** * Suppress all normal output. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption(name = { "q", "quiet", "silent" }) public void setQuiet() { this.grep.setOperation(Operation.QUIET); this.levelFilteredPrinter.setQuiet(); SimpleLogging.setQuiet(); } /** * 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 messages except errors. * * @main.commandLineOptionGroup Output-Generation */ @CommandLineOption public void setNowarn() { this.levelFilteredPrinter.setNoWarn(); SimpleLogging.setNoWarn(); } /** * Print the names of files and archive entries that are searched. * * @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(); } /** * Useful for multiple regexes or regexes starting with "-" * * @main.commandLineOptionGroup Contents-Processing */ @CommandLineOption( name = { "-e", "--regexp", "--regex" }, cardinality = CommandLineOption.Cardinality.ANY ) public void addPattern(String regex) { this.regexes.add(regex); } /** * 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); } // Deprecated options. /** * @main.commandLineOptionGroup File-Selection * @deprecated Use {@code --look-into} instead. */ @Deprecated @CommandLineOption(name = { "zip", "zz", "nested-zip", "gzip" }) public static void noLongerSupported() { System.err.println("Command line option is no longer supported - try \"--help\"."); System.exit(2); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy