added block name and line numbers to all exception output

This commit is contained in:
Rudis Muiznieks 2015-07-19 15:51:10 -05:00
parent 46ec2df6df
commit 4fc90cf8b7
17 changed files with 358 additions and 182 deletions

View File

@ -8,7 +8,7 @@
internal class Program internal class Program
{ {
private static void Main(string[] args) private static int Main(string[] args)
{ {
string infile = null; string infile = null;
string output = null; string output = null;
@ -23,6 +23,7 @@
if (args[0] == "/?" || args[0] == "/help" || args[0] == "-help" || args[0] == "--help") if (args[0] == "/?" || args[0] == "/help" || args[0] == "-help" || args[0] == "--help")
{ {
ShowHelp(); ShowHelp();
return 0;
} }
} }
else if (args.Length > 1) else if (args.Length > 1)
@ -55,23 +56,24 @@
break; break;
default: default:
Console.WriteLine(@"Unknown option: {0}", args[i]); Console.WriteLine(@"Unknown option: {0}", args[i]);
return; return 1;
} }
} }
} }
else else
{ {
ShowHelp(); ShowHelp();
return 0;
} }
if (string.IsNullOrWhiteSpace(format)) if (string.IsNullOrWhiteSpace(format) || string.IsNullOrWhiteSpace(infile))
{ {
ShowHelp(); ShowHelp();
return; return 1;
} }
if (string.IsNullOrWhiteSpace(infile) || !File.Exists(infile)) if (!File.Exists(infile))
{ {
Console.WriteLine(@"Source file {0} not found.", infile); Console.WriteLine(@"Source file {0} not found.", infile);
return; return 2;
} }
if (string.IsNullOrWhiteSpace(output)) if (string.IsNullOrWhiteSpace(output))
if (format == "html") if (format == "html")
@ -82,14 +84,14 @@
if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output))) if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output)))
{ {
Console.WriteLine(@"Specified output {0} already exists.", output); Console.WriteLine(@"Specified output {0} already exists.", output);
return; return 2;
} }
if (!string.IsNullOrWhiteSpace(tempdir)) if (!string.IsNullOrWhiteSpace(tempdir))
{ {
if (!Directory.Exists(tempdir)) if (!Directory.Exists(tempdir))
{ {
Console.WriteLine(@"Template directory {0} does not exist.", tempdir); Console.WriteLine(@"Template directory {0} does not exist.", tempdir);
return; return 2;
} }
if (!File.Exists(Path.Combine(tempdir, "index.html")) || if (!File.Exists(Path.Combine(tempdir, "index.html")) ||
!File.Exists(Path.Combine(tempdir, "scene.html")) || !File.Exists(Path.Combine(tempdir, "scene.html")) ||
@ -103,7 +105,7 @@
if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images)) if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images))
{ {
Console.WriteLine(@"Images directory {0} does not exist.", images); Console.WriteLine(@"Images directory {0} does not exist.", images);
return; return 2;
} }
var parser = new FicdownParser(); var parser = new FicdownParser();
@ -124,13 +126,13 @@
if (string.IsNullOrWhiteSpace(author)) if (string.IsNullOrWhiteSpace(author))
{ {
Console.WriteLine(@"Epub format requires the --author argument."); Console.WriteLine(@"Epub format requires the --author argument.");
return; return 1;
} }
rend = new EpubRenderer(author); rend = new EpubRenderer(author);
break; break;
default: default:
ShowHelp(); ShowHelp();
return; return 1;
} }
if (!string.IsNullOrWhiteSpace(tempdir)) if (!string.IsNullOrWhiteSpace(tempdir))
@ -147,6 +149,7 @@
rend.Render(story, output, debug); rend.Render(story, output, debug);
Console.WriteLine(@"Done."); Console.WriteLine(@"Done.");
return 0;
} }

View File

@ -6,6 +6,11 @@
public class UtilityTests public class UtilityTests
{ {
private Utilities Utilities
{
get { return Utilities.GetInstance("none", 0); }
}
[Fact] [Fact]
public void FullAnchorMatches() public void FullAnchorMatches()
{ {

View File

@ -1,110 +1,112 @@
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup>
<PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{780F652D-7541-4171-BB89-2D263D3961DC}</ProjectGuid>
<ProjectGuid>{780F652D-7541-4171-BB89-2D263D3961DC}</ProjectGuid> <OutputType>Library</OutputType>
<OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder>
<AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>Ficdown.Parser</RootNamespace>
<RootNamespace>Ficdown.Parser</RootNamespace> <AssemblyName>Ficdown.Parser</AssemblyName>
<AssemblyName>Ficdown.Parser</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment>
<FileAlignment>512</FileAlignment> </PropertyGroup>
</PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols>
<DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType>
<DebugType>full</DebugType> <Optimize>false</Optimize>
<Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath>
<OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport>
<ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel>
<WarningLevel>4</WarningLevel> </PropertyGroup>
</PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType>
<DebugType>pdbonly</DebugType> <Optimize>true</Optimize>
<Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath>
<OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport>
<ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel>
<WarningLevel>4</WarningLevel> </PropertyGroup>
</PropertyGroup> <ItemGroup>
<ItemGroup> <Reference Include="Epub4Net">
<Reference Include="Epub4Net"> <HintPath>..\packages\Epub4Net.1.2.0\lib\net40\Epub4Net.dll</HintPath>
<HintPath>..\packages\Epub4Net.1.2.0\lib\net40\Epub4Net.dll</HintPath> </Reference>
</Reference> <Reference Include="Ionic.Zip">
<Reference Include="Ionic.Zip"> <HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
<HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath> </Reference>
</Reference> <Reference Include="MarkdownSharp">
<Reference Include="MarkdownSharp"> <HintPath>..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll</HintPath>
<HintPath>..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll</HintPath> </Reference>
</Reference> <Reference Include="System" />
<Reference Include="System" /> <Reference Include="System.Core" />
<Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" />
<Reference Include="System.Data" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml" /> </ItemGroup>
</ItemGroup> <ItemGroup>
<ItemGroup> <Compile Include="Model\Parser\ResolvedPage.cs" />
<Compile Include="Model\Parser\ResolvedPage.cs" /> <Compile Include="Model\Parser\ResolvedStory.cs" />
<Compile Include="Model\Parser\ResolvedStory.cs" /> <Compile Include="Model\Player\PageState.cs" />
<Compile Include="Model\Player\PageState.cs" /> <Compile Include="Model\Player\State.cs" />
<Compile Include="Model\Player\State.cs" /> <Compile Include="Model\Player\StateQueueItem.cs" />
<Compile Include="Model\Player\StateQueueItem.cs" /> <Compile Include="Parser\BlockHandler.cs" />
<Compile Include="Parser\BlockHandler.cs" /> <Compile Include="Parser\IBlockHandler.cs" />
<Compile Include="Parser\IBlockHandler.cs" /> <Compile Include="Parser\RegexLib.cs" />
<Compile Include="Parser\RegexLib.cs" /> <Compile Include="Parser\StateResolver.cs" />
<Compile Include="Parser\StateResolver.cs" /> <Compile Include="Parser\Utilities.cs" />
<Compile Include="Parser\Utilities.cs" /> <Compile Include="FicDownParser.cs" />
<Compile Include="FicDownParser.cs" /> <Compile Include="Model\Parser\Anchor.cs" />
<Compile Include="Model\Parser\Anchor.cs" /> <Compile Include="Model\Parser\Block.cs" />
<Compile Include="Model\Parser\Block.cs" /> <Compile Include="Model\Parser\BlockType.cs" />
<Compile Include="Model\Parser\BlockType.cs" /> <Compile Include="Parser\IStateResolver.cs" />
<Compile Include="Parser\IStateResolver.cs" /> <Compile Include="Model\Parser\Href.cs" />
<Compile Include="Model\Parser\Href.cs" /> <Compile Include="Model\Player\PlayerState.cs" />
<Compile Include="Model\Player\PlayerState.cs" /> <Compile Include="Model\Story\Extensions\SceneExtensions.cs" />
<Compile Include="Model\Story\Extensions\SceneExtensions.cs" /> <Compile Include="Player\GameTraverser.cs" />
<Compile Include="Player\GameTraverser.cs" /> <Compile Include="Player\IGameTraverser.cs" />
<Compile Include="Player\IGameTraverser.cs" /> <Compile Include="Player\StateManager.cs" />
<Compile Include="Player\StateManager.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Model\Story\Action.cs" />
<Compile Include="Model\Story\Action.cs" /> <Compile Include="Model\Story\Scene.cs" />
<Compile Include="Model\Story\Scene.cs" /> <Compile Include="Model\Story\Story.cs" />
<Compile Include="Model\Story\Story.cs" /> <Compile Include="Render\EpubRenderer.cs" />
<Compile Include="Render\EpubRenderer.cs" /> <Compile Include="Render\HtmlRenderer.cs" />
<Compile Include="Render\HtmlRenderer.cs" /> <Compile Include="Render\IRenderer.cs" />
<Compile Include="Render\IRenderer.cs" /> <Compile Include="Render\MimeHelper.cs" />
<Compile Include="Render\MimeHelper.cs" /> <Compile Include="Render\Template.Designer.cs">
<Compile Include="Render\Template.Designer.cs"> <AutoGen>True</AutoGen>
<AutoGen>True</AutoGen> <DesignTime>True</DesignTime>
<DesignTime>True</DesignTime> <DependentUpon>Template.resx</DependentUpon>
<DependentUpon>Template.resx</DependentUpon> </Compile>
</Compile> <Compile Include="Model\Parser\Line.cs" />
</ItemGroup> <Compile Include="Model\Parser\FicdownException.cs" />
<ItemGroup> <Compile Include="Parser\ParserExtensions.cs" />
<None Include="packages.config" /> </ItemGroup>
<Content Include="Render\Views\scene.html" /> <ItemGroup>
</ItemGroup> <None Include="packages.config" />
<ItemGroup> <Content Include="Render\Views\scene.html" />
<Content Include="Render\Assets\styles.css" /> </ItemGroup>
</ItemGroup> <ItemGroup>
<ItemGroup> <Content Include="Render\Assets\styles.css" />
<Content Include="Render\Views\index.html" /> </ItemGroup>
</ItemGroup> <ItemGroup>
<ItemGroup> <Content Include="Render\Views\index.html" />
<EmbeddedResource Include="Render\Template.resx"> </ItemGroup>
<Generator>ResXFileCodeGenerator</Generator> <ItemGroup>
<LastGenOutput>Template.Designer.cs</LastGenOutput> <EmbeddedResource Include="Render\Template.resx">
</EmbeddedResource> <Generator>ResXFileCodeGenerator</Generator>
</ItemGroup> <LastGenOutput>Template.Designer.cs</LastGenOutput>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -6,6 +6,7 @@
{ {
public BlockType Type { get; set; } public BlockType Type { get; set; }
public string Name { get; set; } public string Name { get; set; }
public IList<string> Lines { get; set; } public IList<Line> Lines { get; set; }
public int LineNumber { get; set; }
} }
} }

View File

@ -0,0 +1,34 @@
namespace Ficdown.Parser.Model.Parser
{
using System;
public class FicdownException : Exception
{
private string _blockName;
private int? _lineNumber;
private string _message;
public FicdownException(string blockName, int? lineNumber, string message) : base(message)
{
_blockName = blockName;
_lineNumber = lineNumber;
_message = message;
}
public FicdownException(string message) : base(message)
{
_message = message;
}
public override string ToString()
{
return !string.IsNullOrEmpty(_blockName)
? string.Format("Error in block \"{0}\" (Line {1}): {2}",
_blockName,
_lineNumber.HasValue
? _lineNumber.ToString()
: "unknown", _message)
: string.Format("Error: {0}", _message);
}
}
}

View File

@ -0,0 +1,8 @@
namespace Ficdown.Parser.Model.Parser
{
public class Line
{
public int Number { get; set; }
public string Text { get; set; }
}
}

View File

@ -5,5 +5,6 @@
public int Id { get; set; } public int Id { get; set; }
public string Toggle { get; set; } public string Toggle { get; set; }
public string Description { get; set; } public string Description { get; set; }
public int LineNumber { get; set; }
} }
} }

View File

@ -9,5 +9,6 @@
public string Key { get; set; } public string Key { get; set; }
public string Description { get; set; } public string Description { get; set; }
public IDictionary<string, bool> Conditions { get; set; } public IDictionary<string, bool> Conditions { get; set; }
public int LineNumber { get; set; }
} }
} }

View File

@ -14,6 +14,7 @@
{ {
var blocks = new List<Block>(); var blocks = new List<Block>();
Block currentBlock = null; Block currentBlock = null;
var lineNum = 1;
foreach (var line in lines) foreach (var line in lines)
{ {
var match = Regex.Match(line, @"^(?<level>#{1,3})\s+(?<name>[^#].*)$"); var match = Regex.Match(line, @"^(?<level>#{1,3})\s+(?<name>[^#].*)$");
@ -24,12 +25,17 @@
{ {
Type = (BlockType) match.Groups["level"].Length, Type = (BlockType) match.Groups["level"].Length,
Name = match.Groups["name"].Value, Name = match.Groups["name"].Value,
Lines = new List<string>() Lines = new List<Line>(),
LineNumber = lineNum++
}; };
} }
else else
{ {
if (currentBlock != null) currentBlock.Lines.Add(line); if (currentBlock != null) currentBlock.Lines.Add(new Line
{
Number = lineNum++,
Text = line
});
} }
} }
if (currentBlock != null) blocks.Add(currentBlock); if (currentBlock != null) blocks.Add(currentBlock);
@ -40,14 +46,13 @@
{ {
// get the story // get the story
var storyBlock = blocks.SingleOrDefault(b => b.Type == BlockType.Story); var storyBlock = blocks.SingleOrDefault(b => b.Type == BlockType.Story);
if(storyBlock == null) throw new FormatException("No story block found"); if(storyBlock == null) throw new FicdownException("No story block found");
var storyAnchor = Utilities.ParseAnchor(storyBlock.Name); var storyAnchor = Utilities.GetInstance(storyBlock.Name, storyBlock.LineNumber).ParseAnchor(storyBlock.Name);
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 FormatException(string.Format("Story href should only have target: {0}", throw new FicdownException(storyBlock.Name, storyBlock.LineNumber, "Story href should only have target");
storyAnchor.Original));
var story = new Story var story = new Story
{ {
@ -69,7 +74,7 @@
blocks.Where(b => b.Type == BlockType.Action).Select(b => BlockToAction(b, aid++)).ToDictionary(a => a.Toggle, a => a); blocks.Where(b => b.Type == BlockType.Action).Select(b => BlockToAction(b, aid++)).ToDictionary(a => a.Toggle, a => a);
if (!story.Scenes.ContainsKey(storyAnchor.Href.Target)) if (!story.Scenes.ContainsKey(storyAnchor.Href.Target))
throw new FormatException(string.Format("Story targets non-existent scene: {0}", storyAnchor.Href.Target)); throw new FicdownException(storyBlock.Name, storyBlock.LineNumber, string.Format("Story targets non-existent scene: {0}", storyAnchor.Href.Target));
story.FirstScene = storyAnchor.Href.Target; story.FirstScene = storyAnchor.Href.Target;
return story; return story;
@ -81,22 +86,23 @@
var scene = new Scene var scene = new Scene
{ {
Id = id, Id = id,
Description = string.Join("\n", block.Lines).Trim() LineNumber = block.LineNumber,
Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim()
}; };
try try
{ {
var sceneName = Utilities.ParseAnchor(block.Name); var sceneName = Utilities.GetInstance(block.Name, block.LineNumber).ParseAnchor(block.Name);
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.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 FormatException(string.Format("Scene href should only have conditions: {0}", block.Name)); throw new FicdownException(block.Name, block.LineNumber, string.Format("Scene href should only have conditions: {0}", block.Name));
scene.Conditions = sceneName.Href.Conditions; scene.Conditions = sceneName.Href.Conditions;
} }
catch(FormatException) catch(FicdownException)
{ {
scene.Name = block.Name.Trim(); scene.Name = block.Name.Trim();
scene.Key = Utilities.NormalizeString(block.Name); scene.Key = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name);
} }
return scene; return scene;
@ -107,9 +113,10 @@
return new Action return new Action
{ {
Id = id, Id = id,
Toggle = Utilities.NormalizeString(block.Name), Toggle = Utilities.GetInstance(block.Name, block.LineNumber).NormalizeString(block.Name),
Description = string.Join("\n", block.Lines).Trim() Description = string.Join("\n", block.Lines.Select(l => l.Text)).Trim(),
LineNumber = block.LineNumber
}; };
} }
} }
} }

View File

@ -0,0 +1,24 @@
namespace Ficdown.Parser.Parser
{
using System;
using System.Linq;
using System.Collections.Generic;
public static class ParserExtensions
{
public static string ToHrefString(this IDictionary<string, bool> values, string separator)
{
return values != null
? string.Join(separator,
values.Where(v => !v.Key.StartsWith(">"))
.Select(v => string.Format("{0}{1}", v.Value ? null : "!", v.Key))
.ToArray())
: null;
}
public static string ToHrefString(this IEnumerable<string> values, string separator)
{
return values != null ? string.Join(separator, values.ToArray()) : null;
}
}
}

View File

@ -40,13 +40,13 @@
}; };
} }
private string ResolveAnchor(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.Conditions != null)
{ {
var satisfied = Utilities.ConditionsMet(playerState, anchor.Href.Conditions); var satisfied = Utilities.GetInstance(blockName, lineNumber).ConditionsMet(playerState, anchor.Href.Conditions);
var alts = Utilities.ParseConditionalText(text); var alts = Utilities.GetInstance(blockName, lineNumber).ParseConditionalText(text);
var replace = alts[satisfied]; var replace = alts[satisfied];
text = RegexLib.EscapeChar.Replace(replace, string.Empty); text = RegexLib.EscapeChar.Replace(replace, string.Empty);
} }
@ -66,7 +66,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.ParseAnchors(actionTuple.Value.Description); var actionAnchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(actionTuple.Value.Description);
var anchorDict = GetStateDictionary(page); var anchorDict = GetStateDictionary(page);
if ( if (
actionAnchors.Any( actionAnchors.Any(
@ -80,19 +80,19 @@
resolved.AppendFormat("{0}\n\n", actionAnchors.Aggregate(actionTuple.Value.Description, resolved.AppendFormat("{0}\n\n", actionAnchors.Aggregate(actionTuple.Value.Description,
(current, anchor) => (current, anchor) =>
current.Replace(anchor.Original, current.Replace(anchor.Original,
ResolveAnchor(anchor, anchorDict, ResolveAnchor(page.Scene.Name, page.Scene.LineNumber, anchor, anchorDict,
page.Links.ContainsKey(anchor.Original) ? page.Links[anchor.Original] : null)))); page.Links.ContainsKey(anchor.Original) ? page.Links[anchor.Original] : null))));
} }
} }
var anchors = Utilities.ParseAnchors(page.Scene.Description); var anchors = Utilities.GetInstance(page.Scene.Name, page.Scene.LineNumber).ParseAnchors(page.Scene.Description);
var stateDict = GetStateDictionary(page); var stateDict = GetStateDictionary(page);
var text = var text =
RegexLib.EmptyListItem.Replace( RegexLib.EmptyListItem.Replace(
anchors.Aggregate(page.Scene.Description, anchors.Aggregate(page.Scene.Description,
(current, anchor) => (current, anchor) =>
current.Replace(anchor.Original, current.Replace(anchor.Original,
ResolveAnchor(anchor, stateDict, ResolveAnchor(page.Scene.Name, page.Scene.LineNumber, anchor, stateDict,
page.Links.ContainsKey(anchor.Original) ? page.Links[anchor.Original] : null))), page.Links.ContainsKey(anchor.Original) ? page.Links[anchor.Original] : null))),
string.Empty); string.Empty);
var seen = page.State.ScenesSeen[page.Scene.Id - 1]; var seen = page.State.ScenesSeen[page.Scene.Id - 1];
@ -135,4 +135,4 @@
return builder.ToString(); return builder.ToString();
} }
} }
} }

View File

@ -7,14 +7,34 @@ namespace Ficdown.Parser.Parser
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Model.Parser; using Model.Parser;
internal static class Utilities internal class Utilities
{ {
public static string NormalizeString(string raw) public static Utilities GetInstance(string blockName, int lineNumber)
{
return new Utilities
{
_blockName = blockName,
_lineNumber = lineNumber
};
}
public static Utilities GetInstance(string blockName)
{
return new Utilities
{
_blockName = blockName
};
}
protected string _blockName;
protected int? _lineNumber;
public string NormalizeString(string raw)
{ {
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 static Href ParseHref(string href) private Href ParseHref(string href)
{ {
var match = RegexLib.Href.Match(href); var match = RegexLib.Href.Match(href);
if (match.Success) if (match.Success)
@ -37,23 +57,23 @@ namespace Ficdown.Parser.Parser
: null : null
}; };
} }
throw new FormatException(string.Format("Invalid href: {0}", href)); throw new FicdownException(_blockName, _lineNumber, string.Format("Invalid href: {0}", href));
} }
public static Anchor ParseAnchor(string anchorText) public Anchor ParseAnchor(string anchorText)
{ {
var match = RegexLib.Anchors.Match(anchorText); var match = RegexLib.Anchors.Match(anchorText);
if (!match.Success) throw new FormatException(string.Format("Invalid anchor: {0}", anchorText)); if (!match.Success) throw new FicdownException(_blockName, _lineNumber, string.Format("Invalid anchor: {0}", anchorText));
return MatchToAnchor(match); return MatchToAnchor(match);
} }
public static 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(MatchToAnchor).ToList();
} }
private static Anchor MatchToAnchor(Match match) private Anchor MatchToAnchor(Match match)
{ {
var astr = match.Groups["anchor"].Value; var astr = match.Groups["anchor"].Value;
var txstr = match.Groups["text"].Value; var txstr = match.Groups["text"].Value;
@ -73,10 +93,10 @@ namespace Ficdown.Parser.Parser
}; };
} }
public static IDictionary<bool, string> ParseConditionalText(string text) public IDictionary<bool, string> ParseConditionalText(string text)
{ {
var match = RegexLib.ConditionalText.Match(text); var match = RegexLib.ConditionalText.Match(text);
if (!match.Success) throw new FormatException(string.Format(@"Invalid conditional text: {0}", text)); if (!match.Success) throw new FicdownException(_blockName, _lineNumber, string.Format(@"Invalid conditional text: {0}", text));
return new Dictionary<bool, string> return new Dictionary<bool, string>
{ {
{true, match.Groups["true"].Value}, {true, match.Groups["true"].Value},
@ -84,22 +104,7 @@ namespace Ficdown.Parser.Parser
}; };
} }
public static string ToHrefString(this IDictionary<string, bool> values, string separator) public bool ConditionsMet(IDictionary<string, bool> playerState, IDictionary<string, bool> conditions)
{
return values != null
? string.Join(separator,
values.Where(v => !v.Key.StartsWith(">"))
.Select(v => string.Format("{0}{1}", v.Value ? null : "!", v.Key))
.ToArray())
: null;
}
public static string ToHrefString(this IEnumerable<string> values, string separator)
{
return values != null ? string.Join(separator, values.ToArray()) : null;
}
public static bool ConditionsMet(IDictionary<string, bool> playerState, IDictionary<string, bool> conditions)
{ {
return return
conditions.All( conditions.All(

View File

@ -99,9 +99,9 @@
{ {
var states = new HashSet<string>(); var states = new HashSet<string>();
var anchors = Utilities.ParseAnchors(currentState.Page.Scene.Description).ToList(); var anchors = Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseAnchors(currentState.Page.Scene.Description).ToList();
foreach (var action in GetActionsForPage(currentState.Page)) foreach (var action in GetActionsForPage(currentState.Page))
anchors.AddRange(Utilities.ParseAnchors(action.Description)); anchors.AddRange(Utilities.GetInstance(action.Toggle, action.LineNumber).ParseAnchors(action.Description));
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.Conditions != null ? a.Href.Conditions.Select(c => c.Key) : new string[] {})
@ -126,8 +126,8 @@
// 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.ParseConditionalText(anchor.Text)[ Utilities.GetInstance(currentState.Page.Scene.Name, currentState.Page.Scene.LineNumber).ParseConditionalText(anchor.Text)[
Utilities.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;
var newState = _manager.ResolveNewState(anchor, currentState.Page); var newState = _manager.ResolveNewState(anchor, currentState.Page);
@ -144,4 +144,4 @@
} }
} }
} }
} }

View File

@ -21,14 +21,14 @@
_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);
_actionCount = _story.Actions.Max(a => a.Value.Id); _actionCount = _story.Actions.Count > 0 ? _story.Actions.Max(a => a.Value.Id) : 0;
_stateMatrix = new Dictionary<string, int>(); _stateMatrix = new Dictionary<string, int>();
var state = 0; var state = 0;
foreach ( foreach (
var toggle in var toggle in
allScenes.SelectMany( allScenes.SelectMany(
sc => sc =>
Utilities.ParseAnchors(sc.Description) Utilities.GetInstance(sc.Name, sc.LineNumber).ParseAnchors(sc.Description)
.SelectMany( .SelectMany(
a => a =>
a.Href.Toggles != null a.Href.Toggles != null
@ -83,7 +83,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.ParseAnchors(_story.Actions[toggle].Description) Utilities.GetInstance(_story.Actions[toggle].Toggle, _story.Actions[toggle].LineNumber).ParseAnchors(_story.Actions[toggle].Description)
.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,7 +93,7 @@
newState.State.ActionFirstToggles = actionFirstToggles != null newState.State.ActionFirstToggles = actionFirstToggles != null
? new BitArray(actionFirstToggles.ToArray()) ? new BitArray(actionFirstToggles.ToArray())
: null; : null;
newState.Scene = GetScene(target, newState.State.PlayerState); newState.Scene = GetScene(current.Scene.Name, current.Scene.LineNumber, target, newState.State.PlayerState);
return newState; return newState;
} }
@ -138,10 +138,10 @@
return GetUniqueHash(compressed, page.Scene.Key); return GetUniqueHash(compressed, page.Scene.Key);
} }
private Scene GetScene(string target, BitArray playerState) private Scene GetScene(string blockName, int lineNumber, string target, BitArray playerState)
{ {
if (!_story.Scenes.ContainsKey(target)) if (!_story.Scenes.ContainsKey(target))
throw new FormatException(string.Format("Encountered link to non-existant scene: {0}", target)); throw new FicdownException(blockName, lineNumber, string.Format("Encountered link to non-existent scene: {0}", target));
Scene newScene = null; Scene newScene = null;
foreach (var scene in _story.Scenes[target]) foreach (var scene in _story.Scenes[target])
@ -154,13 +154,17 @@
} }
} }
if (newScene == null) if (newScene == null)
throw new FormatException(string.Format("Scene {0} reached with unmatched player state", target)); throw new FicdownException(blockName, lineNumber, string.Format("Scene {0} reached with unmatched player state", target));
return newScene; return newScene;
} }
private bool ConditionsMatch(Scene scene, BitArray playerState) private bool ConditionsMatch(Scene scene, BitArray playerState)
{ {
if (scene.Conditions == null) return true; if (scene.Conditions == null) return true;
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));
});
return scene.Conditions.All(c => playerState[_stateMatrix[c.Key]] == c.Value); return scene.Conditions.All(c => playerState[_stateMatrix[c.Key]] == c.Value);
} }

View File

@ -1,10 +1,91 @@
namespace Ficdown.Parser.Render namespace Ficdown.Parser.Render
{ {
using System; using System;
using System.Text;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Epub4Net; using Epub4Net;
using Ionic.Zip;
using Ionic.Zlib;
#region fix for bug in epub4net
// https://bitbucket.org/dalager/epub4net/issues/2/windows-path-separator-hard-coded-in
public class FixedFileSystemManager : IFileSystemManager
{
private FileSystemManager _fsm;
public FixedFileSystemManager()
{
_fsm = new FileSystemManager(Guid.NewGuid().ToString());
}
public void SetupOutputDir()
{
_fsm.SetupOutputDir();
}
public string ContentDir
{
get { return _fsm.ContentDir; }
set { _fsm.ContentDir = value; }
}
public string BuildDirectory
{
get { return _fsm.BuildDirectory; }
set { _fsm.BuildDirectory = value; }
}
public void CreateTocFile(Epub epub)
{
_fsm.CreateTocFile(epub);
}
public void CreateContentOpfFile(Epub epub)
{
_fsm.CreateContentOpfFile(epub);
}
public void CopyChapterFilesToContentFolder(Epub epub)
{
_fsm.CopyChapterFilesToContentFolder(epub);
}
public string ZipEpub(Epub epub)
{
var epubFilename = epub.Title + ".epub";
if(File.Exists(epubFilename)) File.Delete(epubFilename);
using(var zip = new ZipFile(epubFilename, Encoding.UTF8))
{
zip.EmitTimesInWindowsFormatWhenSaving = false;
zip.CompressionLevel = CompressionLevel.None;
zip.AddFile(Path.Combine(_fsm.BuildDirectory, "mimetype"), "\\");
zip.Save();
File.Delete(Path.Combine(_fsm.BuildDirectory, "mimetype"));
zip.AddDirectory(_fsm.BuildDirectory);
zip.Save();
}
return epubFilename;
}
public void CopyResourceFilesToContentFolder(Epub epub)
{
_fsm.CopyResourceFilesToContentFolder(epub);
}
public void ValidatePathsExists(IEnumerable<IPathed> fileList)
{
_fsm.ValidatePathsExists(fileList);
}
public void DeleteBuildDir()
{
_fsm.DeleteBuildDir();
}
}
#endregion
public class EpubRenderer : HtmlRenderer public class EpubRenderer : HtmlRenderer
{ {
@ -46,7 +127,7 @@
} }
} }
var builder = new EPubBuilder(); var builder = new EPubBuilder(new FixedFileSystemManager(), Guid.NewGuid().ToString());
var built = builder.Build(epub); var built = builder.Build(epub);
File.Move(built, outPath); File.Move(built, outPath);

View File

@ -51,7 +51,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.ParseAnchors(page.Content)) foreach (var anchor in Utilities.GetInstance(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);

View File

@ -3,4 +3,4 @@
<package id="DotNetZip" version="1.9.1.8" targetFramework="net45" /> <package id="DotNetZip" version="1.9.1.8" targetFramework="net45" />
<package id="Epub4Net" version="1.2.0" targetFramework="net45" /> <package id="Epub4Net" version="1.2.0" targetFramework="net45" />
<package id="MarkdownSharp" version="1.13.0.0" targetFramework="net45" /> <package id="MarkdownSharp" version="1.13.0.0" targetFramework="net45" />
</packages> </packages>