Lint mode #8
|
@ -149,12 +149,10 @@
|
||||||
|
|
||||||
var story = parser.ParseStory(storyText);
|
var story = parser.ParseStory(storyText);
|
||||||
|
|
||||||
story.Orphans.ToList().ForEach(o =>
|
parser.Warnings.Select(w => w.ToString()).Distinct().ToList().ForEach(s => Console.WriteLine(s));
|
||||||
{
|
story.Orphans.ToList().ForEach(o => Console.WriteLine("Warning L{0},1: \"{1}\": Unreachable {2}", o.LineNumber, o.Name, o.Type));
|
||||||
Console.WriteLine("Warning L{0},1: \"{1}\": Unreachable {2}", o.LineNumber, o.Name, o.Type);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!lintMode)
|
if(!lintMode && parser.Warnings.Count() == 0)
|
||||||
{
|
{
|
||||||
IRenderer rend;
|
IRenderer rend;
|
||||||
switch (format)
|
switch (format)
|
||||||
|
|
|
@ -4,13 +4,19 @@
|
||||||
using Parser;
|
using Parser;
|
||||||
using Model.Parser;
|
using Model.Parser;
|
||||||
using Extensions;
|
using Extensions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public class BlockHandlerTests
|
public class BlockHandlerTests
|
||||||
{
|
{
|
||||||
|
private BlockHandler NewBlockHandler
|
||||||
|
{
|
||||||
|
get { return new BlockHandler { Warnings = new List<FicdownException>() }; }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NoStoryBlockThrowsException()
|
public void NoStoryBlockThrowsException()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
## this file has no story
|
## this file has no story
|
||||||
just a lonely scene".ToLines())));
|
just a lonely scene".ToLines())));
|
||||||
|
@ -19,7 +25,7 @@ just a lonely scene".ToLines())));
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoryWithNoAnchorThrowsException()
|
public void StoryWithNoAnchorThrowsException()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# my story
|
# my story
|
||||||
doesn't link to a scene
|
doesn't link to a scene
|
||||||
|
@ -30,17 +36,23 @@ nothing links here".ToLines())));
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoriesWithFancyAnchorsThrowExceptions()
|
public void StoriesWithFancyAnchorsThrowExceptions()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [my story](/a-scene?conditional)
|
# [my story](/a-scene?conditional)
|
||||||
story with a conditional
|
story with a conditional
|
||||||
## a scene
|
## a scene
|
||||||
this is a scene".ToLines())));
|
this is a scene".ToLines()));
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
Assert.NotEmpty(bh.Warnings);
|
||||||
|
|
||||||
|
bh = NewBlockHandler;
|
||||||
|
bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [my story](/a-scene#toggle)
|
# [my story](/a-scene#toggle)
|
||||||
story with a toggle
|
story with a toggle
|
||||||
## a scene
|
## a scene
|
||||||
this is a scene".ToLines())));
|
this is a scene".ToLines()));
|
||||||
|
Assert.NotEmpty(bh.Warnings);
|
||||||
|
|
||||||
|
bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [my story](/a-scene#?conditional#toggle)
|
# [my story](/a-scene#?conditional#toggle)
|
||||||
story with a conditional and a toggle
|
story with a conditional and a toggle
|
||||||
|
@ -51,7 +63,7 @@ this is a scene".ToLines())));
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoryLinkingToNonExistentSceneThrowsException()
|
public void StoryLinkingToNonExistentSceneThrowsException()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [a story](/non-existent)
|
# [a story](/non-existent)
|
||||||
this story links to a first scene that doesn't exist
|
this story links to a first scene that doesn't exist
|
||||||
|
@ -62,7 +74,7 @@ this scene is so cold and lonely".ToLines())));
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoryWithALegitAnchorParses()
|
public void StoryWithALegitAnchorParses()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
bh.ParseBlocks(bh.ExtractBlocks(@"
|
bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [my story](/a-scene)
|
# [my story](/a-scene)
|
||||||
story with a simple link
|
story with a simple link
|
||||||
|
@ -73,8 +85,8 @@ this is a scene".ToLines()));
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoryWithDuplicateActionsThrowsException()
|
public void StoryWithDuplicateActionsThrowsException()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
Assert.Throws<FicdownException>(() => bh.ParseBlocks(bh.ExtractBlocks(@"
|
bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [a story](/a-scene)
|
# [a story](/a-scene)
|
||||||
this story is action-happy
|
this story is action-happy
|
||||||
## a scene
|
## a scene
|
||||||
|
@ -84,13 +96,14 @@ this is an action
|
||||||
## another scene
|
## another scene
|
||||||
this is another scene
|
this is another scene
|
||||||
### an action
|
### an action
|
||||||
oops, this is the same action!".ToLines())));
|
oops, this is the same action!".ToLines()));
|
||||||
|
Assert.NotEmpty(bh.Warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StoryWithScenesAndActionsParses()
|
public void StoryWithScenesAndActionsParses()
|
||||||
{
|
{
|
||||||
var bh = new BlockHandler();
|
var bh = NewBlockHandler;
|
||||||
var story = bh.ParseBlocks(bh.ExtractBlocks(@"
|
var story = bh.ParseBlocks(bh.ExtractBlocks(@"
|
||||||
# [my story](/a-scene)
|
# [my story](/a-scene)
|
||||||
story with a simple link
|
story with a simple link
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
namespace Ficdown.Parser.Tests
|
namespace Ficdown.Parser.Tests
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Model.Parser;
|
||||||
using Parser;
|
using Parser;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
public class UtilityTests
|
public class UtilityTests
|
||||||
{
|
{
|
||||||
|
private List<FicdownException> Warnings = new List<FicdownException>();
|
||||||
|
|
||||||
private Utilities Utilities
|
private Utilities Utilities
|
||||||
{
|
{
|
||||||
get { return Utilities.GetInstance("none", 0); }
|
get { return Utilities.GetInstance(Warnings, "none", 0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
namespace Ficdown.Parser
|
namespace Ficdown.Parser
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Model.Parser;
|
using Model.Parser;
|
||||||
using Parser;
|
using Parser;
|
||||||
|
@ -11,25 +12,53 @@ namespace Ficdown.Parser
|
||||||
|
|
||||||
public class FicdownParser
|
public class FicdownParser
|
||||||
{
|
{
|
||||||
|
public List<FicdownException> Warnings { get; private set; }
|
||||||
|
|
||||||
private IBlockHandler _blockHandler;
|
private IBlockHandler _blockHandler;
|
||||||
internal IBlockHandler BlockHandler
|
internal IBlockHandler BlockHandler
|
||||||
{
|
{
|
||||||
get { return _blockHandler ?? (_blockHandler = new BlockHandler()); }
|
get
|
||||||
set { _blockHandler = value; }
|
{
|
||||||
|
return _blockHandler ??
|
||||||
|
(_blockHandler = new BlockHandler { Warnings = Warnings });
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_blockHandler = value;
|
||||||
|
_blockHandler.Warnings = Warnings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IGameTraverser _gameTraverser;
|
private IGameTraverser _gameTraverser;
|
||||||
internal IGameTraverser GameTraverser
|
internal IGameTraverser GameTraverser
|
||||||
{
|
{
|
||||||
get { return _gameTraverser ?? (_gameTraverser = new GameTraverser()); }
|
get { return _gameTraverser ??
|
||||||
set { _gameTraverser = value; }
|
(_gameTraverser = new GameTraverser { Warnings = Warnings }); }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_gameTraverser = value;
|
||||||
|
_gameTraverser.Warnings = Warnings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IStateResolver _stateResolver;
|
private IStateResolver _stateResolver;
|
||||||
internal IStateResolver StateResolver
|
internal IStateResolver StateResolver
|
||||||
{
|
{
|
||||||
get { return _stateResolver ?? (_stateResolver = new StateResolver()); }
|
get
|
||||||
set { _stateResolver = value; }
|
{
|
||||||
|
return _stateResolver ??
|
||||||
|
(_stateResolver = new StateResolver { Warnings = Warnings });
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_stateResolver = value;
|
||||||
|
_stateResolver.Warnings = Warnings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FicdownParser()
|
||||||
|
{
|
||||||
|
Warnings = new List<FicdownException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResolvedStory ParseStory(string storyText)
|
public ResolvedStory ParseStory(string storyText)
|
||||||
|
@ -49,7 +78,7 @@ namespace Ficdown.Parser
|
||||||
|| (scene.Conditions != null && otherScene.Conditions != null
|
|| (scene.Conditions != null && otherScene.Conditions != null
|
||||||
&& scene.Conditions.Count == otherScene.Conditions.Count
|
&& scene.Conditions.Count == otherScene.Conditions.Count
|
||||||
&& !scene.Conditions.Except(otherScene.Conditions).Any()))
|
&& !scene.Conditions.Except(otherScene.Conditions).Any()))
|
||||||
throw new FicdownException(scene.Name, string.Format("Scene defined again on line {0}", otherScene.LineNumber), scene.LineNumber);
|
Warnings.Add(new FicdownException(scene.Name, string.Format("Scene defined again on line {0}", otherScene.LineNumber), scene.LineNumber));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
internal class BlockHandler : IBlockHandler
|
internal class BlockHandler : IBlockHandler
|
||||||
{
|
{
|
||||||
|
public List<FicdownException> Warnings { get; set; }
|
||||||
|
|
||||||
public IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines)
|
public IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines)
|
||||||
{
|
{
|
||||||
var blocks = new List<Block>();
|
var blocks = new List<Block>();
|
||||||
|
@ -51,19 +53,16 @@
|
||||||
|
|
||||||
var storyBlock = storyBlocks.Single();
|
var storyBlock = storyBlocks.Single();
|
||||||
|
|
||||||
Anchor storyAnchor;
|
var storyAnchor = Utilities.GetInstance(Warnings, storyBlock.Name, storyBlock.LineNumber).ParseAnchor(storyBlock.Name, storyBlock.LineNumber, 1);
|
||||||
try
|
|
||||||
|
if(storyAnchor == null || storyAnchor.Href == null)
|
||||||
{
|
{
|
||||||
storyAnchor = Utilities.GetInstance(storyBlock.Name, storyBlock.LineNumber).ParseAnchor(storyBlock.Name, storyBlock.LineNumber, 1);
|
throw new FicdownException(storyBlock.Name, "Story name must be an anchor pointing to the first scene", storyBlock.LineNumber);
|
||||||
}
|
|
||||||
catch(FicdownException ex)
|
|
||||||
{
|
|
||||||
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, "Story href should only have a target", storyBlock.LineNumber);
|
Warnings.Add(new FicdownException(storyBlock.Name, "Story href should only have a target", storyBlock.LineNumber));
|
||||||
|
|
||||||
var story = new Story
|
var story = new Story
|
||||||
{
|
{
|
||||||
|
@ -91,7 +90,7 @@
|
||||||
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, string.Format("Action is defined again on line {0}", dupe.LineNumber), actionA.LineNumber);
|
Warnings.Add(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))
|
||||||
|
@ -112,19 +111,19 @@
|
||||||
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim()
|
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
if(RegexLib.Anchors.IsMatch(block.Name))
|
Anchor sceneName;
|
||||||
|
if(RegexLib.Anchors.IsMatch(block.Name) && (sceneName = Utilities.GetInstance(Warnings, block.Name, block.LineNumber).ParseAnchor(block.Name, block.LineNumber, 1)).Href != null)
|
||||||
{
|
{
|
||||||
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(Warnings, 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, "Scene href should only have conditions", block.LineNumber);
|
Warnings.Add(new FicdownException(block.Name, "Scene href should only have conditions", block.LineNumber));
|
||||||
scene.Conditions = sceneName.Href.Conditions;
|
scene.Conditions = sceneName.Href.Conditions;
|
||||||
}
|
}
|
||||||
else
|
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(Warnings, block.Name, block.LineNumber).NormalizeString(block.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return scene;
|
return scene;
|
||||||
|
@ -135,7 +134,7 @@
|
||||||
return new Action
|
return new Action
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Toggle = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name),
|
Toggle = Utilities.GetInstance(Warnings, block.Name, block.LineNumber).NormalizeString(block.Name),
|
||||||
RawDescription = string.Join("\n", block.Lines.Select(l => l.Text)),
|
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
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
internal interface IBlockHandler
|
internal interface IBlockHandler
|
||||||
{
|
{
|
||||||
|
List<FicdownException> Warnings { set; }
|
||||||
IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines);
|
IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines);
|
||||||
Story ParseBlocks(IEnumerable<Block> blocks);
|
Story ParseBlocks(IEnumerable<Block> blocks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
internal interface IStateResolver
|
internal interface IStateResolver
|
||||||
{
|
{
|
||||||
|
List<FicdownException> Warnings { set; }
|
||||||
ResolvedStory Resolve(IEnumerable<PageState> pages, Story story);
|
ResolvedStory Resolve(IEnumerable<PageState> pages, Story story);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
private readonly HashSet<string> _usedNames;
|
private readonly HashSet<string> _usedNames;
|
||||||
private Story _story;
|
private Story _story;
|
||||||
|
|
||||||
|
public List<FicdownException> Warnings { private get; set; }
|
||||||
|
|
||||||
public StateResolver()
|
public StateResolver()
|
||||||
{
|
{
|
||||||
_pageNames = new Dictionary<string, string>();
|
_pageNames = new Dictionary<string, string>();
|
||||||
|
@ -43,12 +45,15 @@
|
||||||
private string ResolveAnchor(string blockName, int lineNumber, Anchor anchor, IDictionary<string, bool> playerState, string targetHash)
|
private string ResolveAnchor(string blockName, int lineNumber, Anchor anchor, IDictionary<string, bool> playerState, string targetHash)
|
||||||
{
|
{
|
||||||
var text = anchor.Text;
|
var text = anchor.Text;
|
||||||
if (anchor.Href.Conditions != null)
|
if (anchor.Href != null && anchor.Href.Conditions != null)
|
||||||
{
|
{
|
||||||
var satisfied = Utilities.GetInstance(blockName, lineNumber).ConditionsMet(playerState, anchor.Href.Conditions);
|
var satisfied = Utilities.GetInstance(Warnings, blockName, lineNumber).ConditionsMet(playerState, anchor.Href.Conditions);
|
||||||
var alts = Utilities.GetInstance(blockName, lineNumber).ParseConditionalText(anchor);
|
var alts = Utilities.GetInstance(Warnings, blockName, lineNumber).ParseConditionalText(anchor);
|
||||||
var replace = alts[satisfied];
|
if(alts != null)
|
||||||
text = RegexLib.EscapeChar.Replace(replace, string.Empty);
|
{
|
||||||
|
var replace = alts[satisfied];
|
||||||
|
text = RegexLib.EscapeChar.Replace(replace, string.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(targetHash)
|
return !string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(targetHash)
|
||||||
? string.Format("[{0}](/{1})", text, GetPageNameForHash(targetHash))
|
? string.Format("[{0}](/{1})", text, GetPageNameForHash(targetHash))
|
||||||
|
@ -67,7 +72,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.RawDescription);
|
var actionAnchors = Utilities.GetInstance(Warnings, 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 +91,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var anchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(page.Scene.RawDescription);
|
var anchors = Utilities.GetInstance(Warnings, 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,6 +1,5 @@
|
||||||
namespace Ficdown.Parser.Parser
|
namespace Ficdown.Parser.Parser
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -8,19 +7,23 @@
|
||||||
|
|
||||||
internal class Utilities
|
internal class Utilities
|
||||||
{
|
{
|
||||||
public static Utilities GetInstance(string blockName, int lineNumber)
|
private List<FicdownException> _warnings { get; set; }
|
||||||
|
|
||||||
|
public static Utilities GetInstance(List<FicdownException> warnings, string blockName, int lineNumber)
|
||||||
{
|
{
|
||||||
return new Utilities
|
return new Utilities
|
||||||
{
|
{
|
||||||
|
_warnings = warnings,
|
||||||
_blockName = blockName,
|
_blockName = blockName,
|
||||||
_lineNumber = lineNumber
|
_lineNumber = lineNumber
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Utilities GetInstance(string blockName)
|
public static Utilities GetInstance(List<FicdownException> warnings, string blockName)
|
||||||
{
|
{
|
||||||
return new Utilities
|
return new Utilities
|
||||||
{
|
{
|
||||||
|
_warnings = warnings,
|
||||||
_blockName = blockName
|
_blockName = blockName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -56,13 +59,18 @@
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new FicdownException(_blockName, string.Format("Invalid href: {0}", href), lineNumber, colNumber);
|
_warnings.Add(new FicdownException(_blockName, string.Format("Invalid href: {0}", href), lineNumber, colNumber));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Anchor ParseAnchor(string anchorText, int lineNumber, int colNumber)
|
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, string.Format("Invalid anchor: {0}", anchorText), lineNumber, colNumber);
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
_warnings.Add(new FicdownException(_blockName, string.Format("Invalid anchor: {0}", anchorText), lineNumber, colNumber));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return MatchToAnchor(match, lineNumber, colNumber);
|
return MatchToAnchor(match, lineNumber, colNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +129,10 @@
|
||||||
{
|
{
|
||||||
var match = RegexLib.ConditionalText.Match(anchor.Text);
|
var match = RegexLib.ConditionalText.Match(anchor.Text);
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
throw new FicdownException(_blockName, string.Format(@"Invalid conditional text: {0}", anchor.Text), anchor.LineNumber, anchor.ColNumber);
|
{
|
||||||
|
_warnings.Add(new FicdownException(_blockName, string.Format(@"Invalid conditional text: {0}", anchor.Text), anchor.LineNumber, anchor.ColNumber));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new Dictionary<bool, string>
|
return new Dictionary<bool, string>
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Model.Parser;
|
||||||
using Model.Player;
|
using Model.Player;
|
||||||
using Model.Story;
|
using Model.Story;
|
||||||
using Parser;
|
using Parser;
|
||||||
|
@ -17,6 +18,8 @@
|
||||||
private IDictionary<int, Action> _actionMatrix;
|
private IDictionary<int, Action> _actionMatrix;
|
||||||
private bool _wasRun = false;
|
private bool _wasRun = false;
|
||||||
|
|
||||||
|
public List<FicdownException> Warnings { private get; set; }
|
||||||
|
|
||||||
private Story _story;
|
private Story _story;
|
||||||
public Story Story
|
public Story Story
|
||||||
{
|
{
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
{
|
{
|
||||||
_story = value;
|
_story = value;
|
||||||
_actionMatrix = _story.Actions.ToDictionary(a => a.Value.Id, a => a.Value);
|
_actionMatrix = _story.Actions.ToDictionary(a => a.Value.Id, a => a.Value);
|
||||||
_manager = new StateManager(_story);
|
_manager = new StateManager(_story, Warnings);
|
||||||
_processingQueue = new Queue<StateQueueItem>();
|
_processingQueue = new Queue<StateQueueItem>();
|
||||||
_processed = new Dictionary<string, PageState>();
|
_processed = new Dictionary<string, PageState>();
|
||||||
_compressed = new Dictionary<string, PageState>();
|
_compressed = new Dictionary<string, PageState>();
|
||||||
|
@ -125,15 +128,15 @@
|
||||||
|
|
||||||
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.RawDescription).ToList();
|
var anchors = Utilities.GetInstance(Warnings, 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.RawDescription));
|
anchors.AddRange(Utilities.GetInstance(Warnings, action.Toggle, action.LineNumber).ParseAnchors(action.RawDescription));
|
||||||
}
|
}
|
||||||
var conditionals =
|
var conditionals =
|
||||||
anchors.SelectMany(
|
anchors.SelectMany(
|
||||||
a => a.Href.Conditions != null ? a.Href.Conditions.Select(c => c.Key) : new string[] {})
|
a => a.Href != null && a.Href.Conditions != null ? a.Href.Conditions.Select(c => c.Key) : new string[] {})
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var hasFirstSeen = RegexLib.BlockQuotes.IsMatch(currentState.Page.Scene.Description);
|
var hasFirstSeen = RegexLib.BlockQuotes.IsMatch(currentState.Page.Scene.Description);
|
||||||
|
@ -147,39 +150,42 @@
|
||||||
var anchor = anchors.FirstOrDefault(a =>
|
var anchor = anchors.FirstOrDefault(a =>
|
||||||
a.Href.Conditions != null
|
a.Href.Conditions != null
|
||||||
&& a.Href.Conditions.Keys.Contains(conditional.Key));
|
&& a.Href.Conditions.Keys.Contains(conditional.Key));
|
||||||
_manager.ToggleStateOn(affected, conditional.Key, currentState.Page.Scene.Name, anchor);
|
_manager.ToggleStateOn(affected, conditional.Key, currentState.Page.Scene.Name, anchor != null ? anchor.LineNumber : currentState.Page.Scene.LineNumber, anchor != null ? anchor.ColNumber : 1);
|
||||||
}
|
}
|
||||||
foreach (var conditional in conditionals)
|
foreach (var conditional in conditionals)
|
||||||
{
|
{
|
||||||
var anchor = anchors.FirstOrDefault(a =>
|
var anchor = anchors.FirstOrDefault(a =>
|
||||||
a.Href.Conditions != null
|
a.Href.Conditions != null
|
||||||
&& a.Href.Conditions.Keys.Contains(conditional));
|
&& a.Href.Conditions.Keys.Contains(conditional));
|
||||||
_manager.ToggleStateOn(affected, conditional, currentState.Page.Scene.Name, anchor);
|
_manager.ToggleStateOn(affected, conditional, currentState.Page.Scene.Name, anchor != null ? anchor.LineNumber : currentState.Page.Scene.LineNumber, anchor != null ? anchor.ColNumber : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var anchor in anchors.Where(a => a.Href.Target != null || a.Href.Toggles != null))
|
foreach (var anchor in anchors.Where(a => a.Href != null && (a.Href.Target != null || a.Href.Toggles != null)))
|
||||||
{
|
{
|
||||||
// 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)[
|
Utilities.GetInstance(Warnings, 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(Warnings, currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ConditionsMet(StateResolver.GetStateDictionary(currentState.Page),
|
||||||
anchor.Href.Conditions)])) continue;
|
anchor.Href.Conditions)])) continue;
|
||||||
|
|
||||||
var newState = _manager.ResolveNewState(anchor, currentState.Page);
|
var newState = _manager.ResolveNewState(anchor, currentState.Page);
|
||||||
if (!currentState.Page.Links.ContainsKey(anchor.Original))
|
if(newState.Scene != null)
|
||||||
currentState.Page.Links.Add(anchor.Original, newState.UniqueHash);
|
|
||||||
|
|
||||||
if (!states.Contains(newState.UniqueHash) && !_processed.ContainsKey(newState.UniqueHash))
|
|
||||||
{
|
{
|
||||||
states.Add(newState.UniqueHash);
|
if (!currentState.Page.Links.ContainsKey(anchor.Original))
|
||||||
var newAffected = new List<State>(currentState.AffectedStates);
|
currentState.Page.Links.Add(anchor.Original, newState.UniqueHash);
|
||||||
newAffected.Add(newState.AffectedState);
|
|
||||||
_processingQueue.Enqueue(new StateQueueItem {Page = newState, AffectedStates = newAffected});
|
if (!states.Contains(newState.UniqueHash) && !_processed.ContainsKey(newState.UniqueHash))
|
||||||
|
{
|
||||||
|
states.Add(newState.UniqueHash);
|
||||||
|
var newAffected = new List<State>(currentState.AffectedStates);
|
||||||
|
newAffected.Add(newState.AffectedState);
|
||||||
|
_processingQueue.Enqueue(new StateQueueItem {Page = newState, AffectedStates = newAffected});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
namespace Ficdown.Parser.Player
|
namespace Ficdown.Parser.Player
|
||||||
{
|
{
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Model.Parser;
|
||||||
using Model.Player;
|
using Model.Player;
|
||||||
using Model.Story;
|
using Model.Story;
|
||||||
|
|
||||||
internal interface IGameTraverser
|
internal interface IGameTraverser
|
||||||
{
|
{
|
||||||
|
List<FicdownException> Warnings { set; }
|
||||||
Story Story { get; set; }
|
Story Story { get; set; }
|
||||||
IEnumerable<PageState> Enumerate();
|
IEnumerable<PageState> Enumerate();
|
||||||
IEnumerable<Scene> OrphanedScenes { get; }
|
IEnumerable<Scene> OrphanedScenes { get; }
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
private readonly int _sceneCount;
|
private readonly int _sceneCount;
|
||||||
private readonly int _actionCount;
|
private readonly int _actionCount;
|
||||||
|
|
||||||
public StateManager(Story story)
|
private List<FicdownException> _warnings { get; set; }
|
||||||
|
|
||||||
|
public StateManager(Story story, List<FicdownException> warnings)
|
||||||
{
|
{
|
||||||
|
_warnings = warnings;
|
||||||
_story = story;
|
_story = story;
|
||||||
var allScenes = _story.Scenes.SelectMany(s => s.Value);
|
var allScenes = _story.Scenes.SelectMany(s => s.Value);
|
||||||
_sceneCount = allScenes.Max(s => s.Id);
|
_sceneCount = allScenes.Max(s => s.Id);
|
||||||
|
@ -28,10 +31,10 @@
|
||||||
var toggle in
|
var toggle in
|
||||||
allScenes.SelectMany(
|
allScenes.SelectMany(
|
||||||
sc =>
|
sc =>
|
||||||
Utilities.GetInstance(sc.Name, sc.LineNumber).ParseAnchors(sc.RawDescription)
|
Utilities.GetInstance(_warnings, sc.Name, sc.LineNumber).ParseAnchors(sc.RawDescription)
|
||||||
.SelectMany(
|
.SelectMany(
|
||||||
a =>
|
a =>
|
||||||
a.Href.Toggles != null
|
a.Href != null && a.Href.Toggles != null
|
||||||
? a.Href.Toggles.Where(t => !_stateMatrix.ContainsKey(t))
|
? a.Href.Toggles.Where(t => !_stateMatrix.ContainsKey(t))
|
||||||
: new string[] {})))
|
: new string[] {})))
|
||||||
{
|
{
|
||||||
|
@ -47,7 +50,7 @@
|
||||||
if(scene.Count() == 0)
|
if(scene.Count() == 0)
|
||||||
throw new FicdownException(_story.Name, string.Format("Story links to undefined scene: {0}", _story.FirstScene));
|
throw new FicdownException(_story.Name, string.Format("Story links to undefined scene: {0}", _story.FirstScene));
|
||||||
if(scene.Count() > 1)
|
if(scene.Count() > 1)
|
||||||
throw new FicdownException(_story.Name, string.Format("Story links to scene that is defined more than once: {0}", _story.FirstScene));
|
_warnings.Add(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
|
||||||
{
|
{
|
||||||
|
@ -67,7 +70,7 @@
|
||||||
ActionsToShow = new BitArray(_actionCount),
|
ActionsToShow = new BitArray(_actionCount),
|
||||||
ActionFirstToggles = null
|
ActionFirstToggles = null
|
||||||
},
|
},
|
||||||
Scene = scene.Single(),
|
Scene = scene.First(),
|
||||||
StateMatrix = _stateMatrix
|
StateMatrix = _stateMatrix
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -89,7 +92,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].RawDescription)
|
Utilities.GetInstance(_warnings, _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]]);
|
||||||
}
|
}
|
||||||
|
@ -103,11 +106,12 @@
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleStateOn(State state, string toggle, string blockName, Anchor anchor)
|
public void ToggleStateOn(State state, string toggle, string blockName, int lineNumber, int colNumber)
|
||||||
{
|
{
|
||||||
if(!_stateMatrix.ContainsKey(toggle))
|
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;
|
else
|
||||||
|
_warnings.Add(new FicdownException(blockName, string.Format("Conditional for undefined state: {0}", toggle), lineNumber, colNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleSeenSceneOn(State state, int sceneId)
|
public void ToggleSeenSceneOn(State state, int sceneId)
|
||||||
|
@ -148,22 +152,24 @@
|
||||||
|
|
||||||
private Scene GetScene(string blockName, Anchor anchor, 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, string.Format("Link to undefined scene: {0}", target), anchor.LineNumber, anchor.ColNumber);
|
|
||||||
|
|
||||||
Scene newScene = null;
|
|
||||||
foreach (var scene in _story.Scenes[target])
|
|
||||||
{
|
{
|
||||||
if (ConditionsMatch(scene, playerState) &&
|
Scene newScene = null;
|
||||||
(newScene == null || newScene.Conditions == null ||
|
foreach (var scene in _story.Scenes[target])
|
||||||
scene.Conditions.Count > newScene.Conditions.Count))
|
|
||||||
{
|
{
|
||||||
newScene = scene;
|
if (ConditionsMatch(scene, playerState) &&
|
||||||
|
(newScene == null || newScene.Conditions == null ||
|
||||||
|
scene.Conditions.Count > newScene.Conditions.Count))
|
||||||
|
{
|
||||||
|
newScene = scene;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (newScene == null)
|
||||||
|
_warnings.Add(new FicdownException(blockName, string.Format("Link to scene that is undefined for conditionals: {0}", target), anchor.LineNumber, anchor.ColNumber));
|
||||||
|
return newScene;
|
||||||
}
|
}
|
||||||
if (newScene == null)
|
_warnings.Add(new FicdownException(blockName, string.Format("Link to undefined scene: {0}", target), anchor.LineNumber, anchor.ColNumber));
|
||||||
throw new FicdownException(blockName, string.Format("Link to scene that is undefined for conditionals: {0}", target), anchor.LineNumber, anchor.ColNumber);
|
return null;
|
||||||
return newScene;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ConditionsMatch(Scene scene, BitArray playerState)
|
private bool ConditionsMatch(Scene scene, BitArray playerState)
|
||||||
|
@ -171,9 +177,10 @@
|
||||||
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, string.Format("Conditional for undefined state: {0}", c.Key), scene.LineNumber);
|
if(!_stateMatrix.ContainsKey(c.Key))
|
||||||
|
_warnings.Add(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.Where(c => _stateMatrix.ContainsKey(c.Key)).All(c => playerState[_stateMatrix[c.Key]] == c.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageState ClonePage(PageState page)
|
private PageState ClonePage(PageState page)
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
protected readonly Markdown Markdown;
|
protected readonly Markdown Markdown;
|
||||||
|
|
||||||
|
public List<FicdownException> Warnings { private get; set; }
|
||||||
|
|
||||||
public string IndexTemplate { get; set; }
|
public string IndexTemplate { get; set; }
|
||||||
public string SceneTemplate { get; set; }
|
public string SceneTemplate { get; set; }
|
||||||
public string StylesTemplate { get; set; }
|
public string StylesTemplate { get; set; }
|
||||||
|
@ -55,7 +57,7 @@
|
||||||
File.WriteAllText(Path.Combine(outPath, "styles.css"), StylesTemplate ?? Template.Styles);
|
File.WriteAllText(Path.Combine(outPath, "styles.css"), StylesTemplate ?? Template.Styles);
|
||||||
|
|
||||||
var content = page.Content;
|
var content = page.Content;
|
||||||
foreach (var anchor in Utilities.GetInstance(page.Name).ParseAnchors(page.Content))
|
foreach (var anchor in Utilities.GetInstance(Warnings, page.Name).ParseAnchors(page.Content))
|
||||||
{
|
{
|
||||||
var newAnchor = string.Format("[{0}]({1}.html)", anchor.Text, anchor.Href.Target);
|
var newAnchor = string.Format("[{0}]({1}.html)", anchor.Text, anchor.Href.Target);
|
||||||
content = content.Replace(anchor.Original, newAnchor);
|
content = content.Replace(anchor.Original, newAnchor);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
namespace Ficdown.Parser.Render
|
namespace Ficdown.Parser.Render
|
||||||
{
|
{
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Collections.Generic;
|
||||||
using Model.Parser;
|
using Model.Parser;
|
||||||
|
|
||||||
public interface IRenderer
|
public interface IRenderer
|
||||||
{
|
{
|
||||||
|
List<FicdownException> Warnings { set; }
|
||||||
string IndexTemplate { get; set; }
|
string IndexTemplate { get; set; }
|
||||||
string SceneTemplate { get; set; }
|
string SceneTemplate { get; set; }
|
||||||
string StylesTemplate { get; set; }
|
string StylesTemplate { get; set; }
|
||||||
|
|
Loading…
Reference in New Issue