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

cdc.gv.tools.GvToAny Maven / Gradle / Ivy

package cdc.gv.tools;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.io.IoBuilder;

import cdc.gv.tools.GvToAny.MainArgs.Feature;
import cdc.util.cli.AbstractMainSupport;
import cdc.util.cli.FeatureMask;
import cdc.util.cli.OptionEnum;
import cdc.util.files.Files;
import cdc.util.files.SearchPath;
import cdc.util.lang.Piper;
import cdc.util.time.Chronometer;

/**
 * Program used to convert a Graphviz file to different image formats, using one
 * of the available layout engines.
 *
 * @author Damien Carbonne
 *
 */
public final class GvToAny {
    protected static final Logger LOGGER = LogManager.getLogger(GvToAny.class);
    private static final PrintStream OUT = IoBuilder.forLogger(LOGGER).setLevel(Level.INFO).buildPrintStream();
    private final MainArgs margs;

    public static class MainArgs {
        /** Name of the input Graphviz file. */
        public File input;

        public MainArgs setInput(File input) {
            this.input = input;
            return this;
        }

        /** Name of the output directory. */
        public File outputDir;

        public MainArgs setOutputDir(File outputDir) {
            this.outputDir = outputDir;
            return this;
        }

        /** Paths where Graphviz layout engines can be found. */
        public SearchPath paths = new SearchPath();

        /**
         * The timeout in milliseconds before killing GraphViz.
         * 

* A negative value has no effect. */ public long timeout = -1L; /** Engine to use for layout. */ public GvEngine engine; public MainArgs setEngine(GvEngine engine) { this.engine = engine; return this; } /** Extra arguments to pass to the layout engine. */ public final List args = new ArrayList<>(); public MainArgs addArgs(String... args) { for (final String arg : args) { this.args.add(arg); } return this; } /** * Extra arguments to pass to gvpack. *

* Related to use of {@link Feature#PACK} */ public final List gvpackArgs = new ArrayList<>(); public MainArgs addGvpackArgs(String... args) { for (final String arg : args) { this.gvpackArgs.add(arg); } return this; } /** * Extra arguments to pass to ccomps. *

* Related to use of {@link Feature#PACK} */ public final List ccompsArgs = new ArrayList<>(); public MainArgs addCcompsArgs(String... args) { for (final String arg : args) { this.ccompsArgs.add(arg); } return this; } /** Output formats to use. */ public final Set formats = EnumSet.noneOf(GvFormat.class); public MainArgs addFormat(GvFormat format) { this.formats.add(format); return this; } /** Enabling of features. */ protected final FeatureMask features = new FeatureMask<>(); /** * Enumeration of possible boolean options. */ public enum Feature implements OptionEnum { PACK("pack", "Split graph in subgraphs of connected components (using ccomps) and uses gvpack."), VERBOSE("verbose", "Enable verbose mode. Add '-v' to command args to make Graphviz engine verbose."), INVOKE_IF_NEWER("invoke-if-newer", "Invoke engine if target files don't exist or are older than input file."); private final String name; private final String description; private Feature(String name, String description) { this.name = name; this.description = description; } @Override public final String getName() { return name; } @Override public final String getDescription() { return description; } } public MainArgs setEnabled(Feature feature, boolean enabled) { features.setEnabled(feature, enabled); return this; } public boolean isEnabled(Feature feature) { return features.isEnabled(feature); } } private GvToAny(MainArgs margs) { this.margs = margs; } private boolean appendFormats(List command, String basename) { // True if conversion should be invoked // If INVOKE_IF_NEWER is disabled, always invoke // Otherwise, we must check boolean invoke = !margs.isEnabled(Feature.INVOKE_IF_NEWER); for (final GvFormat format : margs.formats) { command.add("-T" + format.getFormatName()); final File target = new File(margs.outputDir, basename + "." + format.getFormatName()); command.add("-o" + target.getPath()); if (!invoke && (!target.exists() || Files.isNewerThan(margs.input, target))) { invoke = true; } } return invoke; } private void execute() { final String basename = margs.input.getName().replace(".gv", ""); final File exec = margs.paths.resolveExe(margs.engine.getEngineName()); final List> commands = new ArrayList<>(); final boolean invoke; if (margs.isEnabled(Feature.PACK)) { final File ccomps = margs.paths.resolveExe("ccomps"); final File gvpack = margs.paths.resolveExe("gvpack"); final File neato = margs.paths.resolveExe("neato"); // ccomps final List cmd1 = new ArrayList<>(); cmd1.add(ccomps.getPath()); // cmd1.add("-x"); cmd1.add(margs.input.getPath()); for (final String arg : margs.ccompsArgs) { cmd1.add(arg); } commands.add(cmd1); // engine final List cmd2 = new ArrayList<>(); cmd2.add(exec.getPath()); for (final String arg : margs.args) { cmd2.add(arg); } commands.add(cmd2); // gvpack final List cmd3 = new ArrayList<>(); cmd3.add(gvpack.getPath()); for (final String arg : margs.gvpackArgs) { cmd3.add(arg); } commands.add(cmd3); // neato final List cmd4 = new ArrayList<>(); cmd4.add(neato.getPath()); cmd4.add("-s"); cmd4.add("-n2"); invoke = appendFormats(cmd4, basename); commands.add(cmd4); } else { // engine final List cmd1 = new ArrayList<>(); cmd1.add(exec.getPath()); cmd1.add(margs.input.getPath()); for (final String arg : margs.args) { cmd1.add(arg); } invoke = appendFormats(cmd1, basename); commands.add(cmd1); } if (invoke) { final boolean verbose = margs.isEnabled(MainArgs.Feature.VERBOSE); if (verbose) { final StringBuilder builder = new StringBuilder(); boolean first = true; for (final List command : commands) { if (first) { first = false; } else { builder.append(" | "); } builder.append(command); } LOGGER.info(builder); } final Chronometer chrono = new Chronometer(); chrono.start(); final OutputStream os = new ByteArrayOutputStream(); try (final InputStream is = Piper.pipeSplitList(margs.timeout, commands)) { int b; while ((b = is.read()) != -1) { os.write(b); if (verbose) { OUT.write(b); } } chrono.suspend(); if (verbose) { LOGGER.info("Done in {}", chrono); } } catch (final IOException | InterruptedException e) { LOGGER.error("execute() failed with commands: {} {}", commands, e.getMessage()); LOGGER.error(os.toString()); } } } public static void execute(MainArgs margs) { final GvToAny main = new GvToAny(margs); main.execute(); } public static void main(String[] args) { final MainSupport support = new MainSupport(); support.main(args); } private static class MainSupport extends AbstractMainSupport { private static final String ARG = "arg"; private static final String CCOMPS_ARG = "ccomps-arg"; private static final String GVPACK_ARG = "gvpack-arg"; private static final String TIMEOUT = "timeout"; public MainSupport() { super(GvToAny.class, LOGGER); } @Override protected String getVersion() { return Config.VERSION; } @Override protected void addSpecificOptions(Options options) { options.addOption(Option.builder("i") .longOpt(INPUT) .desc("Gv input FILE/URL") .hasArg() .required() .build()); options.addOption(Option.builder("o") .longOpt(OUTPUT) .desc("Output directory") .hasArg() .required() .build()); options.addOption(Option.builder() .longOpt(ARG) .desc("Optional arguments to be passed to Graphviz layout engine.") .hasArgs() .build()); options.addOption(Option.builder() .longOpt(CCOMPS_ARG) .desc("Optional arguments to be passed to ccomps. Useful when PACK option is enabled.") .hasArgs() .build()); options.addOption(Option.builder() .longOpt(GVPACK_ARG) .desc("Optional arguments to be passed to gvpack. Useful when PACK option is enabled.") .hasArgs() .build()); options.addOption(Option.builder() .longOpt(PATH) .desc("Directory(ies) where the Graphviz binaries can be found.") .hasArgs() .build()); options.addOption(Option.builder() .longOpt(TIMEOUT) .desc("Optional timeout, in milliseconds, to wait before killing GraphViz.") .hasArg() .build()); addNoArgOptions(options, MainArgs.Feature.class); addNoArgOptions(options, GvEngine.class); addNoArgOptions(options, GvFormat.class); } @Override protected MainArgs analyze(CommandLine cl) throws ParseException { final MainArgs margs = new MainArgs(); margs.input = new File(cl.getOptionValue(INPUT)); if (!margs.input.isFile()) { throw new ParseException("Invalid input: " + cl.getOptionValue('i')); } margs.outputDir = new File(cl.getOptionValue(OUTPUT)); margs.timeout = getValueAsLong(cl, TIMEOUT, -1L); if (cl.hasOption(PATH)) { margs.paths = new SearchPath(cl.getOptionValues(PATH)); } for (final GvEngine engine : GvEngine.values()) { if (cl.hasOption(engine.getName())) { margs.engine = engine; } } if (margs.engine == null) { margs.engine = GvEngine.DOT; } if (cl.hasOption(ARG)) { for (final String arg : cl.getOptionValues(ARG)) { margs.args.add(arg); } } if (cl.hasOption(CCOMPS_ARG)) { for (final String arg : cl.getOptionValues(CCOMPS_ARG)) { margs.ccompsArgs.add(arg); } } if (cl.hasOption(GVPACK_ARG)) { for (final String arg : cl.getOptionValues(GVPACK_ARG)) { margs.gvpackArgs.add(arg); } } for (final GvFormat format : GvFormat.values()) { if (cl.hasOption(format.getName())) { margs.formats.add(format); } } if (margs.formats.isEmpty()) { throw new ParseException("No output format"); } setMask(cl, MainArgs.Feature.class, margs.features::setEnabled); return margs; } @Override protected Void execute(MainArgs margs) { GvToAny.execute(margs); return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy