initial commit, incomplete parsing but a good start

This commit is contained in:
Rudis Muiznieks 2014-06-29 23:04:51 -05:00
parent 9190faa9d1
commit 5f995fe06d
20 changed files with 879 additions and 0 deletions

View File

@ -0,0 +1,29 @@
namespace Ficdown.Parser.Tests
{
using System;
using System.Collections.Generic;
using System.Text;
using Engine;
using Model.Parser;
using Model.Story;
using ServiceStack.Text;
using TestStories;
using Xunit;
public class BlockHandlerTests
{
[Fact]
public void GoodTestStoryShouldParse()
{
var lines = Encoding.UTF8.GetString(Resources.the_robot_king).Split(new[] {'\r', '\n'});
var handler = new BlockHandler();
IEnumerable<Block> blocks = null;
Story story = null;
Assert.DoesNotThrow(() => blocks = handler.ExtractBlocks(lines));
Assert.DoesNotThrow(() => story = handler.ParseBlocks(blocks));
Assert.NotNull(story);
Console.WriteLine(story.Dump());
}
}
}

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<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')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{756192E2-BA47-4850-8096-289D44878A7E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Ficdown.Parser.Tests</RootNamespace>
<AssemblyName>Ficdown.Parser.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Text">
<HintPath>..\packages\ServiceStack.Text.4.0.22\lib\net40\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xunit">
<HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="BlockHandlerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestStories\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="TestStories\the-robot-king.md" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\Ficdown.Parser\Ficdown.Parser.csproj">
<Project>{780f652d-7541-4171-bb89-2d263d3961dc}</Project>
<Name>Ficdown.Parser</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="TestStories\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</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.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Ficdown.Parser.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ficdown.Parser.Tests")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("7380fc2c-7382-4fbb-be17-574662992d92")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34014
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Ficdown.Parser.Tests.TestStories {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ficdown.Parser.Tests.TestStories.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] the_robot_king {
get {
object obj = ResourceManager.GetObject("the_robot_king", resourceCulture);
return ((byte[])(obj));
}
}
}
}

View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="the_robot_king" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>the-robot-king.md;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

View File

@ -0,0 +1,170 @@
# [The Robot King](/robot-cave)
An experiment in markdown-based interactive fiction by Rudis Muiznieks.
Release r2014-06-29
## Robot Cave
> You wake up and emit a great robot yawn as you stretch your metallic arms. Today is the big day! You have to start your new job working for the Robot King at the Robot Palace!
You're in your cave and you can see [a peg by the door where you usually hang your raincoat|your raincoat hanging by the door](?raincoat).
Your cave only has one tiny window, and through it you can see [the sun shining through the clouds|that it's raining heavily outside](?stopped-raining).
**What do you want to do?**
- [Go outside and start walking to the palace.](/outside)
- [Wait for it to stop raining.](#stopped-raining)
- [Put on your raincoat.](#raincoat)
### Raincoat
You take your raincoat and put it on. It fits perfectly!
### Stopped Raining
It feels like hours, but it finally stops raining. You hope you won't be late for your new job!
## [Outside](?!raincoat&!stopped-raining)
> You step through the door and feel the water flowing over your metal body. Brrr! That's cold! You start to think that maybe getting your raincoat would be a good idea. This is just the kind of rain that might turn you into a rusty robot statue if you stay in it too long.
You're standing on your front porch in the pouring rain. You need to get to the palace for your new job, but you don't want to rust!
**What will you do?**
- [Continue walking to the palace.](/rusted)
- [Go back into your cave.](/robot-cave)
## Outside
You step through the door and feel the early afternoon sun warming your metal body. It feels good, but you were supposed to start your new job early in the morning!
You run as fast as you can all the way to the Robot Palace, but it's already too late.
"You were supposed to be here first thing in the morning," says the palace guard. "We can't have sleepy-head robots working at the Robot Palace! Try finding a different job instead."
**You've been fired!**
## Rusted!
You start walking toward the Robot Palace in the rain. Who needs a raincoat anyway? As you move down the path, rust starts forming on your legs and knees so you have to walk slower. Eventually the rust gets so bad that you can't move anymore at all!
As your whole body rusts over, you wonder what you could have been thinking. Only a crazy robot would ever go out into the rain without a raincoat!
You will have a long time to think about your mistake while you wait for another robot to come and help scrape off all the rust so you can move again. Since you never made it to the palace for your new job, you'll probably be fired.
**You have turned into a rusty robot statue!**
## [Outside](?raincoat&!stopped-raining)
You head out the door and into the rain. It's a good thing you put on your raincoat, because it's just the kind of rain that would probably turn you into a rusty robot statue if you stayed in it for too long.
You follow the road by your house all the way through town until you reach the door to the Robot Palace.
The palace guard looks you up and down. "What do you want?" he asks.
**What will you tell him?**
- ["I'm the new janitor-bot!"](/palace-entrance#new-job)
- ["I'd like a tour of the palace!"](/palace-entrance)
## Palace Entrance
The robot guard looks at you and [nods|frowns](?new-job). "[Oh yeah, they told me to expect you. You're supposed to be starting today right?|We don't do tours on weekdays. Hey, aren't you the new janitor-bot who's starting today?](?new-job)"
**How will you answer?**
- ["Yup!"](/palace-entrance)
- ["Nope!"](/back-to-bed)
## Back to Bed
The robot guard looks at you with a confused expression on his face, then stops paying attention to you.
I guess you decided that you don't want a new job today after all. You turn around and walk all the way back home, where you hop back into bed for a quick nap.
Without a job, you fail to earn any money and you can no longer afford fuel to power yourself.
**You run out of fuel and shut down forever!**
## Palace Entrance
> The robot guard nods and ushers you into the palace through the large front doors.
> "You'll want to report to the Master Janitor Robot downstairs. He'll give you your uniform and get you started," the guard says, then quickly leaves and shuts the doors behind him.
The palace entrance is one of the biggest rooms you've ever seen! There are statues of knight-robots and pictures of all of the old Robot Kings going back for centuries lining the walls. The picture closest to you is a picture of the current Robot King. He looks a lot like you!
There is a grand double staircase leading up to the throne room, a hallway straight ahead that leads to the living quarters, and a door to your left that says "Stairs."
**Where do you want to go?**
- [Go upstairs to the throne room.](/throne-room)
- [Go through the hall to the living quarters.](/living-quarters)
- [Go downstairs to see the Master Janitor Robot.](/palace-basement)
## Living Quarters
You walk into the hall that leads to the living quarters, and find a gate blocking your way. There is a robot scanner installed on the gate. I guess it only opens for robots who live or work here. Maybe the Master Janitor Robot will have a way for you to get through.
- [Go back to the palace entrance.](/palace-entrance#tried-gate)
## Palace Basement
> You walk down three flights of stairs until you reach the basement. The staircase is dark, but the basement is even darker. It's a little scary! You hope you can get the information you need from the Master Janitor Robot and get out of here as quickly as possible.
You're standing in the basement where new employees can pick up their uniforms and learn what their jobs are for the day.
[The Master Janitor Robot is pacing back and forth here, muttering to himself.|There is a funny looking robot here pacing back and forth, muttering to himself. That must be the Master Janitor Robot. When he notices you, he stops muttering and stares at you with crazy eyes.](#talked-to-master)
**What will you do?**
- [Go back upstairs.](/palace-entrance)
- [Ask the Master Janitor Robot what he's muttering about.](#talked-to-master+muttering)
- [Ask the Master Janitor Robot about your uniform.](#talked-to-master+uniform)
- [Ask the Master Janitor Robot about the gate upstairs.](?tried-gate#talked-to-master+about-the-gate)
- [Ask the Master Janitor Robot about your job.](?uniform#started-job)
### Muttering
"Muttering?" says the Master Janitor Robot. "Was I muttering? I hadn't noticed."
The Master Janitor Robot pauses thoughtfully for a moment, then resumes muttering.
### Uniform
The Master Janitor Robot's eyes light up a pleasant shade of blue. "Ahh, you must be the new janitor-bot starting today!" he says.
He walks to a box in the corner and pulls out a blue janitor's uniform, then hands it to you. You put it on.
### About the Gate
"Ahh, yes, the gate," says the Master Janitor Robot. "Quite a clever contraption. There's a scanner attached that looks for a special device that's sewn into the [uniform I gave you|uniform that employees here wear](?uniform).[ As I said, you'll want to head up there now to start cleaning room 13.](?started-job)"
### Started Job
"Ready to get going?" says the Master Janitor Robot. He continues before you have a chance to answer. "Good, good. Your first job will be to clean room 13 in the living quarters. That's where the Robot King keeps all of his spare robes and crowns. There's a janitor's closet right next to that room where you can get a mop to clean the floors, and a duster to dust off the crowns."
The Master Janitor Robot scratches his chin for a moment, then resumes pacing back and forth and muttering to himself.
## [Living Quarters](?)
You head into the hallway that leads to the living quarters and come to a large gate. A scanner attached to the gate lights up and beeps a few times. After a moment, you hear a click and a soft hiss as the gate opens to let you pass. Once you walk through, the gate hisses and clicks shut behind you.
You notice with some alarm that there's no scanner on the inside of the gate. You don't know how to get back out!
## [Living Quarters](?uniform&!job-started)
That's when you realize that you never asked the Master Janitor Bot what your job here was. You just took your uniform and left!
**You have failed to perform your new job because you never found out what it was.**
## [Living Quarters](?uniform&job-started)
That's no problem though, because you already know what your job is. You continue down the hall, looking at and passing all of the doors until you come to the one marked with a "13." Right next to it is another door labeled "Janitor's Closet."
You open the closet and grab the mop and duster. You're so excited! Your first day as a janitor working for a Robot King that looks just like you, and you are about to enter a room containing all of his spare robes and crowns. What fun!
**You have reached the end of the intro to The Robot King.**

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1402.2112" targetFramework="net45" />
<package id="ServiceStack.Text" version="4.0.22" targetFramework="net45" />
<package id="xunit" version="1.9.2" targetFramework="net45" />
</packages>

View File

@ -0,0 +1,87 @@
namespace Ficdown.Parser.Engine
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Model.Parser;
using Model.Story;
using Action = Model.Story.Action;
public class BlockHandler : IBlockHandler
{
public IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines)
{
var blocks = new List<Block>();
Block currentBlock = null;
foreach (var line in lines)
{
var match = Regex.Match(line, @"^(?<level>#{1,3})\s+(?<name>[^#].*)$");
if (match.Success)
{
if (currentBlock != null) blocks.Add(currentBlock);
currentBlock = new Block()
{
Type = (BlockType) match.Groups["level"].Length,
Name = match.Groups["name"].Value,
Lines = new List<string>()
};
}
else
{
if (currentBlock != null) currentBlock.Lines.Add(line);
}
}
if (currentBlock != null) blocks.Add(currentBlock);
return blocks;
}
public Story ParseBlocks(IEnumerable<Block> blocks)
{
// get the story
var storyBlock = blocks.Single(b => b.Type == BlockType.Story);
var storyName = RegexLib.Anchors.Match(storyBlock.Name);
if (!storyName.Success)
throw new FormatException("Story name must be a link to the first scene.");
var story = new Story
{
Name = storyName.Groups["text"].Value,
Description = string.Join("\n", storyBlock.Lines).Trim(),
Scenes = new Dictionary<string, IList<Scene>>(),
States = new Dictionary<string, IList<Action>>()
};
var scenes = blocks.Where(b => b.Type == BlockType.Scene).Select(b => BlockToScene(b));
foreach (var scene in scenes)
{
var key = Utilities.NormalizeString(scene.Name);
if (!story.Scenes.ContainsKey(key)) story.Scenes.Add(key, new List<Scene>());
story.Scenes[key].Add(scene);
}
return story;
}
private Scene BlockToScene(Block block)
{
var scene = new Scene
{
Description = string.Join("\n", block.Lines).Trim()
};
var sceneName = RegexLib.Anchors.Match(block.Name);
if (sceneName.Success)
{
scene.Name = sceneName.Groups["text"].Value;
}
else
{
scene.Name = block.Name;
}
return scene;
}
}
}

View File

@ -0,0 +1,12 @@
namespace Ficdown.Parser.Engine
{
using System.Collections.Generic;
using Model.Parser;
using Model.Story;
public interface IBlockHandler
{
IEnumerable<Block> ExtractBlocks(IEnumerable<string> lines);
Story ParseBlocks(IEnumerable<Block> blocks);
}
}

View File

@ -0,0 +1,40 @@
namespace Ficdown.Parser.Engine
{
using System.Text;
using System.Text.RegularExpressions;
internal static class RegexLib
{
public static Regex Anchors = new Regex(string.Format(@"(?<anchor>\[(?<text>{0})\]\([ ]*(?<href>{1})[ ]*\))",
GetNestedBracketsPattern(), GetNestedParensPattern()),
RegexOptions.Singleline | RegexOptions.Compiled);
private const int _nestDepth = 6;
private static string RepeatString(string text, int count)
{
var sb = new StringBuilder(text.Length * count);
for (int i = 0; i < count; i++)
sb.Append(text);
return sb.ToString();
}
private static string _nestedBracketsPattern;
private static string GetNestedBracketsPattern()
{
if (_nestedBracketsPattern == null)
_nestedBracketsPattern =
RepeatString(@"(?>[^\[\]]+|\[", _nestDepth) + RepeatString(@"\])*", _nestDepth);
return _nestedBracketsPattern;
}
private static string _nestedParensPattern;
private static string GetNestedParensPattern()
{
if (_nestedParensPattern == null)
_nestedParensPattern =
RepeatString(@"(?>[^()\s]+|\(", _nestDepth) + RepeatString(@"\))*", _nestDepth);
return _nestedParensPattern;
}
}
}

View File

@ -0,0 +1,12 @@
namespace Ficdown.Parser.Engine
{
using System.Text.RegularExpressions;
internal static class Utilities
{
public static string NormalizeString(string raw)
{
return Regex.Replace(Regex.Replace(raw.ToLower(), @"^\W+|\W+$", string.Empty), @"\W+", "-");
}
}
}

View File

@ -0,0 +1,24 @@
namespace Ficdown.Parser
{
using System.IO;
using Engine;
using Model.Story;
public class FicdownParser
{
private IBlockHandler _blockHandler;
public IBlockHandler BlockHandler
{
get { return _blockHandler ?? (_blockHandler = new BlockHandler()); }
set { _blockHandler = value; }
}
public Story ParseStory(string storyFilePath)
{
var lines = File.ReadAllLines(storyFilePath);
var blocks = BlockHandler.ExtractBlocks(lines);
return BlockHandler.ParseBlocks(blocks);
}
}
}

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<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')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{780F652D-7541-4171-BB89-2D263D3961DC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Ficdown.Parser</RootNamespace>
<AssemblyName>Ficdown.Parser</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Engine\BlockHandler.cs" />
<Compile Include="Engine\IBlockHandler.cs" />
<Compile Include="Engine\RegexLib.cs" />
<Compile Include="Engine\Utilities.cs" />
<Compile Include="FicDownParser.cs" />
<Compile Include="Model\Parser\Block.cs" />
<Compile Include="Model\Parser\BlockType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Model\Story\Action.cs" />
<Compile Include="Model\Story\Scene.cs" />
<Compile Include="Model\Story\Story.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,11 @@
namespace Ficdown.Parser.Model.Parser
{
using System.Collections.Generic;
public class Block
{
public BlockType Type { get; set; }
public string Name { get; set; }
public IList<string> Lines { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Ficdown.Parser.Model.Parser
{
public enum BlockType
{
Story = 1,
Scene = 2,
Action = 3
}
}

View File

@ -0,0 +1,11 @@
namespace Ficdown.Parser.Model.Story
{
using System.Collections.Generic;
public class Action
{
public string Description { get; set; }
public IList<string> Conditions { get; set; }
public IList<string> Toggles { get; set; }
}
}

View File

@ -0,0 +1,12 @@
namespace Ficdown.Parser.Model.Story
{
using System.Collections.Generic;
public class Scene
{
public string Name { get; set; }
public string Description { get; set; }
public IList<string> Conditions { get; set; }
public IList<string> Toggles { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Ficdown.Parser.Model.Story
{
using System.Collections.Generic;
public class Story
{
public string Name { get; set; }
public string Description { get; set; }
public Scene FirstScene { get; set; }
public IDictionary<string, IList<Action>> States { get; set; }
public IDictionary<string, IList<Scene>> Scenes { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FicDown.Parser")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FicDown.Parser")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f59ab52e-a106-4ed4-b004-71f417a67edf")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

28
Ficdown.sln Normal file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.30110.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ficdown.Parser", "Ficdown.Parser\Ficdown.Parser.csproj", "{780F652D-7541-4171-BB89-2D263D3961DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ficdown.Parser.Tests", "Ficdown.Parser.Tests\Ficdown.Parser.Tests.csproj", "{756192E2-BA47-4850-8096-289D44878A7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{780F652D-7541-4171-BB89-2D263D3961DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{780F652D-7541-4171-BB89-2D263D3961DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{780F652D-7541-4171-BB89-2D263D3961DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{780F652D-7541-4171-BB89-2D263D3961DC}.Release|Any CPU.Build.0 = Release|Any CPU
{756192E2-BA47-4850-8096-289D44878A7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{756192E2-BA47-4850-8096-289D44878A7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{756192E2-BA47-4850-8096-289D44878A7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{756192E2-BA47-4850-8096-289D44878A7E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal