continuing scene expansion
This commit is contained in:
parent
12ac1cfadc
commit
154410fb54
|
@ -51,11 +51,13 @@
|
||||||
<Compile Include="BlockHandlerTests.cs" />
|
<Compile Include="BlockHandlerTests.cs" />
|
||||||
<Compile Include="IntegrationTests.cs" />
|
<Compile Include="IntegrationTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="SceneLinkerTests.cs" />
|
||||||
<Compile Include="TestStories\Resources.Designer.cs">
|
<Compile Include="TestStories\Resources.Designer.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="UtilityTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
namespace Ficdown.Parser.Tests
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Engine;
|
||||||
|
using Model.Story;
|
||||||
|
using ServiceStack.Text;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
public class SceneLinkerTests
|
||||||
|
{
|
||||||
|
private Story MockStoryWithScenes(IEnumerable<Scene> scenes)
|
||||||
|
{
|
||||||
|
var sceneDict = new Dictionary<string, IList<Scene>>();
|
||||||
|
foreach (var scene in scenes)
|
||||||
|
{
|
||||||
|
var key = Utilities.NormalizeString(scene.Name);
|
||||||
|
if(!sceneDict.ContainsKey(key)) sceneDict.Add(key, new List<Scene>());
|
||||||
|
sceneDict[key].Add(scene);
|
||||||
|
}
|
||||||
|
return new Story
|
||||||
|
{
|
||||||
|
Name = "Test Story",
|
||||||
|
Description = "Story description.",
|
||||||
|
FirstScene = sceneDict.First().Key,
|
||||||
|
Scenes = sceneDict
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ConditionalAnchorGetsReplacedCorrectly()
|
||||||
|
{
|
||||||
|
var sl = new SceneLinker();
|
||||||
|
var story = MockStoryWithScenes(new[]
|
||||||
|
{
|
||||||
|
new Scene
|
||||||
|
{
|
||||||
|
Name = "Test Scene",
|
||||||
|
Description = "Test [passed|failed](?test-condition) text."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sl.ExpandScenes(story);
|
||||||
|
Assert.Equal(2, story.Scenes["test-scene"].Count);
|
||||||
|
Scene passed = null, failed = null;
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
passed =
|
||||||
|
story.Scenes["test-scene"].SingleOrDefault(
|
||||||
|
s =>
|
||||||
|
s.Conditions != null && s.Conditions.Contains("test-condition") &&
|
||||||
|
s.Description.Equals("Test passed text.")));
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
failed =
|
||||||
|
story.Scenes["test-scene"].SingleOrDefault(
|
||||||
|
s => s.Conditions == null && s.Description.Equals("Test failed text.")));
|
||||||
|
Assert.NotNull(passed);
|
||||||
|
Assert.NotNull(failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NegativeConditionalAnchorGetsReplacedCorrectly()
|
||||||
|
{
|
||||||
|
var sl = new SceneLinker();
|
||||||
|
var story = MockStoryWithScenes(new[]
|
||||||
|
{
|
||||||
|
new Scene
|
||||||
|
{
|
||||||
|
Name = "Test Scene",
|
||||||
|
Description = "Test [passed|failed](?!test-condition) text."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sl.ExpandScenes(story);
|
||||||
|
Console.WriteLine(story.Dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MultipleConditionalAnchorsGetReplacedCorrectly()
|
||||||
|
{
|
||||||
|
var sl = new SceneLinker();
|
||||||
|
var story = MockStoryWithScenes(new[]
|
||||||
|
{
|
||||||
|
new Scene
|
||||||
|
{
|
||||||
|
Name = "Test Scene",
|
||||||
|
Description =
|
||||||
|
"Test1 [passed1|failed1](?test1-condition). Test2 [passed2|failed2](?test2-condition)."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sl.ExpandScenes(story);
|
||||||
|
Console.WriteLine(story.Dump());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,8 @@ Your cave only has one tiny window, and through it you can see [the sun shining
|
||||||
**What do you want to do?**
|
**What do you want to do?**
|
||||||
|
|
||||||
- [Go outside and start walking to the palace.](/outside)
|
- [Go outside and start walking to the palace.](/outside)
|
||||||
- [Wait for it to stop raining.](#stopped-raining)
|
- [Wait for it to stop raining.](?!stopped-raining#stopped-raining)
|
||||||
- [Put on your raincoat.](#raincoat)
|
- [Put on your raincoat.](?!raincoat#raincoat)
|
||||||
|
|
||||||
### Raincoat
|
### Raincoat
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
namespace Ficdown.Parser.Tests
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Engine;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
public class UtilityTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AnchorWithTargetMatches()
|
||||||
|
{
|
||||||
|
var anchor = RegexLib.Anchors.Match(@"[Link text](/target-scene)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("/target-scene", anchor.Groups["href"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AnchorsWithConditionsMatch()
|
||||||
|
{
|
||||||
|
var anchor = RegexLib.Anchors.Match(@"[Link text](?condition-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("?condition-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](?!condition-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("?!condition-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](?condition-1&!condition-2)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("?condition-1&!condition-2", anchor.Groups["href"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AnchorsWithTogglesMatch()
|
||||||
|
{
|
||||||
|
var anchor = RegexLib.Anchors.Match(@"[Link text](#toggle-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("#toggle-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](#toggle-1+toggle-2)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("#toggle-1+toggle-2", anchor.Groups["href"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComplexAnchorsMatch()
|
||||||
|
{
|
||||||
|
var anchor = RegexLib.Anchors.Match(@"[Link text](/target-scene?condition-state#toggle-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("/target-scene?condition-state#toggle-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](/target-scene#toggle-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("/target-scene#toggle-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](/target-scene?condition-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("/target-scene?condition-state", anchor.Groups["href"].Value);
|
||||||
|
|
||||||
|
anchor = RegexLib.Anchors.Match(@"[Link text](?condition-state#toggle-state)");
|
||||||
|
Assert.True(anchor.Success);
|
||||||
|
Assert.Equal("Link text", anchor.Groups["text"].Value);
|
||||||
|
Assert.Equal("?condition-state#toggle-state", anchor.Groups["href"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HrefWithTargetParses()
|
||||||
|
{
|
||||||
|
string target;
|
||||||
|
IList<string> conditions, toggles;
|
||||||
|
Utilities.ParseHref("/target-scene", out target, out conditions, out toggles);
|
||||||
|
Assert.Equal("target-scene", target);
|
||||||
|
Assert.Null(conditions);
|
||||||
|
Assert.Null(toggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HrefsWithConditionsParse()
|
||||||
|
{
|
||||||
|
string target;
|
||||||
|
IList<string> conditions, toggles;
|
||||||
|
Utilities.ParseHref("?condition-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Equal(1, conditions.Count);
|
||||||
|
Assert.Contains("condition-state", conditions);
|
||||||
|
Assert.Null(toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("?!condition-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Equal(1, conditions.Count);
|
||||||
|
Assert.Contains("!condition-state", conditions);
|
||||||
|
Assert.Null(toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("?condition-1&!condition-2", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Equal(2, conditions.Count);
|
||||||
|
Assert.Contains("condition-1", conditions);
|
||||||
|
Assert.Contains("!condition-2", conditions);
|
||||||
|
Assert.Null(toggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HrefsWithTogglesParse()
|
||||||
|
{
|
||||||
|
string target;
|
||||||
|
IList<string> conditions, toggles;
|
||||||
|
Utilities.ParseHref("#toggle-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Null(conditions);
|
||||||
|
Assert.Equal(1, toggles.Count);
|
||||||
|
Assert.Contains("toggle-state", toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("#toggle-1+toggle-2", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Null(conditions);
|
||||||
|
Assert.Equal(2, toggles.Count);
|
||||||
|
Assert.Contains("toggle-1", toggles);
|
||||||
|
Assert.Contains("toggle-2", toggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComplexHrefsParse()
|
||||||
|
{
|
||||||
|
string target;
|
||||||
|
IList<string> conditions, toggles;
|
||||||
|
Utilities.ParseHref("/target-scene?condition-state#toggle-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Equal("target-scene", target);
|
||||||
|
Assert.Equal(1, conditions.Count);
|
||||||
|
Assert.Contains("condition-state", conditions);
|
||||||
|
Assert.Equal(1, toggles.Count);
|
||||||
|
Assert.Contains("toggle-state", toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("/target-scene?condition-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Equal("target-scene", target);
|
||||||
|
Assert.Equal(1, conditions.Count);
|
||||||
|
Assert.Contains("condition-state", conditions);
|
||||||
|
Assert.Null(toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("/target-scene#toggle-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Equal("target-scene", target);
|
||||||
|
Assert.Null(conditions);
|
||||||
|
Assert.Equal(1, toggles.Count);
|
||||||
|
Assert.Contains("toggle-state", toggles);
|
||||||
|
|
||||||
|
Utilities.ParseHref("?!condition-one&condition-two#toggle-state", out target, out conditions, out toggles);
|
||||||
|
Assert.Null(target);
|
||||||
|
Assert.Equal(2, conditions.Count);
|
||||||
|
Assert.Contains("!condition-one", conditions);
|
||||||
|
Assert.Contains("condition-two", conditions);
|
||||||
|
Assert.Equal(1, toggles.Count);
|
||||||
|
Assert.Contains("toggle-state", toggles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,11 @@
|
||||||
string.Format(@"(?<anchor>\[(?<text>{0})\]\([ ]*(?<href>{1})[ ]*\))", GetNestedBracketsPattern(),
|
string.Format(@"(?<anchor>\[(?<text>{0})\]\([ ]*(?<href>{1})[ ]*\))", GetNestedBracketsPattern(),
|
||||||
GetNestedParensPattern()), RegexOptions.Singleline | RegexOptions.Compiled);
|
GetNestedParensPattern()), RegexOptions.Singleline | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public static Regex ConditionalText = new Regex(@"^(?<true>([^|\\]|\\.)*)(\|(?<false>([^|\\]|\\.)+))?$",
|
||||||
|
RegexOptions.Singleline | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public static Regex EscapeChar = new Regex(@"(?<!\\)\\", RegexOptions.Compiled);
|
||||||
|
|
||||||
private const string RegexValidName = @"[a-zA-Z](-?[a-zA-Z0-9])*";
|
private const string RegexValidName = @"[a-zA-Z](-?[a-zA-Z0-9])*";
|
||||||
private static readonly string RegexHrefTarget = string.Format(@"\/({0})", RegexValidName);
|
private static readonly string RegexHrefTarget = string.Format(@"\/({0})", RegexValidName);
|
||||||
private static readonly string RegexHrefConditions = string.Format(@"\?((!?{0})(&!?{0})*)?", RegexValidName);
|
private static readonly string RegexHrefConditions = string.Format(@"\?((!?{0})(&!?{0})*)?", RegexValidName);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
namespace Ficdown.Parser.Engine
|
namespace Ficdown.Parser.Engine
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Model.Story;
|
using Model.Story;
|
||||||
|
@ -41,11 +43,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// resolve the current scene
|
// resolve the current scene
|
||||||
|
var original = scene.Clone();
|
||||||
newScenes[key].Add(ResolveScene(scene, anchors));
|
newScenes[key].Add(ResolveScene(scene, anchors));
|
||||||
// resolve the uniques
|
// resolve the uniques
|
||||||
foreach (var unique in uniques)
|
foreach (var unique in uniques)
|
||||||
{
|
{
|
||||||
var uscene = scene.Clone();
|
var uscene = original.Clone();
|
||||||
uscene.Conditions = unique;
|
uscene.Conditions = unique;
|
||||||
newScenes[key].Add(ResolveScene(uscene, anchors));
|
newScenes[key].Add(ResolveScene(uscene, anchors));
|
||||||
}
|
}
|
||||||
|
@ -64,6 +67,7 @@
|
||||||
// make sure this is actually unique
|
// make sure this is actually unique
|
||||||
if (uniques.Any(u => u.Intersect(conditions).Count() == conditions.Count)) return;
|
if (uniques.Any(u => u.Intersect(conditions).Count() == conditions.Count)) return;
|
||||||
|
|
||||||
|
|
||||||
uniques.Add(conditions);
|
uniques.Add(conditions);
|
||||||
|
|
||||||
// we need to treat this unioned with all other existing uniques as another potential unique
|
// we need to treat this unioned with all other existing uniques as another potential unique
|
||||||
|
@ -78,7 +82,42 @@
|
||||||
{
|
{
|
||||||
foreach (Match anchor in anchors)
|
foreach (Match anchor in anchors)
|
||||||
{
|
{
|
||||||
var satisfied = Utilities.ConditionsSatisfied(anchor.Groups["conditions"].Value, scene.Conditions);
|
string target;
|
||||||
|
IList<string> conditions, toggles;
|
||||||
|
Utilities.ParseHref(anchor.Groups["href"].Value, out target, out conditions, out toggles);
|
||||||
|
if (conditions != null)
|
||||||
|
{
|
||||||
|
var satisfied = scene.Conditions == null
|
||||||
|
? conditions.All(c => c.StartsWith("!"))
|
||||||
|
: conditions.All(
|
||||||
|
c => scene.Conditions.Contains(c) ||
|
||||||
|
(c.StartsWith("!") && !scene.Conditions.Contains(c)));
|
||||||
|
|
||||||
|
var text = anchor.Groups["text"].Value;
|
||||||
|
var alts = RegexLib.ConditionalText.Match(text);
|
||||||
|
if (!alts.Success)
|
||||||
|
throw new FormatException(string.Format("Bad conditional anchor: {0}",
|
||||||
|
anchor.Groups["anchor"].Value));
|
||||||
|
|
||||||
|
var replace =
|
||||||
|
RegexLib.EscapeChar.Replace(satisfied ? alts.Groups["true"].Value : alts.Groups["false"].Value,
|
||||||
|
string.Empty);
|
||||||
|
|
||||||
|
// if there's no target or toggles, replace the whole anchor
|
||||||
|
if (target == null && toggles == null)
|
||||||
|
{
|
||||||
|
scene.Description = scene.Description.Replace(anchor.Groups["anchor"].Value, replace);
|
||||||
|
}
|
||||||
|
// if there's a target or toggles, replace the text and remove the conditions on the anchor
|
||||||
|
else
|
||||||
|
{
|
||||||
|
scene.Description = scene.Description.Replace(anchor.Groups["anchor"].Value,
|
||||||
|
string.Format("[{0}]({1}{2})", replace, anchor.Groups["target"].Value,
|
||||||
|
anchor.Groups["toggles"].Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
namespace Ficdown.Parser.Engine
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Ficdown.Parser.Tests")]
|
||||||
|
|
||||||
|
namespace Ficdown.Parser.Engine
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -47,10 +51,5 @@
|
||||||
}
|
}
|
||||||
else throw new FormatException(string.Format("Invalid href: {0}", href));
|
else throw new FormatException(string.Format("Invalid href: {0}", href));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ConditionsSatisfied(string cquery, IList<string> conditions)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue