Skip to content
Snippets Groups Projects
Select Git revision
  • 3813f3da74d37b4ed8cafcad43e7b7097871c046
  • master default protected
2 results

splay_operation.h

Blame
  • ConfigParser.cs 15.10 KiB
    // i3csstatus - Alternative generator of i3 status bar written in c#
    // (c)   2022 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
    
    // This program is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // any later version.
    
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    
    // You should have received a copy of the GNU General Public License
    // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    
    #nullable enable
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    using System.IO;
    
    using System.Linq;
    
    using Color = System.Drawing.Color;
    
    namespace Config;
    
    abstract class ConfigException: Exception
    {
    	public ConfigParser Parser;
    	internal ConfigException(ConfigParser parser)
    	{
    		Parser = parser;
    	}
    	public override string ToString()
    	{
    		return $"Mistake in config: {ErrorString()}";
    	}
    	abstract public string ErrorString();
    }
    abstract class ConfigDuplicitException: ConfigException
    {
    	public string Name;
    	public int LineA, LineB;
    	public ConfigDuplicitException(ConfigParser parser, string name, int lineA, int lineB): base(parser)
    	{
    		Name = name;
    		LineA = lineA;
    		LineB = lineB;
    	}
    }
    class ConfigDuplicitSectionException: ConfigDuplicitException
    {
    	public ConfigDuplicitSectionException(ConfigParser parser, string name, int lineA, int lineB)
    		:base(parser, name, lineA, lineB){}
    	public override string ErrorString()
    	{
    		return $"Duplicit section name {Name} on lines {LineA}, {LineB}";
    	}
    }
    class ConfigDuplicitKeyException: ConfigDuplicitException
    {
    	public ConfigDuplicitKeyException(ConfigSection section, string name, int lineA, int lineB)
    		:base(section.Root, name, lineA, lineB){}
    	public override string ErrorString()
    	{
    		return $"Duplicit key name {Name} on lines {LineA}, {LineB}";
    	}
    }
    class ConfigNoAmenException: ConfigException
    {
    	public ConfigOption Value;
    	public ConfigNoAmenException(ConfigOption value):base(value.Section.Root)
    	{
    		Value = value;
    	}
    	public override string ErrorString()
    	{
    		return $"End string \"{Value.amen}\" for block \"{Value.Key}\" starting at line {Value.Line} not found";
    	}
    }
    class ConfigMixedTabException: ConfigException
    {
    	public ConfigOption Value;
    	public int Line;
    	public ConfigMixedTabException(ConfigOption value, int line):base(value.Section.Root)
    	{
    		Value = value;
    		Line = line;
    	}
    	public override string ErrorString()
    	{
    		return $"{Value.Place}: Combination of tabs and spaces is not supported.";
    	}
    }
    class ConfigWrongIndentException: ConfigException
    {
    	public ConfigOption? Value;
    	public int Line;
    	public ConfigWrongIndentException(ConfigSection section, ConfigOption? value, int line):base(section.Root)
    	{
    		Value = value;
    		Line = line;
    	}
    	public override string ErrorString()
    	{
    		if(Value == null)
    			return $"{Line}: Wrong indent.";
    		return $"{Value.Place}: Wrong indent.";
    	}
    }
    class ConfigSectionNotDefinedException: ConfigException
    {
    	public string Name;
    	public ConfigSectionNotDefinedException(ConfigParser parser, string name):base(parser)
    	{
    		Name = name;
    	}
    	public override string ErrorString()
    	{
    		return $"Section \"{Name}\" is not defined.";
    	}
    }
    class ConfigNotDefinedException: ConfigException
    {
    	public ConfigSection Section;
    	public string Name;
    	public ConfigNotDefinedException(ConfigSection section, string name):base(section.Root)
    	{
    		Section = section;
    		Name = name;
    	}
    	public override string ErrorString()
    	{
    		return $"{Section.SectionName ?? "[global]"}: Option \"{Name}\" is not defined.";
    	}
    }
    class ConfigNoValueException: ConfigException
    {
    	public ConfigValue Value;
    	public string? Reason;
    	public ConfigNoValueException(ConfigValue value, string? reason=null):base(value.Root)
    	{
    		Value = value;
    		Reason = reason;
    	}
    	public override string ErrorString()
    	{
    		return $"{Value.Place}: Value needed." ;
    	}
    }
    class ConfigUnusedOptionException: ConfigException
    {
    	public ConfigOption Value;
    	public ConfigUnusedOptionException(ConfigOption value):base(value.Root)
    	{
    		Value = value;
    	}
    	public override string ErrorString()
    	{
    		return $"{Value.Place}: No such option." ;
    	}
    }
    class ConfigUnusedSectionException: ConfigException
    {
    	public ConfigSection Section;
    	public ConfigUnusedSectionException(ConfigSection section):base(section.Root)
    	{
    		Section = section;
    	}
    	public override string ErrorString()
    	{
    		if(Section.SectionName == null)
    			return $"Global section not allowed." ;
    		else
    			return $"{Section.SectionName}: No such section." ;
    	}
    }
    class ConfigParseValueException: ConfigException
    {
    	public ConfigValue Value;
    	public string? Reason;
    	public ConfigParseValueException(ConfigValue value, string? reason=null):base(value.Root)
    	{
    		Value = value;
    		Reason = reason;
    	}
    	public override string ErrorString()
    	{
    		return $"{Value.Place}: Parsing fail" +
    			(Reason==null?".":$": {Reason}.");
    	}
    }
    class ConfigMistake: ConfigException
    {
    	public ConfigValue Value;
    	public string Text;
    	public ConfigMistake(ConfigValue value, string text):base(value.Root)
    	{
    		Value = value;
    		Text = text;
    	}
    	public override string ErrorString()
    	{
    		return $"{Value.Place}: {Text}";
    	}
    }
    
    abstract class ConfigValue
    {
    	public ConfigParser Root;
    	public int Line;
    	internal string? Value;
    	public bool HasText() => Value != null;
    
    	public virtual string Place{get => $"{Line}: {FullName}";}
    	public abstract string FullName{get;}   // For showing in error
    
    	public ConfigValue(ConfigParser _Root)
    	{
    		Root = _Root;
    	}
    
    	public string AsString()
    	{
    		if(Value == null) throw new ConfigNoValueException(this);
    		return Value;
    	}
    	public string AsPath()
    	{
    		string s = AsString();
    		StringBuilder output = new();
    		for(int i=0;i<s.Length;i++)
    		{
    			if(i == 0 && s[i]=='~')
    			{
    				output.Append(Environment.GetEnvironmentVariable("HOME") ?? "");
    			}
    			else  if(s[i]=='$')
    			{
    				int start, end;
    				if(s[i+1]=='{')
    				{
    					start = i+2;
    					while(i<s.Length && s[i]!='}') i++;
    					end = i;
    				}
    				else
    				{
    					start = i+1;
    					while(i+1<s.Length && "qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM_".Contains(s[i+1])) i++;
    					end = i+1;
    				}
    				output.Append(Environment.GetEnvironmentVariable(s[start..end]) ?? "");
    			}
    			else
    				output.Append(s[i]);
    		}
    		return output.ToString();
    	}
    	public Color AsColor()
    	{
    		try
    		{
    			return System.Drawing.ColorTranslator.FromHtml(AsString());
    		} catch (ArgumentException)
    		{
    			throw new ConfigParseValueException(this, "String is not color");
    		}
    	}
    	public bool AsBool()
    	{
    		if(Value == null) return true;
    		string v = Value.ToLower();
    		if(v == "true" || v == "yes" || v == "use" || v == "1")
    			return true;
    		if(v == "false" || v == "no" || v == "unuse" || v == "0")
    			return false;
    		throw new ConfigParseValueException(this, "String is not bool");
    
    	}
    	public double AsDouble()
    	{
    		try
    		{
    			return double.Parse(AsString());
    		} catch (FormatException)
    		{
    			throw new ConfigParseValueException(this, "String is not number");
    		}
    	}
    	public int AsInt()
    	{
    		try
    		{
    			return int.Parse(AsString());
    		} catch (FormatException)
    		{
    			throw new ConfigParseValueException(this, "String is not int");
    		}
    	}
    	public int AsMs()
    	{
    		return (int)(1000 * AsDouble());
    	}
    	public ConfigParser AsConfig()
    	{
    		return new ConfigParser(AsString(), Root.FileName, Line);
    	}
    	public static implicit operator string(ConfigValue v) => v.AsString();
    }
    class ConfigOption: ConfigValue
    {
    	public ConfigSection Section {get; init;}
    	internal string? amen;
    	internal string? tabs;
    	public string Key {get; init;}
    	public bool Used = false;
    	public ConfigOption Use()
    	{
    		Used = true;
    		return this;
    	}
    	public override string FullName
    	{
    		get => Section.SectionName != null ? $"{Section.SectionName}: {Key}" : Key;
    	}
    	internal ConfigOption(ConfigSection _Section, string _Key, int _line):base(_Section.Root)
    	{
    		Section = _Section;
    		Key = _Key;
    		Line = _line;
    	}
    	public ConfigKeyAsValue KeyAsValue() => new(this);
    }
    class ConfigKeyAsValue: ConfigValue
    {
    	public ConfigSection Section {get; init;}
    	public override string FullName
    	{
    		get => (Section.SectionName != null ? $"{Section.SectionName}: " : "") + $"Key {Value}";
    	}
    	internal ConfigKeyAsValue(ConfigOption keyValue):base(keyValue.Root)
    	{
    		Line = keyValue.Line;
    		Value = keyValue.Key;
    		Section = keyValue.Section;
    	}
    }
    class ConfigSectionNameAsValue: ConfigValue
    {
    	public ConfigSection Section {get; init;}
    	public override string FullName
    	{
    		get => "Section name " + (Section.SectionName != null ? $"\"{Section.SectionName}\"" : "of main section");
    	}
    	internal ConfigSectionNameAsValue(ConfigSection _Section):base(_Section.Root)
    	{
    		Section = _Section;
    		Value = Section.SectionName;
    		Line = Section.Line;
    	}
    }
    class ConfigSectionNameAsDefaultValue: ConfigSectionNameAsValue
    {
    	protected string defaultFor;
    	internal ConfigSectionNameAsDefaultValue(ConfigSection _Section, string _defaultFor): base(_Section)
    	{
    		defaultFor = _defaultFor;
    	}
    	public override string FullName
    	{
    		get => base.FullName + $" as default for \"{defaultFor}\" option";
    	}
    }
    class ConfigSection: IReadOnlyList<ConfigOption>
    {
    	public ConfigParser Root {get; init;}
    	public string? SectionName {get; init;}
    	public int Line;
    	List<ConfigOption> values = new();
    	Dictionary<string, ConfigOption> valueByName = new();
    	public bool Used = false;
    	public ConfigSection Use()
    	{
    		Used = true;
    		return this;
    	}
    	internal ConfigSection(ConfigParser _Root, string? _SectionName, int _line)
    	{
    		Root = _Root;
    		SectionName = _SectionName;
    		Line = _line;
    	}
    	internal void Add(ConfigOption v)
    	{
    		if(valueByName.ContainsKey(v.Key))
    			throw new ConfigDuplicitKeyException(this, v.Key, v.Line, valueByName[v.Key].Line);
    		valueByName[v.Key] = v;
    		values.Add(v);
    	}
    	public IEnumerator<ConfigOption> GetEnumerator()
    	{
    		foreach(var s in values)
    			yield return s;
    	}
    	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    		=> GetEnumerator();
    	public int Count{ get{return values.Count;}}
    	public ConfigOption this[int i]
    	{
    		get { return values[i]; }
    	}
    	public ConfigOption? this[string key]
    	{
    		get { return Optional(key); }
    	}
    	public ConfigOption? Optional(string key)
    	{
    		if(!valueByName.TryGetValue(key, out var r))
    			return null;
    		return r.Use();
    	}
    	public ConfigValue OptionalDefaultIsSectionName(string key)
    	{
    		return (ConfigValue?)Optional(key) ?? new ConfigSectionNameAsDefaultValue(this, key);
    	}
    	public ConfigOption Mandatory(string key)
    	{
    		if(!valueByName.TryGetValue(key, out var r))
    			throw new ConfigNotDefinedException(this, key);
    		return r.Use();
    	}
    	public bool Contains(string key)
    	{
    		return valueByName.ContainsKey(key);
    	}
    	public void Print(TextWriter w)
    	{
    		w.WriteLine($"### {SectionName}");
    		foreach(var x in values)
    		{
    			w.WriteLine($"'{x.Key}' = '{(x.HasText() ? x.AsString() : "null")}'");
    		}
    	}
    	public ConfigSectionNameAsValue SectionNameAsValue() => new(this);
    	public void CheckUnused()
    	{
    		foreach(ConfigOption it in this)
    		{
    			if(it.Used == false)
    				throw new ConfigUnusedOptionException(it);
    		}
    	}
    }
    class ConfigParser: IReadOnlyList<ConfigSection>
    {
    	public string? FileName {get; init;}
    	public ConfigSection MainSection;
    	List<ConfigSection> sections = new();
    	Dictionary<string, ConfigSection> sectionByName = new();
    	public ConfigParser(string s, string? _FileName = null, int firstLineIndex = 1)
    	{
    		FileName = _FileName;
    		string[] lines = s.Split("\n");
    		var currentSection = MainSection = new(this, null, 0);
    		ConfigOption? lastVal = null;
    		for(int i=0;i<lines.Count();i++)
    		{
    			string l = lines[i];
    			string tl = l.TrimEnd();
    			//Console.WriteLine($"l: '{l}' sl:'{tl}'");
    			if(l.Trim() == "") continue;
    			if(l.Trim()[0] == '#') continue;
    			if(tl[0] == '[' && tl[^1] == ']' && tl.Length >= 3)
    			{
    			#pragma warning disable 8604
    				lastVal = null;
    				currentSection = new ConfigSection(this, tl[1..^1], firstLineIndex + i);
    				if(sectionByName.ContainsKey(currentSection.SectionName))
    				{
    					throw new ConfigDuplicitSectionException(this, currentSection.SectionName, sectionByName[currentSection.SectionName].Line, firstLineIndex + i);
    				}
    				sections.Add(currentSection);
    				sectionByName[currentSection.SectionName] = currentSection;
    				continue;
    			#pragma warning restore 8604
    			}
    			if(tl[0]==' ' || tl[0]=='\t')
    			{
    				if(lastVal == null)
    					throw new ConfigWrongIndentException(currentSection, null, firstLineIndex + i);
    				if(lastVal.tabs == null)
    				{
    					int spacesLen=0;
    					for(;tl[spacesLen]==tl[0];spacesLen++);
    					if(tl[spacesLen]==' ' || tl[spacesLen]=='\t')
    						throw new ConfigMixedTabException(lastVal, firstLineIndex + i);
    					lastVal.tabs = tl[0..spacesLen];
    					if(lastVal.Value == null || lastVal.Value == "")
    					{
    						lastVal.Line = firstLineIndex + i;
    						lastVal.Value = tl[lastVal.tabs.Length..];
    					}
    					else
    						lastVal.Value += "\n" + tl[lastVal.tabs.Length..];
    				}
    				else
    				{
    					if(!tl.StartsWith(lastVal.tabs))
    						throw new ConfigWrongIndentException(currentSection, lastVal, firstLineIndex + i);
    					lastVal.Value += "\n" + tl[lastVal.tabs.Length..];
    				}
    				continue;
    			}
    			int indexIs = l.IndexOf("=");
    			int indexArrow = l.IndexOf("<<");
    			if(indexIs == -1 && indexArrow == -1)
    			{
    				currentSection.Add(lastVal = new ConfigOption(currentSection, tl, firstLineIndex + i));
    				continue;
    			}
    			int index = indexIs == -1 ? indexArrow : indexArrow == -1 ? indexIs : Math.Min(indexIs, indexArrow);
    			string key = l[0..index].Trim();
    			currentSection.Add(lastVal = new ConfigOption(currentSection, key, firstLineIndex + i));
    			if(indexIs != -1 && (indexArrow == -1 || indexIs < indexArrow))
    			{
    				string val = l[(indexIs+1)..].Trim();
    				lastVal.Value = val;
    			}
    			else
    			{
    				lastVal.amen = l[(indexArrow+2)..].Trim();
    				StringBuilder val = new();
    				i++;
    				bool first = true;
    				while(true)
    				{
    					if(i>=lines.Count())
    						throw new ConfigNoAmenException(lastVal);
    					if(lines[i].Trim() == lastVal.amen) break;
    					if(!first) val.Append("\n");
    					val.Append(lines[i]);
    					i++;
    					first = false;
    				}
    				lastVal.Value = val.ToString();
    			}
    		}
    	}
    	public void Print(TextWriter w)
    	{
    		MainSection.Print(w);
    		foreach(var x in sections) x.Print(w);
    	}
    	public IEnumerator<ConfigSection> GetEnumerator()
    	{
    		foreach(var s in sections)
    			yield return s;
    	}
    	System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    		=> GetEnumerator();
    	public int Count{ get{return sections.Count;}}
    	public ConfigSection this[int i]
    	{
    		get { return sections[i]; }
    	}
    	public ConfigSection? this[string? key]
    	{
    		get { return Optional(key); }
    	}
    	public ConfigSection? Optional(string? key)
    	{
    		if(key == null) return MainSection;
    		if(!sectionByName.TryGetValue(key, out var r))
    			return null;
    		return r.Use();
    	}
    	public ConfigSection Mandatory(string? key)
    	{
    		if(key == null) return MainSection;
    		if(!sectionByName.TryGetValue(key, out var r))
    			throw new ConfigSectionNotDefinedException(this, key);
    		return r.Use();
    	}
    	public void CheckUnused()
    	{
    		foreach(ConfigSection it in this)
    		{
    			if(it.Used == false)
    				throw new ConfigUnusedSectionException(it);
    		}
    	}
    }