ConfigurationFileStore now parses category definitions and some cases of options; wrote a few methods for changing options and creating options and categories

git-svn-id: file:///srv/devel/repo-conversion/nusu@302 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2014-07-20 18:14:06 +00:00
parent ea68676cd2
commit 7e39bd684f
3 changed files with 273 additions and 15 deletions

View File

@ -122,10 +122,13 @@ namespace Nuclex.Support.Settings {
return; // No closing brace in line return; // No closing brace in line
} }
// Skip any whitespaces between the last character and the closing brace
do { do {
--nameEndIndex; --nameEndIndex;
} while(char.IsWhiteSpace(line, nameEndIndex)); } while(char.IsWhiteSpace(line, nameEndIndex));
// Now we know that the line holds a category definition and where exactly in
// the line the category name is located. Create the category.
state.Category = new Category() { state.Category = new Category() {
LineIndex = state.Store.lines.Count - 1, LineIndex = state.Store.lines.Count - 1,
CategoryName = new StringSegment( CategoryName = new StringSegment(
@ -134,6 +137,7 @@ namespace Nuclex.Support.Settings {
OptionLookup = new Dictionary<string, Option>() OptionLookup = new Dictionary<string, Option>()
}; };
state.Store.categories.Add(state.Category); state.Store.categories.Add(state.Category);
state.Store.categoryLookup.Add(state.Category.CategoryName.ToString(), state.Category);
} }
/// <summary>Parses an option definition encountered on a line</summary> /// <summary>Parses an option definition encountered on a line</summary>
@ -143,10 +147,43 @@ namespace Nuclex.Support.Settings {
private static void parseOption( private static void parseOption(
ParserState state, string line, int firstCharacterIndex ParserState state, string line, int firstCharacterIndex
) { ) {
int assignmentIndex = line.IndexOf('=', firstCharacterIndex + 1);
if(assignmentIndex == -1) {
return; // No assignment took place
}
// Cut off any whitespaces between the option name and the assignment
int nameEndIndex = assignmentIndex;
do {
--nameEndIndex;
} while(char.IsWhiteSpace(line, nameEndIndex));
// We have enough information to know that this is an assignment of some kind
Option option = new Option() { Option option = new Option() {
LineIndex = state.Store.lines.Count - 1 LineIndex = state.Store.lines.Count - 1,
OptionName = new StringSegment(
line, firstCharacterIndex, nameEndIndex - firstCharacterIndex + 1
),
}; };
throw new NotImplementedException();
// 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);
}
// We've got the option assignment, either with an empty or proper value
state.Store.options.Add(option);
state.Category.OptionLookup.Add(option.OptionName.ToString(), option);
}
/// <summary>Parses the value assigned to an option</summary>
/// <param name="option">Option to which a value is being assigned</param>
/// <param name="line">Line containing the option assignment</param>
/// <param name="valueStartIndex">Index of the value's first character</param>
private static void parseOptionValue(Option option, string line, int valueStartIndex) {
} }
/// <summary>Determines the best matching type for an option value</summary> /// <summary>Determines the best matching type for an option value</summary>

View File

@ -21,10 +21,10 @@ License along with this library
#if UNITTEST #if UNITTEST
using System; using System;
using System.IO;
using NUnit.Framework; using NUnit.Framework;
using System.IO; using System.Collections.Generic;
using System.Linq;
namespace Nuclex.Support.Settings { namespace Nuclex.Support.Settings {
@ -60,6 +60,79 @@ namespace Nuclex.Support.Settings {
Assert.That(configurationFile.EnumerateCategories(), Is.EquivalentTo(categoryNames)); Assert.That(configurationFile.EnumerateCategories(), Is.EquivalentTo(categoryNames));
} }
/// <summary>
/// Verifies that malformed categories can be handled by the parser
/// </summary>
[Test]
public void MalformedCategoriesAreIgnored() {
string fileContents =
"[ Not a category\r\n" +
" [";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that empty lines in the configuration file have no meaning
/// </summary>
[Test]
public void EmptyLinesAreSkipped() {
string fileContents =
"\r\n" +
" ";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that category definitions after a comment sign are ignored
/// </summary>
[Test]
public void CommentedOutCategoriesAreIgnored() {
string fileContents =
"#[NotACategory]\r\n" +
"; [ Also Not A Category ]\r\n";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
}
/// <summary>
/// Verifies that assignments without an option name are ignored by the parser
/// </summary>
[Test]
public void NamelessAssignmentsAreIgnored() {
string fileContents =
"=\r\n" +
" = \r\n" +
" = hello";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
Assert.That(configurationFile.EnumerateOptions(), Is.Empty);
}
/// <summary>
/// Verifies that assignments without an option name are ignored by the parser
/// </summary>
[Test]
public void OptionsCanHaveEmptyValues() {
string fileContents =
"a =\r\n" +
"b = \r\n" +
"c = ; hello";
ConfigurationFileStore configurationFile = load(fileContents);
Assert.That(configurationFile.EnumerateCategories(), Is.Empty);
var options = new List<OptionInfo>(configurationFile.EnumerateOptions());
Assert.That(options.Count, Is.EqualTo(3));
for(int index = 0; index < options.Count; ++index) {
Assert.That(
configurationFile.Get<string>(null, options[index].Name), Is.Null
);
}
}
} }
} // namespace Nuclex.Support.Settings } // namespace Nuclex.Support.Settings

View File

@ -23,6 +23,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using Nuclex.Support.Parsing; using Nuclex.Support.Parsing;
using System.Text;
namespace Nuclex.Support.Settings { namespace Nuclex.Support.Settings {
@ -101,15 +102,7 @@ namespace Nuclex.Support.Settings {
/// <param name="category">Category whose options will be enumerated</param> /// <param name="category">Category whose options will be enumerated</param>
/// <returns>An enumerable list of all options in the category</returns> /// <returns>An enumerable list of all options in the category</returns>
public IEnumerable<OptionInfo> EnumerateOptions(string category = null) { public IEnumerable<OptionInfo> EnumerateOptions(string category = null) {
Category enumeratedCategory; Category enumeratedCategory = getCategoryByName(category);
if(string.IsNullOrEmpty(category)) {
enumeratedCategory = this.RootCategory;
} else if(!this.categoryLookup.TryGetValue(category, out enumeratedCategory)) {
throw new KeyNotFoundException(
"There is no category named '" + category + "' in the configuration file"
);
}
foreach(Option option in this.RootCategory.OptionLookup.Values) { foreach(Option option in this.RootCategory.OptionLookup.Values) {
OptionInfo optionInfo = new OptionInfo() { OptionInfo optionInfo = new OptionInfo() {
@ -153,7 +146,16 @@ namespace Nuclex.Support.Settings {
/// parameter, false otherwise /// parameter, false otherwise
/// </returns> /// </returns>
public bool TryGet<TValue>(string category, string optionName, out TValue value) { public bool TryGet<TValue>(string category, string optionName, out TValue value) {
throw new NotImplementedException(); Category containingCategory = getCategoryByName(category);
Option option;
if(containingCategory.OptionLookup.TryGetValue(optionName, out option)) {
value = (TValue)Convert.ChangeType(option.OptionValue.ToString(), typeof(TValue));
return true;
} else {
value = default(TValue);
return false;
}
} }
/// <summary>Saves an option in the settings store</summary> /// <summary>Saves an option in the settings store</summary>
@ -162,7 +164,30 @@ namespace Nuclex.Support.Settings {
/// <param name="optionName">Name of the option that will be saved</param> /// <param name="optionName">Name of the option that will be saved</param>
/// <param name="value">The value under which the option will be saved</param> /// <param name="value">The value under which the option will be saved</param>
public void Set<TValue>(string category, string optionName, TValue value) { public void Set<TValue>(string category, string optionName, TValue value) {
throw new NotImplementedException(); Category targetCategory;
bool optionMightExist;
if(string.IsNullOrEmpty(category)) {
targetCategory = this.RootCategory;
optionMightExist = true;
} else if(this.categoryLookup.TryGetValue(category, out targetCategory)) {
optionMightExist = true;
} else {
targetCategory = createCategory(category);
optionMightExist = false;
}
Option targetOption;
if(optionMightExist) {
if(targetCategory.OptionLookup.TryGetValue(optionName, out targetOption)) {
return;
}
}
// Append at bottom of category
} }
/// <summary>Removes the option with the specified name</summary> /// <summary>Removes the option with the specified name</summary>
@ -173,6 +198,129 @@ namespace Nuclex.Support.Settings {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <summary>Looks a category up by its name</summary>
/// <param name="categoryName">
/// Name of the category. Can be null for the root category
/// </param>
/// <returns>The category with the specified name</returns>
private Category getCategoryByName(string categoryName) {
Category category;
if(string.IsNullOrEmpty(categoryName)) {
category = this.RootCategory;
} else if(!this.categoryLookup.TryGetValue(categoryName, out category)) {
throw new KeyNotFoundException(
"There is no category named '" + categoryName + "' in the configuration file"
);
}
return category;
}
/// <summary>Creates a new option</summary>
/// <param name="category">Category the option will be added to</param>
/// <param name="name">Name of the option</param>
/// <param name="value">Value that will be assigned to the option</param>
private void createOption(Category category, string name, string value) {
int valueLength;
if(value == null) {
valueLength = 0;
} else {
valueLength = value.Length;
}
// Build the complete line containing the option assignment
string line;
{
StringBuilder builder = new StringBuilder(name.Length + 3 + valueLength);
builder.Append(name);
builder.Append(" = ");
if(valueLength > 0) {
builder.Append(value);
}
line = builder.ToString();
}
Option newOption = new Option() {
LineIndex = this.lines.Count,
OptionName = new StringSegment(line, 0, name.Length),
OptionValue = new StringSegment(line, name.Length + 3, valueLength)
};
// TODO: Find end line of category and add line
}
/// <summary>Changes the value of an option</summary>
/// <param name="option">Option whose value will be changed</param>
/// <param name="newValue">New value that will be assigned to the option</param>
private void changeOption(Option option, string newValue) {
int newValueLength;
if(newValue == null) {
newValueLength = 0;
} else {
newValueLength = newValue.Length;
}
// Form the new line
string line = option.OptionValue.Text;
{
StringBuilder builder = new StringBuilder(
line.Length - option.OptionValue.Count + newValue.Length
);
// Stuff before the value
if(option.OptionValue.Offset > 0) {
builder.Append(line, 0, option.OptionValue.Offset);
}
// The value itself
if(newValueLength > 0) {
builder.Append(newValue);
}
// Stuff after the value
int endIndex = option.OptionValue.Offset + option.OptionValue.Count;
if(endIndex < line.Length) {
builder.Append(line, endIndex, line.Length - endIndex);
}
line = builder.ToString();
}
this.lines[option.LineIndex] = line;
option.OptionValue = new StringSegment(line, option.OptionValue.Offset, newValueLength);
}
/// <summary>Creates a new category in the configuration file</summary>
/// <param name="category">Name of the new category</param>
/// <returns>The category that was created</returns>
private Category createCategory(string category) {
string categoryDefinition;
{
StringBuilder builder = new StringBuilder(category.Length + 2);
builder.Append('[');
builder.Append(category);
builder.Append(']');
categoryDefinition = builder.ToString();
}
// An empty line before the category definition for better readability
this.lines.Add(string.Empty);
Category newCategory = new Category() {
LineIndex = this.lines.Count,
CategoryName = new StringSegment(categoryDefinition, 1, category.Length),
OptionLookup = new Dictionary<string, Option>()
};
this.lines.Add(categoryDefinition);
this.categoryLookup.Add(category, newCategory);
return newCategory;
}
/// <summary>Lines contained in the configuration file</summary> /// <summary>Lines contained in the configuration file</summary>
private IList<string> lines; private IList<string> lines;