Skip to content

Commit f4fa6e4

Browse files
Changes to support time range queries and operations (#196)
* TimeRange code * Upgrade recognisers * Avoid infinite recursion when self-references used in scripts * Hopefully make time tests cross-platform * code tidy * Remove faulty test * Improve example * Add documentation
1 parent 44cb9c0 commit f4fa6e4

31 files changed

+809
-40
lines changed

Engine/Application/ApplicationEngine.cs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class RuntimeInfo
1616

1717
public void AddModel(string name, ModelFormat format)
1818
{
19-
Models = Models.Append(new ModelInfo {Name = name, Format = format.ToString()}).ToArray();
19+
Models = Models.Append(new ModelInfo { Name = name, Format = format.ToString() }).ToArray();
2020
}
2121

2222
public class ModelInfo
@@ -43,33 +43,27 @@ public class ModelInfo
4343
/// </remarks>
4444
public class ApplicationEngine
4545
{
46+
private readonly CancellationToken _cancel;
4647
private readonly RunTimeEnvironment _environment;
4748

48-
private readonly RuntimeInfo _info = new RuntimeInfo();
49+
private readonly RuntimeInfo _info = new();
4950

5051
/// <summary>
5152
/// provides generic scriban Template operations
5253
/// </summary>
5354
private readonly TemplateManager _templateManager;
5455

55-
private readonly CancellationToken Cancel;
56-
57-
/// <summary>
58-
/// Used to automatically name models as they are added
59-
/// </summary>
60-
private int _modelCount;
61-
6256
/// <summary>
6357
/// Create a new application engine
6458
/// </summary>
65-
public ApplicationEngine(RunTimeEnvironment environment, CancellationToken cancel = new CancellationToken())
59+
public ApplicationEngine(RunTimeEnvironment environment, CancellationToken cancel = new())
6660
{
6761
_environment = environment;
6862
_templateManager = new TemplateManager(environment.FileSystem);
6963
//we always add the location of the application executable as an include path for
7064
//scripts. This allows us to easily ship a library of standard scripts
7165
_templateManager.AddIncludePath(environment.ApplicationFolder());
72-
Cancel = cancel;
66+
_cancel = cancel;
7367
}
7468

7569
/// <summary>
@@ -106,13 +100,12 @@ public ApplicationEngine WithModel(string name, string modelText, ModelFormat fo
106100
{
107101
try
108102
{
109-
Cancel.ThrowIfCancellationRequested();
103+
_cancel.ThrowIfCancellationRequested();
110104
//parse the text and create a model
111105
var serializer = ModelDeserializerFactory.Fetch(format);
112106
var model = serializer.Deserialize(modelText);
113107
_templateManager.AddVariable(name, model.Untyped);
114108
_info.AddModel(name, model.SourceFormat);
115-
_modelCount++;
116109
}
117110
catch (Exception e)
118111
{
@@ -149,7 +142,7 @@ public ApplicationEngine WithDefinitions(IEnumerable<string> definitionAssignmen
149142
try
150143
{
151144
var definitions = DefinitionParser.CreateDefinitions(definitionAssignments)
152-
.ToDictionary(kv => kv.Key, kv => (object) kv.Value);
145+
.ToDictionary(kv => kv.Key, kv => (object)kv.Value);
153146
_templateManager.AddVariable(ScribanNamespaces.DefinitionsNamespace, definitions);
154147
}
155148
catch (ArgumentException e)
@@ -183,7 +176,7 @@ public ApplicationEngine Render()
183176
try
184177
{
185178
_templateManager.AddVariable("_runtime", _info);
186-
Output = _templateManager.Render(Cancel);
179+
Output = _templateManager.Render(_cancel);
187180
}
188181
catch (Exception e)
189182
{
@@ -218,10 +211,9 @@ void Add(ExtensionCache.KnownAssemblies name, ScriptObject scriptObject)
218211

219212
Add(ExtensionCache.KnownAssemblies.Group,
220213
ExtensionCache.GetGroupingMethods());
221-
/*
214+
222215
Add(ExtensionCache.KnownAssemblies.TimeComparison,
223216
ExtensionCache.GetTimeComparisonMethods());
224-
*/
225217
return this;
226218
}
227219

Engine/Application/TemplateManager.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public TemplateManager(IFileSystemOperations ops)
4343
{
4444
_context.StrictVariables = true;
4545
_context.LoopLimit = int.MaxValue;
46+
_context.ObjectRecursionLimit = 100;
4647
_scriptLoader = new ScriptLoader(ops);
4748
_context.TemplateLoader = _scriptLoader;
4849
_context.PushGlobal(_top);
@@ -137,7 +138,7 @@ public bool TryGetVariableObject<T>(string variableName, out T val)
137138
var scribanVariable = new ScriptVariableGlobal(variableName);
138139
try
139140
{
140-
val = (T) _context.GetValue(scribanVariable);
141+
val = (T)_context.GetValue(scribanVariable);
141142
return true;
142143
}
143144
catch
@@ -177,7 +178,7 @@ public static ImmutableArray<ModelPath> PathsForObjectTree(IDictionary<string, o
177178
public ImmutableArray<ModelPath> GetBuiltIns() => PathsForObjectTree(_context.BuiltinObject, ModelPath.Empty);
178179
public ImmutableArray<ModelPath> GetObjectTree() => PathsForObjectTree(_top, ModelPath.Empty);
179180

180-
public ImmutableArray<ModelPath> GetKeywords()
181+
public static ImmutableArray<ModelPath> GetKeywords()
181182
{
182183
var keywords =
183184
@"func end if else for break continue

Engine/Engine.csproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,25 @@
22

33
<PropertyGroup>
44
<TargetFramework>net5.0</TargetFramework>
5+
<PackageId>textrude.engine</PackageId>
6+
<Description>The engine for Textrude</Description>
7+
<Authors>Neil MacMullen</Authors>
8+
<Copyright>Copyright Neil MacMullen 2021</Copyright>
9+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
10+
<PackageProjectUrl>https://github.com/NeilMacMullen/Textrude</PackageProjectUrl>
11+
<PackageReleaseNotes>First release</PackageReleaseNotes>
12+
<PackageTags></PackageTags>
13+
<IncludeSymbols>true</IncludeSymbols>
14+
<RepositoryUrl>https://github.com/NeilMacMullen/Textrude.git</RepositoryUrl>
15+
<RepositoryType>git</RepositoryType>
16+
<RepositoryBranch>main</RepositoryBranch>
517
</PropertyGroup>
618

719
<ItemGroup>
820
<PackageReference Include="CsvHelper" Version="27.1.1" />
921
<PackageReference Include="Humanizer" Version="2.11.10" />
22+
<PackageReference Include="Microsoft.Recognizers.Text" Version="1.8.0" />
23+
<PackageReference Include="Microsoft.Recognizers.Text.DateTime" Version="1.8.0" />
1024
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
1125
<PackageReference Include="Scriban" Version="5.0.0" />
1226
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />

Engine/Extensions/ExtensionCache.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Engine.Extensions.TimeRange;
45
using Humanizer;
56
using Scriban.Runtime;
67

@@ -14,7 +15,8 @@ public enum KnownAssemblies
1415
Humanizr,
1516
Misc,
1617
Textrude,
17-
Group
18+
Group,
19+
TimeComparison
1820
}
1921

2022
private static readonly Dictionary<KnownAssemblies, ScriptObject> CachedResults =
@@ -62,5 +64,8 @@ public static ScriptObject GetTextrudeMethods() =>
6264

6365
public static ScriptObject GetGroupingMethods() =>
6466
GetOrCreate(KnownAssemblies.Group, () => new[] {typeof(Group)});
67+
68+
public static ScriptObject GetTimeComparisonMethods() =>
69+
GetOrCreate(KnownAssemblies.TimeComparison, () => new[] {typeof(TimeRangeMethods)});
6570
}
6671
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
3+
namespace Engine.Extensions.TimeRange
4+
{
5+
public static class DateTimeExtensions
6+
{
7+
public static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
8+
9+
public static DateTime FromUnixTime(this long unixTime) => UnixEpoch.AddSeconds(unixTime);
10+
11+
public static long ToUnixTime(this DateTime date) => Convert.ToInt64((date - UnixEpoch).TotalSeconds);
12+
13+
public static DateTime Min(DateTime a, DateTime b) => a < b ? a : b;
14+
15+
16+
public static DateTime Max(DateTime a, DateTime b) => a > b ? a : b;
17+
18+
public static DateTime FromUnixTimeString(string sourceTimeSecondsSinceEpoch) =>
19+
long.Parse(sourceTimeSecondsSinceEpoch)
20+
.FromUnixTime();
21+
}
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
3+
namespace Engine.Extensions.TimeRange
4+
{
5+
public static class MyRecogniser
6+
{
7+
public static DateTime RecogniseFromObject(object o)
8+
{
9+
switch (o)
10+
{
11+
case null:
12+
throw new ArgumentException("Unable to treat NULL as a DateTime");
13+
case DateTime d:
14+
return d;
15+
case int unixTimeShort:
16+
return ((long) unixTimeShort).FromUnixTime();
17+
case long unixTime:
18+
return unixTime.FromUnixTime();
19+
case string str when long.TryParse(str, out var unixTime2):
20+
return unixTime2.FromUnixTime();
21+
case string str:
22+
{
23+
var val = TimeRangeRecogniser.Recognise(str);
24+
if (val is DateTime t)
25+
return t;
26+
throw new ArgumentException($"Unable to interpret '{str}' as a DateTime");
27+
}
28+
default:
29+
throw new ArgumentException("Unable to treat argument as DateTime");
30+
}
31+
}
32+
}
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Engine.Extensions.TimeRange
5+
{
6+
/// <summary>
7+
/// Represents a range of time... Start to End
8+
/// </summary>
9+
public class TimeRange
10+
{
11+
public static readonly TimeRange Zero = new(DateTime.MinValue, DateTime.MinValue);
12+
13+
public DateTime End { get; set; }
14+
15+
public DateTime Start { get; set; }
16+
17+
public TimeRange(DateTime t, TimeSpan span) : this(t, t + span)
18+
{
19+
}
20+
21+
public TimeRange(DateTime t, DateTime end)
22+
{
23+
Start = t;
24+
End = end;
25+
}
26+
27+
public TimeSpan Duration => End - Start;
28+
29+
public static TimeRange CreateEnclosingRange(DateTime[] startDates) => new(startDates.Min(), startDates.Max());
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
3+
namespace Engine.Extensions.TimeRange
4+
{
5+
/// <summary>
6+
/// Provides some helpful functions for templates
7+
/// </summary>
8+
public static class TimeRangeMethods
9+
{
10+
public static bool After(object a, object b) => Recognise(a) > Recognise(b);
11+
12+
public static bool Before(object a, object b) => Recognise(a) < Recognise(b);
13+
14+
public static bool Within(object a, string b)
15+
{
16+
if (TimeRangeRecogniser.Recognise(b) is TimeRange range)
17+
{
18+
var da = Recognise(a);
19+
return da >= range.Start && da <= range.End;
20+
}
21+
22+
throw new ArgumentException($"Unable to interpret '{b}' as a range of time");
23+
}
24+
25+
26+
public static DateTime Recognise(object o) =>
27+
MyRecogniser.RecogniseFromObject(o);
28+
}
29+
}

0 commit comments

Comments
 (0)