Search in sources :

Example 1 with CliOption

use of org.springframework.roo.shell.CliOption in project spring-roo by spring-projects.

the class HelpServiceImpl method helpReferenceGuide.

/**
 * {@inheritDoc}
 *
 * TODO: Refactor this method to use the Freemarker template engine. See {@link #obtainHelp(String)}.
 */
public void helpReferenceGuide() {
    synchronized (mutex) {
        // Get all Services implement CommandMarker interface
        try {
            ServiceReference<?>[] references = this.context.getAllServiceReferences(CommandMarker.class.getName(), null);
            for (ServiceReference<?> ref : references) {
                CommandMarker command = (CommandMarker) this.context.getService(ref);
                if (!commands.contains(command)) {
                    add(command);
                }
            }
        } catch (InvalidSyntaxException e) {
            LOGGER.warning("Cannot load CommandMarker on SimpleParser.");
        }
        final File f = new File(".");
        final File[] existing = f.listFiles(new FileFilter() {

            public boolean accept(final File pathname) {
                return pathname.getName().startsWith("appendix_");
            }
        });
        for (final File e : existing) {
            e.delete();
        }
        // Compute the sections we'll be outputting, and get them into a
        // nice order
        final SortedMap<String, Object> sections = new TreeMap<String, Object>(COMPARATOR);
        next_target: for (final Object target : commands) {
            final Method[] methods = target.getClass().getMethods();
            for (final Method m : methods) {
                final CliCommand cmd = m.getAnnotation(CliCommand.class);
                if (cmd != null) {
                    String sectionName = target.getClass().getSimpleName();
                    final Pattern p = Pattern.compile("[A-Z][^A-Z]*");
                    final Matcher matcher = p.matcher(sectionName);
                    final StringBuilder string = new StringBuilder();
                    while (matcher.find()) {
                        string.append(matcher.group()).append(" ");
                    }
                    sectionName = string.toString().trim();
                    if (sections.containsKey(sectionName)) {
                        throw new IllegalStateException("Section name '" + sectionName + "' not unique");
                    }
                    sections.put(sectionName, target);
                    continue next_target;
                }
            }
        }
        // Build each section of the appendix
        final DocumentBuilder builder = XmlUtils.getDocumentBuilder();
        final Document document = builder.newDocument();
        final List<Element> builtSections = new ArrayList<Element>();
        for (final Entry<String, Object> entry : sections.entrySet()) {
            final String section = entry.getKey();
            final Object target = entry.getValue();
            final SortedMap<String, Element> individualCommands = new TreeMap<String, Element>(COMPARATOR);
            final Method[] methods = target.getClass().getMethods();
            for (final Method m : methods) {
                final CliCommand cmd = m.getAnnotation(CliCommand.class);
                if (cmd != null) {
                    final StringBuilder cmdSyntax = new StringBuilder();
                    cmdSyntax.append(cmd.value()[0]);
                    // Build the syntax list
                    // Store the order options appear
                    final List<String> optionKeys = new ArrayList<String>();
                    // key: option key, value: help text
                    final Map<String, String> optionDetails = new HashMap<String, String>();
                    for (final Annotation[] ann : m.getParameterAnnotations()) {
                        for (final Annotation a : ann) {
                            if (a instanceof CliOption) {
                                final CliOption option = (CliOption) a;
                                // Figure out which key we want to use (use
                                // first non-empty string, or make it
                                // "(default)" if needed)
                                String key = option.key()[0];
                                if ("".equals(key)) {
                                    for (final String otherKey : option.key()) {
                                        if (!"".equals(otherKey)) {
                                            key = otherKey;
                                            break;
                                        }
                                    }
                                    if ("".equals(key)) {
                                        key = "[default]";
                                    }
                                }
                                final StringBuilder help = new StringBuilder();
                                if ("".equals(option.help())) {
                                    help.append("No help available");
                                } else {
                                    help.append(option.help());
                                }
                                if (option.specifiedDefaultValue().equals(option.unspecifiedDefaultValue())) {
                                    if (option.specifiedDefaultValue().equals(null)) {
                                        help.append("; no default value");
                                    } else {
                                        help.append("; default: '").append(option.specifiedDefaultValue()).append("'");
                                    }
                                } else {
                                    if (!"".equals(option.specifiedDefaultValue()) && !NULL.equals(option.specifiedDefaultValue())) {
                                        help.append("; default if option present: '").append(option.specifiedDefaultValue()).append("'");
                                    }
                                    if (!"".equals(option.unspecifiedDefaultValue()) && !NULL.equals(option.unspecifiedDefaultValue())) {
                                        help.append("; default if option not present: '").append(option.unspecifiedDefaultValue()).append("'");
                                    }
                                }
                                help.append(option.mandatory() ? " " : "");
                                // Store details for later
                                key = "--" + key;
                                optionKeys.add(key);
                                optionDetails.put(key, help.toString());
                                // Include it in the mandatory syntax
                                if (option.mandatory()) {
                                    cmdSyntax.append(" ").append(key);
                                }
                            }
                        }
                    }
                    // Make a variable list element
                    Element variableListElement = document.createElement("variablelist");
                    boolean anyVars = false;
                    for (final String optionKey : optionKeys) {
                        anyVars = true;
                        final String help = optionDetails.get(optionKey);
                        variableListElement.appendChild(new XmlElementBuilder("varlistentry", document).addChild(new XmlElementBuilder("term", document).setText(optionKey).build()).addChild(new XmlElementBuilder("listitem", document).addChild(new XmlElementBuilder("para", document).setText(help).build()).build()).build());
                    }
                    if (!anyVars) {
                        variableListElement = new XmlElementBuilder("para", document).setText("This command does not accept any options.").build();
                    }
                    // Now we've figured out the options, store this
                    // individual command
                    final CDATASection progList = document.createCDATASection(cmdSyntax.toString());
                    final String safeName = cmd.value()[0].replace("\\", "BCK").replace("/", "FWD").replace("*", "ASX");
                    final Element element = new XmlElementBuilder("section", document).addAttribute("xml:id", "command-index-" + safeName.toLowerCase().replace(' ', '-')).addChild(new XmlElementBuilder("title", document).setText(cmd.value()[0]).build()).addChild(new XmlElementBuilder("para", document).setText(cmd.help()).build()).addChild(new XmlElementBuilder("programlisting", document).addChild(progList).build()).addChild(variableListElement).build();
                    individualCommands.put(cmdSyntax.toString(), element);
                }
            }
            final Element topSection = document.createElement("section");
            topSection.setAttribute("xml:id", "command-index-" + section.toLowerCase().replace(' ', '-'));
            topSection.appendChild(new XmlElementBuilder("title", document).setText(section).build());
            topSection.appendChild(new XmlElementBuilder("para", document).setText(section + " are contained in " + target.getClass().getName() + ".").build());
            for (final Element value : individualCommands.values()) {
                topSection.appendChild(value);
            }
            builtSections.add(topSection);
        }
        final Element appendix = document.createElement("appendix");
        appendix.setAttribute("xmlns", "http://docbook.org/ns/docbook");
        appendix.setAttribute("version", "5.0");
        appendix.setAttribute("xml:id", "command-index");
        appendix.appendChild(new XmlElementBuilder("title", document).setText("Command Index").build());
        appendix.appendChild(new XmlElementBuilder("para", document).setText("This appendix was automatically built from Roo " + AbstractShell.versionInfo() + ".").build());
        appendix.appendChild(new XmlElementBuilder("para", document).setText("Commands are listed in alphabetic order, and are shown in monospaced font with any mandatory options you must specify when using the command. Most commands accept a large number of options, and all of the possible options for each command are presented in this appendix.").build());
        for (final Element section : builtSections) {
            appendix.appendChild(section);
        }
        document.appendChild(appendix);
        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        final Transformer transformer = XmlUtils.createIndentingTransformer();
        // Causes an
        // "Error reported by XML parser: Multiple notations were used which had the name 'linespecific', but which were not determined to be duplicates."
        // when creating the DocBook
        // transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
        // "-//OASIS//DTD DocBook XML V4.5//EN");
        // transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
        // "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd");
        XmlUtils.writeXml(transformer, byteArrayOutputStream, document);
        try {
            final File output = new File(f, "appendix-command-index.xml");
            FileUtils.writeByteArrayToFile(output, byteArrayOutputStream.toByteArray());
        } catch (final IOException ioe) {
            throw new IllegalStateException(ioe);
        } finally {
            IOUtils.closeQuietly(byteArrayOutputStream);
        }
    }
}
Also used : CommandMarker(org.springframework.roo.shell.CommandMarker) Transformer(javax.xml.transform.Transformer) Matcher(java.util.regex.Matcher) HashMap(java.util.HashMap) Element(org.w3c.dom.Element) ArrayList(java.util.ArrayList) Document(org.w3c.dom.Document) CliOption(org.springframework.roo.shell.CliOption) CDATASection(org.w3c.dom.CDATASection) InvalidSyntaxException(org.osgi.framework.InvalidSyntaxException) FileFilter(java.io.FileFilter) Pattern(java.util.regex.Pattern) Method(java.lang.reflect.Method) ByteArrayOutputStream(java.io.ByteArrayOutputStream) IOException(java.io.IOException) TreeMap(java.util.TreeMap) XmlElementBuilder(org.springframework.roo.support.util.XmlElementBuilder) Annotation(java.lang.annotation.Annotation) ServiceReference(org.osgi.framework.ServiceReference) DocumentBuilder(javax.xml.parsers.DocumentBuilder) CliCommand(org.springframework.roo.shell.CliCommand) File(java.io.File)

