Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Alexander Yakovlev 2023-04-25 22:15:23 +06:00
commit af618a5788
7 changed files with 376 additions and 13 deletions

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ build/
*.org
*.pdf
*~
example2.dot
example2.md
*.rtf

View file

@ -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
View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -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.

View file

@ -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
View 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},
}