From 62b73c9aadc3db7ab1fbb7a1c9b992f581296313 Mon Sep 17 00:00:00 2001 From: Alejandro R Mosteo Date: Mon, 23 Sep 2024 20:12:19 +0200 Subject: [PATCH] JSON, TOML output for tables (#1759) * Headers * Global option --format * fix tables without headers * Testsuite test * Testsuite-found fixes * Bump submodules, testsuite dependencies --- .gitmodules | 3 + alire.gpr | 1 + alire.toml | 18 +- alr_env.gpr | 1 + deps/aaa | 2 +- deps/ansi | 2 +- deps/den | 2 +- deps/lml | 1 + src/alire/alire-index-search.adb | 7 +- src/alire/alire-solutions-diffs.adb | 2 +- src/alire/alire-solutions.adb | 11 +- src/alire/alire-utils-did_you_mean.adb | 6 +- src/alire/alire-utils-tables.adb | 37 +++- src/alire/alire-utils-tables.ads | 30 ++- src/alr/alr-commands-index.adb | 16 +- src/alr/alr-commands-search.adb | 12 +- src/alr/alr-commands-toolchain.adb | 18 +- src/alr/alr-commands-version.adb | 186 +++++++++--------- src/alr/alr-commands.adb | 64 ++++++ testsuite/requirements.txt | 1 + .../tests/misc/structured-tables/test.py | 72 +++++++ .../tests/misc/structured-tables/test.yaml | 4 + testsuite/tests/pin/branch/test.py | 6 +- 23 files changed, 356 insertions(+), 146 deletions(-) create mode 160000 deps/lml create mode 100644 testsuite/tests/misc/structured-tables/test.py create mode 100644 testsuite/tests/misc/structured-tables/test.yaml diff --git a/.gitmodules b/.gitmodules index ec5a589b..3054b037 100644 --- a/.gitmodules +++ b/.gitmodules @@ -66,3 +66,6 @@ [submodule "deps/cstrings"] path = deps/cstrings url = https://github.com/mosteo/cstrings +[submodule "deps/lml"] + path = deps/lml + url = https://github.com/mosteo/lml_ada.git diff --git a/alire.gpr b/alire.gpr index a00b73f3..05206784 100644 --- a/alire.gpr +++ b/alire.gpr @@ -9,6 +9,7 @@ with "den"; with "dirty_booleans"; with "diskflags"; with "gnatcoll"; +with "lml"; with "minirest"; with "optional"; with "semantic_versioning"; diff --git a/alire.toml b/alire.toml index 282d9d15..b853e5fa 100644 --- a/alire.toml +++ b/alire.toml @@ -18,13 +18,14 @@ executables = ["alr"] aaa = "~0.3.0" ada_toml = "~0.3" ajunitgen = "^1.0.1" -ansiada = "^1.0" +ansiada = "^1.1" c_strings = "^1.0" clic = "~0.3" den = "~0.1" dirty_booleans = "~0.1" diskflags = "~0.1" gnatcoll = "^21" +lml = "~0.1" minirest = "~0.3" optional = "~0.1" semantic_versioning = "^3.0" @@ -50,13 +51,19 @@ windows = { ALIRE_OS = "windows" } # Some dependencies require precise versions during the development cycle: [[pins]] + [pins.aaa] url = "https://github.com/mosteo/aaa" -commit = "0c3b440ac183c450345d4a67d407785678779aae" +commit = "ddfeffe2d6c8f9d19161df7b31d16d37bef4ba71" [pins.ada_toml] url = "https://github.com/mosteo/ada-toml" commit = "da4e59c382ceb0de6733d571ecbab7ea4919b33d" + +[pins.ansiada] +url = "https://github.com/mosteo/ansi-ada" +commit = "0772e48d3e1f640829d142745a36b37edcd5470b" + [pins.c_strings] url = "https://github.com/mosteo/cstrings" commit = "e4d58ad90bf32bc44304197e5906a519f5a9a7bf" @@ -64,9 +71,10 @@ commit = "e4d58ad90bf32bc44304197e5906a519f5a9a7bf" [pins.clic] url = "https://github.com/alire-project/clic" commit = "56bbdc008e16996b6f76e443fd0165a240de1b13" + [pins.den] url = "https://github.com/mosteo/den" -commit = "681ab5ca522585953f2e1d70763731df34c96012" +commit = "b12e8461bf41e2cfe199c8196b45fa4fc213a6aa" [pins.dirty_booleans] url = "https://github.com/mosteo/dirty_booleans" @@ -80,6 +88,10 @@ commit = "60729edf31816aca0036b13b2794c39a9bd0172e" url = "https://github.com/alire-project/gnatcoll-core.git" commit = "4e663b87a028252e7e074f054f8f453661397166" +[pins.lml] +url = "https://github.com/mosteo/lml_ada.git" +commit = "ae156ef82a2fedb7e28bb4dcaeb3d5c0a2e046ec" + [pins.minirest] url = "https://github.com/mosteo/minirest.git" commit = "9a9c660f9c6f27f5ef75417e7fac7061dff14d78" diff --git a/alr_env.gpr b/alr_env.gpr index 78592c5e..c9a6a6b3 100644 --- a/alr_env.gpr +++ b/alr_env.gpr @@ -19,6 +19,7 @@ aggregate project Alr_Env is "deps/dirty_booleans", "deps/diskflags", "deps/gnatcoll-slim", + "deps/lml", "deps/minirest", "deps/optional", "deps/semantic_versioning", diff --git a/deps/aaa b/deps/aaa index 0c3b440a..ddfeffe2 160000 --- a/deps/aaa +++ b/deps/aaa @@ -1 +1 @@ -Subproject commit 0c3b440ac183c450345d4a67d407785678779aae +Subproject commit ddfeffe2d6c8f9d19161df7b31d16d37bef4ba71 diff --git a/deps/ansi b/deps/ansi index dc770a5a..0772e48d 160000 --- a/deps/ansi +++ b/deps/ansi @@ -1 +1 @@ -Subproject commit dc770a5a6cdaad8668c32b0cd4625a7d648f8ca2 +Subproject commit 0772e48d3e1f640829d142745a36b37edcd5470b diff --git a/deps/den b/deps/den index 681ab5ca..b12e8461 160000 --- a/deps/den +++ b/deps/den @@ -1 +1 @@ -Subproject commit 681ab5ca522585953f2e1d70763731df34c96012 +Subproject commit b12e8461bf41e2cfe199c8196b45fa4fc213a6aa diff --git a/deps/lml b/deps/lml new file mode 160000 index 00000000..ae156ef8 --- /dev/null +++ b/deps/lml @@ -0,0 +1 @@ +Subproject commit ae156ef82a2fedb7e28bb4dcaeb3d5c0a2e046ec diff --git a/src/alire/alire-index-search.adb b/src/alire/alire-index-search.adb index 7e7fb85f..54adf891 100644 --- a/src/alire/alire-index-search.adb +++ b/src/alire/alire-index-search.adb @@ -24,6 +24,11 @@ package body Alire.Index.Search is Busy : Simple_Logging.Ongoing := Simple_Logging.Activity ("Searching"); begin + -- Preserve old behavior for human output + if Utils.Tables.Structured_Output then + Table.Header ("NAME").Header ("DESCRIPTION"); + end if; + for Crate of Alire.Index.All_Crates.all loop if Lookup = "" or else Contains (To_Lower_Case (+Crate.Name), Lookup) or else @@ -37,7 +42,7 @@ package body Alire.Index.Search is Busy.Step; end loop; - if Found = 0 then + if Found = 0 and then not Utils.Tables.Structured_Output then Trace.Always ("No hits"); else Table.Print (Always, Separator => " "); diff --git a/src/alire/alire-solutions-diffs.adb b/src/alire/alire-solutions-diffs.adb index dc74563d..169ae631 100644 --- a/src/alire/alire-solutions-diffs.adb +++ b/src/alire/alire-solutions-diffs.adb @@ -551,7 +551,7 @@ package body Alire.Solutions.Diffs is end loop; if Changed then - Table.Print (Level); + Table.Print (Level, Structured => False); Warn_Toolchain_Download; Warn_Unsatisfiable_GNAT_External; diff --git a/src/alire/alire-solutions.adb b/src/alire/alire-solutions.adb index 8e5228c4..d9609eb4 100644 --- a/src/alire/alire-solutions.adb +++ b/src/alire/alire-solutions.adb @@ -923,8 +923,17 @@ package body Alire.Solutions is Table : Utils.Tables.Table; begin if This.Links.Is_Empty and then Dependency_Map'(This.Pins).Is_Empty then - Trace.Always ("There are no pins"); + if Utils.Tables.Structured_Output then + Table.Print (Always); + else + Trace.Always ("There are no pins"); + end if; else + -- To preserve old behavior with human output + if Utils.Tables.Structured_Output then + Table.Header ("Crate").Header ("Target").Header ("Origin").New_Row; + end if; + for Dep of This.Dependencies loop if Dep.Is_Linked then Table diff --git a/src/alire/alire-utils-did_you_mean.adb b/src/alire/alire-utils-did_you_mean.adb index c7d09697..75c492be 100644 --- a/src/alire/alire-utils-did_you_mean.adb +++ b/src/alire/alire-utils-did_you_mean.adb @@ -81,10 +81,10 @@ package body Alire.Utils.Did_You_Mean with Preelaborate is for V in Enum loop Possible_Values.Append (case Transform is - when None => V'Img, + when None => V'Img, when Lower_Case => AAA.Strings.To_Lower_Case (V'Img), - when Upper_Case => AAA.Strings.To_Lower_Case (V'Img), - when Tomify => TOML_Adapters.Tomify (V'Img)); + when Upper_Case => AAA.Strings.To_Upper_Case (V'Img), + when Tomify => TOML_Adapters.Tomify (V'Img)); end loop; return Suggestion (Input, Possible_Values); diff --git a/src/alire/alire-utils-tables.adb b/src/alire/alire-utils-tables.adb index a4ea9d76..243a6844 100644 --- a/src/alire/alire-utils-tables.adb +++ b/src/alire/alire-utils-tables.adb @@ -6,27 +6,39 @@ package body Alire.Utils.Tables is -- Header -- ------------ + overriding procedure Header (T : in out Table; Cell : String) is + Text : constant String := + (if Structured_Output + then AAA.Strings.To_Lower_Case (Cell) + else TTY.Emph (AAA.Strings.To_Upper_Case (Cell))); begin - T.Append (TTY.Emph (AAA.Strings.To_Upper_Case (Cell))); + Parent (T).Header (Text); end Header; + ------------ + -- Header -- + ------------ + + overriding function Header (T : aliased in out Table; Cell : String) - return access Table is + return AAA.Table_IO.Reference + is begin T.Header (Cell); - return T'Access; + return AAA.Table_IO.Reference'(Table => T'Access); end Header; ----------- -- Print -- ----------- - procedure Print (T : Table; - Level : Trace.Levels := Info; - Separator : String := " "; - Align : AAA.Table_IO.Alignments := (1 .. 0 => <>)) + procedure Print (T : Table; + Level : Trace.Levels := Info; + Separator : String := " "; + Align : AAA.Table_IO.Alignments := (1 .. 0 => <>); + Structured : Boolean := Structured_Output) is procedure Print (Line : String) is @@ -37,9 +49,14 @@ package body Alire.Utils.Tables is end Print; begin - T.Print (Separator => Separator, - Align => Align, - Put_Line => Print'Access); + if Structured then + T.Print (Structured_Output_Format, + Put_Line => Print'Access); + else + T.Print (Separator => Separator, + Align => Align, + Put_Line => Print'Access); + end if; end Print; end Alire.Utils.Tables; diff --git a/src/alire/alire-utils-tables.ads b/src/alire/alire-utils-tables.ads index f6f3ef93..19dd53d5 100644 --- a/src/alire/alire-utils-tables.ads +++ b/src/alire/alire-utils-tables.ads @@ -1,19 +1,33 @@ with AAA.Table_IO; -package Alire.Utils.Tables with Preelaborate is +with LML; - type Table is new AAA.Table_IO.Table with null record; +package Alire.Utils.Tables is + subtype Formats is LML.Formats; + + Structured_Output : Boolean := False; + + Structured_Output_Format : Formats; + + subtype Parent is AAA.Table_IO.Table; + + type Table is new Parent with null record; + + overriding procedure Header (T : in out Table; Cell : String); + overriding function Header (T : aliased in out Table; Cell : String) - return access Table; + return AAA.Table_IO.Reference; - procedure Print (T : Table; - Level : Trace.Levels := Info; - Separator : String := " "; - Align : AAA.Table_IO.Alignments := (1 .. 0 => <>)); - -- Hook so tables use the default output facilities of Alire + procedure Print (T : Table; + Level : Trace.Levels := Info; + Separator : String := " "; + Align : AAA.Table_IO.Alignments := (1 .. 0 => <>); + Structured : Boolean := Structured_Output); + -- Hook so tables use the default output facilities of Alire. When + -- Structured_Output is enabled, formatting information is ignored. end Alire.Utils.Tables; diff --git a/src/alr/alr-commands-index.adb b/src/alr/alr-commands-index.adb index 9f261746..cb212a17 100644 --- a/src/alr/alr-commands-index.adb +++ b/src/alr/alr-commands-index.adb @@ -1,10 +1,8 @@ -with AAA.Table_IO; - with Alire.Settings.Edit; with Alire.Index; with Alire.Index_On_Disk.Loading; with Alire.Index_On_Disk.Updates; -with Alire.Utils; +with Alire.Utils.Tables; package body Alr.Commands.Index is @@ -155,7 +153,7 @@ package body Alr.Commands.Index is Index_Load.Find_All (Alire.Settings.Edit.Indexes_Directory, Result); - Table : AAA.Table_IO.Table; + Table : Alire.Utils.Tables.Table; Count : Natural := 0; begin if not Result.Success then @@ -163,10 +161,10 @@ package body Alr.Commands.Index is end if; Table - .Append (TTY.Emph ("#")) - .Append (TTY.Emph ("NAME")) - .Append (TTY.Emph ("URL")) - .Append (TTY.Emph ("PATH")); + .Header ("#") + .Header ("NAME") + .Header ("URL") + .Header ("PATH"); if Alire.Log_Level = Alire.Trace.Debug then Table.Append (TTY.Emph ("PRIORITY")); @@ -187,7 +185,7 @@ package body Alr.Commands.Index is end loop; if Count > 0 then - Table.Print; + Table.Print (Always); else Trace.Info ("No index configured."); end if; diff --git a/src/alr/alr-commands-search.adb b/src/alr/alr-commands-search.adb index f5ff1716..61f6feee 100644 --- a/src/alr/alr-commands-search.adb +++ b/src/alr/alr-commands-search.adb @@ -194,12 +194,12 @@ package body Alr.Commands.Search is -- End of option verification, start of search. First load the index, -- required to look at its entries. - Tab.Append (TTY.Bold ("NAME")); - Tab.Append (TTY.Bold ("STATUS")); - Tab.Append (TTY.Bold ("VERSION")); - Tab.Append (TTY.Bold ("DESCRIPTION")); - Tab.Append (TTY.Bold ("NOTES")); - Tab.Append (TTY.Bold ("MATCHES")); + Tab.Header ("NAME"); + Tab.Header ("STATUS"); + Tab.Header ("VERSION"); + Tab.Header ("DESCRIPTION"); + Tab.Header ("NOTES"); + Tab.Header ("MATCHES"); declare Busy : Simple_Logging.Ongoing := diff --git a/src/alr/alr-commands-toolchain.adb b/src/alr/alr-commands-toolchain.adb index b5f78188..f8495578 100644 --- a/src/alr/alr-commands-toolchain.adb +++ b/src/alr/alr-commands-toolchain.adb @@ -1,6 +1,4 @@ -with AAA.Table_IO; - with Alire.Settings.Edit; with Alire.Containers; with Alire.Dependencies; @@ -10,7 +8,7 @@ with Alire.Origins.Deployers; with Alire.Releases.Containers; with Alire.Solver; with Alire.Toolchains; -with Alire.Utils; use Alire.Utils; +with Alire.Utils.Tables; with Alire.Utils.TTY; with Alire.Warnings; @@ -18,6 +16,8 @@ with Semantic_Versioning.Extended; package body Alr.Commands.Toolchain is + use Alire.Utils; + package Name_Sets renames Alire.Containers.Crate_Name_Sets; -------------------- @@ -268,7 +268,7 @@ package body Alr.Commands.Toolchain is pragma Unreferenced (Cmd); use Alire; use type Dependencies.Dependency; - Table : AAA.Table_IO.Table; + Table : Tables.Table; begin Alire.Toolchains.Detect_Externals; -- Even if we have selected a non-external toolchain, in this case we @@ -281,10 +281,10 @@ package body Alr.Commands.Toolchain is end if; Table - .Append (TTY.Emph ("CRATE")) - .Append (TTY.Emph ("VERSION")) - .Append (TTY.Emph ("STATUS")) - .Append (TTY.Emph ("NOTES")) + .Header ("CRATE") + .Header ("VERSION") + .Header ("STATUS") + .Header ("NOTES") .New_Row; for Dep of Alire.Toolchains.Available loop @@ -311,7 +311,7 @@ package body Alr.Commands.Toolchain is end if; end loop; - Table.Print; + Table.Print (Always); end List; ------------- diff --git a/src/alr/alr-commands-version.adb b/src/alr/alr-commands-version.adb index b2d2fa30..a89057d9 100644 --- a/src/alr/alr-commands-version.adb +++ b/src/alr/alr-commands-version.adb @@ -38,8 +38,9 @@ package body Alr.Commands.Version is Args : AAA.Strings.Vector) is use Alire; + use Alire.Utils; use all type Alire.Roots.Optional.States; - Table : Alire.Utils.Tables.Table; + Table : Tables.Table; Index_Outcome : Alire.Outcome; Indexes : constant Alire.Index_On_Disk.Loading.Set := Alire.Index_On_Disk.Loading.Find_All @@ -57,66 +58,82 @@ package body Alr.Commands.Version is / Paths.Cache_Folder_Inside_Working_Folder / Paths.Deps_Folder_Inside_Cache_Folder) else Builds.Path); + + --------- + -- Add -- + --------- + + procedure Add (Key : String; Val : String := "") is + use AAA.Strings; + begin + if (Key = "" or else Val = "") and then Tables.Structured_Output then + return; -- Skip cosmetic rows in structured output + end if; + + if Tables.Structured_Output and then Contains (Key, ":") then + Add (Replace (Key, ":", ""), Val); + return; + end if; + + Table.Append (Key).Append (Val).New_Row; + end Add; + begin if Args.Count /= 0 then Reportaise_Wrong_Arguments (Cmd.Name & " doesn't take arguments"); end if; - Table.Append ("APPLICATION").Append ("").New_Row; - Table.Append ("alr version:") - .Append (Alire.Version.Current.Image).New_Row; - Table.Append ("libalire version:") - .Append (Alire.Version.Current.Image).New_Row; - Table.Append ("compilation date:") - .Append (GNAT.Source_Info.Compilation_ISO_Date & " " - & GNAT.Source_Info.Compilation_Time).New_Row; - Table.Append ("compiled with version:") - .Append (GNAT_Version.Version).New_Row; - - Table.Append ("").New_Row; - Table.Append ("CONFIGURATION").New_Row; - Table.Append ("settings folder:") - .Append (Alire.Settings.Edit.Path).New_Row; - Table.Append ("cache folder:") - .Append (Alire.Cache.Path).New_Row; - Table.Append ("vault folder:").Append (Paths.Vault.Path).New_Row; - Table.Append ("build folder:").Append (Build_Path).New_Row; - Table.Append ("temp folder:") - .Append (Alire.Platforms.Folders.Temp).New_Row; - Table.Append ("force flag:").Append (Alire.Force'Image).New_Row; - Table.Append ("non-interactive flag:") - .Append (CLIC.User_Input.Not_Interactive'Image).New_Row; - Table.Append ("community index branch:") - .Append (Alire.Index.Community_Branch).New_Row; - Table.Append ("compatible index versions:") - .Append (Alire.Index.Valid_Versions.Image).New_Row; - Table.Append ("indexes folder:") - .Append (Alire.Settings.Edit.Indexes_Directory).New_Row; - Table.Append ("indexes metadata:") - .Append (if Index_Outcome.Success - then "OK" - else "ERROR: " & Index_Outcome.Message).New_Row; + -- Enrich output when using a structured format only + if Alire.Utils.Tables.Structured_Output then + Table.Header ("key").Header ("Value").New_Row; + end if; + + Add ("APPLICATION", ""); + Add ("alr version:", Alire.Version.Current.Image); + Add ("libalire version:", Alire.Version.Current.Image); + Add ("compilation date:", + GNAT.Source_Info.Compilation_ISO_Date & " " + & GNAT.Source_Info.Compilation_Time); + Add ("compiled with version:", GNAT_Version.Version); + + Add (""); + Add ("CONFIGURATION"); + Add ("settings folder:", Alire.Settings.Edit.Path); + Add ("cache folder:", Alire.Cache.Path); + Add ("vault folder:", Paths.Vault.Path); + Add ("build folder:", Build_Path); + Add ("temp folder:", Alire.Platforms.Folders.Temp); + Add ("force flag:", Alire.Force'Image); + Add ("non-interactive flag:", + CLIC.User_Input.Not_Interactive'Image); + Add ("community index branch:", Alire.Index.Community_Branch); + Add ("compatible index versions:", + Alire.Index.Valid_Versions.Image); + Add ("indexes folder:", + Alire.Settings.Edit.Indexes_Directory); + Add ("indexes metadata:", + (if Index_Outcome.Success + then "OK" + else "ERROR: " & Index_Outcome.Message)); for Index of Indexes loop - Table.Append ("index #" - & AAA.Strings.Trim (Index.Priority'Image) & ":") - .Append ("(" & Index.Name & ") " & Index.Origin).New_Row; + Add ("index #" + & AAA.Strings.Trim (Index.Priority'Image) & ":", + "(" & Index.Name & ") " & Index.Origin); end loop; - Table.Append ("toolchain folder:") - .Append (Alire.Toolchains.Path).New_Row; - Table.Append ("toolchain assistant:") - .Append (if Alire.Toolchains.Assistant_Enabled - then "enabled" - else "disabled").New_Row; + Add ("toolchain folder:", Alire.Toolchains.Path); + Add ("toolchain assistant:", + (if Alire.Toolchains.Assistant_Enabled + then "enabled" + else "disabled")); declare I : Positive := 1; begin for Tool of Alire.Toolchains.Tools loop - Table - .Append ("tool #" & AAA.Strings.Trim (I'Image) - & " " & Tool.As_String & ":") - .Append (if Alire.Toolchains.Tool_Is_Configured (Tool) - then Alire.Toolchains.Tool_Milestone (Tool).Image - else "not configured").New_Row; + Add ("tool #" & AAA.Strings.Trim (I'Image) + & " " & Tool.As_String & ":", + (if Alire.Toolchains.Tool_Is_Configured (Tool) + then Alire.Toolchains.Tool_Milestone (Tool).Image + else "not configured")); I := I + 1; end loop; end; @@ -125,47 +142,40 @@ package body Alr.Commands.Version is System_Manager : constant String := Origins.Deployers.System.Executable_Path; begin - Table - .Append ("system package manager:") - .Append (if System_Manager /= "" - then System_Manager - else "not found: " - & (if Origins.Deployers.System.Executable_Name /= "" - then "`" & Origins.Deployers.System.Executable_Name & "`" - else "unknown package manager")) - .New_Row; - Table - .Append ("distro detection disabled:") - .Append (Platforms.Current.Disable_Distribution_Detection'Image) - .New_Row; + Add ("system package manager:", + (if System_Manager /= "" + then System_Manager + else "not found: " + & (if Origins.Deployers.System.Executable_Name /= "" + then "`" & Origins.Deployers.System.Executable_Name & "`" + else "unknown package manager"))); + Add ("distro detection disabled:", + Platforms.Current.Disable_Distribution_Detection'Image); end; - Table.Append ("").New_Row; - Table.Append ("WORKSPACE").New_Row; - - Table.Append ("root status:") - .Append (Root.Status'Image).New_Row; - Table.Append ("root release:") - .Append (case Root.Status is - when Valid => Root.Value.Release.Milestone.Image, - when others => "N/A").New_Row; - Table.Append ("root load error:") - .Append (case Root.Status is - when Broken => Cmd.Optional_Root.Message, - when Valid => "none", - when Outside => "N/A").New_Row; - Table.Append ("root folder:") - .Append (case Root.Status is - when Outside => "N/A", - when Broken => "N/A", - when Valid => Root.Value.Path).New_Row; - Table.Append ("current folder:").Append (Alire.Directories.Current) - .New_Row; - - Table.Append ("").New_Row; - Table.Append ("SYSTEM").New_Row; + Add (""); + Add ("WORKSPACE"); + Add ("root status:", Root.Status'Image); + Add ("root release:", + (case Root.Status is + when Valid => Root.Value.Release.Milestone.Image, + when others => "N/A")); + Add ("root load error:", + (case Root.Status is + when Broken => Cmd.Optional_Root.Message, + when Valid => "none", + when Outside => "N/A")); + Add ("root folder:", + (case Root.Status is + when Outside => "N/A", + when Broken => "N/A", + when Valid => Root.Value.Path)); + Add ("current folder:", Alire.Directories.Current); + + Add (""); + Add ("SYSTEM"); for Prop of Platform.Properties loop - Table.Append (Prop.Key & ":").Append (Prop.Image).New_Row; + Add (Prop.Key & ":", Prop.Image); end loop; Table.Print (Level => Always); diff --git a/src/alr/alr-commands.adb b/src/alr/alr-commands.adb index 214546ee..acaf016a 100644 --- a/src/alr/alr-commands.adb +++ b/src/alr/alr-commands.adb @@ -20,6 +20,8 @@ with Alire.Platforms.Current; with Alire.Root; with Alire.Solutions; with Alire.Toolchains; +with Alire.Utils.Did_You_Mean; +with Alire.Utils.Tables; with Alr.Commands.Action; with Alr.Commands.Build; @@ -83,6 +85,9 @@ package body Alr.Commands is No_TTY : aliased Boolean := False; -- Used to disable control characters in output + Structured_Format : aliased GNAT.OS_Lib.String_Access + := new String'("unset"); + Version_Only : aliased Boolean := False; -- Just display the current version and exit @@ -173,6 +178,13 @@ package body Alr.Commands is "-n", "--non-interactive", "Assume default answers for all user prompts"); + Define_Switch (Config, + Structured_Format'Access, + Long_Switch => "--format?", + Argument => "FORMAT", + Help => + "Use structured output for tables (JSON, TOML)"); + Define_Switch (Config, No_Color'Access, Long_Switch => "--no-color", @@ -468,6 +480,46 @@ package body Alr.Commands is Trace.Debug ("End command line."); end Log_Command_Line; + --------------------------- + -- Set_Structured_Output -- + --------------------------- + + procedure Set_Structured_Output is + use Alire.Utils; + use all type Tables.Formats; + + Format_Str : constant String + := AAA.Strings.Replace (Structured_Format.all, "=", ""); + + function Is_Valid is + new AAA.Enum_Tools.Is_Valid (Tables.Formats); + + function Suggest is + new Alire.Utils.Did_You_Mean.Enum_Suggestion + (Tables.Formats, + Alire.Utils.Did_You_Mean.Upper_Case); + + begin + if Structured_Format.all /= "unset" then + Alire.Utils.Tables.Structured_Output := True; + else + return; + end if; + + if Format_Str /= "" and then not Is_Valid (Format_Str) then + Reportaise_Wrong_Arguments + ("Unknown argument in --format" & Structured_Format.all + & "." & Suggest (Format_Str)); + end if; + + if Format_Str /= "" then + Alire.Utils.Tables.Structured_Output_Format + := Alire.Utils.Tables.Formats'Value (Format_Str); + else + Alire.Utils.Tables.Structured_Output_Format := JSON; + end if; + end Set_Structured_Output; + use all type Alire.Platforms.Operating_Systems; begin @@ -529,6 +581,10 @@ package body Alr.Commands is Ada.Directories.Set_Directory (Command_Line_Chdir_Target_Path.all); end if; + Set_Structured_Output; + + -- End of global switches + Create_Alire_Folders; begin @@ -586,6 +642,14 @@ package body Alr.Commands is OS_Lib.Bailout (1); end if; end; + exception + when Wrong_Command_Arguments => + Trace.Detail ("alr global switches are invalid"); + if Alire.Log_Level = Debug then + raise; + else + OS_Lib.Bailout (1); + end if; end Execute; ------------------------ diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt index dcaf78ee..4293dd90 100644 --- a/testsuite/requirements.txt +++ b/testsuite/requirements.txt @@ -1,3 +1,4 @@ docker e3-testsuite pexpect +toml diff --git a/testsuite/tests/misc/structured-tables/test.py b/testsuite/tests/misc/structured-tables/test.py new file mode 100644 index 00000000..e4d7da95 --- /dev/null +++ b/testsuite/tests/misc/structured-tables/test.py @@ -0,0 +1,72 @@ +""" +Verify structured output of tables +""" + +import json + +import toml +from drivers.alr import run_alr, init_local_crate +from drivers.asserts import assert_eq, assert_match + +# Check a few commands that output tables + +# Listing of crates +assert_eq("""\ +[ + { + "name": "gnat_external", + "description": "GNAT is a compiler for the Ada programming language" + }, + { + "name": "gprbuild", + "description": "Fake gprbuild external" + } +] +""", + run_alr("--format=JSON", "search", "--crates").out) + +assert_eq("""\ +[[data]] +description = "GNAT is a compiler for the Ada programming language" +name = "gnat_external" +[[data]] +description = "Fake gprbuild external" +name = "gprbuild" + +""", + run_alr("--format=TOML", "search", "--crates").out) + +# Empty pin list + +init_local_crate() +assert_eq("""\ +[ +] +""", + run_alr("--format=JSON", "pin").out) + +assert_eq("""\ + +""", + run_alr("--format=TOML", "pin").out) + +# Check that objects can be reconstructed and queried from the output + +for fmt in ["JSON", "TOML"]: + # List of releases + out = run_alr(f"--format={fmt}", "-q", "search", "--list", "--external").out + # Load and adjust according to format + if fmt == "TOML": + releases = toml.loads(out)["data"] + # In the TOML case, top-level is always a table with a single key "data" + else: + releases = json.loads(out) + + # Check that it is a list of the expected length + assert_eq(list, type(releases)) + assert_eq(2, len(releases)) + # Check some data + assert_eq("gnat_external", releases[0]["name"]) + + +print("SUCCESS") diff --git a/testsuite/tests/misc/structured-tables/test.yaml b/testsuite/tests/misc/structured-tables/test.yaml new file mode 100644 index 00000000..70201052 --- /dev/null +++ b/testsuite/tests/misc/structured-tables/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +build_mode: both +indexes: + compiler_only_index: {} diff --git a/testsuite/tests/pin/branch/test.py b/testsuite/tests/pin/branch/test.py index 53d2806c..ad580c1f 100644 --- a/testsuite/tests/pin/branch/test.py +++ b/testsuite/tests/pin/branch/test.py @@ -3,14 +3,12 @@ Check pinning to a branch, and changing branches """ from drivers.alr import run_alr, alr_pin, alr_unpin, init_local_crate -from drivers.asserts import assert_eq, assert_match -from drivers.helpers import git_branch, git_head, init_git_repo +from drivers.asserts import assert_match +from drivers.helpers import git_branch, init_git_repo from e3.os.fs import touch -from re import escape import re import os -import shutil import subprocess # "remote" is going to be the remote crate -- 2.39.5