This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-nitride-cil/src/MfGames.Nitride.Temporal.Schedules/NumericalPathSchedule.cs
D. Moonfire 070cf2bfb8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
fix(scheduler): added better error messages and regular expression for numerical paths
2023-01-18 23:51:37 -06:00

161 lines
4.8 KiB
C#

using System;
using System.Text.RegularExpressions;
using MfGames.Gallium;
using MfGames.Nitride.Generators;
using NodaTime;
using TimeSpanParserUtil;
using Zio;
namespace MfGames.Nitride.Temporal.Schedules;
/// <summary>
/// A schedule that uses the `UPath` of the entity to determine an offset from
/// a starting point and calculates the information from there.
/// </summary>
[WithProperties]
public partial class NumericalPathSchedule : ISchedule
{
public NumericalPathSchedule()
{
this.PathRegex = @"^.*?(\d+)[^\d]*$";
this.GetPath = entity => entity.Get<UPath>().ToString();
this.CaptureGroup = 1;
this.CaptureOffset = -1;
}
/// <summary>
/// Gets or sets the group number of the capture group with 1 being being the
/// first group in PathRegex.
/// </summary>
public int CaptureGroup { get; set; }
/// <summary>
/// Gets or sets the offset to make the resulting capture group a zero-based
/// number.
/// </summary>
public int CaptureOffset { get; set; }
/// <summary>
/// Gets or sets the method for retrieving the path for the entity.
/// </summary>
public Func<Entity, string> GetPath { get; set; }
/// <summary>
/// Gets or sets the regular expression to identify a matching path.
/// </summary>
public string? PathRegex { get; set; }
/// <summary>
/// Gets or sets the period between each matching entity. The period is
/// amount of time between each item based on the number. So the first will
/// be on the ScheduleDate, the second SchedulePeriod later, the third
/// SchedulePeriod after the second, etc. More precisely, the date for
/// any item TimeSpan * ((int)CaptureGroup + (int)CaptureOffset).
/// </summary>
public string? SchedulePeriod { get; set; }
/// <summary>
/// Gets or sets the schedule period as a TimeSpan object. This is converted
/// from SchedulePeriod using https://github.com/pengowray/TimeSpanParser.
/// If the value is null or blank or "immediate", then this will be instant
/// (TimeSpan.Zero).
/// </summary>
public virtual TimeSpan SchedulePeriodTimeSpan
{
get => string.IsNullOrWhiteSpace(this.SchedulePeriod)
|| this.SchedulePeriod.Equals(
"immediate",
StringComparison.InvariantCultureIgnoreCase)
? TimeSpan.Zero
: TimeSpanParser.Parse(this.SchedulePeriod);
set => this.SchedulePeriod = value.ToString();
}
/// <summary>
/// Gets or sets when the first item is scheduled.
/// </summary>
public DateTime? ScheduleStart { get; set; }
/// <inheritdoc />
public virtual Entity Apply(
Entity entity,
Timekeeper timekeeper)
{
// Get the path and match it.
string? path = this.GetPath(entity);
Regex? regex = this.GetRegex();
Match? match = regex?.Match(path)
?? throw new NullReferenceException(
"PathRegex was not configured for the "
+ this.GetType().Name
+ ".");
if (!match.Success)
{
return entity;
}
if (match.Groups.Count < 2)
{
throw new InvalidOperationException(
"There must be at least one capture group in '"
+ this.PathRegex
+ "'.");
}
// Figure out the index/number of this entry.
string numberValue = match.Groups[this.CaptureGroup].Value;
if (!int.TryParse(numberValue, out int number))
{
throw new FormatException(
path + ": Cannot parse '" + numberValue + "' as integer.");
}
number += this.CaptureOffset;
// Figure out the time from the start.
DateTime start = this.ScheduleStart
?? throw new NullReferenceException(
"Cannot use a schedule without a start date.");
DateTime when = start + this.SchedulePeriodTimeSpan * number;
Instant instant = timekeeper.CreateInstant(when);
// If the time hasn't past, then we don't apply it.
Instant now = timekeeper.Clock.GetCurrentInstant();
return instant > now
? entity
: this.Apply(entity, number, instant);
}
/// <inheritdoc />
public virtual bool CanApply(Entity entity)
{
string path = this.GetPath(entity);
Regex? regex = this.GetRegex();
bool match = regex?.IsMatch(path) ?? false;
return match;
}
public Regex? GetRegex()
{
return this.PathRegex == null
? null
: new Regex(this.PathRegex);
}
protected virtual Entity Apply(
Entity entity,
int number,
Instant instant)
{
return entity.Set(instant);
}
}