http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import org.osgi.annotation.versioning.ProviderType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+/**
+ * An abstract command.
+ */
+
+@ProviderType
+@Parameters(resourceBundle = "com.io7m.claypot.core.Claypot")
+public abstract class CLPAbstractCommand implements CLPCommandType
+{
+ private final JCommander commander;
+ private final CLPCommandContextType context;
+
+ @Parameter(
+ names = "--verbose",
+ converter = CLPLogLevelConverter.class,
+ descriptionKey = "com.io7m.claypot.rootDescription"
+ )
+ private CLPLogLevel verbose = CLPLogLevel.LOG_INFO;
+
+ /**
+ * Construct a command.
+ *
+ * @param inContext The command context
+ */
+
+ public CLPAbstractCommand(
+ final CLPCommandContextType inContext)
+ {
+ this.context = Objects.requireNonNull(inContext, "context");
+ this.commander = this.context.commander();
+ }
+
+ protected final JCommander commander()
+ {
+ return this.commander;
+ }
+
+ protected final CLPCommandContextType context()
+ {
+ return this.context;
+ }
+
+ protected final Logger logger()
+ {
+ return this.configuration().logger();
+ }
+
+ protected final CLPApplicationConfiguration configuration()
+ {
+ return this.context().configuration();
+ }
+
+ protected abstract Status executeActual()
+ throws Exception;
+
+ /**
+ * Set up logging for other commands.
+ *
+ * @return The command status
+ *
+ * @throws Exception On errors
+ */
+
+ @Override
+ public final Status execute()
+ throws Exception
+ {
+ if (this.verbose == null) {
+ this.verbose = CLPLogLevel.LOG_INFO;
+ }
+
+ final ch.qos.logback.classic.Logger root =
+ (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(
+ Logger.ROOT_LOGGER_NAME);
+ root.setLevel(this.verbose.toLevel());
+ return this.executeActual();
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPAbstractStrings.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPAbstractStrings.java
new file mode 100644
index 0000000..22c31dd
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPAbstractStrings.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+import java.text.MessageFormat;
+import java.util.Objects;
+import java.util.ResourceBundle;
+
+/**
+ * An abstract implementation of the {@link CLPStringsType} interface.
+ */
+
+@ProviderType
+public abstract class CLPAbstractStrings implements CLPStringsType
+{
+ private final ResourceBundle resources;
+
+ protected CLPAbstractStrings(
+ final ResourceBundle inResources)
+ {
+ this.resources = Objects.requireNonNull(inResources, "inResources");
+ }
+
+ @Override
+ public final ResourceBundle resources()
+ {
+ return this.resources;
+ }
+
+ @Override
+ public final String format(
+ final String id,
+ final Object... args)
+ {
+ Objects.requireNonNull(id, "id");
+ Objects.requireNonNull(args, "args");
+ return MessageFormat.format(this.resources.getString(id), args);
+ }
+}
+
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPApplicationConfigurationType.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPApplicationConfigurationType.java
new file mode 100644
index 0000000..985c9a0
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPApplicationConfigurationType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.io7m.immutables.styles.ImmutablesStyleType;
+import org.immutables.value.Value;
+import org.slf4j.Logger;
+
+import java.util.List;
+
+@ImmutablesStyleType
+@Value.Immutable
+public interface CLPApplicationConfigurationType
+{
+ Logger logger();
+
+ String programName();
+
+ List commands();
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPBriefUsageFormatter.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPBriefUsageFormatter.java
new file mode 100644
index 0000000..a44152f
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPBriefUsageFormatter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.DefaultUsageFormatter;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.JCommander.ProgramName;
+import com.beust.jcommander.Parameters;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Objects;
+
+public final class CLPBriefUsageFormatter extends DefaultUsageFormatter
+{
+ private final JCommander commander;
+ private final CLPStringsType strings;
+
+ public CLPBriefUsageFormatter(
+ final JCommander inCommander)
+ {
+ super(inCommander);
+
+ this.commander =
+ Objects.requireNonNull(inCommander, "commander");
+ this.strings =
+ CLPStrings.create();
+ }
+
+ @Override
+ public void appendCommands(
+ final StringBuilder out,
+ final int indentCount,
+ final int descriptionIndent,
+ final String indent)
+ {
+ out.append('\n');
+ out.append(indent);
+ out.append(" ");
+ out.append(this.strings.format("com.io7m.claypot.commands"));
+ out.append(":\n");
+
+ final var rawCommands = this.commander.getRawCommands();
+ final var commandNames = new ArrayList<>(rawCommands.keySet());
+ commandNames.sort(Comparator.comparing(ProgramName::getDisplayName));
+
+ for (final var commandName : commandNames) {
+ final var commands = rawCommands.get(commandName);
+ final Object arg = commands.getObjects().get(0);
+ final Parameters p = arg.getClass().getAnnotation(Parameters.class);
+
+ if (p == null || !p.hidden()) {
+ final String description =
+ String.format(
+ " %-32s %s",
+ commandName.getDisplayName(),
+ this.getCommandDescription(commandName.getName())
+ );
+
+ out.append(description);
+ out.append('\n');
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[CLPBriefUsageFormatter 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandConstructorType.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandConstructorType.java
new file mode 100644
index 0000000..f4cb4d4
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandConstructorType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+/**
+ * A command constructor.
+ */
+
+public interface CLPCommandConstructorType
+{
+ CLPCommandType create(CLPCommandContextType context);
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandContextType.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandContextType.java
new file mode 100644
index 0000000..e70fffc
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandContextType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.JCommander;
+
+/**
+ * A command context.
+ */
+
+public interface CLPCommandContextType
+{
+ CLPApplicationConfiguration configuration();
+
+ JCommander commander();
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandHelp.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandHelp.java
new file mode 100644
index 0000000..36480e7
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandHelp.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.DefaultUsageFormatter;
+import com.beust.jcommander.Parameters;
+
+import static com.io7m.claypot.core.CLPCommandType.Status.SUCCESS;
+
+@Parameters(
+ resourceBundle = "com.io7m.claypot.core.Claypot",
+ commandDescriptionKey = "com.io7m.claypot.helpDescription")
+public final class CLPCommandHelp extends CLPAbstractCommand
+{
+ public CLPCommandHelp(
+ final CLPCommandContextType inContext)
+ {
+ super(inContext);
+ }
+
+ @Override
+ protected Status executeActual()
+ {
+ final var console = new CLPStringBuilderConsole();
+ final var commander = this.commander();
+ commander.setUsageFormatter(new DefaultUsageFormatter(commander));
+ commander.setConsole(console);
+ commander.usage();
+
+ this.logger().info("{}", console.builder().toString());
+ return SUCCESS;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[CLPCommandHelp 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+
+ @Override
+ public String name()
+ {
+ return "help";
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandRoot.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandRoot.java
new file mode 100644
index 0000000..f142874
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandRoot.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import static com.io7m.claypot.core.CLPCommandType.Status.SUCCESS;
+
+public final class CLPCommandRoot extends CLPAbstractCommand
+{
+ public CLPCommandRoot(
+ final CLPCommandContextType inContext)
+ {
+ super(inContext);
+ }
+
+ @Override
+ protected Status executeActual()
+ {
+ return SUCCESS;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[CLPCommandRoot 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+
+ @Override
+ public String name()
+ {
+ return "";
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandType.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandType.java
new file mode 100644
index 0000000..b0c2773
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPCommandType.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The base type of commands.
+ */
+
+@ProviderType
+public interface CLPCommandType
+{
+ /**
+ * @return The name of the command
+ */
+
+ String name();
+
+ /**
+ * Execute the command.
+ *
+ * @return The resulting command status
+ *
+ * @throws Exception On errors.
+ */
+
+ Status execute()
+ throws Exception;
+
+ /**
+ * The result of executing a command.
+ */
+
+ enum Status
+ {
+ /**
+ * The command succeeded.
+ */
+
+ SUCCESS,
+
+ /**
+ * The command failed.
+ */
+
+ FAILURE;
+
+ /**
+ * @return The shell exit code
+ */
+
+ public int exitCode()
+ {
+ switch (this) {
+ case SUCCESS:
+ return 0;
+ case FAILURE:
+ return 1;
+ }
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevel.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevel.java
new file mode 100644
index 0000000..65b3c21
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevel.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import ch.qos.logback.classic.Level;
+
+import java.util.Objects;
+
+/**
+ * Log level.
+ */
+
+public enum CLPLogLevel
+{
+ /**
+ * @see Level#TRACE
+ */
+
+ LOG_TRACE("trace"),
+
+ /**
+ * @see Level#DEBUG
+ */
+
+ LOG_DEBUG("debug"),
+
+ /**
+ * @see Level#INFO
+ */
+
+ LOG_INFO("info"),
+
+ /**
+ * @see Level#WARN
+ */
+
+ LOG_WARN("warn"),
+
+ /**
+ * @see Level#ERROR
+ */
+
+ LOG_ERROR("error");
+
+
+ private final String name;
+
+ CLPLogLevel(final String in_name)
+ {
+ this.name = Objects.requireNonNull(in_name, "Name");
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.name;
+ }
+
+ /**
+ * @return The short name of the level
+ */
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ Level toLevel()
+ {
+ switch (this) {
+ case LOG_TRACE:
+ return Level.TRACE;
+ case LOG_DEBUG:
+ return Level.DEBUG;
+ case LOG_INFO:
+ return Level.INFO;
+ case LOG_WARN:
+ return Level.WARN;
+ case LOG_ERROR:
+ return Level.ERROR;
+ }
+
+ throw new IllegalStateException();
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelConverter.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelConverter.java
new file mode 100644
index 0000000..b49a336
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelConverter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.IStringConverter;
+
+/**
+ * A converter for {@link CLPLogLevel} values.
+ */
+
+public final class CLPLogLevelConverter
+ implements IStringConverter
+{
+ private final CLPStringsType strings;
+
+ /**
+ * Construct a new converter.
+ */
+
+ public CLPLogLevelConverter()
+ {
+ this.strings = CLPStrings.create();
+ }
+
+ @Override
+ public CLPLogLevel convert(final String value)
+ {
+ for (final CLPLogLevel v : CLPLogLevel.values()) {
+ if (value.equals(v.getName())) {
+ return v;
+ }
+ }
+
+ throw new CLPLogLevelUnrecognized(
+ this.strings.format("com.io7m.claypot.logLevelUnrecognized", value)
+ );
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[CLPLogLevelConverter 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelUnrecognized.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelUnrecognized.java
new file mode 100644
index 0000000..d1566ab
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPLogLevelUnrecognized.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+/**
+ * A log level was unrecognized.
+ */
+
+public final class CLPLogLevelUnrecognized extends RuntimeException
+{
+ /**
+ * Construct an exception.
+ *
+ * @param message The error message
+ */
+
+ CLPLogLevelUnrecognized(final String message)
+ {
+ super(message);
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringBuilderConsole.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringBuilderConsole.java
new file mode 100644
index 0000000..cd02266
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringBuilderConsole.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.internal.Console;
+
+public final class CLPStringBuilderConsole implements Console
+{
+ private final StringBuilder builder;
+
+ public CLPStringBuilderConsole()
+ {
+ this.builder = new StringBuilder(128);
+ }
+
+ @Override
+ public void print(final String s)
+ {
+ this.builder.append(s);
+ }
+
+ @Override
+ public void println(final String s)
+ {
+ this.builder.append(s);
+ this.builder.append('\n');
+ }
+
+ public StringBuilder builder()
+ {
+ return this.builder;
+ }
+
+ @Override
+ public char[] readPassword(final boolean b)
+ {
+ return new char[0];
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[CLPStringBuilderConsole 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStrings.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStrings.java
new file mode 100644
index 0000000..2a13ad4
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStrings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import java.util.ResourceBundle;
+
+public final class CLPStrings
+ extends CLPAbstractStrings
+{
+ private CLPStrings(
+ final ResourceBundle inResources)
+ {
+ super(inResources);
+ }
+
+ public static CLPStringsType create()
+ {
+ return new CLPStrings(
+ ResourceBundle.getBundle("com.io7m.claypot.core.Claypot")
+ );
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringsType.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringsType.java
new file mode 100644
index 0000000..9b46e36
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/CLPStringsType.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import java.text.MessageFormat;
+import java.util.Objects;
+import java.util.ResourceBundle;
+
+/**
+ * A provider of string resources.
+ */
+
+public interface CLPStringsType
+{
+ /**
+ * @return The underlying resource bundle
+ */
+
+ ResourceBundle resources();
+
+ /**
+ * Format a message.
+ *
+ * @param id The string resource ID
+ * @param args Any required string format arguments
+ *
+ * @return A formatted string
+ *
+ * @see MessageFormat
+ */
+
+ default String format(
+ final String id,
+ final Object... args)
+ {
+ Objects.requireNonNull(id, "id");
+ Objects.requireNonNull(args, "args");
+ return MessageFormat.format(this.resources().getString(id), args);
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/Claypot.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/Claypot.java
new file mode 100644
index 0000000..cf4d182
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/Claypot.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.core;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Objects;
+
+public final class Claypot
+{
+ private final CLPApplicationConfiguration configuration;
+ private final JCommander commander;
+ private final HashMap commandMap;
+ private final CLPStringsType strings;
+ private int exitCode;
+
+ private Claypot(
+ final CLPApplicationConfiguration inConfiguration,
+ final JCommander inCommander,
+ final HashMap inCommandMap,
+ final CLPStringsType inStrings)
+ {
+ this.configuration =
+ Objects.requireNonNull(inConfiguration, "inConfiguration");
+ this.commander =
+ Objects.requireNonNull(inCommander, "commander");
+ this.commandMap =
+ Objects.requireNonNull(inCommandMap, "commandMap");
+ this.strings =
+ Objects.requireNonNull(inStrings, "inStrings");
+ }
+
+ public static Claypot create(
+ final CLPApplicationConfiguration configuration)
+ {
+ final var strings = CLPStrings.create();
+
+ final var commander = new JCommander();
+ final var context = new Context(commander, configuration);
+
+ commander.setProgramName(configuration.programName());
+ commander.addObject(new CLPCommandRoot(context));
+
+ final var constructors =
+ configuration.commands();
+ final var commandMap =
+ new HashMap(constructors.size() + 1);
+
+ commandMap.put("help", new CLPCommandHelp(context));
+
+ for (final var constructor : constructors) {
+ final var command = constructor.create(context);
+ final var name = command.name();
+ if (commandMap.containsKey(name)) {
+ throw new IllegalStateException(
+ strings.format("com.io7m.claypot.commandConflict", name)
+ );
+ }
+ commandMap.put(name, command);
+ }
+
+ for (final var entry : commandMap.entrySet()) {
+ commander.addCommand(entry.getKey(), entry.getValue());
+ }
+
+ return new Claypot(configuration, commander, commandMap, strings);
+ }
+
+ public int exitCode()
+ {
+ return this.exitCode;
+ }
+
+ public void execute(
+ final String[] args)
+ {
+ final var logger = this.configuration.logger();
+
+ try {
+ this.commander.parse(args);
+
+ final String cmd = this.commander.getParsedCommand();
+ if (cmd == null) {
+ final var console = new CLPStringBuilderConsole();
+ this.commander.setUsageFormatter(new CLPBriefUsageFormatter(this.commander));
+ this.commander.setConsole(console);
+ this.commander.usage();
+ logger.info("{}", console.builder().toString());
+ this.exitCode = 1;
+ return;
+ }
+
+ final CLPCommandType command = this.commandMap.get(cmd);
+ final CLPCommandType.Status status = command.execute();
+ this.exitCode = status.exitCode();
+ } catch (final ParameterException e) {
+ logger.error("{}", e.getMessage());
+ this.exitCode = 1;
+ } catch (final Exception e) {
+ this.logExceptionFriendly(logger, false, e);
+ this.exitCode = 1;
+ }
+ }
+
+ private void logExceptionFriendly(
+ final Logger logger,
+ final boolean isCause,
+ final Throwable e)
+ {
+ if (e == null) {
+ return;
+ }
+
+ logger.error(
+ "{}{}: {}",
+ isCause ? this.strings.format("com.io7m.claypot.causedBy") : "",
+ e.getClass().getCanonicalName(),
+ e.getMessage());
+
+ if (logger.isDebugEnabled()) {
+ final var trace = new StringBuilder(256);
+ final String lineSeparator = System.lineSeparator();
+ trace.append(lineSeparator);
+ final var elements = e.getStackTrace();
+ for (final var element : elements) {
+ trace.append(" at ");
+
+ final var moduleName = element.getModuleName();
+ if (moduleName != null) {
+ trace.append(moduleName);
+ trace.append('/');
+ } else {
+ trace.append("/");
+ }
+
+ trace.append(element.getClassName());
+ trace.append('.');
+ trace.append(element.getMethodName());
+ trace.append('(');
+ trace.append(element.getFileName());
+ trace.append(':');
+ trace.append(element.getLineNumber());
+ trace.append(')');
+ trace.append(lineSeparator);
+ }
+ logger.debug(
+ "{}",
+ this.strings.format(
+ "com.io7m.claypot.stackTraceOf",
+ e.getClass().getCanonicalName(),
+ trace
+ ));
+ }
+
+ final var causes = e.getSuppressed();
+ if (causes.length > 0) {
+ for (final var cause : causes) {
+ this.logExceptionFriendly(logger, true, cause);
+ }
+ }
+ this.logExceptionFriendly(logger, true, e.getCause());
+ }
+
+ private static final class Context implements CLPCommandContextType
+ {
+ private final JCommander commander;
+ private final CLPApplicationConfiguration configuration;
+
+ private Context(
+ final JCommander inCommander,
+ final CLPApplicationConfiguration inConfiguration)
+ {
+ this.commander =
+ Objects.requireNonNull(inCommander, "commander");
+ this.configuration =
+ Objects.requireNonNull(inConfiguration, "inConfiguration");
+ }
+
+ @Override
+ public CLPApplicationConfiguration configuration()
+ {
+ return this.configuration;
+ }
+
+ @Override
+ public JCommander commander()
+ {
+ return this.commander;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format(
+ "[Claypot 0x%s]",
+ Long.toUnsignedString(System.identityHashCode(this), 16)
+ );
+ }
+}
diff --git a/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/package-info.java b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/package-info.java
new file mode 100644
index 0000000..b4c157c
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/java/com/io7m/claypot/core/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * JCommander conventions for io7m projects (Core)
+ */
+
+@Export
+@Version("1.0.0")
+package com.io7m.claypot.core;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/com.io7m.claypot.core/src/main/resources/com/io7m/claypot/core/Claypot.properties b/com.io7m.claypot.core/src/main/resources/com/io7m/claypot/core/Claypot.properties
new file mode 100644
index 0000000..95356a7
--- /dev/null
+++ b/com.io7m.claypot.core/src/main/resources/com/io7m/claypot/core/Claypot.properties
@@ -0,0 +1,23 @@
+#
+# Copyright © 2020 Mark Raynsford http://io7m.com
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+com.io7m.claypot.commandConflict=Multiple commands registered with the same name: {0}
+com.io7m.claypot.commands=Commands
+com.io7m.claypot.helpDescription=Show a detailed help message describing all available commands.
+com.io7m.claypot.logLevelUnrecognized=Unrecognized log level: {0}
+com.io7m.claypot.rootDescription=Set the minimum logging verbosity level
+com.io7m.claypot.stackTraceOf=Stacktrace of {0}: {1}
+com.io7m.claypot.causedBy=Caused by:
diff --git a/com.io7m.claypot.example/pom.xml b/com.io7m.claypot.example/pom.xml
new file mode 100644
index 0000000..4380481
--- /dev/null
+++ b/com.io7m.claypot.example/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ 4.0.0
+
+
+ com.io7m.claypot
+ com.io7m.claypot
+ 0.0.1
+
+
+ com.io7m.claypot.example
+
+ JCommander conventions for io7m projects (Example application)
+ com.io7m.claypot.example
+ https://www.github.com/io7m/claypot
+
+
+
+ ${project.groupId}
+ com.io7m.claypot.core
+ ${project.version}
+
+
+
+ com.beust
+ jcommander
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.osgi
+ org.osgi.annotation.bundle
+ provided
+
+
+ org.osgi
+ org.osgi.annotation.versioning
+ provided
+
+
+
+
\ No newline at end of file
diff --git a/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXEmptyMain.java b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXEmptyMain.java
new file mode 100644
index 0000000..3bec8aa
--- /dev/null
+++ b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXEmptyMain.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.example;
+
+import com.io7m.claypot.core.CLPApplicationConfiguration;
+import com.io7m.claypot.core.Claypot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class CEXEmptyMain
+{
+ private static final Logger LOG =
+ LoggerFactory.getLogger(CEXEmptyMain.class);
+
+ private CEXEmptyMain()
+ {
+
+ }
+
+ public static void main(
+ final String[] args)
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(LOG)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(args);
+ System.exit(claypot.exitCode());
+ }
+}
diff --git a/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXOthersMain.java b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXOthersMain.java
new file mode 100644
index 0000000..dcc48dd
--- /dev/null
+++ b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXOthersMain.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.example;
+
+import com.io7m.claypot.core.CLPApplicationConfiguration;
+import com.io7m.claypot.core.Claypot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class CEXOthersMain
+{
+ private static final Logger LOG =
+ LoggerFactory.getLogger(CEXOthersMain.class);
+
+ private CEXOthersMain()
+ {
+
+ }
+
+ public static void main(
+ final String[] args)
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .addCommands(CEXRed::new)
+ .setLogger(LOG)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(args);
+ System.exit(claypot.exitCode());
+ }
+}
diff --git a/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXRed.java b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXRed.java
new file mode 100644
index 0000000..d6216f2
--- /dev/null
+++ b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/CEXRed.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.example;
+
+import com.beust.jcommander.Parameters;
+import com.io7m.claypot.core.CLPCommandContextType;
+import com.io7m.claypot.core.CLPAbstractCommand;
+
+@Parameters(commandDescription = "Paint things red.")
+public final class CEXRed extends CLPAbstractCommand
+{
+ public CEXRed(final CLPCommandContextType inContext)
+ {
+ super(inContext);
+ }
+
+ @Override
+ protected Status executeActual()
+ {
+ return Status.SUCCESS;
+ }
+
+ @Override
+ public String name()
+ {
+ return "red";
+ }
+}
diff --git a/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/package-info.java b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/package-info.java
new file mode 100644
index 0000000..adad157
--- /dev/null
+++ b/com.io7m.claypot.example/src/main/java/com/io7m/claypot/example/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * JCommander conventions for io7m projects (Example)
+ */
+
+@Export
+@Version("1.0.0")
+package com.io7m.claypot.example;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git a/com.io7m.claypot.example/src/main/resources/logback.xml b/com.io7m.claypot.example/src/main/resources/logback.xml
new file mode 100644
index 0000000..eb4b83a
--- /dev/null
+++ b/com.io7m.claypot.example/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ %level %logger: %msg%n
+
+ System.err
+
+
+
+
+
+
+
diff --git a/com.io7m.claypot.tests/pom.xml b/com.io7m.claypot.tests/pom.xml
new file mode 100644
index 0000000..9cc9842
--- /dev/null
+++ b/com.io7m.claypot.tests/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+
+ 4.0.0
+
+
+ com.io7m.claypot
+ com.io7m.claypot
+ 0.0.1
+
+
+ com.io7m.claypot.tests
+
+ JCommander conventions for io7m projects (Test suite)
+ com.io7m.claypot.tests
+ https://www.github.com/io7m/claypot
+
+
+ true
+
+
+
+
+ ${project.groupId}
+ com.io7m.claypot.core
+ ${project.version}
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.osgi
+ org.osgi.annotation.versioning
+ provided
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/ClaypotTest.java b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/ClaypotTest.java
new file mode 100644
index 0000000..4cfc60f
--- /dev/null
+++ b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/ClaypotTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.tests;
+
+import com.io7m.claypot.core.CLPApplicationConfiguration;
+import com.io7m.claypot.core.Claypot;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.internal.verification.Times;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public final class ClaypotTest
+{
+ private static final Logger LOG = LoggerFactory.getLogger(Claypot.class);
+ private Logger spyLog;
+
+ @BeforeEach
+ public void setup()
+ {
+ this.spyLog = mock(Logger.class, delegatesTo(LOG));
+ }
+
+ @Test
+ public void noArgumentsShowsUsage()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{});
+
+ assertEquals(1, claypot.exitCode());
+ final var captor = ArgumentCaptor.forClass(String.class);
+ verify(this.spyLog).info(eq("{}"), captor.capture());
+
+ final var argument = captor.getValue();
+ assertTrue(argument.contains("Usage: cex"));
+ assertTrue(argument.contains("Commands:"));
+ }
+
+ @Test
+ public void noArgumentsUnrecognizedLogLevel()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{"--verbose", "who-knows"});
+
+ assertEquals(1, claypot.exitCode());
+ final var captor0 = ArgumentCaptor.forClass(String.class);
+ final var captor1 = ArgumentCaptor.forClass(String.class);
+
+ verify(this.spyLog).error(
+ eq("{}{}: {}"),
+ any(),
+ captor0.capture(),
+ captor1.capture()
+ );
+
+ final var arg0 = captor0.getValue();
+ final var arg1 = captor1.getValue();
+ assertTrue(arg1.contains("Unrecognized log level: who-knows"));
+ }
+
+ @Test
+ public void helpShowsDetailedUsage()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{"help"});
+
+ assertEquals(0, claypot.exitCode());
+ final var captor = ArgumentCaptor.forClass(String.class);
+ verify(this.spyLog).info(eq("{}"), captor.capture());
+
+ final var argument = captor.getValue();
+ assertTrue(argument.contains("Usage: cex"));
+ assertTrue(argument.contains("Usage: help [options]"));
+ }
+
+ @Test
+ public void helpUnrecognizedParameter()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{"help", "--unrecognized"});
+
+ assertEquals(1, claypot.exitCode());
+ final var captor = ArgumentCaptor.forClass(String.class);
+ verify(this.spyLog).error(eq("{}"), captor.capture());
+
+ final var argument = captor.getValue();
+ assertTrue(argument.contains("Was passed main parameter '--unrecognized'"));
+ }
+
+ @Test
+ public void commandCrashes()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .addCommands(CrashCommand::new)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{"crash"});
+
+ assertEquals(1, claypot.exitCode());
+ final var captor1 = ArgumentCaptor.forClass(String.class);
+ final var captor2 = ArgumentCaptor.forClass(String.class);
+ verify(this.spyLog, new Times(2))
+ .error(
+ eq("{}{}: {}"),
+ any(),
+ captor1.capture(),
+ captor2.capture());
+
+ final var argument = captor1.getValue();
+ assertTrue(argument.contains("java.io.IOException"));
+ }
+
+ @Test
+ public void commandCrashesVerbose()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .addCommands(CrashCommand::new)
+ .build();
+
+ final var claypot = Claypot.create(applicationConfiguration);
+ claypot.execute(new String[]{"crash", "--verbose", "debug"});
+
+ assertEquals(1, claypot.exitCode());
+
+ final var captor1 = ArgumentCaptor.forClass(String.class);
+ final var captor2 = ArgumentCaptor.forClass(String.class);
+ verify(this.spyLog, new Times(2))
+ .error(
+ eq("{}{}: {}"),
+ any(),
+ captor1.capture(),
+ captor2.capture());
+
+ verify(this.spyLog, new Times(2))
+ .debug(eq("{}"), captor2.capture());
+
+ final var arg0 = captor1.getValue();
+ assertTrue(arg0.contains("java.io.IOException"));
+
+ final var arg1 = captor2.getValue();
+ assertTrue(arg1.contains("Stacktrace of java.io.IOException:"));
+ }
+
+ @Test
+ public void commandDuplicate()
+ {
+ final var applicationConfiguration =
+ CLPApplicationConfiguration.builder()
+ .setProgramName("cex")
+ .setLogger(this.spyLog)
+ .addCommands(EmptyCommand::new)
+ .addCommands(EmptyCommand::new)
+ .build();
+
+ Assertions.assertThrows(IllegalStateException.class, () -> {
+ Claypot.create(applicationConfiguration);
+ });
+ }
+}
diff --git a/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/CrashCommand.java b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/CrashCommand.java
new file mode 100644
index 0000000..a16d86c
--- /dev/null
+++ b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/CrashCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.tests;
+
+import com.io7m.claypot.core.CLPAbstractCommand;
+import com.io7m.claypot.core.CLPCommandContextType;
+
+import java.io.IOException;
+
+public final class CrashCommand extends CLPAbstractCommand
+{
+ /**
+ * Construct a command.
+ *
+ * @param inContext The command context
+ */
+
+ public CrashCommand(
+ final CLPCommandContextType inContext)
+ {
+ super(inContext);
+ }
+
+ @Override
+ protected Status executeActual()
+ throws Exception
+ {
+ throw new IOException(new IOException());
+ }
+
+ @Override
+ public String name()
+ {
+ return "crash";
+ }
+}
diff --git a/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/EmptyCommand.java b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/EmptyCommand.java
new file mode 100644
index 0000000..dac591c
--- /dev/null
+++ b/com.io7m.claypot.tests/src/test/java/com/io7m/claypot/tests/EmptyCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2020 Mark Raynsford http://io7m.com
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package com.io7m.claypot.tests;
+
+import com.io7m.claypot.core.CLPAbstractCommand;
+import com.io7m.claypot.core.CLPCommandContextType;
+
+import static com.io7m.claypot.core.CLPCommandType.Status.SUCCESS;
+
+public final class EmptyCommand extends CLPAbstractCommand
+{
+ /**
+ * Construct a command.
+ *
+ * @param inContext The command context
+ */
+
+ public EmptyCommand(
+ final CLPCommandContextType inContext)
+ {
+ super(inContext);
+ }
+
+ @Override
+ protected Status executeActual()
+ {
+ return SUCCESS;
+ }
+
+ @Override
+ public String name()
+ {
+ return "empty";
+ }
+}
diff --git a/com.io7m.claypot.tests/src/test/resources/logback.xml b/com.io7m.claypot.tests/src/test/resources/logback.xml
new file mode 100644
index 0000000..eb4b83a
--- /dev/null
+++ b/com.io7m.claypot.tests/src/test/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ %level %logger: %msg%n
+
+ System.err
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b138781
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,241 @@
+
+
+
+
+ 4.0.0
+
+
+ com.io7m.primogenitor
+ com.io7m.primogenitor.full
+ 5.0.0
+
+
+ com.io7m.claypot
+ com.io7m.claypot
+ 0.0.1
+ pom
+
+ JCommander conventions for io7m projects
+ com.io7m.claypot
+ https://www.github.com/io7m/claypot
+
+
+ com.io7m.claypot.core
+ com.io7m.claypot.example
+ com.io7m.claypot.tests
+
+
+
+ e6b8b59da8fb4d8383f26601bcc37d48
+ 0.0.1
+ 0.0.1-SNAPSHOT
+ 11,[14,15)
+ 5.7.0-M1
+ 2.8.3
+
+
+
+
+ ISC License
+ http://io7m.com/license/isc.txt
+
+
+
+
+ https://github.com/io7m/claypot
+ scm:git:https://github.com/io7m/claypot
+ scm:git:https://github.com/io7m/claypot
+
+
+
+
+ io7m
+ io7m
+ code@io7m.com
+ http://io7m.com
+
+
+
+
+ http://github.com/io7m/claypot/issues
+ GitHub Issues
+
+
+
+
+ io7m.com
+ io7m.com
+ https://www.io7m.com/software/claypot
+
+
+ sonatype-nexus-staging
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+ Travis CI
+ https://travis-ci.org/io7m/claypot
+
+
+
+
+
+ org.osgi
+ org.osgi.annotation.bundle
+ 1.0.0
+
+
+ org.osgi
+ org.osgi.annotation.versioning
+ 1.1.0
+
+
+ com.io7m.immutables.style
+ com.io7m.immutables.style
+ 0.0.1
+
+
+ com.io7m.blackthorne
+ com.io7m.blackthorne.api
+ 0.0.5
+
+
+ com.io7m.jxe
+ com.io7m.jxe.core
+ 0.0.2
+
+
+ com.io7m.junreachable
+ com.io7m.junreachable.core
+ 3.0.0
+
+
+ com.io7m.jlexing
+ com.io7m.jlexing.core
+ 2.0.0
+
+
+ com.io7m.jaffirm
+ com.io7m.jaffirm.core
+ 3.0.4
+
+
+ com.io7m.jranges
+ com.io7m.jranges.core
+ 4.0.0
+
+
+ org.immutables
+ value
+ ${org.immutables.version}
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.0-alpha1
+
+
+ ch.qos.logback
+ logback-classic
+ 1.3.0-alpha4
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ org.apache.commons
+ commons-lang3
+ 3.10
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+
+
+ org.mock-server
+ mockserver-netty
+ 5.10.0
+
+
+ com.beust
+ jcommander
+ 1.78
+
+
+ nl.jqno.equalsverifier
+ equalsverifier
+ 3.1.13
+
+
+ com.io7m.primogenitor
+ com.io7m.primogenitor.support
+ 5.0.0
+
+
+ com.io7m.xstructural
+ com.io7m.xstructural.cmdline
+ 0.0.1
+
+
+ com.github.marschall
+ memoryfilesystem
+ 2.1.0
+
+
+ org.mockito
+ mockito-core
+ 3.3.3
+
+
+ net.java.dev.jna
+ jna
+ 5.5.0
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.immutables
+ value
+ ${org.immutables.version}
+
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+ spotbugs-filter.xml
+
+
+
+
+
+ com.io7m.minisite
+ com.io7m.minisite.maven_plugin
+ false
+
+
+
+
+
diff --git a/spotbugs-filter.xml b/spotbugs-filter.xml
new file mode 100644
index 0000000..9997fe8
--- /dev/null
+++ b/spotbugs-filter.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/site/resources/claypot.jpg b/src/site/resources/claypot.jpg
new file mode 100644
index 0000000..98383c3
Binary files /dev/null and b/src/site/resources/claypot.jpg differ
diff --git a/src/site/resources/documentation.xml b/src/site/resources/documentation.xml
new file mode 100644
index 0000000..1ee34dc
--- /dev/null
+++ b/src/site/resources/documentation.xml
@@ -0,0 +1,6 @@
+
+
+
+ User documentation
+ No user documentation is currently available.
+
diff --git a/src/site/resources/features.xml b/src/site/resources/features.xml
new file mode 100644
index 0000000..6e09c09
--- /dev/null
+++ b/src/site/resources/features.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/src/site/resources/header.xml b/src/site/resources/header.xml
new file mode 100644
index 0000000..4507597
--- /dev/null
+++ b/src/site/resources/header.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/src/site/resources/icon.png b/src/site/resources/icon.png
new file mode 100644
index 0000000..002f655
Binary files /dev/null and b/src/site/resources/icon.png differ
diff --git a/src/site/resources/overview.xml b/src/site/resources/overview.xml
new file mode 100644
index 0000000..8e5e75b
--- /dev/null
+++ b/src/site/resources/overview.xml
@@ -0,0 +1,28 @@
+
diff --git a/src/site/resources/site.css b/src/site/resources/site.css
new file mode 100644
index 0000000..e4bb857
--- /dev/null
+++ b/src/site/resources/site.css
@@ -0,0 +1,7 @@
+p.shields a {
+ text-decoration: none;
+}
+
+#header {
+ font-family: monospace;
+}