Last active
August 19, 2023 11:49
-
-
Save bozidarsk/d372a1f689b0499d43e8df7eeab2b13a to your computer and use it in GitHub Desktop.
Easy way of adding configurations to c# projects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #define NO_FILES | |
// #define NO_CSS | |
/* or as command line option when compiling '-define:NO_FILES,NO_CSS' */ | |
/* | |
NO_FILES - no config and no style.css files | |
NO_CSS - a config file but not a style.css file | |
the order you call Config.Initialize(ref string[] args): | |
- 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) | |
- do anything else | |
in another file: (example 'ProjectConfig.cs') | |
'public static partial class Config': | |
- 'public static string ConfigDir { private set; get; } = "DEFAULT/CONFIG/DIR";' (only if NO_FILES is not defined) | |
- 'public static string Css { private set; get; } = "DEFAULTCSS";' (only if NO_CSS and NO_FILES are not defined) | |
- 'private static readonly Option[] OptionsDefinition' (describes all command line options and their behaviour) | |
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 '$', 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; } = DEFAULTVALUE;' | |
- 'public static TYPE[] NAME { private set; get; } = DEFAULTVALUE;' | |
arrays are defined in config file as: | |
'NAME[]=ITEM0,ITEM1,ITEM2' (supports ony 1d arrays; again be carefull of spaces) | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
public static partial class Config | |
{ | |
// if a property is set from a command line option dont override it with the config file | |
private static List<string> propertiesFromOptions = new List<string>(); | |
#if !NO_FILES | |
public static void CreateDefaults() | |
{ | |
if (string.IsNullOrEmpty(Config.ConfigDir)) | |
{ | |
Console.WriteLine("'Config.ConfigDir' must not be null or empty."); | |
Environment.Exit(1); | |
} | |
if (!Directory.Exists(Config.ConfigDir)) { Directory.CreateDirectory(Config.ConfigDir); } | |
PropertyInfo[] properties = typeof(Config).GetProperties(); | |
string config = ""; | |
for (int i = 0; i < properties.Length; i++) | |
{ | |
if (properties[i].Name == "ConfigDir" || properties[i].Name == "Css") { continue; } | |
if (properties[i].PropertyType.ToString().EndsWith("[]")) | |
{ | |
config += $"{properties[i].Name}[]="; | |
Array array = (Array)properties[i].GetValue(null); | |
for (int t = 0; t < array.Length; t++) { config += $"{array.GetValue(t)}{(t < array.Length - 1) ? "," : "\n"}"; } | |
} else { config += $"{properties[i].Name}={properties[i].GetValue(null)}\n"; } | |
} | |
File.WriteAllText(Config.ConfigDir + "/config", config); | |
#if !NO_CSS | |
File.WriteAllText(Config.ConfigDir + "/style.css", Config.Css); | |
#endif | |
} | |
#endif | |
private static object ParseContent(string content, Type type) => type.IsEnum ? Enum.Parse(type, content) : Convert.ChangeType(content, type); | |
#if !NO_FILES | |
private static void InitializeFromFile() | |
{ | |
if (Config.ConfigDir == null || !Directory.Exists(Config.ConfigDir)) { return; } | |
#if !NO_CSS | |
if (File.Exists(Config.ConfigDir + "/style.css") && !propertiesFromOptions.Contains("Css")) | |
{ Config.Css = File.ReadAllText(Config.ConfigDir + "/style.css"); } | |
#endif | |
if (!File.Exists(Config.ConfigDir + "/config")) { return; } | |
string[] config = | |
File.ReadAllText(Config.ConfigDir + "/config") | |
.Split('\n') | |
.Where(x => !string.IsNullOrEmpty(x) && x[0] != '#') | |
.ToArray() | |
; | |
for (int i = 0; i < config.Length; i++) | |
{ | |
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] == '$') | |
{ | |
name = name.Remove(0, 1); | |
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)))); } | |
} | |
// skip this property because it is already set from options (dont override command line options with config file) | |
if (propertiesFromOptions.Contains(name)) { continue; } | |
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 | |
private static void InitializeFromOptions(ref string[] args) | |
{ | |
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(); | |
} | |
public static void Initialize(ref string[] args) | |
{ | |
InitializeFromOptions(ref args); | |
#if !NO_FILES | |
InitializeFromFile(); | |
#endif | |
} | |
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); | |
Config.propertiesFromOptions.Add(this.Name); | |
} | |
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