improved error handling and reporting, plus lint mode for checking files for errors
This commit is contained in:
parent
ed54a71fb3
commit
4ff4693fa3
|
@ -3,7 +3,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.SqlServer.Server;
|
|
||||||
using Parser;
|
using Parser;
|
||||||
using Parser.Render;
|
using Parser.Render;
|
||||||
using Parser.Model.Parser;
|
using Parser.Model.Parser;
|
||||||
|
@ -16,8 +15,7 @@
|
||||||
{
|
{
|
||||||
if(e.ExceptionObject is FicdownException)
|
if(e.ExceptionObject is FicdownException)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
Console.WriteLine(e.ExceptionObject.ToString());
|
||||||
Console.Error.WriteLine(e.ExceptionObject.ToString());
|
|
||||||
Environment.Exit(3);
|
Environment.Exit(3);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -85,6 +83,11 @@
|
||||||
ShowHelp();
|
ShowHelp();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lintMode = format == "lint";
|
||||||
|
|
||||||
|
if(!lintMode)
|
||||||
|
{
|
||||||
if (string.IsNullOrWhiteSpace(format) || string.IsNullOrWhiteSpace(infile))
|
if (string.IsNullOrWhiteSpace(format) || string.IsNullOrWhiteSpace(infile))
|
||||||
{
|
{
|
||||||
ShowHelp();
|
ShowHelp();
|
||||||
|
@ -100,6 +103,8 @@
|
||||||
output = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"html");
|
output = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"html");
|
||||||
else if (format == "epub")
|
else if (format == "epub")
|
||||||
output = "output.epub";
|
output = "output.epub";
|
||||||
|
else if(format == "lint")
|
||||||
|
lintMode = true;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output)))
|
if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output)))
|
||||||
{
|
{
|
||||||
|
@ -127,22 +132,30 @@
|
||||||
Console.WriteLine(@"Images directory {0} does not exist.", images);
|
Console.WriteLine(@"Images directory {0} does not exist.", images);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var parser = new FicdownParser();
|
var parser = new FicdownParser();
|
||||||
var storyText = File.ReadAllText(infile);
|
|
||||||
|
|
||||||
|
string storyText;
|
||||||
|
if(!lintMode)
|
||||||
|
{
|
||||||
|
storyText = File.ReadAllText(infile);
|
||||||
Console.WriteLine(@"Parsing story...");
|
Console.WriteLine(@"Parsing story...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storyText = Console.In.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
var story = parser.ParseStory(storyText);
|
var story = parser.ParseStory(storyText);
|
||||||
|
|
||||||
story.Orphans.ToList().ForEach(o =>
|
story.Orphans.ToList().ForEach(o =>
|
||||||
{
|
{
|
||||||
var currentColor = Console.ForegroundColor;
|
Console.WriteLine("Warning L{0},1: \"{1}\": Unreachable {2}", o.LineNumber, o.Name, o.Type);
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.Error.WriteLine("Warning (line {0}): {1} {2} is unreachable", o.LineNumber, o.Type, o.Name);
|
|
||||||
Console.ForegroundColor = currentColor;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!lintMode)
|
||||||
|
{
|
||||||
IRenderer rend;
|
IRenderer rend;
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
|
@ -177,6 +190,7 @@
|
||||||
rend.Render(story, output, debug);
|
rend.Render(story, output, debug);
|
||||||
|
|
||||||
Console.WriteLine(@"Done.");
|
Console.WriteLine(@"Done.");
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +199,7 @@
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
@"Usage: ficdown.exe
|
@"Usage: ficdown.exe
|
||||||
--format (html|epub)
|
--format (html|epub|lint)
|
||||||
--in ""/path/to/source.md""
|
--in ""/path/to/source.md""
|
||||||
[--out ""/path/to/output""]
|
[--out ""/path/to/output""]
|
||||||
[--template ""/path/to/template/dir""]
|
[--template ""/path/to/template/dir""]
|
||||||
|
|
|
@ -15,22 +15,22 @@
|
||||||
public void FullAnchorMatches()
|
public void FullAnchorMatches()
|
||||||
{
|
{
|
||||||
var anchorStr = @"[Link text](/target-scene)";
|
var anchorStr = @"[Link text](/target-scene)";
|
||||||
var anchor = Utilities.ParseAnchor(anchorStr);
|
var anchor = Utilities.ParseAnchor(anchorStr, 0, 0);
|
||||||
Assert.Equal(anchorStr, anchor.Original);
|
Assert.Equal(anchorStr, anchor.Original);
|
||||||
|
|
||||||
anchorStr = @"[Link text](?condition-state#toggle-state ""Title text"")";
|
anchorStr = @"[Link text](?condition-state#toggle-state ""Title text"")";
|
||||||
anchor = Utilities.ParseAnchor(anchorStr);
|
anchor = Utilities.ParseAnchor(anchorStr, 0, 0);
|
||||||
Assert.Equal(anchorStr, anchor.Original);
|
Assert.Equal(anchorStr, anchor.Original);
|
||||||
|
|
||||||
anchorStr = @"[Link text](""Title text"")";
|
anchorStr = @"[Link text](""Title text"")";
|
||||||
anchor = Utilities.ParseAnchor(anchorStr);
|
anchor = Utilities.ParseAnchor(anchorStr, 0, 0);
|
||||||
Assert.Equal(anchorStr, anchor.Original);
|
Assert.Equal(anchorStr, anchor.Original);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AnchorWithTargetMatches()
|
public void AnchorWithTargetMatches()
|
||||||
{
|
{
|
||||||
var anchor = Utilities.ParseAnchor(@"[Link text](/target-scene)");
|
var anchor = Utilities.ParseAnchor(@"[Link text](/target-scene)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("target-scene", anchor.Href.Target);
|
Assert.Equal("target-scene", anchor.Href.Target);
|
||||||
}
|
}
|
||||||
|
@ -38,15 +38,15 @@
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AnchorsWithConditionsMatch()
|
public void AnchorsWithConditionsMatch()
|
||||||
{
|
{
|
||||||
var anchor = Utilities.ParseAnchor(@"[Link text](?condition-state)");
|
var anchor = Utilities.ParseAnchor(@"[Link text](?condition-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.True(anchor.Href.Conditions["condition-state"]);
|
Assert.True(anchor.Href.Conditions["condition-state"]);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](?!condition-state)");
|
anchor = Utilities.ParseAnchor(@"[Link text](?!condition-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.False(anchor.Href.Conditions["condition-state"]);
|
Assert.False(anchor.Href.Conditions["condition-state"]);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](?condition-1&!condition-2)");
|
anchor = Utilities.ParseAnchor(@"[Link text](?condition-1&!condition-2)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.True(anchor.Href.Conditions["condition-1"]);
|
Assert.True(anchor.Href.Conditions["condition-1"]);
|
||||||
Assert.False(anchor.Href.Conditions["condition-2"]);
|
Assert.False(anchor.Href.Conditions["condition-2"]);
|
||||||
|
@ -55,15 +55,15 @@
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AnchorsWithTogglesMatch()
|
public void AnchorsWithTogglesMatch()
|
||||||
{
|
{
|
||||||
var anchor = Utilities.ParseAnchor(@"[Link text](#toggle-state)");
|
var anchor = Utilities.ParseAnchor(@"[Link text](#toggle-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("#toggle-state", anchor.Href.Original);
|
Assert.Equal("#toggle-state", anchor.Href.Original);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](#toggle-1+toggle-2)");
|
anchor = Utilities.ParseAnchor(@"[Link text](#toggle-1+toggle-2)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("#toggle-1+toggle-2", anchor.Href.Original);
|
Assert.Equal("#toggle-1+toggle-2", anchor.Href.Original);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](#toggle-1+toggle-2)");
|
anchor = Utilities.ParseAnchor(@"[Link text](#toggle-1+toggle-2)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("#toggle-1+toggle-2", anchor.Href.Original);
|
Assert.Equal("#toggle-1+toggle-2", anchor.Href.Original);
|
||||||
}
|
}
|
||||||
|
@ -71,11 +71,11 @@
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AnchorsWithTitlesMatch()
|
public void AnchorsWithTitlesMatch()
|
||||||
{
|
{
|
||||||
var anchor = Utilities.ParseAnchor(@"[Link text](""Title text"")");
|
var anchor = Utilities.ParseAnchor(@"[Link text](""Title text"")", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("Title text", anchor.Title);
|
Assert.Equal("Title text", anchor.Title);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Talking to Kid](""Lobby"")");
|
anchor = Utilities.ParseAnchor(@"[Talking to Kid](""Lobby"")", 0, 0);
|
||||||
Assert.Equal("Talking to Kid", anchor.Text);
|
Assert.Equal("Talking to Kid", anchor.Text);
|
||||||
Assert.Equal("Lobby", anchor.Title);
|
Assert.Equal("Lobby", anchor.Title);
|
||||||
}
|
}
|
||||||
|
@ -83,20 +83,20 @@
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ComplexAnchorsMatch()
|
public void ComplexAnchorsMatch()
|
||||||
{
|
{
|
||||||
var anchor = Utilities.ParseAnchor(@"[Link text](/target-scene?condition-state#toggle-state ""Title text"")");
|
var anchor = Utilities.ParseAnchor(@"[Link text](/target-scene?condition-state#toggle-state ""Title text"")", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("/target-scene?condition-state#toggle-state", anchor.Href.Original);
|
Assert.Equal("/target-scene?condition-state#toggle-state", anchor.Href.Original);
|
||||||
Assert.Equal("Title text", anchor.Title);
|
Assert.Equal("Title text", anchor.Title);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](/target-scene#toggle-state)");
|
anchor = Utilities.ParseAnchor(@"[Link text](/target-scene#toggle-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("/target-scene#toggle-state", anchor.Href.Original);
|
Assert.Equal("/target-scene#toggle-state", anchor.Href.Original);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](/target-scene?condition-state)");
|
anchor = Utilities.ParseAnchor(@"[Link text](/target-scene?condition-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("/target-scene?condition-state", anchor.Href.Original);
|
Assert.Equal("/target-scene?condition-state", anchor.Href.Original);
|
||||||
|
|
||||||
anchor = Utilities.ParseAnchor(@"[Link text](?condition-state#toggle-state)");
|
anchor = Utilities.ParseAnchor(@"[Link text](?condition-state#toggle-state)", 0, 0);
|
||||||
Assert.Equal("Link text", anchor.Text);
|
Assert.Equal("Link text", anchor.Text);
|
||||||
Assert.Equal("?condition-state#toggle-state", anchor.Href.Original);
|
Assert.Equal("?condition-state#toggle-state", anchor.Href.Original);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,23 @@ namespace Ficdown.Parser
|
||||||
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
||||||
var blocks = BlockHandler.ExtractBlocks(lines);
|
var blocks = BlockHandler.ExtractBlocks(lines);
|
||||||
var story = BlockHandler.ParseBlocks(blocks);
|
var story = BlockHandler.ParseBlocks(blocks);
|
||||||
|
|
||||||
|
// dupe scene sanity check
|
||||||
|
foreach(var key in story.Scenes.Keys)
|
||||||
|
{
|
||||||
|
foreach(var scene in story.Scenes[key])
|
||||||
|
{
|
||||||
|
foreach(var otherScene in story.Scenes[key].Where(s => s != scene))
|
||||||
|
{
|
||||||
|
if((scene.Conditions == null && otherScene.Conditions == null)
|
||||||
|
|| (scene.Conditions != null && otherScene.Conditions != null
|
||||||
|
&& scene.Conditions.Count == otherScene.Conditions.Count
|
||||||
|
&& !scene.Conditions.Except(otherScene.Conditions).Any()))
|
||||||
|
throw new FicdownException(scene.Name, string.Format("Scene defined again on line {0}", otherScene.LineNumber), scene.LineNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GameTraverser.Story = story;
|
GameTraverser.Story = story;
|
||||||
var resolved = StateResolver.Resolve(GameTraverser.Enumerate(), story);
|
var resolved = StateResolver.Resolve(GameTraverser.Enumerate(), story);
|
||||||
resolved.Orphans = GameTraverser.OrphanedScenes.Select(o => new Orphan
|
resolved.Orphans = GameTraverser.OrphanedScenes.Select(o => new Orphan
|
||||||
|
|
|
@ -6,5 +6,7 @@
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
public Href Href { get; set; }
|
public Href Href { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
public int LineNumber { get; set; }
|
||||||
|
public int ColNumber { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,24 +6,25 @@ namespace Ficdown.Parser.Model.Parser
|
||||||
{
|
{
|
||||||
public string BlockName { get; private set; }
|
public string BlockName { get; private set; }
|
||||||
public int? LineNumber { get; private set; }
|
public int? LineNumber { get; private set; }
|
||||||
|
public int? ColNumber { get; private set; }
|
||||||
|
|
||||||
public FicdownException(string blockName, int? lineNumber, string message) : base(message)
|
public FicdownException(string blockName, string message, int? lineNumber = null, int? colNumber = null) : base(message)
|
||||||
{
|
{
|
||||||
BlockName = blockName;
|
BlockName = blockName;
|
||||||
LineNumber = lineNumber;
|
LineNumber = lineNumber;
|
||||||
|
ColNumber = colNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FicdownException(string message) : base(message) { }
|
public FicdownException(string message) : base(message) { }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(BlockName)
|
return string.Format("Error L{0},{1}: {2}",
|
||||||
? string.Format("Error in block \"{0}\" (Line {1}): {2}",
|
LineNumber ?? 1,
|
||||||
BlockName,
|
ColNumber ?? 1,
|
||||||
LineNumber.HasValue
|
!string.IsNullOrEmpty(BlockName)
|
||||||
? LineNumber.ToString()
|
? string.Format("\"{0}\": {1}", BlockName, Message)
|
||||||
: "unknown", Message)
|
: Message);
|
||||||
: string.Format("Error: {0}", Message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Toggle { get; set; }
|
public string Toggle { get; set; }
|
||||||
|
public string RawDescription { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public int LineNumber { get; set; }
|
public int LineNumber { get; set; }
|
||||||
public bool Visited { get; set; }
|
public bool Visited { get; set; }
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
public string RawDescription { get; set; }
|
||||||
public IDictionary<string, bool> Conditions { get; set; }
|
public IDictionary<string, bool> Conditions { get; set; }
|
||||||
public int LineNumber { get; set; }
|
public int LineNumber { get; set; }
|
||||||
public bool Visited { get; set; }
|
public bool Visited { get; set; }
|
||||||
|
|
|
@ -51,16 +51,16 @@
|
||||||
Anchor storyAnchor;
|
Anchor storyAnchor;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
storyAnchor = Utilities.GetInstance(storyBlock.Name, storyBlock.LineNumber).ParseAnchor(storyBlock.Name);
|
storyAnchor = Utilities.GetInstance(storyBlock.Name, storyBlock.LineNumber).ParseAnchor(storyBlock.Name, storyBlock.LineNumber, 1);
|
||||||
}
|
}
|
||||||
catch(FicdownException ex)
|
catch(FicdownException ex)
|
||||||
{
|
{
|
||||||
throw new FicdownException(ex.BlockName, ex.LineNumber, "Story block must be an anchor pointing to the first scene");
|
throw new FicdownException(ex.BlockName, "Story name must be an anchor pointing to the first scene", ex.LineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storyAnchor.Href.Target == null || storyAnchor.Href.Conditions != null ||
|
if (storyAnchor.Href.Target == null || storyAnchor.Href.Conditions != null ||
|
||||||
storyAnchor.Href.Toggles != null)
|
storyAnchor.Href.Toggles != null)
|
||||||
throw new FicdownException(storyBlock.Name, storyBlock.LineNumber, "Story href should only have target");
|
throw new FicdownException(storyBlock.Name, "Story href should only have a target", storyBlock.LineNumber);
|
||||||
|
|
||||||
var story = new Story
|
var story = new Story
|
||||||
{
|
{
|
||||||
|
@ -88,11 +88,11 @@
|
||||||
var a = blocks.First(b => b.Type == BlockType.Action && blocks.Any(d => b != d && BlockToAction(b, 0).Toggle == BlockToAction(d, 0).Toggle));
|
var a = blocks.First(b => b.Type == BlockType.Action && blocks.Any(d => b != d && BlockToAction(b, 0).Toggle == BlockToAction(d, 0).Toggle));
|
||||||
var actionA = BlockToAction(a, a.LineNumber);
|
var actionA = BlockToAction(a, a.LineNumber);
|
||||||
var dupe = blocks.First(b => b.Type == BlockType.Action && b != a && BlockToAction(b, 0).Toggle == actionA.Toggle);
|
var dupe = blocks.First(b => b.Type == BlockType.Action && b != a && BlockToAction(b, 0).Toggle == actionA.Toggle);
|
||||||
throw new FicdownException(actionA.Toggle, actionA.LineNumber, string.Format("Action is defined again on line {0}", dupe.LineNumber));
|
throw new FicdownException(actionA.Toggle, string.Format("Action is defined again on line {0}", dupe.LineNumber), actionA.LineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!story.Scenes.ContainsKey(storyAnchor.Href.Target))
|
if (!story.Scenes.ContainsKey(storyAnchor.Href.Target))
|
||||||
throw new FicdownException(storyBlock.Name, storyBlock.LineNumber, string.Format("Story targets non-existent scene: {0}", storyAnchor.Href.Target));
|
throw new FicdownException(storyBlock.Name, string.Format("Story links to undefined scene: {0}", storyAnchor.Href.Target), storyBlock.LineNumber);
|
||||||
story.FirstScene = storyAnchor.Href.Target;
|
story.FirstScene = storyAnchor.Href.Target;
|
||||||
|
|
||||||
return story;
|
return story;
|
||||||
|
@ -105,19 +105,20 @@
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
LineNumber = block.LineNumber,
|
LineNumber = block.LineNumber,
|
||||||
|
RawDescription = string.Join("\n", block.Lines.Select(l => l.Text)),
|
||||||
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim()
|
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
if(RegexLib.Anchors.IsMatch(block.Name))
|
||||||
{
|
{
|
||||||
var sceneName = Utilities.GetInstance(block.Name, block.LineNumber).ParseAnchor(block.Name);
|
var sceneName = Utilities.GetInstance(block.Name, block.LineNumber).ParseAnchor(block.Name, block.LineNumber, 1);
|
||||||
scene.Name = sceneName.Title != null ? sceneName.Title.Trim() : sceneName.Text.Trim();
|
scene.Name = sceneName.Title != null ? sceneName.Title.Trim() : sceneName.Text.Trim();
|
||||||
scene.Key = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(sceneName.Text);
|
scene.Key = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(sceneName.Text);
|
||||||
if(sceneName.Href.Target != null || sceneName.Href.Toggles != null)
|
if(sceneName.Href.Target != null || sceneName.Href.Toggles != null)
|
||||||
throw new FicdownException(block.Name, block.LineNumber, string.Format("Scene href should only have conditions: {0}", block.Name));
|
throw new FicdownException(block.Name, "Scene href should only have conditions", block.LineNumber);
|
||||||
scene.Conditions = sceneName.Href.Conditions;
|
scene.Conditions = sceneName.Href.Conditions;
|
||||||
}
|
}
|
||||||
catch(FicdownException)
|
else
|
||||||
{
|
{
|
||||||
scene.Name = block.Name.Trim();
|
scene.Name = block.Name.Trim();
|
||||||
scene.Key = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name);
|
scene.Key = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name);
|
||||||
|
@ -132,6 +133,7 @@
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Toggle = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name),
|
Toggle = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name),
|
||||||
|
RawDescription = string.Join("\n", block.Lines.Select(l => l.Text)),
|
||||||
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim(),
|
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim(),
|
||||||
LineNumber = block.LineNumber
|
LineNumber = block.LineNumber
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
namespace Ficdown.Parser.Parser
|
namespace Ficdown.Parser.Parser
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
if (anchor.Href.Conditions != null)
|
if (anchor.Href.Conditions != null)
|
||||||
{
|
{
|
||||||
var satisfied = Utilities.GetInstance(blockName, lineNumber).ConditionsMet(playerState, anchor.Href.Conditions);
|
var satisfied = Utilities.GetInstance(blockName, lineNumber).ConditionsMet(playerState, anchor.Href.Conditions);
|
||||||
var alts = Utilities.GetInstance(blockName, lineNumber).ParseConditionalText(text);
|
var alts = Utilities.GetInstance(blockName, lineNumber).ParseConditionalText(anchor);
|
||||||
var replace = alts[satisfied];
|
var replace = alts[satisfied];
|
||||||
text = RegexLib.EscapeChar.Replace(replace, string.Empty);
|
text = RegexLib.EscapeChar.Replace(replace, string.Empty);
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
if (page.State.ActionsToShow[i])
|
if (page.State.ActionsToShow[i])
|
||||||
{
|
{
|
||||||
var actionTuple = _story.Actions.Single(a => a.Value.Id == i + 1);
|
var actionTuple = _story.Actions.Single(a => a.Value.Id == i + 1);
|
||||||
var actionAnchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(actionTuple.Value.Description);
|
var actionAnchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(actionTuple.Value.RawDescription);
|
||||||
var anchorDict = GetStateDictionary(page);
|
var anchorDict = GetStateDictionary(page);
|
||||||
if (
|
if (
|
||||||
actionAnchors.Any(
|
actionAnchors.Any(
|
||||||
|
@ -86,7 +86,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var anchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(page.Scene.Description);
|
var anchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(page.Scene.RawDescription);
|
||||||
var stateDict = GetStateDictionary(page);
|
var stateDict = GetStateDictionary(page);
|
||||||
var text =
|
var text =
|
||||||
RegexLib.EmptyListItem.Replace(
|
RegexLib.EmptyListItem.Replace(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
namespace Ficdown.Parser.Parser
|
||||||
namespace Ficdown.Parser.Parser
|
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -34,7 +33,7 @@ namespace Ficdown.Parser.Parser
|
||||||
return Regex.Replace(Regex.Replace(raw.ToLower(), @"^\W+|\W+$", string.Empty), @"\W+", "-");
|
return Regex.Replace(Regex.Replace(raw.ToLower(), @"^\W+|\W+$", string.Empty), @"\W+", "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Href ParseHref(string href)
|
private Href ParseHref(string href, int lineNumber, int colNumber)
|
||||||
{
|
{
|
||||||
var match = RegexLib.Href.Match(href);
|
var match = RegexLib.Href.Match(href);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
|
@ -57,27 +56,50 @@ namespace Ficdown.Parser.Parser
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new FicdownException(_blockName, _lineNumber, string.Format("Invalid href: {0}", href));
|
throw new FicdownException(_blockName, string.Format("Invalid href: {0}", href), lineNumber, colNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Anchor ParseAnchor(string anchorText)
|
public Anchor ParseAnchor(string anchorText, int lineNumber, int colNumber)
|
||||||
{
|
{
|
||||||
var match = RegexLib.Anchors.Match(anchorText);
|
var match = RegexLib.Anchors.Match(anchorText);
|
||||||
if (!match.Success) throw new FicdownException(_blockName, _lineNumber, string.Format("Invalid anchor: {0}", anchorText));
|
if (!match.Success) throw new FicdownException(_blockName, string.Format("Invalid anchor: {0}", anchorText), lineNumber, colNumber);
|
||||||
return MatchToAnchor(match);
|
return MatchToAnchor(match, lineNumber, colNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PosFromIndex(string text, int index, out int line, out int col)
|
||||||
|
{
|
||||||
|
line = 1;
|
||||||
|
col = 1;
|
||||||
|
for (int i = 0; i <= index - 1; i++)
|
||||||
|
{
|
||||||
|
col++;
|
||||||
|
if (text[i] == '\n')
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
col = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<Anchor> ParseAnchors(string text)
|
public IList<Anchor> ParseAnchors(string text)
|
||||||
{
|
{
|
||||||
var matches = RegexLib.Anchors.Matches(text);
|
var matches = RegexLib.Anchors.Matches(text);
|
||||||
return matches.Cast<Match>().Select(MatchToAnchor).ToList();
|
return matches.Cast<Match>().Select(m =>
|
||||||
|
{
|
||||||
|
int line, col;
|
||||||
|
PosFromIndex(text, m.Index, out line, out col);
|
||||||
|
if(_lineNumber.HasValue) line += _lineNumber.Value;
|
||||||
|
return MatchToAnchor(m, line, col);
|
||||||
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Anchor MatchToAnchor(Match match)
|
private Anchor MatchToAnchor(Match match, int lineNumber, int colNumber)
|
||||||
{
|
{
|
||||||
var astr = match.Groups["anchor"].Value;
|
var astr = match.Groups["anchor"].Value;
|
||||||
var txstr = match.Groups["text"].Value;
|
var txstr = match.Groups["text"].Value;
|
||||||
var ttstr = match.Groups["title"].Value;
|
var ttstr = match.Groups["title"].Success
|
||||||
|
? match.Groups["title"].Value
|
||||||
|
: null;
|
||||||
var hrefstr = match.Groups["href"].Value;
|
var hrefstr = match.Groups["href"].Value;
|
||||||
if (hrefstr.StartsWith(@""""))
|
if (hrefstr.StartsWith(@""""))
|
||||||
{
|
{
|
||||||
|
@ -89,14 +111,18 @@ namespace Ficdown.Parser.Parser
|
||||||
Original = !string.IsNullOrEmpty(astr) ? astr : null,
|
Original = !string.IsNullOrEmpty(astr) ? astr : null,
|
||||||
Text = !string.IsNullOrEmpty(txstr) ? txstr : null,
|
Text = !string.IsNullOrEmpty(txstr) ? txstr : null,
|
||||||
Title = ttstr,
|
Title = ttstr,
|
||||||
Href = ParseHref(hrefstr)
|
Href = ParseHref(hrefstr, lineNumber, colNumber),
|
||||||
|
LineNumber = lineNumber,
|
||||||
|
ColNumber = colNumber
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary<bool, string> ParseConditionalText(string text)
|
public IDictionary<bool, string> ParseConditionalText(Anchor anchor)
|
||||||
{
|
{
|
||||||
var match = RegexLib.ConditionalText.Match(text);
|
var match = RegexLib.ConditionalText.Match(anchor.Text);
|
||||||
if (!match.Success) throw new FicdownException(_blockName, _lineNumber, string.Format(@"Invalid conditional text: {0}", text));
|
if (!match.Success)
|
||||||
|
throw new FicdownException(_blockName, string.Format(@"Invalid conditional text: {0}", anchor.Text), anchor.LineNumber, anchor.ColNumber);
|
||||||
|
|
||||||
return new Dictionary<bool, string>
|
return new Dictionary<bool, string>
|
||||||
{
|
{
|
||||||
{true, match.Groups["true"].Value},
|
{true, match.Groups["true"].Value},
|
||||||
|
|
|
@ -125,11 +125,11 @@
|
||||||
|
|
||||||
var states = new HashSet<string>();
|
var states = new HashSet<string>();
|
||||||
|
|
||||||
var anchors = Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseAnchors(currentState.Page.Scene.Description).ToList();
|
var anchors = Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseAnchors(currentState.Page.Scene.RawDescription).ToList();
|
||||||
foreach (var action in GetActionsForPage(currentState.Page))
|
foreach (var action in GetActionsForPage(currentState.Page))
|
||||||
{
|
{
|
||||||
action.Visited = true;
|
action.Visited = true;
|
||||||
anchors.AddRange(Utilities.GetInstance(action.Toggle, action.LineNumber).ParseAnchors(action.Description));
|
anchors.AddRange(Utilities.GetInstance(action.Toggle, action.LineNumber).ParseAnchors(action.RawDescription));
|
||||||
}
|
}
|
||||||
var conditionals =
|
var conditionals =
|
||||||
anchors.SelectMany(
|
anchors.SelectMany(
|
||||||
|
@ -143,8 +143,19 @@
|
||||||
// signal to previous scenes that this scene's used conditionals are important
|
// signal to previous scenes that this scene's used conditionals are important
|
||||||
if(currentState.Page.Scene.Conditions != null)
|
if(currentState.Page.Scene.Conditions != null)
|
||||||
foreach (var conditional in currentState.Page.Scene.Conditions)
|
foreach (var conditional in currentState.Page.Scene.Conditions)
|
||||||
_manager.ToggleStateOn(affected, conditional.Key);
|
{
|
||||||
foreach (var conditional in conditionals) _manager.ToggleStateOn(affected, conditional);
|
var anchor = anchors.FirstOrDefault(a =>
|
||||||
|
a.Href.Conditions != null
|
||||||
|
&& a.Href.Conditions.Keys.Contains(conditional.Key));
|
||||||
|
_manager.ToggleStateOn(affected, conditional.Key, currentState.Page.Scene.Name, anchor);
|
||||||
|
}
|
||||||
|
foreach (var conditional in conditionals)
|
||||||
|
{
|
||||||
|
var anchor = anchors.FirstOrDefault(a =>
|
||||||
|
a.Href.Conditions != null
|
||||||
|
&& a.Href.Conditions.Keys.Contains(conditional));
|
||||||
|
_manager.ToggleStateOn(affected, conditional, currentState.Page.Scene.Name, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
// signal to previous scenes if this scene has first-seen text
|
// signal to previous scenes if this scene has first-seen text
|
||||||
if (hasFirstSeen) _manager.ToggleSeenSceneOn(affected, currentState.Page.Scene.Id);
|
if (hasFirstSeen) _manager.ToggleSeenSceneOn(affected, currentState.Page.Scene.Id);
|
||||||
|
@ -155,7 +166,7 @@
|
||||||
// don't follow links that would be hidden
|
// don't follow links that would be hidden
|
||||||
if (anchor.Href.Conditions != null &&
|
if (anchor.Href.Conditions != null &&
|
||||||
string.IsNullOrEmpty(
|
string.IsNullOrEmpty(
|
||||||
Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseConditionalText(anchor.Text)[
|
Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseConditionalText(anchor)[
|
||||||
Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ConditionsMet(StateResolver.GetStateDictionary(currentState.Page),
|
Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ConditionsMet(StateResolver.GetStateDictionary(currentState.Page),
|
||||||
anchor.Href.Conditions)])) continue;
|
anchor.Href.Conditions)])) continue;
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
var toggle in
|
var toggle in
|
||||||
allScenes.SelectMany(
|
allScenes.SelectMany(
|
||||||
sc =>
|
sc =>
|
||||||
Utilities.GetInstance(sc.Name, sc.LineNumber).ParseAnchors(sc.Description)
|
Utilities.GetInstance(sc.Name, sc.LineNumber).ParseAnchors(sc.RawDescription)
|
||||||
.SelectMany(
|
.SelectMany(
|
||||||
a =>
|
a =>
|
||||||
a.Href.Toggles != null
|
a.Href.Toggles != null
|
||||||
|
@ -43,6 +43,12 @@
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
var scene = _story.Scenes[_story.FirstScene].Where(s => s.Conditions == null);
|
||||||
|
if(scene == null)
|
||||||
|
throw new FicdownException(_story.Name, string.Format("Story links to undefined scene: {0}", _story.FirstScene));
|
||||||
|
if(scene.Count() > 1)
|
||||||
|
throw new FicdownException(_story.Name, string.Format("Story links to scene that is defined more than once: {0}", _story.FirstScene));
|
||||||
|
|
||||||
return new PageState
|
return new PageState
|
||||||
{
|
{
|
||||||
Id = Guid.Empty,
|
Id = Guid.Empty,
|
||||||
|
@ -61,7 +67,7 @@
|
||||||
ActionsToShow = new BitArray(_actionCount),
|
ActionsToShow = new BitArray(_actionCount),
|
||||||
ActionFirstToggles = null
|
ActionFirstToggles = null
|
||||||
},
|
},
|
||||||
Scene = _story.Scenes[_story.FirstScene].Single(s => s.Conditions == null),
|
Scene = scene.Single(),
|
||||||
StateMatrix = _stateMatrix
|
StateMatrix = _stateMatrix
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -83,7 +89,7 @@
|
||||||
if(actionFirstToggles == null) actionFirstToggles = new List<bool>();
|
if(actionFirstToggles == null) actionFirstToggles = new List<bool>();
|
||||||
newState.State.ActionsToShow[_story.Actions[toggle].Id - 1] = true;
|
newState.State.ActionsToShow[_story.Actions[toggle].Id - 1] = true;
|
||||||
if (
|
if (
|
||||||
Utilities.GetInstance(_story.Actions[toggle].Toggle, _story.Actions[toggle].LineNumber).ParseAnchors(_story.Actions[toggle].Description)
|
Utilities.GetInstance(_story.Actions[toggle].Toggle, _story.Actions[toggle].LineNumber).ParseAnchors(_story.Actions[toggle].RawDescription)
|
||||||
.Any(a => a.Href.Conditions != null && a.Href.Conditions.ContainsKey(toggle)))
|
.Any(a => a.Href.Conditions != null && a.Href.Conditions.ContainsKey(toggle)))
|
||||||
actionFirstToggles.Add(!current.State.PlayerState[_stateMatrix[toggle]]);
|
actionFirstToggles.Add(!current.State.PlayerState[_stateMatrix[toggle]]);
|
||||||
}
|
}
|
||||||
|
@ -93,12 +99,14 @@
|
||||||
newState.State.ActionFirstToggles = actionFirstToggles != null
|
newState.State.ActionFirstToggles = actionFirstToggles != null
|
||||||
? new BitArray(actionFirstToggles.ToArray())
|
? new BitArray(actionFirstToggles.ToArray())
|
||||||
: null;
|
: null;
|
||||||
newState.Scene = GetScene(current.Scene.Name, current.Scene.LineNumber, target, newState.State.PlayerState);
|
newState.Scene = GetScene(current.Scene.Name, anchor, target, newState.State.PlayerState);
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleStateOn(State state, string toggle)
|
public void ToggleStateOn(State state, string toggle, string blockName, Anchor anchor)
|
||||||
{
|
{
|
||||||
|
if(!_stateMatrix.ContainsKey(toggle))
|
||||||
|
throw new FicdownException(blockName, string.Format("Conditional for undefined state: {0}", toggle), anchor != null ? anchor.LineNumber : 1, anchor != null ? anchor.ColNumber : 1);
|
||||||
state.PlayerState[_stateMatrix[toggle]] = true;
|
state.PlayerState[_stateMatrix[toggle]] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,10 +146,10 @@
|
||||||
return GetUniqueHash(compressed, page.Scene.Key);
|
return GetUniqueHash(compressed, page.Scene.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Scene GetScene(string blockName, int lineNumber, string target, BitArray playerState)
|
private Scene GetScene(string blockName, Anchor anchor, string target, BitArray playerState)
|
||||||
{
|
{
|
||||||
if (!_story.Scenes.ContainsKey(target))
|
if (!_story.Scenes.ContainsKey(target))
|
||||||
throw new FicdownException(blockName, lineNumber, string.Format("Encountered link to non-existent scene: {0}", target));
|
throw new FicdownException(blockName, string.Format("Link to undefined scene: {0}", target), anchor.LineNumber, anchor.ColNumber);
|
||||||
|
|
||||||
Scene newScene = null;
|
Scene newScene = null;
|
||||||
foreach (var scene in _story.Scenes[target])
|
foreach (var scene in _story.Scenes[target])
|
||||||
|
@ -154,7 +162,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newScene == null)
|
if (newScene == null)
|
||||||
throw new FicdownException(blockName, lineNumber, string.Format("Scene {0} reached with unmatched player state", target));
|
throw new FicdownException(blockName, string.Format("Link to scene that is undefined for conditionals: {0}", target), anchor.LineNumber, anchor.ColNumber);
|
||||||
return newScene;
|
return newScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +171,7 @@
|
||||||
if (scene.Conditions == null) return true;
|
if (scene.Conditions == null) return true;
|
||||||
scene.Conditions.ToList().ForEach(c =>
|
scene.Conditions.ToList().ForEach(c =>
|
||||||
{
|
{
|
||||||
if(!_stateMatrix.ContainsKey(c.Key)) throw new FicdownException(scene.Name, scene.LineNumber, string.Format("Reference to non-existent state: {0}", c.Key));
|
if(!_stateMatrix.ContainsKey(c.Key)) throw new FicdownException(scene.Name, string.Format("Conditional for undefined state: {0}", c.Key), scene.LineNumber);
|
||||||
});
|
});
|
||||||
return scene.Conditions.All(c => playerState[_stateMatrix[c.Key]] == c.Value);
|
return scene.Conditions.All(c => playerState[_stateMatrix[c.Key]] == c.Value);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue