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
|
||||
}
|
||||
|
||||
// Skip any whitespaces between the last character and the closing brace
|
||||
do {
|
||||
--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() {
|
||||
LineIndex = state.Store.lines.Count - 1,
|
||||
CategoryName = new StringSegment(
|
||||
|
@ -134,6 +137,7 @@ namespace Nuclex.Support.Settings {
|
|||
OptionLookup = new Dictionary<string, Option>()
|
||||
};
|
||||
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>
|
||||
|
@ -143,10 +147,43 @@ namespace Nuclex.Support.Settings {
|
|||
private static void parseOption(
|
||||
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() {
|
||||
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>
|
||||
|
|
|
@ -21,10 +21,10 @@ License along with this library
|
|||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Settings {
|
||||
|
||||
|
@ -60,6 +60,79 @@ namespace Nuclex.Support.Settings {
|
|||
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
|
||||
|
|
|
@ -23,6 +23,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
|
||||
using Nuclex.Support.Parsing;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Settings {
|
||||
|
||||
|
@ -101,15 +102,7 @@ namespace Nuclex.Support.Settings {
|
|||
/// <param name="category">Category whose options will be enumerated</param>
|
||||
/// <returns>An enumerable list of all options in the category</returns>
|
||||
public IEnumerable<OptionInfo> EnumerateOptions(string category = null) {
|
||||
Category enumeratedCategory;
|
||||
|
||||
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"
|
||||
);
|
||||
}
|
||||
Category enumeratedCategory = getCategoryByName(category);
|
||||
|
||||
foreach(Option option in this.RootCategory.OptionLookup.Values) {
|
||||
OptionInfo optionInfo = new OptionInfo() {
|
||||
|
@ -153,7 +146,16 @@ namespace Nuclex.Support.Settings {
|
|||
/// parameter, false otherwise
|
||||
/// </returns>
|
||||
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>
|
||||
|
@ -162,7 +164,30 @@ namespace Nuclex.Support.Settings {
|
|||
/// <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>
|
||||
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>
|
||||
|
@ -173,6 +198,129 @@ namespace Nuclex.Support.Settings {
|
|||
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>
|
||||
private IList<string> lines;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user