From 0753aeb220e2cdec266e9f4d1d31bd0456003357 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Sun, 20 Jul 2014 21:30:41 +0000 Subject: [PATCH] Configuration file parsing should be complete now (but creation and changing not so much) git-svn-id: file:///srv/devel/repo-conversion/nusu@303 d2e56fa2-650e-0410-a79f-9358c0239efd --- .../ConfigurationFileStore.Parsing.cs | 62 +++++++++++--- .../Settings/ConfigurationFileStore.Test.cs | 80 ++++++++++++++++++- Source/Settings/ConfigurationFileStore.cs | 2 - 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/Source/Settings/ConfigurationFileStore.Parsing.cs b/Source/Settings/ConfigurationFileStore.Parsing.cs index 4d537a2..6500b89 100644 --- a/Source/Settings/ConfigurationFileStore.Parsing.cs +++ b/Source/Settings/ConfigurationFileStore.Parsing.cs @@ -116,7 +116,7 @@ namespace Nuclex.Support.Settings { if(nameStartIndex >= lastCharacterIndex) { return; // No space left for closing brace } - + int nameEndIndex = line.IndexOf(']', nameStartIndex); if(nameEndIndex == -1) { return; // No closing brace in line @@ -163,15 +163,10 @@ namespace Nuclex.Support.Settings { LineIndex = state.Store.lines.Count - 1, OptionName = new StringSegment( line, firstCharacterIndex, nameEndIndex - firstCharacterIndex + 1 - ), + ) }; - // If there is a value in this assignment, parse it too - int valueStartIndex = assignmentIndex + 1; - ParserHelper.SkipSpaces(line, ref valueStartIndex); - if(valueStartIndex < line.Length) { - parseOptionValue(option, line, valueStartIndex); - } + parseOptionValue(option, line, assignmentIndex + 1); // We've got the option assignment, either with an empty or proper value state.Store.options.Add(option); @@ -181,9 +176,54 @@ namespace Nuclex.Support.Settings { /// Parses the value assigned to an option /// Option to which a value is being assigned /// Line containing the option assignment - /// Index of the value's first character - private static void parseOptionValue(Option option, string line, int valueStartIndex) { - + /// Index one after the assignment character + private static void parseOptionValue(Option option, string line, int assignmentEndIndex) { + int firstCharacterIndex = assignmentEndIndex; + ParserHelper.SkipSpaces(line, ref firstCharacterIndex); + + // Just for beauty, when the option value is empty but padded with spaces, + // leave one space between the equals sign and the value. + if(firstCharacterIndex > assignmentEndIndex) { + ++assignmentEndIndex; + } + + // If the line consists of only whitespace, create an empty value + if(firstCharacterIndex == line.Length) { + option.OptionValue = new StringSegment(line, assignmentEndIndex, 0); + return; + } + + char firstCharacter = line[firstCharacterIndex]; + + // Values can be quoted to allow for comments characters appearing in them + int lastCharacterIndex; + if(firstCharacter == '"') { + lastCharacterIndex = line.LastIndexOf('"'); + } else { + lastCharacterIndex = firstCharacterIndex; + } + + int commentStartIndex = line.IndexOf(';', lastCharacterIndex); + if(commentStartIndex == -1) { + commentStartIndex = line.IndexOf('#', lastCharacterIndex); + } + if(commentStartIndex == -1) { + lastCharacterIndex = line.Length - 1; + } else { + lastCharacterIndex = commentStartIndex - 1; + } + + while(lastCharacterIndex > firstCharacterIndex) { + if(char.IsWhiteSpace(line, lastCharacterIndex)) { + --lastCharacterIndex; + } else { + break; + } + } + + option.OptionValue = new StringSegment( + line, firstCharacterIndex, lastCharacterIndex - firstCharacterIndex + 1 + ); } /// Determines the best matching type for an option value diff --git a/Source/Settings/ConfigurationFileStore.Test.cs b/Source/Settings/ConfigurationFileStore.Test.cs index db96786..4d36eaf 100644 --- a/Source/Settings/ConfigurationFileStore.Test.cs +++ b/Source/Settings/ConfigurationFileStore.Test.cs @@ -21,10 +21,10 @@ License along with this library #if UNITTEST using System; +using System.Collections.Generic; using System.IO; using NUnit.Framework; -using System.Collections.Generic; namespace Nuclex.Support.Settings { @@ -32,6 +32,9 @@ namespace Nuclex.Support.Settings { [TestFixture] internal class ConfigurationFileStoreTest { + /// Loads a configuration file from a string + /// Contents of the configuration file + /// The configuration file loaded from the string private static ConfigurationFileStore load(string fileContents) { using(var reader = new StringReader(fileContents)) { return ConfigurationFileStore.Parse(reader); @@ -128,11 +131,84 @@ namespace Nuclex.Support.Settings { for(int index = 0; index < options.Count; ++index) { Assert.That( - configurationFile.Get(null, options[index].Name), Is.Null + configurationFile.Get(null, options[index].Name), Is.Null.Or.Empty ); } } + /// + /// Verifies that values assigned to options can contain space charcters + /// + [Test] + public void OptionValuesCanContainSpaces() { + string fileContents = + "test = hello world"; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That(configurationFile.Get(null, "test"), Is.EqualTo("hello world")); + } + + /// + /// Verifies that values enclosed in quotes can embed comment characters + /// + [Test] + public void OptionValuesWithQuotesCanEmbedComments() { + string fileContents = + "test = \"This ; is # not a comment\" # but this is"; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That( + configurationFile.Get(null, "test"), + Is.EqualTo("\"This ; is # not a comment\"") + ); + } + + /// + /// Verifies that values can end on a quote without causing trouble + /// + [Test] + public void CommentsCanEndWithAQuote() { + string fileContents = + "test = \"This value ends with a quote\""; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That( + configurationFile.Get(null, "test"), + Is.EqualTo("\"This value ends with a quote\"") + ); + } + + /// + /// Verifies that values can forget the closing quote without causing trouble + /// + [Test] + public void ClosingQuoteCanBeOmmitted() { + string fileContents = + "test = \"No closing quote"; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That( + configurationFile.Get(null, "test"), + Is.EqualTo("\"No closing quote") + ); + } + + /// + /// Verifies that text placed after the closing quote will also be part of + /// an option's value + /// + [Test] + public void TextAfterClosingQuoteBecomesPartOfValue() { + string fileContents = + "test = \"Begins here\" end ends here"; + ConfigurationFileStore configurationFile = load(fileContents); + + Assert.That( + configurationFile.Get(null, "test"), + Is.EqualTo("\"Begins here\" end ends here") + ); + } + } } // namespace Nuclex.Support.Settings diff --git a/Source/Settings/ConfigurationFileStore.cs b/Source/Settings/ConfigurationFileStore.cs index 3e0bb0f..fa15b6d 100644 --- a/Source/Settings/ConfigurationFileStore.cs +++ b/Source/Settings/ConfigurationFileStore.cs @@ -21,8 +21,6 @@ License along with this library using System; using System.Collections.Generic; using System.IO; - -using Nuclex.Support.Parsing; using System.Text; namespace Nuclex.Support.Settings {