Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
af618a5788
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,3 +4,6 @@ build/
|
|||
*.org
|
||||
*.pdf
|
||||
*~
|
||||
example2.dot
|
||||
example2.md
|
||||
*.rtf
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Pelle Nilsson
|
||||
Copyright (c) 2021-2023 Pelle Nilsson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
208
README.md
208
README.md
|
@ -1 +1,207 @@
|
|||
Pangamebook is copyright (c) 2021 Pelle Nilsson, MIT license
|
||||
# Pangamebook
|
||||
[Pandoc](https://pandoc.org) is a free tool that converts documents between a
|
||||
large number of file formats. *Pangamebook* is a filter that can be used with
|
||||
Pandoc to shuffle and number sections in the document being converted. The only
|
||||
known use-case is to create a classic [gamebook](https://en.wikipedia.org/wiki/Gamebook).
|
||||
|
||||
Pandoc and Pangamebook should run on most modern computers. It has been tested
|
||||
on desktop computers running Windows 10, Linux (Lubuntu and Debian) and FreeBSD
|
||||
13.0. Also on a Raspberry Pi 4 running Raspberry Pi OS and on an Android phone
|
||||
(in Termux). Tested versions of Pandoc include 2.9.2.1 and 3.0.
|
||||
|
||||
(TBD: Test in OSX.)
|
||||
|
||||
# Installing
|
||||
To use this filter you need to have Pandoc 2.1 or later installed (see
|
||||
[https://pandoc.org/installing.html]). Also see the [Pandoc Getting Started
|
||||
Article](https://pandoc.org/getting-started.html) if you never used Pandoc
|
||||
before.
|
||||
|
||||
The Pangamebook filter itself does not have to be installed. The file
|
||||
*pangamebook.lua* must be copied to somewhere on your computer and
|
||||
named on the command-line when running pandoc (see examples below).
|
||||
|
||||
## Windows
|
||||
Install Pandoc. Install MikTex (as linked from the Pandoc download page).
|
||||
Restart computer. Download *pangamebook.lua* and *example.md*.
|
||||
That should be enough to be able to run all the
|
||||
examples below. Otherwise search for help.
|
||||
|
||||
# Input Document
|
||||
First you need to write your gamebook. The recommended format is [Pandoc's
|
||||
Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown). That is the format
|
||||
used for this README file and there is also an *example.md* document in this
|
||||
repository.
|
||||
|
||||
Other formats are also possible, but it can be tricky to make Pandoc and
|
||||
Pangamebook to properly interpret cross-references in some formats. Also
|
||||
Pandoc's Markdown supports inserting meta-data and inlining style information
|
||||
that can be very useful for advanced users, so it can be a good idea to get used
|
||||
to that format. Most modern text editors support Markdown, so
|
||||
it should not be difficult to get started.
|
||||
|
||||
# What the Filter Does
|
||||
Pangamebook looks for all top-level headers that contain of only lowercase
|
||||
letters, digits, and underscores. Headers like *start*, *first_room*, or
|
||||
*finding_some_loot_23* will be affected, but headers like *Introduction*, *How
|
||||
To Play*, *Character Sheet*, or *Epilogue* will be ignored.
|
||||
|
||||
A top-level header that is a (positive) number will also be ignored, as that
|
||||
number will be used as-is instead.
|
||||
|
||||
Pangamebook shuffles all affected headers together with everything that
|
||||
follows it up until the next top-level header, including lower-level headers
|
||||
and all text and images and tables etc. That collection of things that
|
||||
is moved together with the header is considered a *section*.
|
||||
|
||||
Sections will never be moved from before an ignored top-level header to after
|
||||
that header, or vice-versa. An important effect of this is that headers that are
|
||||
numbers, like **1** will *stay where they are*, and will also naturally divide
|
||||
the gamebook into parts that keeps the story from jump around too much. Most
|
||||
books will probably have a **1** header to mark the beginning of the story, and
|
||||
that will be guaranteed to remain the first one in the output as well. If the
|
||||
last header has a sufficiently high number (say **400**) it will remain the
|
||||
last. Any other header can also have a number to fix it in the story, but if
|
||||
there are too many sections to shuffle in between fixed headers the filter will
|
||||
not be happy (say if you fix **1** and **400**, but there are actually 410
|
||||
sections in the book).
|
||||
|
||||
After shuffling all sections that are to be shuffled, all their headings
|
||||
are numbered in sequence. There may be gaps created where there are
|
||||
headers that were given a fixed number. Headers that were already numbers,
|
||||
as mentioned above, will not be affected.
|
||||
|
||||
All cross-references in the document will lastly be updated to display
|
||||
the number they refer to, so what was in the original document
|
||||
"see first_room" (where *first_room* is a valid cross-reference, not just text)
|
||||
will become something like "see 12".
|
||||
|
||||
The best way to learn is probably to experiment with the included
|
||||
*example.md* and skim some of Pandoc's documentation.
|
||||
|
||||
# Output Document
|
||||
Most or all of the output formats Pandoc support should be possible (e.g. EPUB,
|
||||
PDF, HTML). By default Pandoc is going to remove almost all styling from
|
||||
documents as part of converting them, but see [Pandoc's User
|
||||
Guide](https://pandoc.org/MANUAL.html) for information on all the ways you can
|
||||
add style to the output document.
|
||||
|
||||
# Configuration
|
||||
Pandoc Metadata can be used to configure the output of pangamebook. The
|
||||
*-M* (or *--metadata*) flag is used for pandoc to add metadata variables.
|
||||
The following variables are supported. Values can also be set in the
|
||||
input document for file formats that support metadata blocks (e.g. Pandoc's
|
||||
Markdown).
|
||||
|
||||
Name Type Default Description
|
||||
---------------------- ------- ------- ---------------------------------
|
||||
gamebook-numbers boolean true Replace section names with numbers
|
||||
gamebook-post-link string '' Text to add after every link
|
||||
gamebook-pre-link string '' Text to add before every link
|
||||
gamebook-randomseed integer 2023 Set random seed for shuffle
|
||||
gamebook-shuffle boolean true Shuffle sections
|
||||
gamebook-strong-links boolean true Use strong text style for links
|
||||
|
||||
# Gamebook Graph (Graphviz DOT)
|
||||
The Pandoc filter *pangamebookdot.lua* is included to create a plain-text
|
||||
DOT file from a generated gamebook, that can then be used with the *dot*
|
||||
command from [Graphviz](https://graphviz.org) to generate an image (e.g PNG)
|
||||
of how all sections in the book are connected. Both the original header
|
||||
and assigned number of each section is included in the graph.
|
||||
|
||||
# Examples
|
||||
The file *example.md* is a Pandoc Markdown example gamebook. Open your favorite
|
||||
terminal and cd to this directory. The following commands can be used to
|
||||
generate a PDF, EPUB, and HTML book:
|
||||
|
||||
pandoc --lua-filter=pangamebook.lua -o example.html example.md
|
||||
pandoc --lua-filter=pangamebook.lua -o example.epub example.md
|
||||
pandoc --lua-filter=pangamebook.lua -o example.pdf example.md
|
||||
|
||||
(Pandoc needs *pdflatex* to be installed to generate a PDF. It will otherwise
|
||||
complain loudly when you run that last line. How to install pdflatex is beyond
|
||||
the scope of this README file.)
|
||||
|
||||
If you want to edit the generated book in a word processor it is also possible
|
||||
to generate for instance a MS Word or LibreOffice Word document:
|
||||
|
||||
pandoc --lua-filter=pangamebook.lua -o example.docx example.md
|
||||
pandoc --lua-filter=pangamebook.lua -o example.odt example.md
|
||||
|
||||
To output RTF the *--standalone* (or *-s*) flag is needed for pandoc, unless
|
||||
you know what you are doing and really want only a partial RTF document (that
|
||||
is not likely, so remember that flag):
|
||||
|
||||
pandoc --lua-filter=pangamebook.lua -s -o example.rtf example.md
|
||||
|
||||
Manually editing the document after running Pandoc is probably a bad idea. Any
|
||||
edits will have to be done again if the document is ever recreated. It is better
|
||||
to read up on how to apply styles to the generated file, for instance by using a
|
||||
template style Word document.
|
||||
|
||||
This is how to set some metadata variables, in this case to put double square brackets
|
||||
around links and disable shuffling:
|
||||
|
||||
pandoc -Mgamebook-shuffle=false -Mgamebook-pre-link="[[" -Mgamebook-post-link="]]" --lua-filter=pangamebook.lua -o example.html example.md
|
||||
|
||||
To generate a graph from the book first create a new Markdown document
|
||||
using the pangamebook filter and then use the pangamebookdot filter
|
||||
on the resulting document, before running the Graphviz dot command.
|
||||
Pandoc must be told to use plain-text as output format when
|
||||
creating the DOT file.
|
||||
|
||||
pandoc --lua-filter=pangamebook.lua -o example2.md example.md
|
||||
pandoc --lua-filter=pangamebookdot.lua -t plain -o example2.dot example2.md
|
||||
dot -Tpng -O example2.dot
|
||||
|
||||
The generated graph will be in the file *example2.dot.png*.
|
||||
|
||||
![example graph example2.dot.png](example2.dot.png)
|
||||
|
||||
# Export Heading Map (Advanced)
|
||||
Most will never need to use this, but
|
||||
the mapping of headers to numbers
|
||||
is added as metadata by the filter, normally not
|
||||
included in the output.
|
||||
One way to look at it is to output a JSON file using Pandoc:
|
||||
|
||||
pandoc --lua-filter=pangamebook.lua -o example.json example.md
|
||||
|
||||
Look for the key **pangamebook-mapping**. The value is an object with
|
||||
all headers mapped to numbers (as strings), although with some extra
|
||||
data that has to be filtered out because of how Pandoc stores metadata.
|
||||
Here is an example of what a key and value can look like:
|
||||
"#second" : {"t" : "MetaString",
|
||||
"c" : "2"}
|
||||
# Development
|
||||
Bug reports and feature requests are welcome on GitHub. The goal is to keep this
|
||||
tool very simple and focus on only numbering the sections. Additions are most
|
||||
likely better done by creating additional Pandoc filters, leaving it to
|
||||
end-users to decide what filters to combine.
|
||||
|
||||
Pangamebook is version managed using a private
|
||||
[Fossil](https://fossil-scm.org/) repository. The git repository on GitHub is a
|
||||
mirror that is updated with new releases.
|
||||
|
||||
# LICENSE
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Pelle Nilsson
|
||||
|
||||
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.
|
||||
|
|
BIN
example2.dot.png
Normal file
BIN
example2.dot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
2
game.md
2
game.md
|
@ -2,8 +2,6 @@
|
|||
title: Pangamebook Gamebook Example
|
||||
author: Pelle Nilsson
|
||||
---
|
||||
|
||||
# Pangamebook Gamebook Example
|
||||
This is just an example to give new users and idea where to start. See [Pandoc
|
||||
Getting Started Article](https://pandoc.org/getting-started.html) and
|
||||
[PangamebookREADME](README.md) for more information.
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
-- pandoc filter to turn headers and links into numbers
|
||||
-- Copyright 2021 Pelle Nilsson
|
||||
-- Copyright 2021-2023 Pelle Nilsson
|
||||
-- MIT License
|
||||
-- source: https://github.com/lifelike/pangamebook
|
||||
|
||||
-- version: 1.5.1 (2023-01-20)
|
||||
-- fossil hash: 7d95c4d608a9545a21e19a3a533617d069ef679641b7c03f3477f394c5ba83c8
|
||||
|
||||
local nr = 1
|
||||
local mapped = {}
|
||||
|
||||
local strong_links = false
|
||||
local link_pre = ''
|
||||
local link_post = ''
|
||||
|
||||
function get_nr_for_header(text, identifier)
|
||||
local key = "#" .. identifier
|
||||
local name_nr = tonumber(text)
|
||||
if name_nr ~= nil and name_nr >= nr then
|
||||
mapped[key] = name_nr
|
||||
nr = name_nr + 1
|
||||
if name_nr ~= nil then
|
||||
if name_nr >= nr then
|
||||
mapped[key] = name_nr
|
||||
nr = name_nr + 1
|
||||
else
|
||||
print("ERROR: Section number " .. name_nr
|
||||
.. " too low (expected >= " .. nr .. ")")
|
||||
os.exit(1)
|
||||
end
|
||||
else
|
||||
mapped[key] = nr
|
||||
nr = nr + 1
|
||||
|
@ -65,11 +80,28 @@ function insert_sections(sections,
|
|||
table.insert(sections, section)
|
||||
end
|
||||
|
||||
function Pandoc(doc)
|
||||
function from_meta_bool(meta, name, default)
|
||||
value = meta[name]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
function from_meta_string(meta, name, default)
|
||||
value = meta[name]
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
function shuffle_blocks(doc)
|
||||
local sections = {}
|
||||
local first_section_i = 0
|
||||
local current_section_start = -1
|
||||
local blocks = {}
|
||||
|
||||
for i,el in pairs(doc.blocks) do
|
||||
if (el.t == "Header"
|
||||
and el.level == 1) then
|
||||
|
@ -103,11 +135,34 @@ function Pandoc(doc)
|
|||
if #sections > 0 then
|
||||
shuffle_insert(blocks, sections)
|
||||
end
|
||||
return pandoc.Pandoc(blocks, doc.meta)
|
||||
return blocks
|
||||
end
|
||||
|
||||
function Pandoc(doc)
|
||||
number_sections = from_meta_bool(doc.meta, "gamebook-numbers", true)
|
||||
strong_links = from_meta_bool(doc.meta, "gamebook-strong-links", true)
|
||||
link_pre = from_meta_string(doc.meta, "gamebook-pre-link", "")
|
||||
link_post = from_meta_string(doc.meta, "gamebook-post-link", "")
|
||||
|
||||
if from_meta_bool(doc.meta, "gamebook-shuffle", true) then
|
||||
local seed_number = 2023
|
||||
local metadata_seed = doc.meta["gamebook-randomseed"]
|
||||
if metadata_seed ~= nil then
|
||||
local metadata_seed_number = tonumber(seed)
|
||||
if metadta_seed_number ~= nil then
|
||||
seed_number = metadta_seed_number
|
||||
end
|
||||
end
|
||||
math.randomseed(seed_number)
|
||||
return pandoc.Pandoc(shuffle_blocks(doc), doc.meta)
|
||||
else
|
||||
return doc
|
||||
end
|
||||
end
|
||||
|
||||
function Header(el)
|
||||
if (el.level ~= 1
|
||||
or not number_sections
|
||||
or not (is_valid_section_name(el)
|
||||
or is_number_section_name(el))) then
|
||||
return el
|
||||
|
@ -135,12 +190,16 @@ function Link(el)
|
|||
return
|
||||
end
|
||||
local nr = mapped[el.target]
|
||||
local content
|
||||
if nr == nil then
|
||||
return pandoc.Link(pandoc.Strong(pandoc.Str(el.target)), el.target)
|
||||
content = pandoc.Str(link_pre .. el.target .. link_post)
|
||||
else
|
||||
local content = pandoc.Strong(pandoc.Str(nr))
|
||||
return pandoc.Link(content, el.target)
|
||||
content = pandoc.Str(link_pre .. nr .. link_post)
|
||||
end
|
||||
if strong_links then
|
||||
content = pandoc.Strong(content)
|
||||
end
|
||||
return pandoc.Link(content, el.target)
|
||||
end
|
||||
|
||||
function Blocks(blocks)
|
||||
|
|
97
pangamebookdot.lua
Normal file
97
pangamebookdot.lua
Normal file
|
@ -0,0 +1,97 @@
|
|||
-- pandoc filter to output Graphviz DOT graph from gamebook
|
||||
-- Copyright 2021-2022 Pelle Nilsson
|
||||
-- MIT License
|
||||
-- source: https://github.com/lifelike/pangamebook
|
||||
|
||||
-- version: 1.5.1 (2023-01-20)
|
||||
-- fossil hash: 7d95c4d608a9545a21e19a3a533617d069ef679641b7c03f3477f394c5ba83c8
|
||||
|
||||
function one_string_from_block(b)
|
||||
local result = ""
|
||||
local found_text = false
|
||||
pandoc.walk_block(b, {
|
||||
Str = function(s)
|
||||
if found_text then
|
||||
return ""
|
||||
else
|
||||
result = s.text
|
||||
end
|
||||
end
|
||||
})
|
||||
return result
|
||||
end
|
||||
|
||||
function is_gamebook_section_header(el)
|
||||
if not (el.t == "Header"
|
||||
and el.level == 1) then
|
||||
return false
|
||||
end
|
||||
local first = one_string_from_block(el)
|
||||
local as_number = tonumber(first)
|
||||
return as_number ~= null and as_number >= 1
|
||||
end
|
||||
|
||||
function dot_label_for_header(b)
|
||||
local label = one_string_from_block(b)
|
||||
local identifier = b.identifier
|
||||
if not (identifier == "section"
|
||||
or identifier:sub(1, 8) == "section-") then
|
||||
label = label .. '\\n' .. identifier
|
||||
end
|
||||
return '"' .. identifier .. '" [label=\"' ..label .. '"];\n'
|
||||
end
|
||||
|
||||
function dot_link(from, to)
|
||||
return '"' .. from .. '" -> "' .. to .. '";\n'
|
||||
end
|
||||
|
||||
local endstyle = '[shape=doubleoctagon]'
|
||||
|
||||
function Pandoc(doc)
|
||||
if not FORMAT:match "plain" then
|
||||
return doc
|
||||
end
|
||||
local in_header = nil
|
||||
local links_out = false
|
||||
local identifiers = {}
|
||||
local output = "digraph gamebook {\nnode[shape=box];\n\n"
|
||||
for i,el in pairs(doc.blocks) do
|
||||
if is_gamebook_section_header(el) then
|
||||
output = output .. dot_label_for_header(el)
|
||||
identifiers["#" .. el.identifier] = el.identifier
|
||||
end
|
||||
end
|
||||
for i,el in pairs(doc.blocks) do
|
||||
if is_gamebook_section_header(el) then
|
||||
if in_section and not links_out then
|
||||
output = output .. '"' .. in_section .. '"' .. endstyle .. ';\n'
|
||||
end
|
||||
in_section = el.identifier
|
||||
links_out = false
|
||||
elseif el.t == "Para" and in_section then
|
||||
for j,c in pairs(el.content) do
|
||||
if c.t == "Link" then
|
||||
local target = identifiers[c.target]
|
||||
if target then
|
||||
output = output .. dot_link(in_section, target)
|
||||
links_out = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if in_section and not links_out then
|
||||
output = output .. '"' .. in_section .. '"' .. endstyle .. ';\n'
|
||||
end
|
||||
output = output .. "}\n"
|
||||
local blocks = pandoc.Para(pandoc.Str(output))
|
||||
return pandoc.Pandoc(blocks, doc.meta)
|
||||
end
|
||||
|
||||
function Blocks(blocks)
|
||||
return blocks
|
||||
end
|
||||
|
||||
return {
|
||||
{Pandoc = Pandoc},
|
||||
}
|
Loading…
Reference in a new issue