Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Hocon.Configuration/Hocon.Configuration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<RootNamespace>Hocon</RootNamespace>
<PackageTags>$(HoconPackageTags)</PackageTags>
<Description>HOCON (Human-Optimized Config Object Notation) parser and application-ready implementation.</Description>
<NoWarn>$(NoWarn);NU1506</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,7 +15,7 @@
<ItemGroup Condition=" '$(TargetFramework)' == '$(NetFrameworkVersion)' ">
<Reference Include="System.Configuration" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == '$(NetStandardLibVersion)'">
<PackageReference Include="System.Configuration.ConfigurationManager" />
</ItemGroup>
Expand Down
15 changes: 14 additions & 1 deletion src/Hocon.Configuration/HoconConfigurationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ public static Config ParseString(string hocon, HoconIncludeCallbackAsync include
return new Config(res);
}

/// <summary>
/// Generates a configuration defined in the supplied
/// HOCON (Human-Optimized Config Object Notation) string.
/// </summary>
/// <param name="document">A document that contains configuration options to use.</param>
/// <param name="includeCallback">callback used to resolve includes</param>
/// <returns>The configuration defined in the supplied HOCON string.</returns>
public static Config ParseDocument(HoconDocument document, HoconIncludeDocumentCallbackAsync includeCallback, HoconEnvironmentGetCallback envGetCallback = null)
{
HoconRoot res = HoconParser.Parse(document, includeCallback, envGetCallback);
return new Config(res);
}

/// <summary>
/// Generates a configuration defined in the supplied
/// HOCON (Human-Optimized Config Object Notation) string.
Expand Down Expand Up @@ -99,7 +112,7 @@ public static Config FromFile(string filePath)
foreach(var extension in DefaultHoconFileExtensions)
{
var path = $"{filePath}.{extension}";
if (File.Exists(path))
if (File.Exists(path))
return ParseString(File.ReadAllText(path));
}
} else
Expand Down
1 change: 1 addition & 0 deletions src/Hocon/Hocon.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
<TargetFrameworks>$(NetFrameworkVersion);$(NetStandardLibVersion)</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>HOCON (Human-Optimized Config Object Notation) core API implementation. For full access inside your application, install the Hocon.Configuration package.</Description>
<NoWarn>$(NoWarn);NU1506</NoWarn>
</PropertyGroup>
</Project>
126 changes: 113 additions & 13 deletions src/Hocon/HoconParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,75 @@

