You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/12/18 15:25:03 UTC

[karaf] branch karaf-4.3.x updated: [KARAF-1717] - add markdown format option to commands-generate-help goal

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch karaf-4.3.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.3.x by this push:
     new 9fb068f735 [KARAF-1717] - add markdown format option to commands-generate-help goal
9fb068f735 is described below

commit 9fb068f73502bd38b60875becd83a6b65885805d
Author: Aleksy Wróblewski <al...@bbbit.io>
AuthorDate: Sat Oct 15 15:23:38 2022 +0200

    [KARAF-1717] - add markdown format option to commands-generate-help goal
    
    (cherry picked from commit c019959531a9ade18e1b274d12e081319db3c3ce)
---
 .../developer-guide/karaf-maven-plugin.adoc        |   2 +-
 pom.xml                                            |   1 +
 .../src/it/test-commands-generate-help/pom.xml     |  47 +++++++
 .../test-markdown/pom.xml                          |  74 +++++++++++
 .../src/main/java/test/BarCommand.java             |  38 ++++++
 .../src/main/java/test/FooCommand.java             |  35 +++++
 .../test-markdown/src/main/resources/commands.md   |   7 +
 .../test-markdown/src/main/resources/test-bar.md   |  24 ++++
 .../test-markdown/src/main/resources/test-foo.md   |  18 +++
 .../src/it/test-commands-generate-help/verify.bsh  |  70 ++++++++++
 .../apache/karaf/tooling/commands/FormatEnum.java  |  49 +++++++
 .../karaf/tooling/commands/GenerateHelpMojo.java   |  66 ++++-----
 .../commands/MarkdownCommandHelpPrinter.java       | 147 +++++++++++++++++++++
 13 files changed, 535 insertions(+), 43 deletions(-)

diff --git a/manual/src/main/asciidoc/developer-guide/karaf-maven-plugin.adoc b/manual/src/main/asciidoc/developer-guide/karaf-maven-plugin.adoc
index 993e6ce249..de3477ced1 100644
--- a/manual/src/main/asciidoc/developer-guide/karaf-maven-plugin.adoc
+++ b/manual/src/main/asciidoc/developer-guide/karaf-maven-plugin.adoc
@@ -119,7 +119,7 @@ The example below generates help for the commands in the current project:
 
 |`format`
 |`String`
-|The output format (docbx, asciidoc, or conf) of the commands documentation. Default value: docbx
+|The output format (docbx, asciidoc, conf or md) of the commands documentation. Default value: docbx
 
 |`classLoader`
 |`String`
diff --git a/pom.xml b/pom.xml
index eaef20aa66..86ba630f89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -831,6 +831,7 @@
                                 <exclude>**/foo</exclude>
                                 <exclude>**/org.ops4j.pax.exam.TestContainerFactory</exclude>
                                 <exclude>**/maven-metadata-local.xml</exclude>
+                                <exclude>**/resources/*.md</exclude>
                                 <!-- jline -->
                                 <exclude>**/src/main/java/jline/**/*.java</exclude>
                                 <!-- test manifests -->
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/pom.xml b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/pom.xml
new file mode 100644
index 0000000000..d88c9951ce
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You 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
+
+            http://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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>test</groupId>
+    <artifactId>test-commands-generate-help</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.karaf.shell</groupId>
+                <artifactId>org.apache.karaf.shell.core</artifactId>
+                <version>@pom.version@</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>test-markdown</module>
+    </modules>
+</project>
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/pom.xml b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/pom.xml
new file mode 100644
index 0000000000..e6de774140
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You 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
+
+            http://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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>test</groupId>
+        <artifactId>test-commands-generate-help</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <packaging>bundle</packaging>
+
+    <artifactId>
+        test-markdown
+    </artifactId>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-maven-plugin</artifactId>
+                <version>@pom.version@</version>
+                <executions>
+                    <execution>
+                        <id>generate-markdown</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>commands-generate-help</goal>
+                        </goals>
+                        <configuration>
+                            <format>markdown</format>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/BarCommand.java b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/BarCommand.java
new file mode 100644
index 0000000000..cdeb46977e
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/BarCommand.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 test;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+
+@Command(scope = "test", name = "bar", description = "Test Bar")
+public class BarCommand implements Action {
+
+    @Argument(index = 0, name = "arg1", description = "Arg1 description")
+    String arg1;
+
+    @Argument(index = 1, name = "arg2", description = "Arg2 description")
+    String arg2;
+
+    public Object execute() throws Exception {
+        return null;
+    }
+}
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/FooCommand.java b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/FooCommand.java
new file mode 100644
index 0000000000..98c8a92861
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/java/test/FooCommand.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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 test;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+
+@Command(scope = "test", name = "foo", description = "Test Foo")
+public class FooCommand implements Action {
+
+    @Option(name = "-o", description = "Option")
+    boolean option;
+
+    public Object execute() throws Exception {
+        return null;
+    }
+}
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/commands.md b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/commands.md
new file mode 100644
index 0000000000..ef371b43bd
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/commands.md
@@ -0,0 +1,7 @@
+# Commands
+
+## test
+
+- [test:bar|test-bar]
+- [test:foo|test-foo]
+
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-bar.md b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-bar.md
new file mode 100644
index 0000000000..542c635afe
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-bar.md
@@ -0,0 +1,24 @@
+# test:bar
+
+# Description
+
+Test Bar
+
+# Syntax
+
+test:bar [options] [arg1] [arg2] 
+
+# Arguments
+
+| Name | Description |
+|------|-------------|
+| arg1 | Arg1 description |
+| arg2 | Arg2 description |
+
+# Options
+
+| Name | Description |
+|------|-------------|
+| --help | Display this help message |
+
+
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-foo.md b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-foo.md
new file mode 100644
index 0000000000..b5bf835fc8
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/test-markdown/src/main/resources/test-foo.md
@@ -0,0 +1,18 @@
+# test:foo
+
+# Description
+
+Test Foo
+
+# Syntax
+
+test:foo [options]
+
+# Options
+
+| Name | Description |
+|------|-------------|
+| -o | Option |
+| --help | Display this help message |
+
+
diff --git a/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/verify.bsh b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/verify.bsh
new file mode 100644
index 0000000000..d57c8dd847
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-commands-generate-help/verify.bsh
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *   http://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.
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.nio.file.Path;
+import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import java.net.URL;
+
+boolean compareFiles(File file1, File file2) throws IOException {
+    Path path1 = file1.toPath();
+    Path path2 = file2.toPath();
+
+    BufferedReader bf1 = Files.newBufferedReader(path1);
+    BufferedReader bf2 = Files.newBufferedReader(path2);
+
+        String line1 = "", line2 = "";
+        while ((line1 = bf1.readLine()) != null) {
+            line2 = bf2.readLine();
+            if (line2 == null || !line1.equals(line2)) {
+                return false;
+            }
+        }
+
+        if (bf2.readLine() == null) {
+            return true;
+        }
+
+        else {
+            return false;
+        }
+
+    bf1.close();
+    bf2.close();
+
+    return true;
+}
+
+File overviewFile = new File(basedir, "test-markdown/target/docbkx/sources/commands.md");
+File fooCommandFile = new File(basedir, "test-markdown/target/docbkx/sources/test-foo.md");
+File barCommandFile = new File(basedir, "test-markdown/target/docbkx/sources/test-bar.md");
+
+if (!barCommandFile.exists() || !fooCommandFile.exists() || !overviewFile.exists()) {
+    throw new FileNotFoundException("Not all expected documentation was generated!");
+}
+
+return compareFiles(overviewFile, new File(basedir, "test-markdown/src/main/resources/commands.md"))
+    && compareFiles(fooCommandFile, new File(basedir, "test-markdown/src/main/resources/test-foo.md"))
+    && compareFiles(barCommandFile, new File(basedir, "test-markdown/src/main/resources/test-bar.md"));
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/FormatEnum.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/FormatEnum.java
new file mode 100644
index 0000000000..da0254a811
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/FormatEnum.java
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.karaf.tooling.commands;
+
+import org.apache.maven.plugin.MojoFailureException;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public enum FormatEnum {
+    ASCIIDOC("asciidoc", "adoc"),
+    DOCBX("docbx", "xml"),
+    CONF("conf", "conf"),
+    MARKDOWN("markdown", "md");
+
+    private static final Map<String, FormatEnum> FORMAT_MAP = Stream.of(FormatEnum.values())
+            .collect(Collectors.toMap(formatEnum -> formatEnum.format, Function.identity()));
+    String format;
+    String fileSuffix;
+
+    FormatEnum(String format, String fileSuffix) {
+        this.format = format;
+        this.fileSuffix = fileSuffix;
+    }
+
+    static FormatEnum fromString(String format) throws MojoFailureException {
+        return Optional.ofNullable(FORMAT_MAP.get(format)).orElseThrow(() ->
+                new MojoFailureException("Unsupported format: " + format + ". Supported formats are: " +
+                        FORMAT_MAP.keySet()));
+    }
+
+}
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/GenerateHelpMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/GenerateHelpMojo.java
index c1faa3a877..b39a604c06 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/GenerateHelpMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/GenerateHelpMojo.java
@@ -79,48 +79,26 @@ public class GenerateHelpMojo extends AbstractMojo {
     @Parameter(defaultValue = "${project}")
     protected MavenProject project;
 
-    private static final String FORMAT_CONF = "conf";
-    private static final String FORMAT_DOCBX = "docbx";
-    private static final String FORMAT_ASCIIDOC = "asciidoc";
-
     public void execute() throws MojoExecutionException, MojoFailureException {
         try {
-            if (!FORMAT_DOCBX.equals(format) && !FORMAT_CONF.equals(format) && !FORMAT_ASCIIDOC.equals(format)) {
-                throw new MojoFailureException("Unsupported format: " + format + ". Supported formats are: asciidoc, docbx, or conf.");
-            }
             if (!targetFolder.exists()) {
                 targetFolder.mkdirs();
             }
 
+            FormatEnum formatEnum = FormatEnum.fromString(format);
+
             ClassFinder finder = createFinder(classLoader);
             List<Class<?>> classes = finder.findAnnotatedClasses(Command.class);
             if (classes.isEmpty()) {
                 throw new MojoFailureException("No command found");
             }
 
-            CommandHelpPrinter helpPrinter = null;
-            if (FORMAT_ASCIIDOC.equals(format)) {
-                helpPrinter = new AsciiDoctorCommandHelpPrinter();
-            }
-            if (FORMAT_CONF.equals(format)) {
-                helpPrinter = new UserConfCommandHelpPrinter();
-            }
-            if (FORMAT_DOCBX.equals(format)) {
-                helpPrinter = new DocBookCommandHelpPrinter();
-            }
+            CommandHelpPrinter helpPrinter = getPrinter(formatEnum);
 
             Map<String, Set<String>> commands = new TreeMap<>();
 
-            String commandSuffix = null;
-            if (FORMAT_ASCIIDOC.equals(format)) {
-                commandSuffix = "adoc";
-            }
-            if (FORMAT_CONF.equals(format)) {
-                commandSuffix = "conf";
-            }
-            if (FORMAT_DOCBX.equals(format)) {
-                commandSuffix = "xml";
-            }
+            String commandSuffix = formatEnum.fileSuffix;
+
             for (Class<?> clazz : classes) {
                 try {
                     Action action = (Action) clazz.newInstance();
@@ -130,11 +108,9 @@ public class GenerateHelpMojo extends AbstractMojo {
                     if (cmd.scope().equals("*")) continue;
 
                     File output = new File(targetFolder, cmd.scope() + "-" + cmd.name() + "." + commandSuffix);
-                    FileOutputStream outStream = new FileOutputStream(output);
-                    PrintStream out = new PrintStream(outStream);
-                    helpPrinter.printHelp(action, out, includeHelpOption);
-                    out.close();
-                    outStream.close();
+                    try (FileOutputStream outStream = new FileOutputStream(output); PrintStream out = new PrintStream(outStream)) {
+                        helpPrinter.printHelp(action, out, includeHelpOption);
+                    }
 
                     commands.computeIfAbsent(cmd.scope(), k -> new TreeSet<>()).add(cmd.name());
                     getLog().info("Found command: " + cmd.scope() + ":" + cmd.name());
@@ -143,16 +119,8 @@ public class GenerateHelpMojo extends AbstractMojo {
                 }
             }
 
-            String overViewSuffix = null;
-            if (FORMAT_ASCIIDOC.equals(format)) {
-                overViewSuffix = "adoc";
-            }
-            if (FORMAT_CONF.equals(format)) {
-                overViewSuffix = "conf";
-            }
-            if (FORMAT_DOCBX.equals(format)) {
-                overViewSuffix = "xml";
-            }
+            String overViewSuffix = formatEnum.fileSuffix;
+
             PrintStream writer = new PrintStream(new FileOutputStream(new File(targetFolder, "commands." + overViewSuffix)));
             helpPrinter.printOverview(commands, writer);
             writer.close();
@@ -180,4 +148,18 @@ public class GenerateHelpMojo extends AbstractMojo {
         return finder;
     }
 
+
+    private CommandHelpPrinter getPrinter(FormatEnum format) {
+        switch (format) {
+            case CONF:
+                return new UserConfCommandHelpPrinter();
+            case ASCIIDOC:
+                return new AsciiDoctorCommandHelpPrinter();
+            case MARKDOWN:
+                return new MarkdownCommandHelpPrinter();
+            case DOCBX:
+            default:
+                return new DocBookCommandHelpPrinter();
+        }
+    }
 }
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/MarkdownCommandHelpPrinter.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/MarkdownCommandHelpPrinter.java
new file mode 100644
index 0000000000..7b54ab773f
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/commands/MarkdownCommandHelpPrinter.java
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.karaf.tooling.commands;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.impl.action.command.HelpOption;
+
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.util.*;
+
+public class MarkdownCommandHelpPrinter extends AbstractCommandHelpPrinter {
+
+    @Override
+    public void printHelp(Action action, PrintStream out, boolean includeHelpOption) {
+        Command command = action.getClass().getAnnotation(Command.class);
+        Set<Option> options = new HashSet<>();
+        List<Argument> arguments = new ArrayList<>();
+        Map<Argument, Field> argFields = new HashMap<>();
+        Map<Option, Field> optFields = new HashMap<>();
+        for (Class<?> type = action.getClass(); type != null; type = type.getSuperclass()) {
+            for (Field field : type.getDeclaredFields()) {
+                Option option = field.getAnnotation(Option.class);
+                if (option != null) {
+                    options.add(option);
+                }
+
+                Argument argument = field.getAnnotation(Argument.class);
+                if (argument != null) {
+                    argument = replaceDefaultArgument(field, argument);
+                    argFields.put(argument, field);
+                    int index = argument.index();
+                    while (arguments.size() <= index) {
+                        arguments.add(null);
+                    }
+                    if (arguments.get(index) != null) {
+                        throw new IllegalArgumentException("Duplicate argument index: " + index + " on Action " + action.getClass().getName());
+                    }
+                    arguments.set(index, argument);
+                }
+            }
+        }
+        if (includeHelpOption)
+            options.add(HelpOption.HELP);
+
+        out.println("# " + command.scope() + ":" + command.name());
+        out.println();
+
+        out.println("# Description");
+        out.println();
+        out.println(command.description());
+        out.println();
+
+        StringBuilder syntax = new StringBuilder();
+        syntax.append(String.format("%s:%s", command.scope(), command.name()));
+        if (!options.isEmpty()) {
+            syntax.append(" [options]");
+        }
+        if (!arguments.isEmpty()) {
+            syntax.append(' ');
+            for (Argument argument : arguments) {
+                syntax.append(String.format(argument.required() ? "%s " : "[%s] ", argument.name()));
+            }
+        }
+        out.println("# Syntax");
+        out.println();
+        out.println(syntax);
+        out.println();
+
+        if (!arguments.isEmpty()) {
+            out.println("# Arguments");
+            out.println();
+            out.println("| Name | Description |");
+            out.println("|------|-------------|");
+            for (Argument argument : arguments) {
+                String description = argument.description();
+                if (!argument.required()) {
+                    Object o = getDefaultValue(action, argFields.get(argument));
+                    String defaultValue = getDefaultValueString(o);
+                    if (defaultValue != null) {
+                        description += " (defaults to " + o.toString() + ")";
+                    }
+                }
+                out.println("| " + argument.name() + " | " + description + " |");
+
+            }
+            out.println();
+        }
+        if (!options.isEmpty()) {
+            out.println("# Options");
+            out.println();
+            out.println("| Name | Description |");
+            out.println("|------|-------------|");
+            for (Option option : options) {
+                StringBuilder opt = new StringBuilder(option.name());
+                String desc = option.description();
+                for (String alias : option.aliases()) {
+                    opt.append(", ").append(alias);
+                }
+                Object o = getDefaultValue(action, optFields.get(option));
+                String defaultValue = getDefaultValueString(o);
+                if (defaultValue != null) {
+                    desc += " (defaults to " + defaultValue + ")";
+                }
+                out.println("| " + opt + " | " + desc + " |");
+            }
+            out.println();
+        }
+        if (command.detailedDescription().length() > 0) {
+            out.println("# Details");
+            out.println();
+            out.println(command.detailedDescription());
+        }
+        out.println();
+    }
+
+    @Override
+    public void printOverview(Map<String, Set<String>> commands, PrintStream writer) {
+        writer.println("# Commands");
+        writer.println();
+        for (String key : commands.keySet()) {
+            writer.println("## " + key);
+            writer.println();
+            for (String cmd : commands.get(key)) {
+                writer.println("- [" + key + ":" + cmd + "|" + key + "-" + cmd + "]");
+            }
+            writer.println();
+        }
+    }
+}