From 154410fb54c7fdfa139a35b1642e0d9f1e10b4f2 Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Mon, 30 Jun 2014 23:55:16 -0500 Subject: [PATCH] continuing scene expansion --- .../Ficdown.Parser.Tests.csproj | 2 + Ficdown.Parser.Tests/SceneLinkerTests.cs | 93 ++++++++++ .../TestStories/the-robot-king.md | 4 +- Ficdown.Parser.Tests/UtilityTests.cs | 163 ++++++++++++++++++ Ficdown.Parser/Engine/RegexLib.cs | 5 + Ficdown.Parser/Engine/SceneLinker.cs | 43 ++++- Ficdown.Parser/Engine/Utilities.cs | 11 +- 7 files changed, 311 insertions(+), 10 deletions(-) create mode 100644 Ficdown.Parser.Tests/SceneLinkerTests.cs create mode 100644 Ficdown.Parser.Tests/UtilityTests.cs diff --git a/Ficdown.Parser.Tests/Ficdown.Parser.Tests.csproj b/Ficdown.Parser.Tests/Ficdown.Parser.Tests.csproj index 1bf08cc..8cc0931 100644 --- a/Ficdown.Parser.Tests/Ficdown.Parser.Tests.csproj +++ b/Ficdown.Parser.Tests/Ficdown.Parser.Tests.csproj @@ -51,11 +51,13 @@ + True True Resources.resx + diff --git a/Ficdown.Parser.Tests/SceneLinkerTests.cs b/Ficdown.Parser.Tests/SceneLinkerTests.cs new file mode 100644 index 0000000..0212e0d --- /dev/null +++ b/Ficdown.Parser.Tests/SceneLinkerTests.cs @@ -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 scenes) + { + var sceneDict = new Dictionary>(); + foreach (var scene in scenes) + { + var key = Utilities.NormalizeString(scene.Name); + if(!sceneDict.ContainsKey(key)) sceneDict.Add(key, new List()); + 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()); + } + } +} diff --git a/Ficdown.Parser.Tests/TestStories/the-robot-king.md b/Ficdown.Parser.Tests/TestStories/the-robot-king.md index 69c0585..6bb727a 100644 --- a/Ficdown.Parser.Tests/TestStories/the-robot-king.md +++ b/Ficdown.Parser.Tests/TestStories/the-robot-king.md @@ -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?** - [Go outside and start walking to the palace.](/outside) -- [Wait for it to stop raining.](#stopped-raining) -- [Put on your raincoat.](#raincoat) +- [Wait for it to stop raining.](?!stopped-raining#stopped-raining) +- [Put on your raincoat.](?!raincoat#raincoat) ### Raincoat diff --git a/Ficdown.Parser.Tests/UtilityTests.cs b/Ficdown.Parser.Tests/UtilityTests.cs new file mode 100644 index 0000000..5766598 --- /dev/null +++ b/Ficdown.Parser.Tests/UtilityTests.cs @@ -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 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 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 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 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); + } + } +} diff --git a/Ficdown.Parser/Engine/RegexLib.cs b/Ficdown.Parser/Engine/RegexLib.cs index 88e21cd..a995c6c 100644 --- a/Ficdown.Parser/Engine/RegexLib.cs +++ b/Ficdown.Parser/Engine/RegexLib.cs @@ -10,6 +10,11 @@ string.Format(@"(?\[(?{0})\]\([ ]*(?{1})[ ]*\))", GetNestedBracketsPattern(), GetNestedParensPattern()), RegexOptions.Singleline | RegexOptions.Compiled); + public static Regex ConditionalText = new Regex(@"^(?([^|\\]|\\.)*)(\|(?([^|\\]|\\.)+))?$", + RegexOptions.Singleline | RegexOptions.Compiled); + + public static Regex EscapeChar = new Regex(@"(? u.Intersect(conditions).Count() == conditions.Count)) return; + uniques.Add(conditions); @@ -78,7 +82,42 @@ { foreach (Match anchor in anchors) { - var satisfied = Utilities.ConditionsSatisfied(anchor.Groups["conditions"].Value, scene.Conditions); + string target; + IList 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; } diff --git a/Ficdown.Parser/Engine/Utilities.cs b/Ficdown.Parser/Engine/Utilities.cs index 7fc766e..6eb6768 100644 --- a/Ficdown.Parser/Engine/Utilities.cs +++ b/Ficdown.Parser/Engine/Utilities.cs @@ -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.Collections.Generic; @@ -47,10 +51,5 @@ } else throw new FormatException(string.Format("Invalid href: {0}", href)); } - - public static bool ConditionsSatisfied(string cquery, IList conditions) - { - return false; - } } }