Skip to content

Instantly share code, notes, and snippets.

@bozidarsk
Last active August 19, 2023 11:49
Show Gist options
  • Save bozidarsk/d372a1f689b0499d43e8df7eeab2b13a to your computer and use it in GitHub Desktop.
Save bozidarsk/d372a1f689b0499d43e8df7eeab2b13a to your computer and use it in GitHub Desktop.
Easy way of adding configurations to c# projects.
// #define NO_FILES
// #define NO_CSS
// or as command line option when compiling '-define:NO_FILES,NO_CSS'
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
/*
the order you call Config.Initialize(ref string[] args, string pathConfigDir):
- check args[] if help message needs to be printed
- call Initialize (passing args with ref will remove any options that were processed from the array), if NO_FILES is defined pathConfigDir is irrelevant
- do anything else
in another file: (example 'ProjectConfig.cs')
'public static partial class Config'
- 'private static readonly Option[] OptionsDefinition' (describes all command line options and their behaviour)
if NO_FILES is not defined it must contain the following fields:
- 'private static readonly string DefaultConfig'
- 'private static readonly string DefaultCss' (only if NO_CSS is not defined)
- 'private static readonly string DefaultConfigDir'
if NO_FILES is defined the following is irrelevant
the config file:
- format is 'NAME=VALUE'
- each space is treated as part of NAME or VALUE depending on its position
- lines that start with '#' are comments and are skipped
- if a line starts with '!' environment variable expansion will not be used
- each environment variable (starting with '$') will be expanded to its corresponding value
properties defined in config file must be defined in 'ProjectConfig.cs' as:
- 'public static TYPE NAME { private set; get; }'
- 'public static TYPE[] NAME { private set; get; }'
you can add a default value like 'public static TYPE NAME { private set; get; } = VALUE;'
arrays are defined in config file as:
'NAME[]=ITEM0,ITEM1,ITEM2' (supports ony 1d arrays; again be carefull of spaces)
*/
public static partial class Config
{
#if !NO_FILES
public static void CreateDefaults()
{
if (!Directory.Exists(Config.DefaultConfigDir)) { Directory.CreateDirectory(Config.DefaultConfigDir); }
File.WriteAllText(Config.DefaultConfigDir + "/config", Config.DefaultConfig);
#if !NO_CSS
File.WriteAllText(Config.DefaultConfigDir + "/style.css", Config.DefaultCss);
#endif
}
#endif
private static object ParseContent(string content, Type type) => type.IsEnum ? Enum.Parse(type, content) : Convert.ChangeType(content, type);
#if !NO_FILES
public static void Initialize(ref string[] args, string pathConfigDir)
#else
public static void Initialize(ref string[] args)
#endif
{
#if !NO_FILES
if (pathConfigDir == null && !Directory.Exists(Config.DefaultConfigDir)) { Config.CreateDefaults(); }
string configPath = (pathConfigDir ?? Config.DefaultConfigDir) + "/config";
#if !NO_CSS
string cssPath = (pathConfigDir ?? Config.DefaultConfigDir) + "/style.css";
Config.Css = File.Exists(cssPath) ? File.ReadAllText(cssPath) : Config.DefaultCss;
#endif
string[] config = (File.Exists(configPath) ? File.ReadAllText(configPath) : Config.DefaultConfig).Split('\n').Where(x => !string.IsNullOrEmpty(x) && x[0] != '#').ToArray();
for (int i = 0; i < config.Length; i++)
{
if (string.IsNullOrEmpty(config[i])) { continue; }
int index = config[i].IndexOf("=");
string name = config[i].Substring(0, index);
string content = config[i].Remove(0, index + 1);
bool isArray = name.EndsWith("[]");
if (isArray) { name = name.Substring(0, name.Length - 2); }
if (name[0] != '!')
{
for (Match match = Regex.Match(content, "\\$[a-zA-Z0-9_\\-]+"); match.Success; match = Regex.Match(content, "\\$[a-zA-Z0-9_\\-]+"))
{ match.Captures.ToList().ForEach(x => content = content.Replace(x.Value, Environment.GetEnvironmentVariable(x.Value.Remove(0, 1)))); }
} else { name = name.Remove(0, 1); }
PropertyInfo property = typeof(Config).GetProperty(name);
if (property == null)
{
Console.WriteLine("Property '" + name + "' was not found.");
Environment.Exit(1);
}
try
{
if (isArray)
{
string[] items = content.Split(',');
Type itemType = Type.GetType(property.PropertyType.ToString().Replace("[]", ""));
Array array = Array.CreateInstance(itemType, items.Length);
for (int t = 0; t < array.Length; t++) { array.SetValue(ParseContent(items[t], itemType), t); }
property.SetValue(null, array);
} else { property.SetValue(null, ParseContent(content, property.PropertyType)); }
}
catch
{
Console.WriteLine("Error parsing '" + content + "' for '" + name + "'.");
Environment.Exit(1);
}
}
#endif
List<string> list = args.ToList();
for (int i = 0; i < list.Count; i++)
{
bool hasArg = false;
if (list[i][0] == '-' && list[i][1] != '-')
{
for (int t = 1; t < list[i].Length; t++)
{
Option option = OptionsDefinition.Where(x => x.ShortName == list[i][t]).FirstOrDefault();
if (option == null) { Console.WriteLine("Unknown option '-" + list[i][t] + "'."); Environment.Exit(1); }
if (option.HasArg && i >= list.Count) { Console.WriteLine("Invalid option."); Environment.Exit(1); }
option.ParseMethod.Invoke(option.HasArg ? list[i + 1] : null);
hasArg = option.HasArg;
}
}
else if (list[i][0] == '-' && list[i][1] == '-')
{
Option option = OptionsDefinition.Where(x => x.Name == list[i]).FirstOrDefault();
if (option == null) { Console.WriteLine("Unknown option '" + list[i] + "'."); Environment.Exit(1); }
if (option.HasArg && i >= list.Count) { Console.WriteLine("Invalid option."); Environment.Exit(1); }
option.ParseMethod.Invoke(option.HasArg ? list[i + 1] : null);
hasArg = option.HasArg;
}
else
{
continue;
}
if (hasArg) { list.RemoveAt(i); }
list.RemoveAt(i--);
}
args = list.ToArray();
}
private sealed class Option
{
public string Name { private set; get; }
public char ShortName { private set; get; }
public bool HasArg { private set; get; }
public Action<string> ParseMethod { private set; get; }
private void DefaultParseMethod(string arg)
{
string propertyName = "";
for (int i = 1; i < this.Name.Length; i++)
{
if (this.Name[i] == '-')
{
propertyName += char.ToUpper(this.Name[++i]);
continue;
}
propertyName += this.Name[i];
}
PropertyInfo property = typeof(Config).GetProperty(propertyName);
property.SetValue(null, this.HasArg ? ParseContent(arg, property.PropertyType) : true);
}
public Option(string name, char shortName, bool hasArg, Action<string> parseMethod)
{
this.Name = name;
this.ShortName = shortName;
this.HasArg = hasArg;
this.ParseMethod = parseMethod ?? DefaultParseMethod;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment