001/*
002 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 */
017package org.nuxeo.shell.cmds;
018
019import java.io.File;
020import java.io.FileWriter;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.PrintWriter;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028
029import jline.ANSIBuffer;
030
031import org.nuxeo.shell.Argument;
032import org.nuxeo.shell.Command;
033import org.nuxeo.shell.CommandRegistry;
034import org.nuxeo.shell.CommandType;
035import org.nuxeo.shell.Context;
036import org.nuxeo.shell.Parameter;
037import org.nuxeo.shell.Shell;
038import org.nuxeo.shell.ShellConsole;
039import org.nuxeo.shell.ShellException;
040import org.nuxeo.shell.CommandType.Token;
041import org.nuxeo.shell.cmds.completors.CommandRegistryCompletor;
042import org.nuxeo.shell.fs.FileCompletor;
043import org.nuxeo.shell.fs.FileSystem;
044import org.nuxeo.shell.utils.ANSICodes;
045import org.nuxeo.shell.utils.StringUtils;
046
047/**
048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
049 */
050@Command(name = "help", help = "The help command")
051public class Help implements Runnable {
052
053    @Context
054    protected Shell shell;
055
056    @Argument(name = "command", required = false, index = 0, help = "the name of the command to get help for")
057    protected CommandType cmd;
058
059    @Parameter(name = "-export", hasValue = true, completor = FileCompletor.class, help = "If used export all the commands available in a wiki formatto the given file. If adirectory is given the export will be made in file help.wiki in that directory.")
060    protected File export;
061
062    @Parameter(name = "-ns", hasValue = true, completor = CommandRegistryCompletor.class, help = "[optional] - to be used to filter commands by namespaces when generating the documentation. By default all namespaces are dumped.")
063    protected CommandRegistry ns;
064
065    public void run() {
066        ShellConsole console = shell.getConsole();
067        if (export != null) {
068            if (export.isDirectory()) {
069                export = new File(export, "help.wiki");
070            }
071            try {
072                if (ns == null) {
073                    exportCommands(shell, export);
074                } else {
075                    exportRegistry(shell, ns, export);
076                }
077                console.println("Commands wiki exported in " + export);
078            } catch (Throwable t) {
079                throw new ShellException("Failed to export commands wiki", t);
080            }
081            return;
082        }
083        if (cmd == null) {
084            showMainPage(console);
085        } else {
086            console.println(getCommandHelp(cmd, false).toString());
087        }
088    }
089
090    public void showMainPage(ShellConsole console) {
091        ANSIBuffer buf = shell.newANSIBuffer();
092        InputStream in = getClass().getClassLoader().getResourceAsStream("META-INF/help.txt");
093        if (in != null) {
094            try {
095                String content = FileSystem.readContent(in);
096                String versionVar = "${version}";
097                int i = content.indexOf(versionVar);
098                if (i > -1) {
099                    content = content.substring(0, i) + Shell.class.getPackage().getImplementationVersion()
100                            + content.substring(i + versionVar.length());
101                }
102                ANSICodes.appendTemplate(buf, content, false);
103                buf.append(ShellConsole.CRLF);
104            } catch (IOException e) {
105                throw new ShellException(e);
106            } finally {
107                try {
108                    in.close();
109                } catch (IOException e) {
110                    e.printStackTrace();
111                }
112            }
113        }
114
115        console.println(buf.toString());
116
117    }
118
119    public void exportRegistry(Shell shell, CommandRegistry reg, File file) throws Exception {
120        StringBuilder sb = new StringBuilder();
121        writeRegistry(reg, sb);
122        PrintWriter w = new PrintWriter(new FileWriter(file));
123        try {
124            w.println(sb.toString());
125        } finally {
126            w.close();
127        }
128    }
129
130    public void exportCommands(Shell shell, File file) throws Exception {
131        HashMap<String, StringBuilder> wikis = new HashMap<String, StringBuilder>();
132        for (String name : shell.getRegistryNames()) {
133            StringBuilder sb = new StringBuilder();
134            writeRegistry(shell.getRegistry(name), sb);
135            wikis.put(name, sb);
136        }
137        // sort registries: first global, then local, then remote, exclude
138        // automation?
139        PrintWriter w = new PrintWriter(new FileWriter(file));
140        try {
141            for (StringBuilder sb : wikis.values()) {
142                w.println(sb.toString());
143            }
144        } finally {
145            w.close();
146        }
147    }
148
149    protected void writeRegistry(CommandRegistry reg, StringBuilder sb) {
150        sb.append("{info:title=Namespce: *" + reg.getName() + "*}" + reg.getDescription());
151        sb.append("{info}\nh1. Index\n{toc:minLevel=2|maxLevel=2}\n\n");
152        HashSet<String> aliases = new HashSet<String>();
153        for (CommandType cmd : reg.getLocalCommandTypes()) {
154            if (!aliases.contains(cmd.getName())) {
155                writeCommand(cmd, sb);
156                aliases.add(cmd.getName());
157            }
158        }
159    }
160
161    protected void writeCommand(CommandType cmd, StringBuilder sb) {
162        ANSIBuffer buf = getCommandHelp(cmd, true);
163        sb.append("h2. ").append(cmd.getName()).append("\n").append(buf.toString(false));
164    }
165
166    protected ANSIBuffer getCommandHelp(CommandType cmd, boolean wiki) {
167        ANSIBuffer buf = shell.newANSIBuffer();
168        header(buf, "NAME", wiki).append(ShellConsole.CRLF).append("\t");
169        buf.append(cmd.getName()).append(" -- ").append(cmd.getHelp());
170        buf.append(ShellConsole.CRLF).append(ShellConsole.CRLF);
171        header(buf, "SYNTAX", wiki).append(ShellConsole.CRLF).append("\t");
172        syntax(buf, cmd.getSyntax(), wiki);
173        buf.append(ShellConsole.CRLF).append(ShellConsole.CRLF);
174
175        String[] aliases = cmd.getAliases();
176        if (aliases != null && aliases.length > 0) {
177            if (aliases != null && aliases.length > 0) {
178                header(buf, "ALIASES", wiki).append(ShellConsole.CRLF).append("\t").append(
179                        StringUtils.join(aliases, ", "));
180                buf.append(ShellConsole.CRLF).append(ShellConsole.CRLF);
181            }
182        }
183
184        List<Token> args = cmd.getArguments();
185        Collection<Token> opts = cmd.getParameters().values();
186
187        if (!opts.isEmpty()) {
188            header(buf, "OPTIONS", wiki).append(ShellConsole.CRLF);
189            for (Token tok : opts) {
190                if (wiki) {
191                    buf.append("*");
192                }
193                String flag = tok.isRequired ? " - " : " - [flag] - ";
194                buf.append("\t" + tok.name + flag + tok.help).append(ShellConsole.CRLF);
195            }
196            buf.append(ShellConsole.CRLF);
197        }
198        if (!args.isEmpty()) {
199            header(buf, "ARGUMENTS", wiki).append(ShellConsole.CRLF);
200            for (Token tok : args) {
201                if (wiki) {
202                    buf.append("*");
203                }
204                String flag = tok.isRequired ? " - [required] - " : " - [optional] -";
205                buf.append("\t" + tok.name + flag + tok.help).append(ShellConsole.CRLF);
206            }
207            buf.append(ShellConsole.CRLF);
208        }
209
210        InputStream in = cmd.getCommandClass().getResourceAsStream(cmd.getCommandClass().getSimpleName() + ".help");
211        if (in != null) {
212            try {
213                String content = FileSystem.readContent(in);
214                ANSICodes.appendTemplate(buf, content, wiki);
215                buf.append(ShellConsole.CRLF);
216            } catch (IOException e) {
217                throw new ShellException(e);
218            } finally {
219                try {
220                    in.close();
221                } catch (IOException e) {
222                    e.printStackTrace();
223                }
224            }
225        }
226        return buf;
227    }
228
229    protected ANSIBuffer header(ANSIBuffer buf, String text, boolean wiki) {
230        if (wiki) {
231            buf.append("*").append(text).append("*");
232        } else {
233            buf.bold(text);
234        }
235        return buf;
236    }
237
238    protected ANSIBuffer boldify(ANSIBuffer buf, String text, boolean wiki) {
239        if (wiki) {
240            buf.append("*").append(text).append("*");
241        } else {
242            buf.bold(text);
243        }
244        return buf;
245    }
246
247    protected ANSIBuffer syntax(ANSIBuffer buf, String syntax, boolean wiki) {
248        if (wiki) {
249            buf.append("{code}").append(syntax).append("{code}");
250        } else {
251            buf.append(syntax);
252        }
253        return buf;
254    }
255
256    protected void formatCommandForWiki() {
257
258    }
259
260}