jgvariant-tool: New module.
Adds a command line tool that can read and (in the future) manipulate
GVariant-formatted files.
Change-Id: Icc92eb409a97e7cf72dfd7535f6a8b3587dd4a48
diff --git a/jgvariant-tool/pom.xml b/jgvariant-tool/pom.xml
new file mode 100644
index 0000000..ee2b8a8
--- /dev/null
+++ b/jgvariant-tool/pom.xml
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+
+SPDX-License-Identifier: GPL-3.0-or-later
+-->
+
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <version>0.1.8-SNAPSHOT</version>
+
+ <artifactId>jgvariant-tool</artifactId>
+ <packaging>jar</packaging>
+
+ <name>JGVariant Command Line Tool</name>
+ <url>https://gerrit.benkard.de/plugins/gitiles/jgvariant</url>
+
+ <description>
+ GVariant command line tool.
+ </description>
+
+ <parent>
+ <groupId>eu.mulk.jgvariant</groupId>
+ <artifactId>jgvariant-parent</artifactId>
+ <version>0.1.8-SNAPSHOT</version>
+ <relativePath>../jgvariant-parent/pom.xml</relativePath>
+ </parent>
+
+ <dependencies>
+ <!-- JGVariant -->
+ <dependency>
+ <groupId>eu.mulk.jgvariant</groupId>
+ <artifactId>jgvariant-core</artifactId>
+ <version>0.1.8-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>eu.mulk.jgvariant</groupId>
+ <artifactId>jgvariant-ostree</artifactId>
+ <version>0.1.8-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Annotations -->
+ <dependency>
+ <groupId>com.google.errorprone</groupId>
+ <artifactId>error_prone_annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains</groupId>
+ <artifactId>annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apiguardian</groupId>
+ <artifactId>apiguardian-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Command line tooling -->
+ <dependency>
+ <groupId>info.picocli</groupId>
+ <artifactId>picocli</artifactId>
+ </dependency>
+
+ <!-- JSON -->
+ <dependency>
+ <groupId>org.eclipse</groupId>
+ <artifactId>yasson</artifactId>
+ </dependency>
+
+ <!-- Guava -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>info.picocli</groupId>
+ <artifactId>picocli-codegen</artifactId>
+ <version>${picocli.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ <compilerArgs>
+ <arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>eu.mulk.jgvariant.tool.Main</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>shade</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>uberjar</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>eu.mulk.jgvariant.tool.Main</mainClass>
+ </manifest>
+ </archive>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>native</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.graalvm.buildtools</groupId>
+ <artifactId>native-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>build-native</id>
+ <goals>
+ <goal>compile-no-fork</goal>
+ </goals>
+ <phase>package</phase>
+ </execution>
+ <execution>
+ <id>test-native</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <phase>test</phase>
+ </execution>
+ </executions>
+ <configuration>
+ <debug>false</debug>
+ <fallback>false</fallback>
+ <buildArgs>
+ <arg>-O3</arg>
+ <arg>--strict-image-heap</arg>
+ </buildArgs>
+ <imageName>jgvariant</imageName>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>jpackage</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.github.akman</groupId>
+ <artifactId>jpackage-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>jpackage</goal>
+ </goals>
+ <configuration>
+ <type>IMAGE</type>
+ <module>eu.mulk.jgvariant.tool/eu.mulk.jgvariant.tool.Main</module>
+ <modulepath>
+ <dependencysets>
+ <dependencyset>
+ <includeoutput>true</includeoutput>
+ <excludeautomatic>true</excludeautomatic>
+ </dependencyset>
+ </dependencysets>
+ </modulepath>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
new file mode 100644
index 0000000..64e375a
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool;
+
+import static java.util.logging.Level.WARNING;
+
+import java.util.logging.Logger;
+import picocli.CommandLine;
+
+/**
+ * A command line tool to read and manipulate GVariant-formatted files.
+ *
+ * <p>Also provides ways to manipulate OSTree repositories.
+ */
+public final class Main {
+ static {
+ Logger.getGlobal().setLevel(WARNING);
+ }
+
+ public static void main(String[] args) {
+ int exitCode = new CommandLine(new MainCommand()).execute(args);
+ System.exit(exitCode);
+ }
+
+ private Main() {}
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
new file mode 100644
index 0000000..7b25dfc
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
@@ -0,0 +1,115 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool;
+
+import static java.util.logging.Level.*;
+
+import eu.mulk.jgvariant.ostree.Summary;
+import eu.mulk.jgvariant.tool.jsonb.*;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.util.logging.Logger;
+import org.jetbrains.annotations.VisibleForTesting;
+import picocli.AutoComplete;
+import picocli.CommandLine;
+import picocli.CommandLine.*;
+
+@Command(
+ name = "jgvariant",
+ mixinStandardHelpOptions = true,
+ description = "Manipulate files in GVariant format.",
+ subcommands = {MainCommand.OstreeCommand.class, AutoComplete.GenerateCompletion.class})
+final class MainCommand {
+
+ private static final Logger LOG = Logger.getLogger("eu.mulk.jgvariant.tool");
+
+ private static final Jsonb jsonb =
+ JsonbBuilder.newBuilder()
+ .withConfig(
+ new JsonbConfig()
+ .withFormatting(true)
+ .withAdapters(ChecksumAdapter.INSTANCE)
+ .withSerializers(
+ ByteStringSerializer.INSTANCE,
+ ByteArraySerializer.INSTANCE,
+ SignatureSerializer.INSTANCE,
+ VariantSerializer.INSTANCE))
+ .build();
+
+ @Option(
+ names = {"-v", "--verbose"},
+ description = "Enable verbose logging.",
+ scope = CommandLine.ScopeType.INHERIT)
+ void setVerbose(boolean[] verbose) {
+ Logger.getGlobal()
+ .setLevel(
+ switch (verbose.length) {
+ case 0 -> WARNING;
+ case 1 -> INFO;
+ case 2 -> FINE;
+ default -> ALL;
+ });
+ }
+
+ @Command(
+ name = "ostree",
+ mixinStandardHelpOptions = true,
+ description = "Manipulate OSTree files.",
+ subcommands = {OstreeCommand.SummaryCommand.class})
+ static final class OstreeCommand {
+
+ @Command(
+ name = "summary",
+ mixinStandardHelpOptions = true,
+ description = "Manipulate OSTree summary files.")
+ static final class SummaryCommand extends BaseCommand {
+
+ @Command(mixinStandardHelpOptions = true)
+ void read(@Parameters(paramLabel = "<file>") File file) throws IOException {
+ LOG.fine(() -> "Reading file %s".formatted(file));
+ var fileBytes = ByteBuffer.wrap(Files.readAllBytes(fs().getPath(file.getPath())));
+ var decoder = Summary.decoder();
+ var thing = decoder.decode(fileBytes);
+ out().println(jsonb.toJson(thing));
+ }
+
+ SummaryCommand() {}
+ }
+
+ OstreeCommand() {}
+ }
+
+ @Command
+ abstract static class BaseCommand {
+
+ @Spec CommandLine.Model.CommandSpec spec;
+
+ @VisibleForTesting FileSystem fs = FileSystems.getDefault();
+
+ protected BaseCommand() {}
+
+ protected PrintWriter out() {
+ return spec.commandLine().getOut();
+ }
+
+ protected PrintWriter err() {
+ return spec.commandLine().getErr();
+ }
+
+ protected FileSystem fs() {
+ return fs;
+ }
+ }
+
+ MainCommand() {}
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java
new file mode 100644
index 0000000..7f80440
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool.jsonb;
+
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import java.util.HexFormat;
+
+@SuppressWarnings("java:S6548")
+public final class ByteArraySerializer implements JsonbSerializer<byte[]> {
+
+ public static final ByteArraySerializer INSTANCE = new ByteArraySerializer();
+
+ private ByteArraySerializer() {}
+
+ @Override
+ public void serialize(
+ byte[] o, JsonGenerator jsonGenerator, SerializationContext serializationContext) {
+ jsonGenerator.write(HexFormat.of().formatHex(o));
+ }
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java
new file mode 100644
index 0000000..3537c3d
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool.jsonb;
+
+import eu.mulk.jgvariant.ostree.ByteString;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+
+@SuppressWarnings("java:S6548")
+public final class ByteStringSerializer implements JsonbSerializer<ByteString> {
+
+ public static final ByteStringSerializer INSTANCE = new ByteStringSerializer();
+
+ private ByteStringSerializer() {}
+
+ @Override
+ public void serialize(
+ ByteString o, JsonGenerator jsonGenerator, SerializationContext serializationContext) {
+ jsonGenerator.write(o.hex());
+ }
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java
new file mode 100644
index 0000000..58a8951
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool.jsonb;
+
+import eu.mulk.jgvariant.ostree.ByteString;
+import eu.mulk.jgvariant.ostree.Checksum;
+import jakarta.json.bind.adapter.JsonbAdapter;
+
+@SuppressWarnings("java:S6548")
+public final class ChecksumAdapter implements JsonbAdapter<Checksum, ByteString> {
+
+ public static final ChecksumAdapter INSTANCE = new ChecksumAdapter();
+
+ private ChecksumAdapter() {}
+
+ @Override
+ public ByteString adaptToJson(Checksum obj) throws Exception {
+ return obj.byteString();
+ }
+
+ @Override
+ public Checksum adaptFromJson(ByteString obj) throws Exception {
+ return new Checksum(obj);
+ }
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java
new file mode 100644
index 0000000..dca1044
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool.jsonb;
+
+import eu.mulk.jgvariant.core.Signature;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+
+@SuppressWarnings("java:S6548")
+public final class SignatureSerializer implements JsonbSerializer<Signature> {
+
+ public static final SignatureSerializer INSTANCE = new SignatureSerializer();
+
+ private SignatureSerializer() {}
+
+ @Override
+ public void serialize(
+ Signature o, JsonGenerator jsonGenerator, SerializationContext serializationContext) {
+ jsonGenerator.write(o.toString());
+ }
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java
new file mode 100644
index 0000000..99ff553
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package eu.mulk.jgvariant.tool.jsonb;
+
+import com.google.common.primitives.Bytes;
+import eu.mulk.jgvariant.core.Signature;
+import eu.mulk.jgvariant.core.Variant;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import java.text.ParseException;
+import java.util.List;
+
+@SuppressWarnings("java:S6548")
+public final class VariantSerializer implements JsonbSerializer<Variant> {
+
+ public static final VariantSerializer INSTANCE = new VariantSerializer();
+
+ private final ByteArraySerializer byteArraySerializer = ByteArraySerializer.INSTANCE;
+
+ private final Signature byteArraySignature;
+
+ private VariantSerializer() {
+ try {
+ byteArraySignature = Signature.parse("ay");
+ } catch (ParseException e) {
+ // impossible
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void serialize(Variant obj, JsonGenerator generator, SerializationContext ctx) {
+ if (obj.signature().equals(byteArraySignature)) {
+ byteArraySerializer.serialize(Bytes.toArray((List<Byte>) obj.value()), generator, ctx);
+ } else {
+ ctx.serialize(obj.value(), generator);
+ }
+ }
+}
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java
new file mode 100644
index 0000000..da5c3ba
--- /dev/null
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java
@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+@API(status = API.Status.INTERNAL)
+package eu.mulk.jgvariant.tool;
+
+import org.apiguardian.api.API;
diff --git a/jgvariant-tool/src/main/java/module-info.java b/jgvariant-tool/src/main/java/module-info.java
new file mode 100644
index 0000000..e66981c
--- /dev/null
+++ b/jgvariant-tool/src/main/java/module-info.java
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+module eu.mulk.jgvariant.tool {
+ requires transitive eu.mulk.jgvariant.ostree;
+ requires com.google.common;
+ requires info.picocli;
+ requires jakarta.json;
+ requires jakarta.json.bind;
+ requires java.logging;
+ requires static com.google.errorprone.annotations;
+ requires static org.apiguardian.api;
+ requires static org.jetbrains.annotations;
+
+ opens eu.mulk.jgvariant.tool to
+ info.picocli;
+
+ exports eu.mulk.jgvariant.tool;
+}