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

dev.jeka.core.tool.PicocliHelp Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024  the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package dev.jeka.core.tool;

import dev.jeka.core.api.file.JkPathSequence;
import dev.jeka.core.api.java.JkClassLoader;
import dev.jeka.core.api.java.JkUrlClassLoader;
import dev.jeka.core.api.system.JkInfo;
import dev.jeka.core.api.system.JkProperties;
import dev.jeka.core.api.utils.JkUtilsString;

import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;

class PicocliHelp {

    private static final String SECTION_OTHER_KBEANS_HEADING = "otherKBeanHeading";

    private static final String SECTION_OTHER_KBEANS_DETAILS = "otherKbeanDetails";

    private static final String SECTION_STD_KBEANS_HEADING = "stdKBeanHeading";

    private static final String SECTION_STD_KBEANS_DETAILS = "stdKbeanDetails";

    private static final String SECTION_SHORTHANDS_HEADING = "shorthandHeading";

    private static final String SECTION_SHORTHANDS_DETAILS = "shortHandDetails";

    static void printUsageHelp(PrintStream printStream) {
        CommandLine commandLine = PicocliCommands.mainCommandLine();
        commandLine.usage(printStream, colorScheme());
    }

    static void printVersionHelp(PrintStream printStream) {
        printStream.print(JkInfo.getJekaVersion());  // no need of carriage return
    }

    static void printCmdHelp(JkPathSequence classpath,
                             Engine.KBeanResolution kbeanResolution,
                             JkProperties props,
                             PrintStream printStream) {
        cmdHelp(classpath, kbeanResolution, props)
                .usage(printStream, colorScheme());
    }

    // return false if kbeanName does not math any KBean
    static boolean printKBeanHelp(JkPathSequence classpath,
                                 List kbeanClassNames,
                                 String kbeanName,
                                 PrintStream printStream) {
        CommandLine commandLine = kbeanHelp(classpath, kbeanClassNames, kbeanName);
        if (commandLine == null) {
            return false;
        }
        commandLine.usage(printStream, colorScheme());
        return true;
    }

    private static CommandLine cmdHelp(
            JkPathSequence classpath,
            Engine.KBeanResolution kbeanResolution,
            JkProperties props) {

        String defaultKBeanClassName = kbeanResolution.defaultKbeanClassname;

        ClassLoader classLoader = JkUrlClassLoader.of(classpath).get();

        // Add commands and options from default KBean
        final CommandLine.Model.CommandSpec main;
        KBeanDescription beanDescription = null;
        if (defaultKBeanClassName != null) {
            Class defaultKBeanClass = JkClassLoader.of(classLoader).load(defaultKBeanClassName);
            beanDescription = KBeanDescription.of(defaultKBeanClass, true);
            main = PicocliCommands.fromKBeanDesc(beanDescription);
        } else {
            main = dev.jeka.core.tool.CommandLine.Model.CommandSpec.create().name("");
        }

        // Configure Usage
        main.usageMessage().synopsisHeading("");
        List synopsis = new LinkedList<>();

        // -- Case defaultKBean is present
        if (defaultKBeanClassName != null) {
            synopsis.add(String.format("Default KBean @|yellow %s:|@ (%s)",
                    KBean.name(defaultKBeanClassName), defaultKBeanClassName));
            if (!JkUtilsString.isBlank(beanDescription.synopsisHeader)) {
                synopsis.add("Description  : " + beanDescription.synopsisHeader);
            }
            synopsis.add("\nFields");
        }
        main.usageMessage()
                .autoWidth(true)
                .customSynopsis(synopsis.toArray(new String[0]))
                .commandListHeading("Methods\n");

        // Add section for Standard KBeans
        Map stdKBeans = new LinkedHashMap<>();
        for (Class kbeanClass : PicocliCommands.STANDARD_KBEAN_CLASSES) {
            KBeanDescription description = KBeanDescription.of(kbeanClass, false);
            String name = KBean.name(kbeanClass);
            stdKBeans.put( String.format("@|yellow %s:|@", name), description.synopsisHeader);
        }
        CommandLine commandLine = new CommandLine(main);
        commandLine.getHelpSectionMap().put(SECTION_STD_KBEANS_HEADING,
                help -> help.createHeading("\nStandard KBeans\n"));
        commandLine.getHelpSectionMap().put(SECTION_STD_KBEANS_DETAILS,
                help -> help.createTextTable(stdKBeans).toString());
        List keys = new ArrayList<>(commandLine.getHelpSectionKeys());
        int index = keys.indexOf(dev.jeka.core.tool.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING);
        keys.add(index, SECTION_STD_KBEANS_HEADING);
        keys.add(index + 1, SECTION_STD_KBEANS_DETAILS);
        commandLine.setHelpSectionKeys(keys);

        // Add section for other KBeans
        List others = new LinkedList<>(kbeanResolution.allKbeans);
        List stdKbeanClassNames = PicocliCommands.STANDARD_KBEAN_CLASSES.stream()
                        .map(Class::getName).collect(Collectors.toList());
        others.removeAll(stdKbeanClassNames);
        others.remove(defaultKBeanClassName);
        if (!others.isEmpty()) {
            Map> kbeanNameClassMap = beanNameClassMap(classLoader, others);
            Map kbeans = new HashMap<>();
            for (Map.Entry> entry : kbeanNameClassMap.entrySet()) {
                KBeanDescription description = KBeanDescription.of(entry.getValue(), false);
                kbeans.put(String.format("@|yellow %s:|@", entry.getKey()), description.synopsisHeader);
            }
            commandLine.getHelpSectionMap().put(SECTION_OTHER_KBEANS_HEADING,
                    help -> help.createHeading("\nOther KBeans\n"));
            commandLine.getHelpSectionMap().put(SECTION_OTHER_KBEANS_DETAILS,
                    help -> help.createTextTable(kbeans).toString());
            index = keys.indexOf(dev.jeka.core.tool.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING);
            keys.add(index, SECTION_OTHER_KBEANS_HEADING);
            keys.add(index + 1, SECTION_OTHER_KBEANS_DETAILS);
            commandLine.setHelpSectionKeys(keys);
        }

        // Add Section for shortcut
        Map shorthands = cmdShortHand(props);
        if (!shorthands.isEmpty()) {
            commandLine.getHelpSectionMap().put(SECTION_SHORTHANDS_HEADING,
                    help -> help.createHeading("\nShorthands\n"));
            commandLine.getHelpSectionMap().put(SECTION_SHORTHANDS_DETAILS,
                    help -> help.createTextTable(shorthands).toString());
            index = keys.indexOf(dev.jeka.core.tool.CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING);
            keys.add(index, SECTION_SHORTHANDS_HEADING);
            keys.add(index + 1, SECTION_SHORTHANDS_DETAILS);
            commandLine.setHelpSectionKeys(keys);
        }

        main.usageMessage()
                .footer("", "Execute @|yellow jeka : --doc|@ (as @|italic jeka docker: --doc|@) " +
                        "to get details on specific KBean.");

        return commandLine;

    }

    private static CommandLine kbeanHelp(JkPathSequence classpath,
                                   List kbeanClassNames,
                                   String kbeanName) {

        String kbeanClassName = kbeanClassNames.stream()
                .filter(clazzName -> KBean.nameMatches(clazzName, kbeanName))
                .findFirst().orElse(null);
        if (kbeanClassName == null) {
            return null;
        }

        ClassLoader classLoader = JkUrlClassLoader.of(classpath).get();
        Class defaultKBeanClass = JkClassLoader.of(classLoader).load(kbeanClassName);
        KBeanDescription beanDescription = KBeanDescription.of(defaultKBeanClass, true);
        CommandLine.Model.CommandSpec main = PicocliCommands.fromKBeanDesc(beanDescription);

        // Configure Usage
        main.usageMessage().synopsisHeading("");
        List synopsis = new LinkedList<>();
        synopsis.add(String.format("KBean @|yellow %s:|@ (%s)",
                    KBean.name(kbeanName), kbeanClassName));
        synopsis.add("");
        if (!JkUtilsString.isBlank(beanDescription.synopsisHeader)) {
            String header = beanDescription.synopsisHeader.trim();
            header = header.endsWith(".") ? header : header + ".";
            synopsis.add(header);
            List descLines = Arrays.asList(beanDescription.synopsisDetail.split("\n"));
            synopsis.addAll(descLines);
        }
        if (!beanDescription.beanFields.isEmpty()) {
            synopsis.add("Fields");
        }
        main.usageMessage()
                .autoWidth(true)
                .customSynopsis(synopsis.toArray(new String[0]))
                .commandListHeading("Methods\n");

        return new CommandLine(main).setUsageHelpAutoWidth(true);
    }


    private static Map> beanNameClassMap(ClassLoader classLoader,
                                                                        List kbeanClasses) {
        Map> result = new LinkedHashMap<>();
        kbeanClasses.stream().forEach(className -> {
            Class clazz = JkClassLoader.of(classLoader).load(className);
            result.put(KBean.name(className), clazz);
        });
        return result;
    }

    private static CommandLine.Help.ColorScheme colorScheme() {
        return new CommandLine.Help.ColorScheme.Builder()
                .commands    (dev.jeka.core.tool.CommandLine.Help.Ansi.Style.fg_yellow)    // combine multiple styles
                .options     (dev.jeka.core.tool.CommandLine.Help.Ansi.Style.fg_yellow)                // yellow foreground color
                .parameters  (dev.jeka.core.tool.CommandLine.Help.Ansi.Style.fg_yellow)
                .optionParams(dev.jeka.core.tool.CommandLine.Help.Ansi.Style.italic)
                .errors      (dev.jeka.core.tool.CommandLine.Help.Ansi.Style.fg_red, dev.jeka.core.tool.CommandLine.Help.Ansi.Style.bold)
                .stackTraces (dev.jeka.core.tool.CommandLine.Help.Ansi.Style.italic)
                .applySystemProperties() // optional: allow end users to customize
                .build();
    }

    private static Map cmdShortHand(JkProperties props) {
        return props.getAllStartingWith(JkConstants.CMD_PREFIX_PROP, false).entrySet().stream()
                .filter(entry -> !entry.getKey().startsWith("_"))
                .collect(Collectors.toMap(entry -> String.format("@|yellow ::%s|@", entry.getKey()),
                        Map.Entry::getValue));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy