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:
parent
ea68676cd2
commit
7e39bd684f
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user