Beispiel #1
0
 public void can_parse_boolean(string value, bool expected)
 {
     Assert.Equal(expected, TextRules.ParseBoolean(value));
 }
Beispiel #2
0
 public void can_parse_number(string value, string expected)
 {
     Assert.Equal(long.Parse(expected), TextRules.ParseNumber(value));
 }
Beispiel #3
0
        static int Run(string[] args)
        {
            var help = false;

            var    action       = ConfigAction.None;
            var    useSystem    = false;
            var    useGlobal    = false;
            var    useLocal     = false;
            var    path         = Directory.GetCurrentDirectory();
            var    nameOnly     = false;
            string?defaultValue = default;
            string?type         = default;
            var    debug        = false;

            var options = new OptionSet
            {
                { Environment.NewLine },
                { Environment.NewLine },
                { "Location (uses all locations by default)" },
                { "local", "use .netconfig.user file", _ => useLocal = true },
                { "global", "use global config file", _ => useGlobal = true },
                { "system", "use system config file", _ => useSystem = true },
                { "path:", "use given config file or directory", f => path = f },

                { Environment.NewLine },
                { "Action" },
                { "get", "get value: name [value-regex]", _ => action = ConfigAction.Get },
                { "get-all", "get all values: key [value-regex]", _ => action = ConfigAction.GetAll },
                { "get-regexp", "get values for regexp: name-regex [value-regex]", _ => action = ConfigAction.GetRegexp },
                { "set", "set value: name value [value-regex]", _ => action = ConfigAction.Set },
                { "set-all", "set all matches: name value [value-regex]", _ => action = ConfigAction.SetAll },
                { "replace-all", "replace all matches: name value [value-regex]", _ => action = ConfigAction.SetAll, true },

                //{ "get-urlmatch", "get value specific for the URL: section[.var] URL", _ => action = ConfigAction.Get },
                { "add", "add a new variable: name value", _ => action = ConfigAction.Add },
                { "unset", "remove a variable: name [value-regex]", _ => action = ConfigAction.Unset },
                { "unset-all", "remove all matches: name [value-regex]", _ => action = ConfigAction.UnsetAll },

                { "remove-section", "remove a section: name", _ => action = ConfigAction.RemoveSection },
                { "rename-section", "rename section: old-name new-name", _ => action = ConfigAction.RenameSection },

                { "l|list", "list all", _ => action = ConfigAction.List },
                { "e|edit", "edit the config file in an editor", _ => action = ConfigAction.Edit },

                { Environment.NewLine },
                { "Other" },
                { "default:", "with --get, use default value when missing entry", v => defaultValue = v },
                { "name-only", "show variable names only", _ => nameOnly = true },
                { "type:", "value is given this type, can be 'boolean', 'datetime' or 'number'", t => type = t },
                { "debug", "add some extra logging for troubleshooting purposes", _ => debug = true, true },

                { "?|h|help", "Display this help", h => help = h != null },
            };

            var extraArgs = options.Parse(args);

            if (debug)
            {
                Console.WriteLine($"::debug::args[{args.Length}]:: {string.Join(" ", args.Select(x => x.IndexOf(' ') != -1 ? $"\"{x}\"" : x))}");
                Console.WriteLine($"::debug::extraargs[{extraArgs.Count}]:: {string.Join(" ", extraArgs.Select(x => x.IndexOf(' ') != -1 ? $"\"{x}\"" : x))}");
            }

            if (args.Length == 1 && help)
            {
                return(ShowHelp(options));
            }

            // Can only use one location
            if ((useGlobal && (useSystem || useLocal)) ||
                (useSystem && (useGlobal || useLocal)) ||
                (useLocal && (useGlobal || useSystem)))
            {
                return(ShowError("Can only specify one config location."));
            }

            ConfigLevel?level = null;
            Config      config;

            if (useGlobal)
            {
                config = Config.Build(ConfigLevel.Global);
                level  = ConfigLevel.Global;
            }
            else if (useSystem)
            {
                config = Config.Build(ConfigLevel.System);
                level  = ConfigLevel.System;
            }
            else
            {
                config = Config.Build(path);
                if (useLocal)
                {
                    level = ConfigLevel.Local;
                }
            }

            // Can be a get or a set, depending on whether a value is provided.
            if (action == ConfigAction.None)
            {
                if (extraArgs.Count == 1)
                {
                    action = ConfigAction.Get;
                }
                else if (extraArgs.Count > 1)
                {
                    action = ConfigAction.Set;
                }
                else
                {
                    return(ShowHelp(options));
                }
            }

            // TODO: For any action that isn't a simple get (which may rely on the output being just the
            // value retrieved), render to the output window the warnings about invalid entries

            Action <ConfigEntry> entryWriter;

            if (nameOnly)
            {
                entryWriter = e => Console.WriteLine(e.Key);
            }
            else
            {
                entryWriter = e => Console.WriteLine($"{e.Key}={(e.RawValue == null ? "" : e.RawValue.Contains(' ') ? "\"" + e.RawValue + "\"" : e.RawValue)}");
            }

            if (type == "date")
            {
                type = "datetime";
            }
            if (type == "bool")
            {
                type = "boolean";
            }

            var kind = ValueKind.String;

            if (type != null && !Enum.TryParse(type, true, out kind))
            {
                Console.Error.WriteLine($"Error: invalid type '{type}'. Expected one of: 'boolean', 'bool', 'datetime', 'date' or 'number'.");
                return(-1);
            }

            string?error = default;

            switch (action)
            {
            case ConfigAction.Add:
            {
                TextRules.ParseKey(extraArgs[0], out var section, out var subsection, out var variable);
                if (extraArgs.Count != 2)
                {
                    return(ShowHelp(options));
                }

                if ((kind == ValueKind.Boolean && !string.IsNullOrEmpty(extraArgs[1]) && !TextRules.TryValidateBoolean(extraArgs[1], out error)) ||
                    (kind == ValueKind.Number && !TextRules.TryValidateNumber(extraArgs[1], out error)) ||
                    kind == ValueKind.DateTime && !DateTime.TryParse(extraArgs[1], CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out _))
                {
                    if (error != null)
                    {
                        return(ShowError(error));
                    }

                    return(ShowError($"Unexpected datetime value `{extraArgs[1]}`, expected ISO 8601 (aka round-trip) format."));
                }

                if (level != null)
                {
                    config.AddString(section !, subsection, variable !, extraArgs[1], level.Value);
                }
                else
                {
                    config.AddString(section !, subsection, variable !, extraArgs[1]);
                }

                break;
            }

            case ConfigAction.Get:
            {
                TextRules.ParseKey(extraArgs[0], out var section, out var subsection, out var variable);

                var value = config.GetString(section !, subsection, variable !);
                Console.WriteLine(value ?? defaultValue ?? "");
                break;
            }

            case ConfigAction.GetAll:
            {
                TextRules.ParseKey(extraArgs[0], out var section, out var subsection, out var variable);

                string?valueRegex = default;
                if (extraArgs.Count > 1)
                {
                    valueRegex = extraArgs[1];
                }

                foreach (var entry in config.GetAll(section !, subsection, variable !, valueRegex))
                {
                    entryWriter(entry);
                }
                break;
            }

            case ConfigAction.GetRegexp:
            {
                if (extraArgs.Count == 0 || extraArgs.Count > 2)
                {
                    return(ShowHelp(options));
                }

                var    nameRegex  = extraArgs[0];
                string?valueRegex = default;
                if (extraArgs.Count > 1)
                {
                    valueRegex = extraArgs[1];
                }

                foreach (var entry in config.GetRegex(nameRegex, valueRegex))
                {
                    entryWriter(entry);
                }
                break;
            }

            case ConfigAction.Set:
            {
                TextRules.ParseKey(extraArgs[0], out var section, out var subsection, out var variable);

                var value = string.Join(' ', extraArgs.Skip(1)).Trim();
                // It's a common mistake to do 'config key = value', so just remove it.
                if (value.StartsWith('='))
                {
                    value = new string(value[1..]).Trim();