From eea19ade59843f56b5f01d140ed34e4e25c8c712 Mon Sep 17 00:00:00 2001 From: Fabien Chouteau Date: Mon, 15 Jun 2020 12:27:43 +0200 Subject: [PATCH] Alire.Config: add support of built-in options (#446) * Alire.Config: add support of built-in options Built-in options have a type that is checked when setting or loading a configuration file. They also have a short help text to explain their meaning and usage. * Create configuration.md * Typos and minor fixes * doc/configuration.md: typo --- doc/configuration.md | 67 +++++++++++++++ src/alire/alire-config-edit.adb | 33 ++------ src/alire/alire-config.adb | 139 +++++++++++++++++++++++++++++++- src/alire/alire-config.ads | 68 ++++++++++++++++ src/alr/alr-commands-config.adb | 29 +++++-- src/alr/alr-commands-config.ads | 13 +-- 6 files changed, 307 insertions(+), 42 deletions(-) create mode 100644 doc/configuration.md diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 00000000..8fd6361f --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,67 @@ +# Configuration + +`alr` provides a generic mechanism to `list`, `get`, `set` or +`unset` configuration options, either in a local or global context. + + Option names (keys) can use lowercase and uppercase alphanumeric characters + from the Latin alphabet. Underscores and dashes can also be used except as + first or last character. Dot '.' is used to specify sub-categories, e.g. + 'user.name' or 'user.email'. + + Option values can be integers, float, Boolean (true or false) or strings. The + type detection is automatic, e.g. 10 is integer, 10.1 is float, true is + Boolean. You can force a value to be set a string by using double-quotes, e.g. + "10.1" or "true". Extra type checking is used for built-in options (see below). + + Specific config options: + + - `--list` List configuration options + - `--show-origin` Show origin of configuration values in `--list` + - `--get` Print value of a configuration option + - `--set` Set a configuration option + - `--unset` Unset a configuration option + - `--global` Set and Unset global configuration instead of the local one + - `--builtins-doc` Print Markdown list of built-in configuration options + + Examples: + + - `alr config --global --set my_option option_value` + + Will set a configuration option with the key `my_option` and the string + value `option_value` in the global configuration file. + + - `alr config --get my_option` + + Will print the value configuration option `my_option` if it is defined, + otherwise the command fails. + + +## Custom configuration options + +The `alr config` command allows you to set an get any combination of +configuration option `key` and `value`. You can use this feature to store you +own project related configuration, or implement tools that integrate in an +`Alire` context. However, be careful when naming custom configuration options +as `Alire` may use the same `key` in the future. We recommend using a +distinctive sub-category name, for instance: `my_project.my_config_option`. + +## Built-in configuration options + +The options used by `Alire` are pre-defined and documented. We call these +options `built-ins`. + +A built-in option have a pre-defined type that is checked when setting or +loading a configuration file. For instance: + + - `alr config --global --set user.email "This is not an email address"` + +will fail because the value tentatively assigned to `user.email` is not an +email address. + +The built-ins also have a short description to document their type and usage. + +## Built-ins list + +Here is the list of `Alire` built-in configuration options. You can also get +this from `alr` with `alr help config`. + diff --git a/src/alire/alire-config-edit.adb b/src/alire/alire-config-edit.adb index d01f1497..19c4eebd 100644 --- a/src/alire/alire-config-edit.adb +++ b/src/alire/alire-config-edit.adb @@ -18,10 +18,6 @@ package body Alire.Config.Edit is Val : TOML_Value) with Pre => Table.Kind = TOML_Table; - function To_TOML_Value (Str : String) return TOML_Value; - -- Use the TOML parser to convert the string Str. If Str is not a valid - -- TOML value, No_TOML_Value is returned. - ----------------------- -- Write_Config_File -- ----------------------- @@ -101,29 +97,6 @@ package body Alire.Config.Edit is end; end Add_In_Table; - ------------------- - -- To_TOML_Value -- - ------------------- - - function To_TOML_Value (Str : String) return TOML_Value is - Result : constant TOML.Read_Result := TOML.Load_String ("key=" & Str); - begin - if not Result.Success - or else - Result.Value.Kind /= TOML_Table - or else - not Result.Value.Has ("key") - then - - -- Conversion failed - - -- Interpret as a string - return Create_String (Str); - else - return Result.Value.Get ("key"); - end if; - end To_TOML_Value; - ----------- -- Unset -- ----------- @@ -154,6 +127,12 @@ package body Alire.Config.Edit is Raise_Checked_Error ("Invalid configuration value: '" & Value & "'"); end if; + if not Valid_Builtin (Key, To_Add) then + Raise_Checked_Error ("Invalid value '" & Value & + "' for builtin configuration. " & + Image (Kind_Of_Builtin (Key)) & " expected."); + end if; + if Table.Is_Null then -- The configuration file doesn't exist or is not valid. Create an -- empty table. diff --git a/src/alire/alire-config.adb b/src/alire/alire-config.adb index 779869af..105288b4 100644 --- a/src/alire/alire-config.adb +++ b/src/alire/alire-config.adb @@ -1,6 +1,6 @@ with Ada.Containers.Hashed_Maps; -with Ada.Strings.Unbounded; with Ada.Strings.Unbounded.Hash; +with Ada.Text_IO; with GNAT.Regexp; @@ -115,7 +115,7 @@ package body Alire.Config is function Defined (Key : Config_Key) return Boolean is begin - return Config_Map.Contains (To_Unbounded_String (Key)); + return Config_Map.Contains (+Key); end Defined; ------------------- @@ -139,7 +139,7 @@ package body Alire.Config is begin if Defined (Key) then - return Config_Map.Element (To_Unbounded_String (Key)); + return Config_Map.Element (+Key); else return No_Config_Value; end if; @@ -332,6 +332,11 @@ package body Alire.Config is "' in configuration file '" & Source & "'"); Trace.Error ("'" & Key & "' is ignored"); + elsif not Valid_Builtin (Key, Ent.Value) then + Trace.Error ("Invalid value for builtin key '" & Key & + "' in configuration file '" & + Source & "'"); + Trace.Error ("'" & Key & "' is ignored"); else -- Insert the config value, potentially replacing a previous -- definition. @@ -401,6 +406,134 @@ package body Alire.Config is return No_TOML_Value; end Load_Config_File; + ------------------- + -- To_TOML_Value -- + ------------------- + + function To_TOML_Value (Str : String) return TOML_Value is + Result : constant TOML.Read_Result := TOML.Load_String ("key=" & Str); + begin + if not Result.Success + or else + Result.Value.Kind /= TOML_Table + or else + not Result.Value.Has ("key") + then + + -- Conversion failed + + -- Interpret as a string + return Create_String (Str); + else + return Result.Value.Get ("key"); + end if; + end To_TOML_Value; + + ----------- + -- Image -- + ----------- + + function Image (Kind : Builtin_Kind) return String + is (case Kind is + when Cfg_Int => "Integer", + when Cfg_Float => "Float", + when Cfg_Bool => "Boolean", + when Cfg_String => "String", + when Cfg_Absolute_Path => "Absolute path", + when Cfg_Email => "Email address", + when Cfg_GitHub_Login => "GitHub login"); + + --------------------- + -- Kind_Of_Builtin -- + --------------------- + + function Kind_Of_Builtin (Key : Config_Key) return Builtin_Kind is + begin + for Ent of Builtins loop + if To_String (Ent.Key) = Key then + return Ent.Kind; + end if; + end loop; + + Raise_Checked_Error ("Kind is only valid for builtin config key"); + end Kind_Of_Builtin; + + ------------------- + -- Builtins_Info -- + ------------------- + + function Builtins_Info return Alire.Utils.String_Vector is + use Alire.Utils; + Results : Alire.Utils.String_Vector; + begin + for Ent of Builtins loop + Results.Append (String'("- " & To_String (Ent.Key) & + " [" & Image (Ent.Kind) & "]")); + Results.Append (To_String (Ent.Help)); + Results.Append (""); + end loop; + return Results; + end Builtins_Info; + + ------------------------ + -- Print_Builtins_Doc -- + ------------------------ + + procedure Print_Builtins_Doc is + use Ada.Text_IO; + begin + for Ent of Builtins loop + Put (" - **`" & To_String (Ent.Key) & "`** "); + Put_Line ("[" & Image (Ent.Kind) & "]:"); + Put_Line (" " & To_String (Ent.Help)); + New_Line; + end loop; + end Print_Builtins_Doc; + + ---------------- + -- Is_Builtin -- + ---------------- + + function Is_Builtin (Key : Config_Key) return Boolean + is (for some Cfg of Builtins => To_String (Cfg.Key) = Key); + + ------------------- + -- Valid_Builtin -- + ------------------- + + function Valid_Builtin (Key : Config_Key; Value : TOML_Value) + return Boolean + is + begin + for Ent of Builtins loop + if To_String (Ent.Key) = Key then + case Ent.Kind is + when Cfg_Int => + return Value.Kind = TOML_Integer; + when Cfg_Float => + return Value.Kind = TOML_Float; + when Cfg_Bool => + return Value.Kind = TOML_Boolean; + when Cfg_String => + return Value.Kind = TOML_String; + when Cfg_Absolute_Path => + return Value.Kind = TOML_String + and then Check_Absolute_Path (Value.As_String); + when Cfg_Email => + return Value.Kind = TOML_String + and then Utils.Could_Be_An_Email (Value.As_String, + With_Name => False); + when Cfg_GitHub_Login => + return Value.Kind = TOML_String + and then Utils.Is_Valid_GitHub_Username (Value.As_String); + end case; + end if; + end loop; + + -- Not a builtin + return True; + end Valid_Builtin; + begin Load_Config; end Alire.Config; diff --git a/src/alire/alire-config.ads b/src/alire/alire-config.ads index d8fac1f7..f1bf808b 100644 --- a/src/alire/alire-config.ads +++ b/src/alire/alire-config.ads @@ -99,6 +99,17 @@ package Alire.Config is function Indexes_Directory return Absolute_Path is (Path / "indexes"); + --------------- + -- Built-ins -- + --------------- + + function Builtins_Info return Alire.Utils.String_Vector; + -- Return a String_Vector with the documentation of builtin configuration + -- options in text format. + + procedure Print_Builtins_Doc; + -- Print a Markdown documentation for the built-in configuration options + private function Load_Config_File (Path : Absolute_Path) return TOML.TOML_Value; @@ -119,4 +130,61 @@ private Default : Return_Type) return Return_Type; + function To_TOML_Value (Str : String) return TOML.TOML_Value; + -- Use the TOML parser to convert the string Str. If Str is not a valid + -- TOML value, No_TOML_Value is returned. + + -------------- + -- Builtins -- + -------------- + + type Builtin_Kind is (Cfg_Int, Cfg_Float, Cfg_Bool, + Cfg_String, Cfg_Absolute_Path, + Cfg_Email, Cfg_GitHub_Login); + + type Builtin_Entry is record + Key : Ada.Strings.Unbounded.Unbounded_String; + Kind : Builtin_Kind; + Help : Ada.Strings.Unbounded.Unbounded_String; + end record; + + function Is_Builtin (Key : Config_Key) return Boolean; + + function Valid_Builtin (Key : Config_Key; Value : TOML.TOML_Value) + return Boolean; + + function Image (Kind : Builtin_Kind) return String; + + function Kind_Of_Builtin (Key : Config_Key) return Builtin_Kind + with Pre => Is_Builtin (Key); + + function "+" (Source : String) return Ada.Strings.Unbounded.Unbounded_String + renames Ada.Strings.Unbounded.To_Unbounded_String; + + Builtins : constant array (Natural range <>) of Builtin_Entry := + ( + (+"user.name", + Cfg_String, + +("User full name. Used for the authors and " & + "maintainers field of a new crate.")), + (+"user.email", + Cfg_Email, + +("User email address. Used for the authors and" & + " maintainers field of a new crate.")), + (+"user.github_login", + Cfg_GitHub_Login, + +("User GitHub login/username. Used to for the maintainers-logins " & + "field of a new crate.")), + + (+"msys2.do_not_install", + Cfg_Bool, + +("If true, Alire will not try to automatically" & + " install msys2 system package manager. (Windows only)")), + + (+"msys2.install_dir", + Cfg_Absolute_Path, + +("Directory where Alire will detect and/or install" & + " msys2 system package manager. (Windows only) ")) + ); + end Alire.Config; diff --git a/src/alr/alr-commands-config.adb b/src/alr/alr-commands-config.adb index 7a3cb388..a47a43a3 100644 --- a/src/alr/alr-commands-config.adb +++ b/src/alr/alr-commands-config.adb @@ -14,11 +14,13 @@ package body Alr.Commands.Config is then Alire.Config.Global else Alire.Config.Local); begin + -- Check no multi-action Enabled := Enabled + (if Cmd.List then 1 else 0); Enabled := Enabled + (if Cmd.Get then 1 else 0); Enabled := Enabled + (if Cmd.Set then 1 else 0); Enabled := Enabled + (if Cmd.Unset then 1 else 0); + Enabled := Enabled + (if Cmd.Builtins_Doc then 1 else 0); if Enabled > 1 then Reportaise_Wrong_Arguments ("Specify at most one subcommand"); @@ -34,6 +36,11 @@ package body Alr.Commands.Config is ("--show-origin only valid with --list"); end if; + if Cmd.Builtins_Doc then + Alire.Config.Print_Builtins_Doc; + return; + end if; + if not Cmd.Global and then not Alire.Root.Current.Is_Valid then Reportaise_Command_Failed ("Not in an Alire project directory." & @@ -105,7 +112,6 @@ package body Alr.Commands.Config is Alire.Config.Edit.Unset (Alire.Config.Filepath (Lvl), Key); end; end if; - end Execute; ---------------------- @@ -121,21 +127,25 @@ package body Alr.Commands.Config is .New_Line .Append ("Option names (keys) can use lowercase and uppercase" & " alphanumeric characters") - .Append ("from the latin alphabet. Underscores and dashes can also be" & + .Append ("from the Latin alphabet. Underscores and dashes can also be" & " used except as") .Append ("first or last character. Dot '.' is used to specify" & " sub-categories, e.g.") .Append ("'user.name' or 'user.email'.") .New_Line - .Append ("Option values can be integers, float, boolean (true or" & + .Append ("Option values can be integers, float, Boolean (true or" & " false) or strings. The") .Append ("type detection is automatic, e.g. 10 is integer, 10.1 is" & " float, true is") - .Append ("boolean. You can force a value to be set a string by using" & + .Append ("Boolean. You can force a value to be set a string by using" & " double-quotes, e.g.") - .Append ("""10.1"" or ""true"".") - ); + .Append ("""10.1"" or ""true"". Extra type checking is used for" & + " built-in options (see below).") + .New_Line + .Append ("Built-in configuration options:") + .New_Line + .Append (Alire.Config.Builtins_Info)); -------------------- -- Setup_Switches -- @@ -183,6 +193,13 @@ package body Alr.Commands.Config is Help => "Set and Unset global configuration instead" & " of the local one"); + GNAT.Command_Line.Define_Switch + (Config => Config, + Output => Cmd.Builtins_Doc'Access, + Long_Switch => "--builtins-doc", + Help => + "Print Markdown list of built-in configuration options"); + end Setup_Switches; end Alr.Commands.Config; diff --git a/src/alr/alr-commands-config.ads b/src/alr/alr-commands-config.ads index c34dae53..b70dded6 100644 --- a/src/alr/alr-commands-config.ads +++ b/src/alr/alr-commands-config.ads @@ -28,12 +28,13 @@ package Alr.Commands.Config is private type Command is new Commands.Command with record - Show_Origin : aliased Boolean := False; - List : aliased Boolean := False; - Get : aliased Boolean := False; - Set : aliased Boolean := False; - Unset : aliased Boolean := False; - Global : aliased Boolean := False; + Show_Origin : aliased Boolean := False; + List : aliased Boolean := False; + Get : aliased Boolean := False; + Set : aliased Boolean := False; + Unset : aliased Boolean := False; + Global : aliased Boolean := False; + Builtins_Doc : aliased Boolean := False; end record; end Alr.Commands.Config; -- 2.39.5