Skip to content

Commit

Permalink
Add more documentation and some slight refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Ygg01 committed Jun 4, 2024
1 parent 10faf0a commit 2139cd4
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 32 deletions.
72 changes: 72 additions & 0 deletions Linguini.Bundle/Builder/FluentBundleOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,91 @@

namespace Linguini.Bundle.Builder
{
/// <summary>
/// Represents the options for configuring a FluentBundle.
/// </summary>
public class FluentBundleOption
{
/// <summary>
/// Specifies whether the FluentBundle is thread-safe or not.
///
/// When true, it will use <see cref="ConcurrentBundle"/>, otherwise it will use <see cref="FluentBundle"/>
/// </summary>
public bool UseConcurrent { get; init; }

/// <summary>
/// Enables Extensions to standard Fluent syntax.
///
/// When true it enables following features:
/// <list type="bullet">
/// <item><b>Dynamic Reference</b> - ability to reference terms/message using <c>$$term_ref</c>.</item>
/// <item><b>Dynamic Reference attribute</b> - ability to reference terms/message using <c>$$term_ref.Attribute</c>.</item>
/// <item><b>Term passing</b> - <c>-term.attribute(arg0: $passed_term)</c>.</item>
/// </list>
/// </summary>
/// <value>Enables the non-standard Fluent syntax. Defaults to <c>false</c>.</value>
public bool EnableExtensions { get; init; }

/// <summary>
/// Use Unicode isolation.
///
/// Most of the time the Unicode BiDi Algorithm handles bidirectional text very well. In some cases it needs
/// some help, so this adds isolating characters to ensure correct behavior.
/// For more detalis see: https://github.com/projectfluent/fluent.js/wiki/Unicode-Isolation
/// </summary>
/// <value>
/// Enables bidirectional text isolation. Defaults to <c>true</c>.
/// </value>
public bool UseIsolating { get; init; } = true;

/// <summary>
/// Maximal number of Placeable(s).
///
/// It prevents the deep nesting of terms, which may lead to https://en.wikipedia.org/wiki/Billion_laughs_attack .
/// </summary>
/// <value>
/// Number of nested placeable values, defaults to one hundred.
/// </value>
public byte MaxPlaceable { get; init; } = 100;


/// <summary>
/// Represents the list of locales for the FluentBundle.
/// </summary>
/// <value>
/// The locales define the languages and regional variations that the FluentBundle supports.
/// </value>
public List<string> Locales { get; init; } = new List<string>();

/// <summary>
/// Specifies the external functions that can be used in a FluentBundle.
/// </summary>
/// <value>
/// The dictionary containing the external functions. The key represents
/// the function name, and the value is a delegate of type
/// <see cref="ExternalFunction"/>.
/// </value>
public IDictionary<string, ExternalFunction> Functions { get; init; } =
new Dictionary<string, ExternalFunction>();

/// <summary>
/// Gets or sets the formatter function that is used to format Fluent type values into strings.
/// </summary>
/// <remarks>
/// The formatter function takes an instance of <see cref="IFluentType"/> and returns the string representation of the value.
/// <para>
/// If not set, the default formatting behavior will be used.
/// </para>
/// </remarks>
/// <value>
/// The formatter function.
/// </value>
public Func<IFluentType, string>? FormatterFunc { get; init; }

/// <summary>
/// Represents a function that transforms a string value before it is used in Fluent message formatting.
/// </summary>
/// <value>The transformed string value.</value>
public Func<string, string>? TransformFunc { get; init; }
}
}
10 changes: 7 additions & 3 deletions Linguini.Bundle/Builder/LinguiniBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ namespace Linguini.Bundle.Builder
{
public static class LinguiniBuilder
{
/// <summary>
/// Builder class for constructing Fluent bundles.
/// </summary>
/// <param name="useExperimental">Sets the <see cref="FluentBundleOption.EnableExtensions"/> flag. Defaults to <c>false</c></param>
public static ILocaleStep Builder(bool useExperimental = false)
{
return new StepBuilder(useExperimental);
Expand Down Expand Up @@ -147,9 +151,9 @@ public FluentBundle UncheckedBuild()

foreach (var resource in _resources)
{
if (!bundle.AddResource(resource,out var resErr))
if (!bundle.AddResource(resource, out var resErr))
{
errors ??= new List<FluentError>();
errors ??= new List<FluentError>();
errors.AddRange(resErr);
}
}
Expand Down Expand Up @@ -268,4 +272,4 @@ public IReadyStep SkipResources()
}
}
}
}
}
59 changes: 31 additions & 28 deletions Linguini.Bundle/Errors/FluentError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ namespace Linguini.Bundle.Errors
{
public abstract record FluentError
{
/// <summary>
/// What type of error was encountered
/// </summary>
/// <returns>Enumeration describing error.</returns>
public abstract ErrorType ErrorKind();

/// <summary>
/// Where in file was error encountered.
/// </summary>
/// <returns>Position of error in file</returns>
public virtual ErrorSpan? GetSpan()
{
return null;
Expand All @@ -26,22 +34,32 @@ public record ErrorSpan(int Row, int StartSpan, int EndSpan, int StartMark, int
{
}

/// <summary>
/// Represents an error that occurs when there is an attempt to override an existing entry in a FluentBundle.
/// </summary>
public record OverrideFluentError : FluentError
{
private readonly string _id;
private readonly EntryKind _kind;

/// <summary>
/// Id of term or message that was overriden.
/// </summary>
public string Id => _id;

public OverrideFluentError(string id, EntryKind kind)
{
_id = id;
_kind = kind;
}

/// <inheritdoc/>
public override ErrorType ErrorKind()
{
return ErrorType.Overriding;
}

/// <inheritdoc/>
public override string ToString()
{
return $"For id:{_id} already exist entry of type: {_kind.ToString()}";
Expand Down Expand Up @@ -91,38 +109,23 @@ public static ResolverFluentError TooManyPlaceables()

public static ResolverFluentError Reference(IInlineExpression self)
{
// TODO only allow references here
if (self is FunctionReference funcRef)
{
return new($"Unknown function: {funcRef.Id}()", ErrorType.Reference);
}

if (self is MessageReference msgRef)
switch (self)
{
if (msgRef.Attribute == null)
{
case FunctionReference funcRef:
return new($"Unknown function: {funcRef.Id}()", ErrorType.Reference);
case MessageReference msgRef when msgRef.Attribute == null:
return new($"Unknown message: {msgRef.Id}", ErrorType.Reference);
}

return new($"Unknown attribute: {msgRef.Id}.{msgRef.Attribute}", ErrorType.Reference);
}

if (self is TermReference termReference)
{
if (termReference.Attribute == null)
{
case MessageReference msgRef:
return new($"Unknown attribute: {msgRef.Id}.{msgRef.Attribute}", ErrorType.Reference);
case TermReference termReference when termReference.Attribute == null:
return new($"Unknown term: -{termReference.Id}", ErrorType.Reference);
}

return new($"Unknown attribute: -{termReference.Id}.{termReference.Attribute}", ErrorType.Reference);
case TermReference termReference:
return new($"Unknown attribute: -{termReference.Id}.{termReference.Attribute}", ErrorType.Reference);
case VariableReference varRef:
return new($"Unknown variable: ${varRef.Id}", ErrorType.Reference);
default:
throw new ArgumentException($"Expected reference got ${self.GetType()}");
}

if (self is VariableReference varRef)
{
return new($"Unknown variable: ${varRef.Id}", ErrorType.Reference);
}

throw new ArgumentException($"Expected reference got ${self.GetType()}");
}

public static ResolverFluentError Cyclic(Pattern pattern)
Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ You can also follow other NuGet installation instructions. E.g. :
Or copy this code to your [PackageReference](https://learn.microsoft.com/en-gb/nuget/consume-packages/package-references-in-project-files)

```xml
<PackageReference Include="Linguini.Bundle" Version="0.7.0" />
<PackageReference Include="Linguini.Bundle" Version="0.8.2" />
```

# How to use it?
Expand Down Expand Up @@ -86,6 +86,33 @@ Let's go line by line and see how `LinguiniBuilder` works.
bundler.GetAttrMessage("hello-user", ("username", (FluentString)"Test"));
```

# How to develop for it?

To develop you need to install [Git](https://git-scm.com/) and [.NET SDK](https://dotnet.microsoft.com/en-us/download) first.
Installation of these tools is out of the scope of the document

```shell
git clone https://github.com/Ygg01/Linguini.git
cd Linguini
dotnet test
```

## Build documentation

To build API documentation for `Linguini`.

1. Install [`docfx`](https://github.com/dotnet/docfx)
```shell
dotnet tool install -g docfx
```
2. Build documentation
```shell
docfx build .\docfx_project\docfx.json --serve
```
3. Open http://localhost:8080/api

# Quick questions and answers

## Why FluentBundle isn't thread-safe?
Expand Down

0 comments on commit 2139cd4

Please sign in to comment.