namespace Hocon
{

/// <summary>
/// The source of a hocon document along with optionally a source that
/// indicates where the document came from.
/// </summary>
public class HoconDocument
{
private readonly string text;
private readonly HoconDocumentSource source;

public HoconDocument(string text, HoconDocumentSource source)
{
this.text = text;
this.source = source;
}

public string Text => text;

public HoconDocumentSource Source => source;

public static HoconDocument FromFile(string path, string contents) =>
new(
text: contents,
source: new HoconDocumentSource(HoconCallbackType.File, path)
);

public static HoconDocument FromString(string contents) =>
new(text: contents, source: null);

}

/// <summary>
/// A description of where a hocon document came from or, in the case of
/// includes, where to load it from.
/// </summary>
public class HoconDocumentSource
{
private readonly HoconCallbackType type;
private readonly string what;

public HoconDocumentSource(HoconCallbackType type, string what)
{
this.type = type;
this.what = what;
}

public HoconCallbackType Type => type;

public string What => what;
}

public delegate Task<string> HoconIncludeCallbackAsync(HoconCallbackType callbackType, string value);

public delegate Task<string> HoconIncludeDocumentCallbackAsync(
HoconDocumentSource include,
HoconDocument document);

public delegate string HoconEnvironmentGetCallback(string key, string defaultValue);

/// <summary>
/// This class contains methods used to parse HOCON (Human-Optimized Config Object Notation)
/// configuration strings.
/// </summary>
public sealed class HoconParser
{
private readonly List<HoconSubstitution> _substitutions = new List<HoconSubstitution>();
private HoconIncludeCallbackAsync _includeCallback = (type, value) => Task.FromResult("{}");
private HoconIncludeDocumentCallbackAsync _includeCallback = (i, p) => Task.FromResult("{}");
private HoconEnvironmentGetCallback _envGetCallback = (k, d) => Environment.GetEnvironmentVariable(k);
private HoconDocument _document = default;
private HoconValue _root;

private HoconTokenizerResult _tokens;
Expand All @@ -31,27 +90,51 @@ public sealed class HoconParser
/// <summary>
/// Parses the supplied HOCON configuration string into a root element.
/// </summary>
/// <param name="text">The string that contains a HOCON configuration string.</param>
/// <param name="doc">The document that contains a HOCON configuration string.</param>
/// <param name="includeCallback">Callback used to resolve includes</param>
/// <returns>The root element created from the supplied HOCON configuration string.</returns>
/// <exception cref="HoconParserException">
/// This exception is thrown when an unresolved substitution is encountered.
/// It also occurs when any error is encountered while tokenizing or parsing the configuration string.
/// </exception>
public static HoconRoot Parse(string text, HoconIncludeCallbackAsync includeCallback = null)
public static HoconRoot Parse(HoconDocument doc, HoconIncludeDocumentCallbackAsync includeCallback = null, HoconEnvironmentGetCallback envGetCallback = null)
{
return new HoconParser().ParseText(text, true, includeCallback).Normalize();
return new HoconParser().ParseText(doc, true, includeCallback, envGetCallback).Normalize();
}

private HoconRoot ParseText(string text, bool resolveSubstitutions, HoconIncludeCallbackAsync includeCallback)
/// <summary>
/// Parses the supplied HOCON configuration string into a root element.
/// </summary>
/// <param name="text">The string that contains a HOCON configuration string.</param>
/// <param name="includeCallback">Callback used to resolve includes</param>
/// <returns>The root element created from the supplied HOCON configuration string.</returns>
/// <exception cref="HoconParserException">
/// This exception is thrown when an unresolved substitution is encountered.
/// It also occurs when any error is encountered while tokenizing or parsing the configuration string.
/// </exception>
public static HoconRoot Parse(string text, HoconIncludeCallbackAsync includeCallback = null)
=> Parse(new HoconDocument(text, null), OldToNewCallback(includeCallback));

private static HoconIncludeDocumentCallbackAsync OldToNewCallback(HoconIncludeCallbackAsync old) =>
(old is null) ? null : (inc, _) => old.Invoke(inc.Type, inc.What);

private HoconRoot ParseText(
HoconDocument doc,
bool resolveSubstitutions,
HoconIncludeDocumentCallbackAsync includeCallback,
HoconEnvironmentGetCallback envGetCallback)
{
string text = doc.Text;
if (string.IsNullOrWhiteSpace(text))
throw new HoconParserException(
$"Parameter {nameof(text)} is null or empty.\n" +
"If you want to create an empty Hocon HoconRoot, use \"{}\" instead.");

if (includeCallback != null)
_includeCallback = includeCallback;
if (envGetCallback != null)
_envGetCallback = envGetCallback;
_document = doc;

try
{
Expand Down Expand Up @@ -105,7 +188,7 @@ private void ResolveSubstitutions()
string envValue = null;
try
{
envValue = Environment.GetEnvironmentVariable(sub.Path.Value);
envValue = _envGetCallback(sub.Path.Value, sub.DefaultValue);
}
catch (Exception)
{
Expand Down Expand Up @@ -185,7 +268,7 @@ private HoconValue ResolveSubstitution(HoconSubstitution sub)
private bool IsValueCyclic(HoconField field, HoconSubstitution sub)
{
var pendingValues = new Stack<HoconValue>();
var visitedFields = new List<HoconField> {field};
var visitedFields = new List<HoconField> { field };
var pendingSubs = new Stack<HoconSubstitution>();
pendingSubs.Push(sub);

Expand Down Expand Up @@ -409,7 +492,9 @@ private HoconValue ParseInclude()
// Consume the last token
_tokens.ToNextSignificant();

var includeHocon = _includeCallback(callbackType, fileName).ConfigureAwait(false).GetAwaiter().GetResult();
var include = new HoconDocumentSource(callbackType, fileName);

var includeHocon = _includeCallback(include, _document).ConfigureAwait(false).GetAwaiter().GetResult();

if (string.IsNullOrWhiteSpace(includeHocon))
{
Expand All @@ -419,7 +504,8 @@ private HoconValue ParseInclude()
return new HoconEmptyValue(null);
}

var includeRoot = new HoconParser().ParseText(includeHocon, false, _includeCallback);
var includeDoc = new HoconDocument(includeHocon, include);
var includeRoot = new HoconParser().ParseText(includeDoc, false, _includeCallback, _envGetCallback);
/*
if (owner != null && owner.Type != HoconType.Empty && owner.Type != includeRoot.Value.Type)
throw HoconParserException.Create(includeToken, Path,
Expand Down Expand Up @@ -469,6 +555,7 @@ private void ParseObject(ref HoconValue owner)
$"found `{_tokens.Current.Type}` instead.");

owner.ReParent(ParseInclude());
hoconObject = owner.GetObject();
valueWasParsed = true;
break;

Expand Down Expand Up @@ -690,9 +777,22 @@ private HoconValue ParseValue(IHoconElement owner)
if (value == null)
value = new HoconValue(owner);

var pointerPath = HoconPath.Parse(_tokens.Current.Value);
HoconPath pointerPath;
string defaultValue;
string refStr = _tokens.Current.Value;
var colonIndex = refStr.IndexOf(":-");
if (colonIndex == -1)
{
pointerPath = HoconPath.Parse(refStr);
defaultValue = null;
}
else
{
pointerPath = HoconPath.Parse(refStr.Substring(0, colonIndex));
defaultValue = refStr.Substring(colonIndex + 2);
}
var sub = new HoconSubstitution(value, pointerPath, _tokens.Current,
_tokens.Current.Type == TokenType.SubstituteRequired);
_tokens.Current.Type == TokenType.SubstituteRequired, defaultValue);
_substitutions.Add(sub);
_tokens.Next();
value.Add(sub);
Expand All @@ -702,7 +802,7 @@ private HoconValue ParseValue(IHoconElement owner)
if (value == null)
value = new HoconValue(owner);

var subAssign = new HoconSubstitution(value, new HoconPath(Path), _tokens.Current, false);
var subAssign = new HoconSubstitution(value, new HoconPath(Path), _tokens.Current, false, null);
_substitutions.Add(subAssign);
value.Add(subAssign);
value.Add(ParsePlusEqualAssignArray(value));
Expand Down Expand Up @@ -773,7 +873,7 @@ private HoconArray ParsePlusEqualAssignArray(IHoconElement owner)
"Invalid Hocon include. Hocon config substitution type must be the same as the field it's merged into. " +
$"Expected type: `{currentArray.Type}`, type returned by include callback: `{includeValue.Type}`");

currentArray.Add((HoconValue) includeValue.Clone(currentArray));
currentArray.Add((HoconValue)includeValue.Clone(currentArray));
break;

case TokenType.StartOfArray:
Expand Down
7 changes: 5 additions & 2 deletions src/Hocon/Impl/HoconSubstitution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public sealed class HoconSubstitution : IHoconElement, IHoconLineInfo
/// <param name="required">Marks wether this substitution uses the ${? notation or not.</param>
/// ///
/// <param name="lineInfo">The <see cref="IHoconLineInfo" /> of this substitution, used for exception generation purposes.</param>
internal HoconSubstitution(IHoconElement parent, HoconPath path, IHoconLineInfo lineInfo, bool required)
internal HoconSubstitution(IHoconElement parent, HoconPath path, IHoconLineInfo lineInfo, bool required, string defaultValue)
{
if (parent == null)
throw new ArgumentNullException(nameof(parent), "HoconSubstitution parent can not be null.");
Expand All @@ -48,7 +48,8 @@ internal HoconSubstitution(IHoconElement parent, HoconPath path, IHoconLineInfo
LineNumber = lineInfo.LineNumber;
Required = required;
Path = path;

DefaultValue = defaultValue;

_parentsToResolveFor.Add(Parent as HoconValue);
}

Expand All @@ -72,6 +73,8 @@ internal HoconField ParentField
/// </summary>
public HoconPath Path { get; }

public string DefaultValue { get; }

/// <summary>
/// The evaluated value from the Path property
/// </summary>
Expand Down
Loading