Example 2 with CliOption

use of org.springframework.roo.shell.CliOption in project spring-roo by spring-projects.

the class HelpServiceImpl method obtainHelp.

/**
 * If the given pattern matches several commands, i.e. "web mvc"
 * or empty, this method writes to {@link #LOGGER} the list of
 * commands (command index) which names start with the given pattern.
 *
 * If the given pattern matches with only one command, this method
 * writes to {@link #LOGGER} the full info about that command.
 *
 * @param pattern
 */
public void obtainHelp(String pattern) {
    synchronized (mutex) {
        if (pattern == null) {
            pattern = "";
        }
        Template helpTemplate;
        // Create the data-model for Freemarker engine.
        Map<String, Object> fmContext = new HashMap<String, Object>();
        fmContext.put("LINE_SEPARATOR", IOUtils.LINE_SEPARATOR);
        fmContext.put("CMD_MAX_LENGTH", CMD_MAX_LENGTH);
        fmContext.put("OPT_MAX_LENGTH", OPT_MAX_LENGTH);
        // Get the methods annotated with @CliCommand that matches the pattern
        final Collection<MethodTarget> matchingTargets = locateTargets(pattern, false, false);
        try {
            // In that case the full command help will be rendered.
            if (matchingTargets.size() == 1) {
                helpTemplate = new Template("cmdTemplate", new StringReader(cmdTemplateStr), new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS));
                // Single command help
                final MethodTarget methodTarget = matchingTargets.iterator().next();
                // Argument conversion time
                final Annotation[][] parameterAnnotations = methodTarget.getMethod().getParameterAnnotations();
                // Offer specified help
                final CliCommand cmd = methodTarget.getMethod().getAnnotation(CliCommand.class);
                Validate.notNull(cmd, "CliCommand not found");
                // The command has options, those method arguments annotated with @CliOption
                if (parameterAnnotations.length > 0) {
                    // Synopsis
                    fmContext.put("synopsis", justify(cmd.value(), CMD_HELP_LEFT_PAD, LINE_MAX_LENGTH));
                    // Description
                    fmContext.put("description", justify(cmd.help(), CMD_HELP_LEFT_PAD, LINE_MAX_LENGTH));
                    // Options
                    Map<String, List<String>> options = new TreeMap<String, List<String>>();
                    fmContext.put("options", options);
                    // method arguments annotated with the @CliOption annotation
                    for (final Annotation[] annotations : parameterAnnotations) {
                        CliOption cliOption = null;
                        for (final Annotation a : annotations) {
                            if (a instanceof CliOption) {
                                cliOption = (CliOption) a;
                                for (final String option : cliOption.key()) {
                                    String dashOption = "--".concat(option);
                                    // Note justification should be done in the Freemarker template,
                                    // but it is easier to do it here and adjust both justifications
                                    // (cmd name and cmd help) depending on the cmd name length
                                    String optStr = StringUtils.repeat(" ", CMD_HELP_LEFT_PAD) + (dashOption.length() <= OPT_MAX_LENGTH ? StringUtils.rightPad(dashOption, OPT_MAX_LENGTH) : dashOption);
                                    // Add as left padding the cmd length to avoid overwrite the command on the left
                                    // +1 to add an empty char (space) between the command and the description
                                    options.put(optStr, justify(cliOption.help(), CMD_HELP_LEFT_PAD + OPT_MAX_LENGTH, LINE_MAX_LENGTH));
                                }
                            }
                        }
                    }
                } else // The command hasn't options, usually methods without arguments
                {
                    // Synopsis
                    fmContext.put("synopsis", justify(cmd.value(), CMD_HELP_LEFT_PAD, LINE_MAX_LENGTH));
                    // Description
                    fmContext.put("description", justify(cmd.help(), CMD_HELP_LEFT_PAD, LINE_MAX_LENGTH));
                }
            } else // There are several commands that matches the pattern. Example: "web mvc"
            // In that case only a list of command names and descriptions will be rendered.
            {
                helpTemplate = new Template("cmdIndexTemplate", new StringReader(cmdIndexTemplateStr), new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS));
                // Create the list of commands that start with the given token.
                // Note that empty token will cause all commands will be rendered.
                Map<String, List<String>> cmdList = new TreeMap<String, List<String>>();
                // method annotation @CliCommand
                for (final MethodTarget mt : matchingTargets) {
                    final CliCommand cmd = mt.getMethod().getAnnotation(CliCommand.class);
                    if (cmd != null) {
                        for (final String value : cmd.value()) {
                            // Note justification should be done in the Freemarker template,
                            // but it is easier to do it here and adjust both justifications
                            // (cmd name and cmd help) depending on the cmd name length
                            String cmdStr = StringUtils.repeat(" ", CMD_INDEX_LEFT_PAD) + (value.length() <= CMD_MAX_LENGTH ? StringUtils.rightPad(value, CMD_MAX_LENGTH) : value);
                            // Add as left padding the cmd length to avoid overwrite the command on the left
                            // +1 to add an empty char (space) between the command and the description
                            cmdList.put(cmdStr, justify(cmd.help(), CMD_INDEX_LEFT_PAD + CMD_MAX_LENGTH, LINE_MAX_LENGTH));
                        }
                    }
                }
                // Add the command list to the Freemarker context
                fmContext.put("commands", cmdList);
            }
            // Merge data-model with template
            Writer strWriter = new StringWriter();
            helpTemplate.process(fmContext, strWriter);
            LOGGER.info(strWriter.toString());
        } catch (TemplateException e) {
            LOGGER.log(Level.SEVERE, "Help engine internal error!", e);
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "Help engine internal error!", e);
        }
        LOGGER.warning("** Type 'hint' (without the quotes) and hit ENTER " + "for step-by-step guidance **" + LINE_SEPARATOR);
    }
}
Also used : MethodTarget(org.springframework.roo.shell.MethodTarget) Configuration(freemarker.template.Configuration) HashMap(java.util.HashMap) TemplateException(freemarker.template.TemplateException) IOException(java.io.IOException) TreeMap(java.util.TreeMap) Annotation(java.lang.annotation.Annotation) Template(freemarker.template.Template) CliOption(org.springframework.roo.shell.CliOption) StringWriter(java.io.StringWriter) StringReader(java.io.StringReader) CliCommand(org.springframework.roo.shell.CliCommand) List(java.util.List) ArrayList(java.util.ArrayList) Writer(java.io.Writer) StringWriter(java.io.StringWriter)

Aggregations

IOException (java.io.IOException)2 Annotation (java.lang.annotation.Annotation)2 ArrayList (java.util.ArrayList)2 HashMap (java.util.HashMap)2 TreeMap (java.util.TreeMap)2 CliCommand (org.springframework.roo.shell.CliCommand)2 CliOption (org.springframework.roo.shell.CliOption)2 Configuration (freemarker.template.Configuration)1 Template (freemarker.template.Template)1 TemplateException (freemarker.template.TemplateException)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 File (java.io.File)1 FileFilter (java.io.FileFilter)1 StringReader (java.io.StringReader)1 StringWriter (java.io.StringWriter)1 Writer (java.io.Writer)1 Method (java.lang.reflect.Method)1 List (java.util.List)1 Matcher (java.util.regex.Matcher)1 Pattern (java.util.regex.Pattern)1