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() {