diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a8542d --- /dev/null +++ b/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/ConfigParser.sln b/ConfigParser.sln new file mode 100644 index 0000000..a980eb8 --- /dev/null +++ b/ConfigParser.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31129.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigParser", "ConfigParser\ConfigParser.csproj", "{CD5AD9E2-73EE-40A9-A8C1-E65DA239735C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParserTest", "ParserTest\ParserTest.csproj", "{0C2FE07D-72ED-4A7F-9656-8BB7CC6AFDDE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CD5AD9E2-73EE-40A9-A8C1-E65DA239735C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD5AD9E2-73EE-40A9-A8C1-E65DA239735C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD5AD9E2-73EE-40A9-A8C1-E65DA239735C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD5AD9E2-73EE-40A9-A8C1-E65DA239735C}.Release|Any CPU.Build.0 = Release|Any CPU + {0C2FE07D-72ED-4A7F-9656-8BB7CC6AFDDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C2FE07D-72ED-4A7F-9656-8BB7CC6AFDDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C2FE07D-72ED-4A7F-9656-8BB7CC6AFDDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C2FE07D-72ED-4A7F-9656-8BB7CC6AFDDE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7550F257-3E9B-4F3F-A7AF-497F65102641} + EndGlobalSection +EndGlobal diff --git a/ConfigParser/Config.cs b/ConfigParser/Config.cs new file mode 100644 index 0000000..7e806f0 --- /dev/null +++ b/ConfigParser/Config.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ConfigParser { + ///Represents a config/settings file. + public interface IConfig : IEnumerable<(string, string)>, IDisposable { + ///The value of a specified setting. + ///The name of the setting. + ///The value of the setting. + string this[string setting] { get; set; } + + ///The character that represents the start of a comment. + char CommentChar { get; } + ///The character that represents the start of a value. + char SeparatorChar { get; } + + ///Save the settings from the object to the config file. + void Save(); + ///Load the settings from the config file to the object. + void Load(); + } + + /// + public class Config : IConfig { + /// + protected char comment; + /// + protected char separator; + ///The stream with the settings. + protected Stream stream; + ///The settings in the config, keyed by their name. + protected Dictionary settings; + ///A function that checks wheter a char is legal as a settings name. + protected Func charAccepted; + + /// + ///The property is retrieved and key does + /// not exist in the collection. + ///Setting name is empty or null. + ///Setting name is illegal(whitespace, comment, + /// or non accepted characters) + public virtual string this[string setting] { + get { + //Check validity + if (string.IsNullOrEmpty(setting)) + throw new ArgumentException("Setting name is null or empty", "setting"); + foreach(char c in setting) { + if (char.IsWhiteSpace(c)) throw new FormatException("Space in " + + $"the middle of the setting name: '{setting}'"); + if (c == comment) throw new FormatException("Comment char" + + $"('{comment}') in the middle of the setting name: '{setting}'"); + if (!charAccepted(c)) throw new FormatException("Illegal " + + $"char '{c}' in the setting name: '{setting}'"); + } + return settings[setting]; + } set { + //Check validity + if (string.IsNullOrEmpty(setting)) + throw new ArgumentException("Setting name is null or empty", "setting"); + foreach (char c in setting) { + if (char.IsWhiteSpace(c)) throw new FormatException("Space in " + + $"the middle of the setting name: '{setting}'"); + if (c == comment) throw new FormatException("Comment char" + + $"('{comment}') in the middle of the setting name: '{setting}'"); + if (!charAccepted(c)) throw new FormatException("Illegal " + + $"char '{c}' in the setting name: '{setting}'"); + } + //Strip the value of comment and leading and trailing whitespace + if (value is null) value = string.Empty; + int start = 0, length = 0; + while (start < value.Length && char.IsWhiteSpace(value[start])) start++; + int spaces = 0; + while (start + length < value.Length) { + char cur = value[start + length]; + if (cur == comment) break; + + //Count spaces after the value + if (char.IsWhiteSpace(cur)) spaces++; + else spaces = 0; + + length++; + } + settings[setting] = value.Substring(start, length - spaces); + } + } + + public virtual char CommentChar => comment; + public virtual char SeparatorChar => separator; + + ///Initializes a new config with a stream, a setting/value separator, and + /// a comment char. + ///The stream of the config. + ///The char that separates between name and value. + ///The char that represents the start of a comment. + ///A function that checks whether a char is legal as a + /// settings name. The default(null) is the . + ///Stream is null. + public Config(Stream stream, char separator = '=', char comment = '#', + Func charAccepted = null) { + if (stream == null) throw new ArgumentNullException("'stream' cannot be null!"); + this.stream = stream; + settings = new Dictionary(); + this.separator = separator; + this.comment = comment; + this.charAccepted = charAccepted is null ? DefaultCharAccepted : charAccepted; + } + + /// + ///An I/O error occurs. + ///The stream does not support seeking. + ///Methods were called after the stream + /// was closed. + ///There is insufficient memory to + /// allocate a buffer for the returned string. + public virtual void Save() { + stream.Position = 0; + + HashSet found = new HashSet(); + StringBuilder lines = new StringBuilder((int)stream.Length); + + //Read the stream line by line + StreamReader reader = new StreamReader(stream); + string line; + while (!((line = reader.ReadLine()) is null)) { + int index = 0; + char cur; + //Skip white space + while (index < line.Length && char.IsWhiteSpace(line[index])) { + lines.Append(line[index]); + index++; + } + //Skip empty lines + if (index >= line.Length) { + lines.AppendLine(); + continue; + } + //Skip comments + cur = line[index]; + if (cur == comment) { + while (index < line.Length) lines.Append(line[index++]); + lines.AppendLine(); + continue; + } + + //Read the name of the setting + StringBuilder builder = new StringBuilder(); + bool space = false; + while ((cur = line[index++]) != separator) { + if (cur == comment) { + lines.Append(cur); + while (index < line.Length) lines.Append(line[index++]); + lines.AppendLine(); + goto nestedBreak; + } + if (char.IsWhiteSpace(cur)) { + lines.Append(cur); + space = true; + continue; + } + if (!charAccepted(cur)) { + lines.Append(cur); + while (index < line.Length) lines.Append(line[index++]); + lines.AppendLine(); + goto nestedBreak; + } + + //The error isn't because of the whitespace, it is because there is + //a non-whitespace, valid, non separator character after the white space + if (space) { + lines.Append(cur); + while (index < line.Length) lines.Append(line[index++]); + lines.AppendLine(); + goto nestedBreak; + } + + builder.Append(cur); + lines.Append(cur); + if (index >= line.Length) { + lines.AppendLine(); + goto nestedBreak; + } + } + lines.Append(cur); + string setting = builder.ToString(); + + if (string.IsNullOrEmpty(setting) || !settings.ContainsKey(setting)) { + while (index < line.Length) lines.Append(line[index++]); + lines.AppendLine(); + break; + } + + //Read the setting value + builder.Clear(); + while (index < line.Length && char.IsWhiteSpace(line[index])) + lines.Append(line[index++]); + while (index < line.Length) { + cur = line[index]; + if (cur == comment) break; + + if (char.IsWhiteSpace(cur)) builder.Append(cur); + else builder.Clear(); + + index++; + } + + lines.Append(settings[setting]); + if (builder.Length > 0) lines.Append(builder.ToString()); + while (index < line.Length) lines.Append(line[index++]); //comments + lines.AppendLine(); + + //Add the setting to the collection + found.Add(setting); +nestedBreak:; + }//end while(line) + + stream.SetLength(0); + stream.Position = 0; + StreamWriter writer = new StreamWriter(stream); + //writer.Write(lines.ToString()); + for (int i = 0; i < lines.Length; i++) writer.Write(lines[i]); + bool first = true; + foreach (var pair in settings) + if (!found.Contains(pair.Key)) { + if (first) { + writer.WriteLine(); + first = false; + } + writer.WriteLine(); + writer.Write($"{pair.Key} = {pair.Value}"); + } + writer.Flush(); + + stream.Seek(0, SeekOrigin.End); + } + + /// + ///Fail to parse text inside the stream. + ///An I/O error occurs. + ///The stream does not support seeking. + ///Methods were called after the stream + /// was closed. + ///There is insufficient memory to + /// allocate a buffer for the returned string. + public virtual void Load() { + long pos = stream.Position; + stream.Position = 0; + + //Read the stream line by line + StreamReader reader = new StreamReader(stream); + string line; + while (!((line = reader.ReadLine()) is null)) { + int index = 0; + char cur; + //Skip white space + while (index < line.Length && char.IsWhiteSpace(line[index])) index++; + //Skip empty lines + if (index >= line.Length) continue; + //Skip comments + cur = line[index]; + if (cur == comment) continue; + + //Read the name of the setting + StringBuilder builder = new StringBuilder(); + bool space = false; + while ((cur = line[index++]) != separator) { + if (cur == comment) throw new FormatException("Comment char" + + $"('{comment}') in the middle of the line:\n'{line}'"); + if (char.IsWhiteSpace(cur)) { + space = true; + continue; + } + if (!charAccepted(cur)) throw new FormatException("Illegal " + + $"char '{cur}' in the line:\n'{line}'"); + + //The exception isn't because of the whitespace, it is because there is + //a non-whitespace, valid, non separator character after the white space + if (space) throw new FormatException("Space in the middle of " + + $"the setting name, in the line:\n'{line}'"); + + builder.Append(cur); + if (index >= line.Length) throw new FormatException("Could not " + + $"find the separator('{separator}') in the line:\n'{line}'"); + } + string setting = builder.ToString(); + if (string.IsNullOrEmpty(setting)) throw new FormatException("No " + + $"Setting name in the line:\n'{line}'"); + + //Read the setting value + builder.Clear(); + while (index < line.Length && char.IsWhiteSpace(line[index])) index++; + int endSpaces = 0; + while (index < line.Length) { + cur = line[index]; + if (cur == comment) break; + + if (char.IsWhiteSpace(cur)) endSpaces++; + else endSpaces = 0; + + builder.Append(cur); + index++; + } + + string value = builder.Length <= endSpaces ? string.Empty : + builder.ToString(0, builder.Length - endSpaces); + + //Add the setting to the collection + settings[setting] = value; + }//end while(line) + + stream.Position = pos; + }//end Load() + + ///The default function for . Accepts letters, + /// digits, and the underscore '_' character. + ///The char to validate. + ///True if the char is a letter, a digit, or an underscore('_') + /// character. + protected virtual bool DefaultCharAccepted(char chr) => + char.IsLetterOrDigit(chr) || chr == '_'; + + public virtual IEnumerator<(string, string)> GetEnumerator() => + new SettingsEnumerator(settings); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Dispose() => stream.Dispose(); + + protected class SettingsEnumerator : IEnumerator<(string, string)> { + IEnumerator> enumerator; + + object IEnumerator.Current => Current; + public (string, string) Current => + (enumerator.Current.Key, enumerator.Current.Value); + + public SettingsEnumerator(Dictionary settings) => + enumerator = settings.GetEnumerator(); + + public void Dispose() => enumerator.Dispose(); + public bool MoveNext() => enumerator.MoveNext(); + public void Reset() => enumerator.Reset(); + } + } +} \ No newline at end of file diff --git a/ConfigParser/ConfigParser.csproj b/ConfigParser/ConfigParser.csproj new file mode 100644 index 0000000..c2f6c94 --- /dev/null +++ b/ConfigParser/ConfigParser.csproj @@ -0,0 +1,12 @@ + + + + netstandard1.0 + YuriKhordal + + + + + + + diff --git a/ParserTest/ParserTest.csproj b/ParserTest/ParserTest.csproj new file mode 100644 index 0000000..b9fa9fb --- /dev/null +++ b/ParserTest/ParserTest.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + + diff --git a/ParserTest/Program.cs b/ParserTest/Program.cs new file mode 100644 index 0000000..d04e63b --- /dev/null +++ b/ParserTest/Program.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using ConfigParser; + +namespace ParserTest { + class Program { + public static readonly string CONFIG_PATH = Path.Combine("..", "..", "..", + "config.cfg"); + public static readonly string CONFIG2_PATH = Path.Combine("..", "..", "..", + "config2.cfg"); + public static readonly string INVALID_CONFIG_PATH = Path.Combine("..", "..", "..", + "invalid.cfg"); + + static void Main(string[] args) { + if (File.Exists(CONFIG2_PATH)) File.Delete(CONFIG2_PATH); + File.Copy(CONFIG_PATH, CONFIG2_PATH); + + FileStream file = File.Open(CONFIG2_PATH, FileMode.OpenOrCreate); + Config config = new Config(file); + try { config.Load(); } + catch (FormatException ex) { Console.WriteLine(ex.ToString()); } + finally { file.Position = 0; } + + Console.WriteLine($"{file.Name}:"); + Console.WriteLine(new StreamReader(file).ReadToEnd()); + Console.WriteLine("=================================="); + + config["pc_health"] = " 25 "; + config["zombie_health"] = "35"; + config["offline"] = "false #dd"; + config["pc_mana"] = null; + Console.WriteLine("Config:"); + foreach((string setting, string value) in config) { + Console.WriteLine($"{setting} => '{value}'"); + } + Console.WriteLine("=================================="); + + config.Save(); + file.Position = 0; + Console.WriteLine($"{file.Name}:"); + Console.WriteLine(new StreamReader(file).ReadToEnd()); + Console.WriteLine("=================================="); + } + + void test() { + Config cfg = new Config(File.Open("config.cfg", FileMode.OpenOrCreate)); + cfg.Load(); + + if (cfg["github"] == "true") { + string link = cfg["link"]; + //Do something with the link + } + + cfg["github"] = "false"; + cfg["link"] = ""; + cfg["colour"] = "Red"; + + cfg.Save(); + } + } +} diff --git a/ParserTest/config.cfg b/ParserTest/config.cfg new file mode 100644 index 0000000..da7464a --- /dev/null +++ b/ParserTest/config.cfg @@ -0,0 +1,12 @@ +#config.cfg - generic config file + +#online setting +online = true + +#health settings + # character + pc_health = 10## sssss + #enemies + dog_health=5 + skeleton_health= 6 #one more then a dog + zombie_health = 20 \ No newline at end of file diff --git a/ParserTest/config2.cfg b/ParserTest/config2.cfg new file mode 100644 index 0000000..02adcc7 --- /dev/null +++ b/ParserTest/config2.cfg @@ -0,0 +1,16 @@ +#config.cfg - generic config file + +#online setting +online = true + +#health settings + # character + pc_health = 25## sssss + #enemies + dog_health=5 + skeleton_health= 6 #one more then a dog + zombie_health = 35 + + +offline = false +pc_mana = \ No newline at end of file diff --git a/ParserTest/invalid.cfg b/ParserTest/invalid.cfg new file mode 100644 index 0000000..ed9e3de --- /dev/null +++ b/ParserTest/invalid.cfg @@ -0,0 +1,10 @@ +#invalid.cfg - generic config file + +#online setting +online = true + +#health settings + # character + pc_health = 25 + #enemies + dog_health = 5 \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..5174750 --- /dev/null +++ b/Readme.md @@ -0,0 +1,147 @@ +# ConfigParser +ConfigParser is a "library" for parsing config files with settings. + +## Usage +Can be used with any text file, The way it works is that it is given a stream through the constructor, and when calling `Load()`, the stream is read line by line, parsing each following these rules: +* Empty lines are allowed and ignored. Empty lines are allowed to have any amount of whitespace characters. +* Any whitespace characters before and after a setting/value pair are ignored. Whitespace characters before and after the separator are also ignored. +* Whitespace characters in the middle of the setting name(the part before the separator) are prohibited and will throw a FormatException. Whitespace characters in the middle of the value(the part after the separator) are treated as part of the value. +* A valid comment line is any(or none) amount of whitespace characters followed by the character specified in the constructor(by default, `#`), followed by anything. A comment line is ignored completely. +* A comment character in the middle of a setting line, before the separator, is prohibited and will throw a FormatException. A comment character after the separator is allowed and will count as the endpoint of the value, even if there is no value, at which point the value will be just an empty string. +* The setting name(before the separator) can only consist of characters specified in the constructor(by default, letters, digits, and underscore (`_`) characters). The setting value(after the separator) can consist of any characters and can be empty. +* A valid setting line is a line with any amount of whitespace followed by a single word(no space in the middle) name consisting of valid characters, followed by a separator character(by default, `=`) with any amount of whitespace before and after it, followed by a value of any type, followed by an optional comment. +* A line that has non\-whitespace and non\-comment characters but no separator is invalid and will throw a FormatException. + +### Examples of valid lines: +``` +``` +(That's an empty line) +``` + # Comment with spaces before and after +``` +``` +name=value +``` +``` + name = v4lu3 w17h sp4c3s and s!mb0ls # Comment after a value and a lot of spaces everywhere +``` +(The value here would be `v4lu3 w17h???sp4c3s and s!mb0ls`) +``` +emptySetting = +``` +``` +empty_setting2 = #But now with a comment +``` + +### Examples of invalid lines: +``` +three words name=value +``` +``` +na #me=value +``` +``` +emptySetting +``` +``` +invalid-name = valid value +``` + +## The `Config` class + +### Constructor +Initializes a new config with a stream, a setting/value separator, and a comment char. +```CSharp +Config(Stream stream, char separator, char comment, Func charAccepted) +``` +stream: A stream where to read and write the settings from. +separator: The char that separates between the setting name and its value. Default is `=` +comment: The char that represents the start of a comment. Default is `#` +charAccepted: A function that checks whether a character is legal as a setting's name. The default(null) is the DefaultCharAccepted method. + +### Properties +The value of a specified setting. Illegal setting names will throw an exception. Values will be stripped of any leading and trailing whitespace characters, and of comments. +```CSharp +string this[string setting] { get; set; } +``` + +The character of a comment. +```CSharp +char CommentChar { get; } +``` + +The character of the separator, like `=` or `:` +```CSharp +char SeparatorChar { get; } +``` + +### Methods +Save the changes to the config file. Any new settings will be appended to the end of the file +```CSharp +void Save(); +``` + +Load the settings from the config file. +```CSharp +void Load(); +``` + +The default function for checking settings name's characters. Accepts letters, digits, and the underscore ( `_` ) character. +```CSharp +bool DefaultCharAccepted(char character) +``` + +## Example +### Main.cs +```CSharp +using ConfigParser; + +Config cfg = new Config(File.Open("config.cfg", FileMode.OpenOrCreate)); +cfg.Load(); + +if (cfg["github"] == "true") { + string link = cfg["link"]; + //Do something with the link +} + +cfg["github"] = "false"; +cfg["link"] = ""; +cfg["colour"] = "Red"; + +cfg.Save(); + +``` + +### config.cfg +``` +#config.cfg - an example config file + +# personal info +name = Yuri Khordal +github = true # a boolean +link = github.com/yuriKhordal/ConfigParser # link to the page + +# settings +username = yKhor +pet = +theme = dark +``` + +### config.cfg after running +``` +#config.cfg - an example config file + +# personal info +name = Yuri Khordal +github = false # a boolean +link = # link to the page + +# settings +username = yKhor +pet = +theme = dark + + +colour = Red + +``` \ No newline at end of file