Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Rudis Muiznieks | f09aab9f82 | |
Rudis Muiznieks | 03a612bfc0 | |
Rudis Muiznieks | 3a81abf1b1 | |
Rudis Muiznieks | 7970c6cd4b | |
Rudis Muiznieks | f7cab514cc | |
Rudis Muiznieks | 44ed5f165b | |
Rudis Muiznieks | bace78ff2b |
|
@ -84,6 +84,9 @@
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Initialize(debug);
|
||||||
|
var logger = Logger.GetLogger<Program>();
|
||||||
|
|
||||||
var lintMode = format == "lint";
|
var lintMode = format == "lint";
|
||||||
|
|
||||||
if(!lintMode)
|
if(!lintMode)
|
||||||
|
@ -95,7 +98,7 @@
|
||||||
}
|
}
|
||||||
if (!File.Exists(infile))
|
if (!File.Exists(infile))
|
||||||
{
|
{
|
||||||
Console.WriteLine(@"Source file {0} not found.", infile);
|
logger.Error($"Source file {infile} not found.");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(output))
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
|
@ -108,28 +111,28 @@
|
||||||
|
|
||||||
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);
|
logger.Error($"Specified output {output} already exists.");
|
||||||
return 2;
|
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);
|
logger.Error($"Template directory {tempdir} does not exist.");
|
||||||
return 2;
|
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")) ||
|
||||||
!File.Exists(Path.Combine(tempdir, "styles.css")))
|
!File.Exists(Path.Combine(tempdir, "styles.css")))
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
logger.Error(
|
||||||
@"Template directory must contain ""index.html"", ""scene.html"", and ""style.css"" files.");
|
@"Template directory must contain ""index.html"", ""scene.html"", and ""style.css"" files.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images))
|
if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images))
|
||||||
{
|
{
|
||||||
Console.WriteLine(@"Images directory {0} does not exist.", images);
|
logger.Error($"Images directory {images} does not exist.");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +143,7 @@
|
||||||
if(!lintMode)
|
if(!lintMode)
|
||||||
{
|
{
|
||||||
storyText = File.ReadAllText(infile);
|
storyText = File.ReadAllText(infile);
|
||||||
Console.WriteLine(@"Parsing story...");
|
logger.Log(@"Parsing story...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -149,8 +152,8 @@
|
||||||
|
|
||||||
var story = parser.ParseStory(storyText);
|
var story = parser.ParseStory(storyText);
|
||||||
|
|
||||||
parser.Warnings.Select(w => w.ToString()).Distinct().ToList().ForEach(s => Console.WriteLine(s));
|
parser.Warnings.Select(w => w.ToString()).Distinct().ToList().ForEach(s => logger.Raw(s));
|
||||||
story.Orphans.ToList().ForEach(o => Console.WriteLine("Warning L{0},1: \"{1}\": Unreachable {2}", o.LineNumber, o.Name, o.Type));
|
story.Orphans.ToList().ForEach(o => logger.Raw($"Warning L{o.LineNumber},1: \"{o.Name}\": Unreachable {o.Type}"));
|
||||||
|
|
||||||
if(!lintMode && parser.Warnings.Count() == 0)
|
if(!lintMode && parser.Warnings.Count() == 0)
|
||||||
{
|
{
|
||||||
|
@ -164,7 +167,7 @@
|
||||||
case "epub":
|
case "epub":
|
||||||
if (string.IsNullOrWhiteSpace(author))
|
if (string.IsNullOrWhiteSpace(author))
|
||||||
{
|
{
|
||||||
Console.WriteLine(@"Epub format requires the --author argument.");
|
logger.Error(@"Epub format requires the --author argument.");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
rend = new EpubRenderer(author, bookid, language);
|
rend = new EpubRenderer(author, bookid, language);
|
||||||
|
@ -183,11 +186,11 @@
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(images)) rend.ImageDir = images;
|
if (!string.IsNullOrWhiteSpace(images)) rend.ImageDir = images;
|
||||||
|
|
||||||
Console.WriteLine(@"Rendering story...");
|
logger.Log(@"Rendering story...");
|
||||||
|
|
||||||
rend.Render(story, output, debug);
|
rend.Render(story, output, debug);
|
||||||
|
|
||||||
Console.WriteLine(@"Done.");
|
logger.Log(@"Done.");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -196,16 +199,18 @@
|
||||||
private static void ShowHelp()
|
private static void ShowHelp()
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
@"Usage: ficdown.exe
|
@"Usage: ficdown
|
||||||
--format (html|epub|lint)
|
--format (html|epub)
|
||||||
--in ""/path/to/source.md"" (lint reads sdtin)
|
--in ""/path/to/source.md""
|
||||||
[--out ""/path/to/output""]
|
[--out ""/path/to/output""]
|
||||||
[--template ""/path/to/template/dir""]
|
[--template ""/path/to/template/dir""]
|
||||||
[--images ""/path/to/images/dir""]
|
[--images ""/path/to/images/dir""]
|
||||||
[--author ""Author Name""]
|
[--author ""Author Name""]
|
||||||
[--bookid ""ePub Book ID""]
|
[--bookid ""ePub Book ID""]
|
||||||
[--language ""language""]
|
[--language ""language""]
|
||||||
[--debug]");
|
[--debug]
|
||||||
|
or: ficdown --format lint
|
||||||
|
(reads input from stdin)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanParseValidStoryFile()
|
public void CanParseValidStoryFile()
|
||||||
{
|
{
|
||||||
|
Logger.Initialize(true);
|
||||||
var parser = new FicdownParser();
|
var parser = new FicdownParser();
|
||||||
var storyText = File.ReadAllText(Path.Combine(Template.BaseDir, "TestStories", "CloakOfDarkness.md"));
|
var storyText = File.ReadAllText(Path.Combine(Template.BaseDir, "TestStories", "CloakOfDarkness.md"));
|
||||||
var story = parser.ParseStory(storyText);
|
var story = parser.ParseStory(storyText);
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Ficdown.Parser
|
||||||
|
|
||||||
public class FicdownParser
|
public class FicdownParser
|
||||||
{
|
{
|
||||||
|
private static Logger _logger = Logger.GetLogger<FicdownParser>();
|
||||||
public List<FicdownException> Warnings { get; private set; }
|
public List<FicdownException> Warnings { get; private set; }
|
||||||
|
|
||||||
private IBlockHandler _blockHandler;
|
private IBlockHandler _blockHandler;
|
||||||
|
@ -64,8 +65,11 @@ namespace Ficdown.Parser
|
||||||
public ResolvedStory ParseStory(string storyText)
|
public ResolvedStory ParseStory(string storyText)
|
||||||
{
|
{
|
||||||
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
|
||||||
|
_logger.Debug($"Parsed {lines.Length} lines.");
|
||||||
var blocks = BlockHandler.ExtractBlocks(lines);
|
var blocks = BlockHandler.ExtractBlocks(lines);
|
||||||
|
_logger.Debug($"Extracted {blocks.Count()} blocks.");
|
||||||
var story = BlockHandler.ParseBlocks(blocks);
|
var story = BlockHandler.ParseBlocks(blocks);
|
||||||
|
_logger.Debug("Finished initial story breakdown.");
|
||||||
|
|
||||||
// dupe scene sanity check
|
// dupe scene sanity check
|
||||||
foreach(var key in story.Scenes.Keys)
|
foreach(var key in story.Scenes.Keys)
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Epub4Net" Version="1.2.0" />
|
<PackageReference Include="Epub4Net" Version="1.2.0" />
|
||||||
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" />
|
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" />
|
||||||
<PackageReference Include="MarkdownSharp" Version="2.0.5" />
|
<PackageReference Include="Markdig" Version="0.17.1" />
|
||||||
|
<PackageReference Include="System.Security.Permissions" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
namespace Ficdown.Parser
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class Logger
|
||||||
|
{
|
||||||
|
private static bool _initialized = false;
|
||||||
|
private static bool _debug = false;
|
||||||
|
private static Dictionary<Type, Logger> _cache;
|
||||||
|
|
||||||
|
public Type Type { get; private set; }
|
||||||
|
|
||||||
|
private Logger(Type type)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize(bool debug)
|
||||||
|
{
|
||||||
|
_debug = debug;
|
||||||
|
_cache = new Dictionary<Type, Logger>();
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Logger GetLogger<T>()
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
lock(_cache)
|
||||||
|
{
|
||||||
|
if(!_cache.ContainsKey(type))
|
||||||
|
_cache.Add(type, new Logger(type));
|
||||||
|
}
|
||||||
|
return _cache[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Decorate(string message)
|
||||||
|
{
|
||||||
|
return $"{DateTime.Now.ToString("")} <{Type.Name}> {message}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Raw(string message)
|
||||||
|
{
|
||||||
|
Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string message)
|
||||||
|
{
|
||||||
|
Raw(Decorate(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Debug(string message)
|
||||||
|
{
|
||||||
|
if(!_debug) return;
|
||||||
|
Log($"DEBUG: {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string message, Exception ex = null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(Decorate($"ERROR: {message}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
internal class PageState
|
internal class PageState
|
||||||
{
|
{
|
||||||
|
public StateManager Manager { get; set; }
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Scene Scene { get; set; }
|
public Scene Scene { get; set; }
|
||||||
public State State { get; set; }
|
public State State { get; set; }
|
||||||
|
@ -21,12 +23,12 @@
|
||||||
|
|
||||||
public string UniqueHash
|
public string UniqueHash
|
||||||
{
|
{
|
||||||
get { return _uniqueHash ?? (_uniqueHash = StateManager.GetUniqueHash(State, Scene.Key)); }
|
get { return _uniqueHash ?? (_uniqueHash = Manager.GetUniqueHash(State, Scene.Key)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CompressedHash
|
public string CompressedHash
|
||||||
{
|
{
|
||||||
get { return _compressedHash ?? (_compressedHash = StateManager.GetCompressedHash(this)); }
|
get { return _compressedHash ?? (_compressedHash = Manager.GetCompressedHash(this)); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
internal class StateResolver : IStateResolver
|
internal class StateResolver : IStateResolver
|
||||||
{
|
{
|
||||||
|
private static Logger _logger = Logger.GetLogger<StateResolver>();
|
||||||
private static readonly Random _random = new Random((int) DateTime.Now.Ticks);
|
private static readonly Random _random = new Random((int) DateTime.Now.Ticks);
|
||||||
private readonly IDictionary<string, string> _pageNames;
|
private readonly IDictionary<string, string> _pageNames;
|
||||||
private readonly HashSet<string> _usedNames;
|
private readonly HashSet<string> _usedNames;
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
|
|
||||||
public ResolvedStory Resolve(IEnumerable<PageState> pages, Story story)
|
public ResolvedStory Resolve(IEnumerable<PageState> pages, Story story)
|
||||||
{
|
{
|
||||||
|
_logger.Debug("Resolving story paths...");
|
||||||
_story = story;
|
_story = story;
|
||||||
return new ResolvedStory
|
return new ResolvedStory
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,6 +9,15 @@
|
||||||
{
|
{
|
||||||
private List<FicdownException> _warnings { get; set; }
|
private List<FicdownException> _warnings { get; set; }
|
||||||
|
|
||||||
|
public static Utilities GetInstance()
|
||||||
|
{
|
||||||
|
return new Utilities
|
||||||
|
{
|
||||||
|
_warnings = new List<FicdownException>(),
|
||||||
|
_blockName = string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static Utilities GetInstance(List<FicdownException> warnings, string blockName, int lineNumber)
|
public static Utilities GetInstance(List<FicdownException> warnings, string blockName, int lineNumber)
|
||||||
{
|
{
|
||||||
return new Utilities
|
return new Utilities
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
internal class GameTraverser : IGameTraverser
|
internal class GameTraverser : IGameTraverser
|
||||||
{
|
{
|
||||||
|
private static Logger _logger = Logger.GetLogger<GameTraverser>();
|
||||||
private StateManager _manager;
|
private StateManager _manager;
|
||||||
private Queue<StateQueueItem> _processingQueue;
|
private Queue<StateQueueItem> _processingQueue;
|
||||||
private IDictionary<string, PageState> _processed;
|
private IDictionary<string, PageState> _processed;
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
_wasRun = true;
|
_wasRun = true;
|
||||||
|
|
||||||
// generate comprehensive enumeration
|
// generate comprehensive enumeration
|
||||||
|
_logger.Debug("Enumerating story scenes...");
|
||||||
|
|
||||||
var initial = _manager.InitialState;
|
var initial = _manager.InitialState;
|
||||||
_processingQueue.Enqueue(new StateQueueItem
|
_processingQueue.Enqueue(new StateQueueItem
|
||||||
|
@ -66,6 +68,7 @@
|
||||||
Page = initial,
|
Page = initial,
|
||||||
AffectedStates = new List<State> {initial.AffectedState}
|
AffectedStates = new List<State> {initial.AffectedState}
|
||||||
});
|
});
|
||||||
|
var interval = 0;
|
||||||
while (_processingQueue.Count > 0)
|
while (_processingQueue.Count > 0)
|
||||||
{
|
{
|
||||||
var state = _processingQueue.Dequeue();
|
var state = _processingQueue.Dequeue();
|
||||||
|
@ -74,9 +77,13 @@
|
||||||
_processed.Add(state.Page.UniqueHash, state.Page);
|
_processed.Add(state.Page.UniqueHash, state.Page);
|
||||||
ProcessState(state);
|
ProcessState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(++interval % 100 == 0 || _processingQueue.Count == 0)
|
||||||
|
_logger.Debug($"Processed {interval} scenes, {_processingQueue.Count} queued...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure every page gets affected data on every page that it links to
|
// make sure every page gets affected data on every page that it links to
|
||||||
|
_logger.Debug("Processing scene links...");
|
||||||
foreach (var pageTuple in _processed)
|
foreach (var pageTuple in _processed)
|
||||||
{
|
{
|
||||||
foreach (var linkTuple in pageTuple.Value.Links)
|
foreach (var linkTuple in pageTuple.Value.Links)
|
||||||
|
@ -95,6 +102,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// compress redundancies
|
// compress redundancies
|
||||||
|
_logger.Debug("Compressing redundant scenes...");
|
||||||
foreach (var row in _processed)
|
foreach (var row in _processed)
|
||||||
{
|
{
|
||||||
if (!_compressed.ContainsKey(row.Value.CompressedHash))
|
if (!_compressed.ContainsKey(row.Value.CompressedHash))
|
||||||
|
|
|
@ -11,10 +11,12 @@
|
||||||
|
|
||||||
internal class StateManager
|
internal class StateManager
|
||||||
{
|
{
|
||||||
|
private static Logger _logger = Logger.GetLogger<StateManager>();
|
||||||
private readonly Story _story;
|
private readonly Story _story;
|
||||||
private readonly Dictionary<string, int> _stateMatrix;
|
private readonly Dictionary<string, int> _stateMatrix;
|
||||||
private readonly int _sceneCount;
|
private readonly int _sceneCount;
|
||||||
private readonly int _actionCount;
|
private readonly int _actionCount;
|
||||||
|
private BitArray _scenesSeenMask;
|
||||||
|
|
||||||
private List<FicdownException> _warnings { get; set; }
|
private List<FicdownException> _warnings { get; set; }
|
||||||
|
|
||||||
|
@ -24,6 +26,18 @@
|
||||||
_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);
|
||||||
|
_scenesSeenMask = new BitArray(_sceneCount);
|
||||||
|
|
||||||
|
// figure out which scenes can affect state
|
||||||
|
var masked = 0;
|
||||||
|
foreach(var scene in allScenes)
|
||||||
|
{
|
||||||
|
if(Utilities.GetInstance().ParseAnchors(scene.RawDescription).Any(a => a.Href.Toggles != null) || RegexLib.BlockQuotes.IsMatch(scene.RawDescription))
|
||||||
|
{
|
||||||
|
_scenesSeenMask[scene.Id - 1] = true;
|
||||||
|
masked++;
|
||||||
|
}
|
||||||
|
}
|
||||||
_actionCount = _story.Actions.Count > 0 ? _story.Actions.Max(a => a.Value.Id) : 0;
|
_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;
|
||||||
|
@ -40,6 +54,9 @@
|
||||||
{
|
{
|
||||||
_stateMatrix.Add(toggle, state++);
|
_stateMatrix.Add(toggle, state++);
|
||||||
}
|
}
|
||||||
|
_logger.Debug($"{_sceneCount} scenes ({masked} can change state).");
|
||||||
|
_logger.Debug($"{_actionCount} actions.");
|
||||||
|
_logger.Debug($"{_stateMatrix.Count()} states.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageState InitialState
|
public PageState InitialState
|
||||||
|
@ -54,6 +71,8 @@
|
||||||
|
|
||||||
return new PageState
|
return new PageState
|
||||||
{
|
{
|
||||||
|
Manager = this,
|
||||||
|
|
||||||
Id = Guid.Empty,
|
Id = Guid.Empty,
|
||||||
Links = new Dictionary<string, string>(),
|
Links = new Dictionary<string, string>(),
|
||||||
State = new State
|
State = new State
|
||||||
|
@ -119,14 +138,14 @@
|
||||||
state.ScenesSeen[sceneId - 1] = true;
|
state.ScenesSeen[sceneId - 1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetUniqueHash(State state, string sceneKey)
|
public string GetUniqueHash(State state, string sceneKey)
|
||||||
{
|
{
|
||||||
var combined =
|
var combined =
|
||||||
new bool[
|
new bool[
|
||||||
state.PlayerState.Count + state.ScenesSeen.Count + state.ActionsToShow.Count +
|
state.PlayerState.Count + state.ScenesSeen.Count + state.ActionsToShow.Count +
|
||||||
(state.ActionFirstToggles != null ? state.ActionFirstToggles.Count : 0)];
|
(state.ActionFirstToggles != null ? state.ActionFirstToggles.Count : 0)];
|
||||||
state.PlayerState.CopyTo(combined, 0);
|
state.PlayerState.CopyTo(combined, 0);
|
||||||
state.ScenesSeen.CopyTo(combined, state.PlayerState.Count);
|
state.ScenesSeen.And(_scenesSeenMask).CopyTo(combined, state.PlayerState.Count);
|
||||||
state.ActionsToShow.CopyTo(combined, state.PlayerState.Count + state.ScenesSeen.Count);
|
state.ActionsToShow.CopyTo(combined, state.PlayerState.Count + state.ScenesSeen.Count);
|
||||||
if (state.ActionFirstToggles != null)
|
if (state.ActionFirstToggles != null)
|
||||||
state.ActionFirstToggles.CopyTo(combined,
|
state.ActionFirstToggles.CopyTo(combined,
|
||||||
|
@ -134,11 +153,15 @@
|
||||||
var ba = new BitArray(combined);
|
var ba = new BitArray(combined);
|
||||||
var byteSize = (int)Math.Ceiling(combined.Length / 8.0);
|
var byteSize = (int)Math.Ceiling(combined.Length / 8.0);
|
||||||
var encoded = new byte[byteSize];
|
var encoded = new byte[byteSize];
|
||||||
|
for(var i = 0; i < byteSize; i++)
|
||||||
|
{
|
||||||
|
encoded[i] = 0;
|
||||||
|
}
|
||||||
ba.CopyTo(encoded, 0);
|
ba.CopyTo(encoded, 0);
|
||||||
return string.Format("{0}=={1}", sceneKey, Convert.ToBase64String(encoded));
|
return string.Format("{0}=={1}", sceneKey, Convert.ToBase64String(encoded));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetCompressedHash(PageState page)
|
public string GetCompressedHash(PageState page)
|
||||||
{
|
{
|
||||||
var compressed = new State
|
var compressed = new State
|
||||||
{
|
{
|
||||||
|
@ -159,7 +182,7 @@
|
||||||
{
|
{
|
||||||
if (ConditionsMatch(scene, playerState) &&
|
if (ConditionsMatch(scene, playerState) &&
|
||||||
(newScene == null || newScene.Conditions == null ||
|
(newScene == null || newScene.Conditions == null ||
|
||||||
scene.Conditions.Count > newScene.Conditions.Count))
|
(scene.Conditions != null && scene.Conditions.Count > newScene.Conditions.Count)))
|
||||||
{
|
{
|
||||||
newScene = scene;
|
newScene = scene;
|
||||||
}
|
}
|
||||||
|
@ -187,6 +210,8 @@
|
||||||
{
|
{
|
||||||
return new PageState
|
return new PageState
|
||||||
{
|
{
|
||||||
|
Manager = this,
|
||||||
|
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
Links = new Dictionary<string, string>(),
|
Links = new Dictionary<string, string>(),
|
||||||
State = new State
|
State = new State
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
|
|
||||||
public class EpubRenderer : HtmlRenderer
|
public class EpubRenderer : HtmlRenderer
|
||||||
{
|
{
|
||||||
|
private static readonly Logger _logger = Logger.GetLogger<EpubRenderer>();
|
||||||
private readonly string _author;
|
private readonly string _author;
|
||||||
private readonly string _bookId;
|
private readonly string _bookId;
|
||||||
private readonly string _language;
|
private readonly string _language;
|
||||||
|
@ -103,6 +104,7 @@
|
||||||
|
|
||||||
public override void Render(Model.Parser.ResolvedStory story, string outPath, bool debug = false)
|
public override void Render(Model.Parser.ResolvedStory story, string outPath, bool debug = false)
|
||||||
{
|
{
|
||||||
|
_logger.Debug("Generating epub...");
|
||||||
var temppath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
var temppath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
Directory.CreateDirectory(temppath);
|
Directory.CreateDirectory(temppath);
|
||||||
base.Render(story, temppath, debug);
|
base.Render(story, temppath, debug);
|
||||||
|
|
|
@ -3,16 +3,15 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MarkdownSharp;
|
using Markdig;
|
||||||
using Model.Parser;
|
using Model.Parser;
|
||||||
using Parser;
|
using Parser;
|
||||||
|
|
||||||
public class HtmlRenderer : IRenderer
|
public class HtmlRenderer : IRenderer
|
||||||
{
|
{
|
||||||
|
private static Logger _logger = Logger.GetLogger<HtmlRenderer>();
|
||||||
private readonly string _language;
|
private readonly string _language;
|
||||||
|
|
||||||
protected readonly Markdown Markdown;
|
|
||||||
|
|
||||||
public List<FicdownException> Warnings { private get; set; }
|
public List<FicdownException> Warnings { private get; set; }
|
||||||
|
|
||||||
public string IndexTemplate { get; set; }
|
public string IndexTemplate { get; set; }
|
||||||
|
@ -25,7 +24,6 @@
|
||||||
public HtmlRenderer(string language)
|
public HtmlRenderer(string language)
|
||||||
{
|
{
|
||||||
_language = language;
|
_language = language;
|
||||||
Markdown = new Markdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Render(ResolvedStory story, string outPath, bool debug = false)
|
public virtual void Render(ResolvedStory story, string outPath, bool debug = false)
|
||||||
|
@ -42,11 +40,12 @@
|
||||||
|
|
||||||
protected void GenerateHtml(ResolvedStory story, string outPath, bool debug)
|
protected void GenerateHtml(ResolvedStory story, string outPath, bool debug)
|
||||||
{
|
{
|
||||||
|
_logger.Debug("Generating HTML...");
|
||||||
var index = FillTemplate(IndexTemplate ?? Template.Index, new Dictionary<string, string>
|
var index = FillTemplate(IndexTemplate ?? Template.Index, new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"Language", _language},
|
{"Language", _language},
|
||||||
{"Title", story.Name},
|
{"Title", story.Name},
|
||||||
{"Description", Markdown.Transform(story.Description)},
|
{"Description", Markdown.ToHtml(story.Description)},
|
||||||
{"FirstScene", string.Format("{0}.html", story.FirstPage)}
|
{"FirstScene", string.Format("{0}.html", story.FirstPage)}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@
|
||||||
{
|
{
|
||||||
{"Language", _language},
|
{"Language", _language},
|
||||||
{"Title", story.Name},
|
{"Title", story.Name},
|
||||||
{"Content", Markdown.Transform(content)}
|
{"Content", Markdown.ToHtml(content)}
|
||||||
});
|
});
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(outPath, string.Format("{0}.html", page.Name)), scene);
|
File.WriteAllText(Path.Combine(outPath, string.Format("{0}.html", page.Name)), scene);
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Rudis Muiznieks
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
2
Makefile
2
Makefile
|
@ -10,7 +10,7 @@ test:
|
||||||
dotnet test Ficdown.Parser.Tests
|
dotnet test Ficdown.Parser.Tests
|
||||||
|
|
||||||
publish: clean
|
publish: clean
|
||||||
rm /tmp/ficdown*
|
rm -rf /tmp/ficdown*
|
||||||
dotnet publish --self-contained -c Release -r linux-x64 Ficdown.Console
|
dotnet publish --self-contained -c Release -r linux-x64 Ficdown.Console
|
||||||
tar -C Ficdown.Console/bin/Release/netcoreapp2.1/linux-x64/publish -cvzf /tmp/ficdown-linux64.tar.gz .
|
tar -C Ficdown.Console/bin/Release/netcoreapp2.1/linux-x64/publish -cvzf /tmp/ficdown-linux64.tar.gz .
|
||||||
dotnet publish --self-contained -c Release -r win-x64 Ficdown.Console
|
dotnet publish --self-contained -c Release -r win-x64 Ficdown.Console
|
||||||
|
|
12
README.md
12
README.md
|
@ -1,6 +1,6 @@
|
||||||
# Ficdown
|
# Ficdown
|
||||||
|
|
||||||
Ficdown is a system for building interactive fiction using MarkDown syntax. See [Ficdown.com](http://www.ficdown.com) for more information.
|
Ficdown is a system for building interactive fiction using MarkDown syntax.
|
||||||
|
|
||||||
This project contains the core Ficdown library for parsing Ficdown stories, as well as a console application that can be used to generate HTML or epub ebook formats.
|
This project contains the core Ficdown library for parsing Ficdown stories, as well as a console application that can be used to generate HTML or epub ebook formats.
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Ficdown is written using .NET Core and should run on Windows, Linux, and OSX wit
|
||||||
|
|
||||||
## Obtaining
|
## Obtaining
|
||||||
|
|
||||||
If you want to use Ficdown to convert your stories into ebooks, download the latest version from the [releases](https://github.com/rudism/Ficdown/releases) page and decompress it somewhere on your hard drive. Ficdown does not include an installer, the application and all of its dependencies are included directly in the zip archive.
|
If you want to use Ficdown to convert your stories into ebooks, download the latest version from the [releases](https://code.sitosis.com/rudism/ficdown/releases) page and decompress it somewhere on your hard drive. Ficdown does not include an installer, the application and all of its dependencies are included directly in the zip archive.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -85,6 +85,10 @@ If you pass this option, all of the pages in your story will include output at t
|
||||||
|
|
||||||
To generate other formats than HTML or epub, you will have to use third party tools. [Calibre](http://www.calibre-ebook.com) is a popular ebook management suite that includes the ability to convert books from almost any format to any other format. Also, Amazon has an official tool called [KindleGen](http://www.amazon.com/gp/feature.html?docId=1000765211) that you can use to convert your epub to a format that can be read on Kindles.
|
To generate other formats than HTML or epub, you will have to use third party tools. [Calibre](http://www.calibre-ebook.com) is a popular ebook management suite that includes the ability to convert books from almost any format to any other format. Also, Amazon has an official tool called [KindleGen](http://www.amazon.com/gp/feature.html?docId=1000765211) that you can use to convert your epub to a format that can be read on Kindles.
|
||||||
|
|
||||||
### Interactive Website
|
## Additional Tools
|
||||||
|
|
||||||
Ficdown stories can be played interactively in a web browser without even requiring the command line utility here. See [Ficdown.js](https://github.com/rudism/Ficdown.js) for a Javascript Ficdown parser and interpreter that you can include on your own website to present your Ficdown stories.
|
- Ficdown stories can be played interactively in a web browser without even requiring the command line utility here. See [Ficdown.js](https://code.sitosis.com/rudism/ficdown.js) for a Javascript Ficdown parser and interpreter that you can include on your own website to present your Ficdown stories.
|
||||||
|
|
||||||
|
- [Ficdown-editor](https://byfernanz.github.io/ficdown-editor/) is a web-based GUI for writing Ficdown.
|
||||||
|
|
||||||
|
- [Prop](https://github.com/ByFernanz/prop) is a YAML-header style preprocessor for Ficdown
|
||||||
|
|
Loading…
Reference in New Issue