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

xyz.ottr.lutra.cli.CLI Maven / Gradle / Ivy

Go to download

This Lutra submodule provides a command-line interface (CLI) for Lutra. This modules is intended used as a Java executable. Other usage interfaces are provided by the lutra-api and lutra-restapi modules. Lutra is the reference implementation of the OTTR framework. For more information about OTTR, see http://ottr.xyz.

There is a newer version: 0.6.19
Show newest version
package xyz.ottr.lutra.cli;

/*-
 * #%L
 * lutra-cli
 * %%
 * Copyright (C) 2018 - 2019 University of Oslo
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiFunction;
import org.apache.commons.lang3.StringUtils;
import org.apache.jena.rdf.model.Model;
import picocli.CommandLine;
import picocli.CommandLine.ParameterException;
import xyz.ottr.lutra.TemplateManager;
import xyz.ottr.lutra.api.StandardFormat;
import xyz.ottr.lutra.api.StandardTemplateManager;
import xyz.ottr.lutra.docttr.DocttrManager;
import xyz.ottr.lutra.io.Files;
import xyz.ottr.lutra.io.Format;
import xyz.ottr.lutra.model.Instance;
import xyz.ottr.lutra.stottr.util.SSyntaxChecker;
import xyz.ottr.lutra.system.Message;
import xyz.ottr.lutra.system.MessageHandler;
import xyz.ottr.lutra.system.Result;
import xyz.ottr.lutra.system.ResultStream;
import xyz.ottr.lutra.wottr.io.RDFIO;

public class CLI {

    private final Settings settings;
    private final PrintStream outStream;
    private final MessageHandler messageHandler;
    private final StandardTemplateManager templateManager;

    public CLI(PrintStream outStream, PrintStream errStream) {
        this.settings = new Settings();
        this.outStream = outStream;
        this.messageHandler = new MessageHandler(errStream);
        this.templateManager = new StandardTemplateManager();
    }

    public CLI() {
        this(System.out, System.err);
    }

    /**
     * NB! This method uses System.exit to return an exit code. System.exit terminates the JVM, therefore
     * this method should only be used for scripting execution, and not for application servers or unit tests;
     * use instead {@link #executeArgs(String[])}.
     */
    public static void main(String[] args) {
        int exitCode = new CLI().executeArgs(args);
        System.exit(exitCode);
    }

    public int executeArgs(String[] args) {

        CommandLine cli = new CommandLine(this.settings);
        try {
            cli.parseArgs(args);
        } catch (ParameterException ex) {
            Message err = Message.error(ex.getMessage());
            this.messageHandler.printMessage(err);
            return cli.getCommandSpec().exitCodeOnInvalidInput();
        }

        this.messageHandler.setQuiet(this.settings.quiet);

        if (cli.isUsageHelpRequested()) {
            cli.usage(this.outStream);
            return cli.getCommandSpec().exitCodeOnUsageHelp();
        }
        if (cli.isVersionHelpRequested()) {
            cli.printVersionHelp(this.outStream);
            return cli.getCommandSpec().exitCodeOnVersionHelp();
        }
        if (!checkOptionsValid()) {
            return cli.getCommandSpec().exitCodeOnInvalidInput();
        }

        try {
            execute();
            if (this.messageHandler.getMostSevere().isGreaterThan(Message.Severity.WARNING)) {
                // with messages of severity error or higher we return with an error return code
                return cli.getCommandSpec().exitCodeOnExecutionException();
            }
            return cli.getCommandSpec().exitCodeOnSuccess();
        } catch (Exception e) {
            Message err = Message.fatal(e);
            this.messageHandler.printMessage(err);
            return cli.getCommandSpec().exitCodeOnExecutionException();
        }
    }

    /**
     * Checks that the provided options form a meaningful execution,
     * otherwise prints an error message.
     */
    private boolean checkOptionsValid() {

        if (this.settings.inputs.isEmpty()
            && (this.settings.mode == Settings.Mode.expand
                || this.settings.mode == Settings.Mode.format
                || this.settings.mode == Settings.Mode.checkSyntax)) {
            this.messageHandler.printMessage(Message.error("Must provide one or more input files. "
                + "For help on usage, use the --help option."));
            return false;
        } else if (this.settings.library == null
            && (this.settings.mode == Settings.Mode.expandLibrary
                || this.settings.mode == Settings.Mode.formatLibrary
                || this.settings.mode == Settings.Mode.docttrLibrary
                || this.settings.mode == Settings.Mode.lint)) {

            this.messageHandler.printMessage(Message.error("Must provide a library. "
                + "For help on usage, use the --help option."));
            return false;
        }
        return true;
    }


    ////////////////////////////////////////////////////////////
    /// MAIN EXECUTION                                       ///
    ////////////////////////////////////////////////////////////

    private void initTemplateManager() {
        this.templateManager.setFullTrace(this.settings.debugFullTrace);
        this.templateManager.setStackTrace(this.settings.debugStackTrace);
        //this.templateManager.setHaltOn(this.settings.haltOn);
        this.templateManager.setFetchMissingDependencies(this.settings.fetchMissingDependencies);
        this.templateManager.setExtensions(this.settings.extensions);
        this.templateManager.setIgnoreExtensions(this.settings.ignoreExtensions);
    }

    private boolean initLibrary() {
        return initStandardLibrary().isLessThan(this.settings.haltOn)
            && parseLibrary().isLessThan(this.settings.haltOn)
            && parsePrefixes().isLessThan(this.settings.haltOn)
            && checkLibrary().isLessThan(this.settings.haltOn);
    }

    private void execute() {

        initTemplateManager();

        if (this.settings.mode == Settings.Mode.checkSyntax) {
            executeCheckSyntax();
        } else {

            if (!initLibrary()) {
                return;
            }

            switch (this.settings.mode) {
                case expand:
                    executeExpand();
                    break;
                case expandLibrary:
                    executeExpandLibrary();
                    break;
                case formatLibrary:
                    executeFormatLibrary();
                    break;
                case format:
                    executeFormat();
                    break;
                case docttrLibrary:
                    docttrTemplates(this.templateManager);
                    break;
                case lint:
                    break;
                default:
                    Message err = Message.error("The mode " + this.settings.mode + " is not yet supported.");
                    this.messageHandler.printMessage(err);
            }

        }
    }

    private void executeCheckSyntax() {

        if (this.settings.inputFormat.equals(StandardFormat.stottr.toString())) {

            for (String file : this.settings.inputs) {
                this.outStream.println("Checking file: " + file);
                var checker = new SSyntaxChecker(this.messageHandler);
                try {
                    checker.checkFile(Paths.get(file));
                    this.messageHandler.printMessages();
                } catch (IOException e) {
                    this.outStream.println("Error checking file.");
                    e.printStackTrace(this.outStream);
                }
            }
        } else {
            this.outStream.println("Unsupported format " + this.settings.inputFormat);
        }
    }

    private void executeExpand() {
        writeInstances(parseAndExpandInstances());
    }

    private void executeFormat() {
        writeInstances(parseInstances());
    }

    private void executeExpandLibrary() {
        this.messageHandler.use(this.templateManager.expandStore(), this::writeTemplates);
    }

    private void executeFormatLibrary() {
        writeTemplates(this.templateManager);
    }

    private Message.Severity checkLibrary() {
        MessageHandler msgs = this.templateManager.checkTemplates();
        Message.Severity severity = this.settings.quiet ? msgs.getMostSevere() : printMessages(msgs);

        if (this.settings.mode == Settings.Mode.lint
            && !this.settings.quiet
            && severity.isLessThan(Message.Severity.WARNING)) {

            // Print message if linting and no errors found
            this.outStream.println("No errors found.");
        }
        return severity;
    }

    ////////////////////////////////////////////////////////////
    /// Parsing and writing                                  ///
    ////////////////////////////////////////////////////////////

    private Message.Severity printMessages(MessageHandler handler) {
        this.messageHandler.combine(handler);
        return this.messageHandler.printMessages();
    }

    private Message.Severity initStandardLibrary() {
        // Load standard library
        var msgs = this.templateManager.loadStandardTemplateLibrary();
        return printMessages(msgs);
    }

    private Message.Severity parseLibrary() {

        if (this.settings.library == null || this.settings.library.length == 0) {
            return Message.Severity.least();
        }

        return printMessages(this.templateManager.readLibrary(this.settings.libraryFormat, Arrays.asList(this.settings.library)));
    }

    private Message.Severity parsePrefixes() {
        if (!StringUtils.isNotBlank(this.settings.prefixes)) {
            return Message.Severity.least();
        }
        Result userPrefixes = RDFIO.fileReader().parse(this.settings.prefixes);
        return this.messageHandler.use(userPrefixes, this.templateManager::addPrefixes);
    }

    public ResultStream parseInstances() {
        return this.templateManager.readInstances(this.settings.inputFormat, this.settings.inputs);
    }

    public ResultStream parseAndExpandInstances() {
        return parseInstances().innerFlatMap(this.templateManager.makeExpander());
    }

    private void writeInstances(ResultStream ins) {
        PrintStream consoleStream = shouldPrintOutput() ? this.outStream : null;
        var msgs = this.templateManager.writeInstances(ins, this.settings.outputFormat, this.settings.out, consoleStream);
        printMessages(msgs);
    }

    private void writeTemplates(TemplateManager templateManager) {

        // TODO: This is a temporary workaround. The lookup (including error handing if no such format
        //  for formats have now moved from here into TemplateManager and FormatManager using Result. The
        //  assumption that a format has only one suffix is probably wrong, e.g., RDF has many serialisations,
        //  so this should probably be separated from the Format.
        String suffix = templateManager.getFormatManager()
            .getFormat(this.settings.outputFormat)
            .map(Format::getDefaultFileSuffix)
            .orElse("");

        var msgs = templateManager.writeTemplates(this.settings.outputFormat, makeTemplateWriter(suffix));
        printMessages(msgs);
    }

    private void docttrTemplates(TemplateManager templateManager) {
        var docttr = new DocttrManager(this.outStream, templateManager);
        docttr.write(Path.of(this.settings.out));
    }

    private BiFunction> makeTemplateWriter(String suffix) {
        return (iri, str) -> {
            if (shouldPrintOutput()) {
                this.outStream.println(str);
            }
            if (this.settings.out != null) {
                return Files.writeTemplatesTo(iri, str, this.settings.out, suffix);
            }
            return Optional.empty();
        };
    }

    private boolean shouldPrintOutput() {
        return this.settings.stdout || this.settings.out == null;
    }
    
    public MessageHandler getMessageHandler() {
        return this.messageHandler;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy