diff --git a/src/Filee.Cli/Cli.cs b/src/Filee.Cli/Cli.cs
index d2d4fa48..a0c8a06c 100644
--- a/src/Filee.Cli/Cli.cs
+++ b/src/Filee.Cli/Cli.cs
@@ -339,6 +339,18 @@ private static int ListFormats(CliOptions options, CliHost host, TextWriter outp
var targets = FormatRegistry.Known.Where(t => t.Writable && t.Id != from.Id).ToList();
var now = targets.Where(t => installed.CanConvert(from.Id, t.Id)).Select(t => t.Id).ToList();
var later = targets.Where(t => !now.Contains(t.Id) && everything.CanConvert(from.Id, t.Id)).Select(t => t.Id).ToList();
+ if (options.Json)
+ {
+ WriteJsonDocument(output, json =>
+ {
+ json.WriteStartObject();
+ WriteFormatFields(json, from);
+ WriteStrings(json, "targets", now);
+ WriteStrings(json, "withEngines", later);
+ json.WriteEndObject();
+ });
+ return Success;
+ }
output.WriteLine($"{from.DisplayName} ({string.Join(", ", from.Extensions)}) converts to:");
output.WriteLine(now.Count > 0 ? " " + string.Join(", ", now) : " (nothing with the engines installed now)");
if (later.Count > 0)
@@ -346,6 +358,24 @@ private static int ListFormats(CliOptions options, CliHost host, TextWriter outp
return Success;
}
+ if (options.Json)
+ {
+ WriteJsonDocument(output, json =>
+ {
+ json.WriteStartObject();
+ json.WriteStartArray("formats");
+ foreach (var format in FormatRegistry.Known.Where(f => f.Id != FormatRegistry.Folder))
+ {
+ json.WriteStartObject();
+ WriteFormatFields(json, format);
+ json.WriteEndObject();
+ }
+ json.WriteEndArray();
+ json.WriteEndObject();
+ });
+ return Success;
+ }
+
foreach (var group in FormatRegistry.Known.Where(f => f.Id != FormatRegistry.Folder).GroupBy(f => f.Category))
{
output.WriteLine($"{group.Key}:");
@@ -357,6 +387,32 @@ private static int ListFormats(CliOptions options, CliHost host, TextWriter outp
return Success;
}
+ /// The fields of one format in the JSON of 'formats': the same names for the list and for one format.
+ private static void WriteFormatFields(Utf8JsonWriter json, FileFormat format)
+ {
+ json.WriteString("id", format.Id);
+ json.WriteString("name", format.DisplayName);
+ json.WriteString("category", format.Category.ToString().ToLowerInvariant());
+ WriteStrings(json, "extensions", format.Extensions);
+ json.WriteBoolean("writable", format.Writable);
+ }
+
+ private static void WriteStrings(Utf8JsonWriter json, string name, IEnumerable values)
+ {
+ json.WriteStartArray(name);
+ foreach (var value in values)
+ json.WriteStringValue(value);
+ json.WriteEndArray();
+ }
+
+ private static void WriteJsonDocument(TextWriter output, Action write)
+ {
+ using var stream = new MemoryStream();
+ using (var json = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
+ write(json);
+ output.WriteLine(System.Text.Encoding.UTF8.GetString(stream.ToArray()));
+ }
+
private static int ListPresets(CliHost host, TextWriter output)
{
output.WriteLine($"{"ID",-24} {"NAME",-28} TARGET");
diff --git a/src/Filee.Cli/Help.cs b/src/Filee.Cli/Help.cs
index b7c81f06..5c508909 100644
--- a/src/Filee.Cli/Help.cs
+++ b/src/Filee.Cli/Help.cs
@@ -14,7 +14,8 @@ internal static class Help
{
"convert" => Convert,
"watch" => Watch,
- "formats" => "Usage: filee formats []\n\nLists every format, or what one format converts to.\n",
+ "formats" => "Usage: filee formats [] [--json]\n\nLists every format, or what one format converts to.\n" +
+ "With --json, prints the same as JSON (for scripts and other programs).\n",
"presets" => "Usage: filee presets\n\nLists the presets saved in Filee (built-in and your own).\n",
_ => General,
};
@@ -26,7 +27,7 @@ internal static class Help
filee convert --to [options]
filee convert --preset [options]
filee watch --to [options]
- filee formats []
+ filee formats [] [--json]
filee presets
filee help []
filee version
diff --git a/tests/Filee.Cli.Tests/CliTests.cs b/tests/Filee.Cli.Tests/CliTests.cs
index 0ffdbaae..3ea02fc1 100644
--- a/tests/Filee.Cli.Tests/CliTests.cs
+++ b/tests/Filee.Cli.Tests/CliTests.cs
@@ -328,6 +328,41 @@ public async Task Archives_list_folder_as_a_target()
Assert.DoesNotContain("folder", output);
}
+ [Fact]
+ public async Task Formats_json_lists_every_format()
+ {
+ var (code, output, _) = await Run("formats", "--json");
+ Assert.Equal(Cli.Success, code);
+
+ using var json = JsonDocument.Parse(output);
+ var formats = json.RootElement.GetProperty("formats").EnumerateArray().ToList();
+ var png = Assert.Single(formats, f => f.GetProperty("id").GetString() == "png");
+ Assert.Equal("image", png.GetProperty("category").GetString());
+ Assert.Contains("png", png.GetProperty("extensions").EnumerateArray().Select(e => e.GetString()));
+ Assert.True(png.GetProperty("writable").GetBoolean());
+ Assert.DoesNotContain(formats, f => f.GetProperty("id").GetString() == "folder");
+ }
+
+ [Fact]
+ public async Task Formats_json_of_one_format_matches_the_text()
+ {
+ var (code, text, _) = await Run("formats", "zip");
+ Assert.Equal(Cli.Success, code);
+ (code, var output, _) = await Run("formats", "zip", "--json");
+ Assert.Equal(Cli.Success, code);
+
+ using var json = JsonDocument.Parse(output);
+ var root = json.RootElement;
+ Assert.Equal("zip", root.GetProperty("id").GetString());
+ Assert.Equal("archive", root.GetProperty("category").GetString());
+ var targets = root.GetProperty("targets").EnumerateArray().Select(e => e.GetString()!).ToList();
+ var withEngines = root.GetProperty("withEngines").EnumerateArray().Select(e => e.GetString()!).ToList();
+ Assert.Contains("folder", targets.Concat(withEngines));
+ Assert.Empty(targets.Intersect(withEngines));
+ foreach (var id in targets.Concat(withEngines))
+ Assert.Contains(id, text);
+ }
+
[Fact]
public async Task Jpeg_xr_converts_on_windows_and_is_no_download_away_elsewhere()
{