Skip to content

Instantly share code, notes, and snippets.

@johnd0e
Last active May 19, 2024 23:33
Show Gist options
  • Save johnd0e/7d00f4d8899ec9920b2b84ceed4a6e02 to your computer and use it in GitHub Desktop.
Save johnd0e/7d00f4d8899ec9920b2b84ceed4a6e02 to your computer and use it in GitHub Desktop.
Makefile for documents converting using pandoc
HelpDown Makefile scripts
-------------------------
Предлагается [система сборки документации][HelpDown Makefile scripts]
в различных форматах (__.hlf__, __.html__, и __.md__ для форума)
из единого источника на базе Markdown.
Система представляет собой базовый `Makefile`, и несколько lua-скриптов ("фильтров"),
необходимых для тонкой настройки вывода.
Предполагается использовать это наподобие конструктора: для каждого проекта создаётся свой локальный `Makefile`,
в котором подключается базовый, и указываются необходимые фильтры (имеющиеся в наборе, либо добавленные локально).
Требования:
- [pandoc]
- [HtmlToFarHelp] (или прямая ссылка на [файл пакета][nupkg])
- [make] или `winget install ezwinports.make`
- `Makefile` + cкрипты из данного репозитория. Или [архив](https://gist.github.com/johnd0e/7d00f4d8899ec9920b2b84ceed4a6e02/archive/master.zip).
Для чего же нужны фильтры, приведу лишь несколько примеров:
- Для презентации какого-либо скрипта на форуме его документацию стоит специально подготовить, в частности:
<details>
- Поместить все разделы кроме первого в спойлеры, чтобы сэкономить место в посте.
- Исправить форматирование при наличии одиночного `*`, поскольку стандартный способ экранирования слешем на форуме не работает.
- _DefinitionList_ ([Description List]) не является стандартной фичей маркдауна, его надо заменить на _BulletList_.
- Преобразование ссылок:
- Движок форума не создаёт внутренние ссылки, поэтому их надо убрать (заменить на просто жирный текст).
- Ссылки на справку фара работают только в __hlf__. А в __html__ и _для форума_ их надо заменять на что-то более полезное.
- Аналогично со ссылками на локальные __\*.chm__ и пр.
</details>
Никто в здравом уме не станет сам с этим возиться, но если всё автоматически, то [почему бы и нет][BufferScroll].
- Для __html__ полезно Title не оставлять пустой, а продублировать туда первый Header.
- Для __hlf__ -- сформировать индекс справки, и в каждый раздел добавить перекрёстные ссылки на другие
(например в верхнем блоке как в справке [LuaCheck]. Или в нижнем, как в [BufferScroll]).
Чтобы подключить всё это к своему проекту достаточно `Makefile` подобного примеру в `Makefile.sample`.
Всё, теперь при наличии исходного документа в файле с расширением __\*.text__ достаточно запустить команду `make` для создания __\*.hlf__.
Или же явно указать цель: `make hlf`, `make html`, `make forum`.
В качестве цели также может быть указано имя результирующего файла.
[HelpDown Makefile scripts]: https://gist.github.com/7d00f4d8899ec9920b2b84ceed4a6e02
[pandoc]: https://pandoc.org/installing.html
[HtmlToFarHelp]: https://www.nuget.org/packages/HtmlToFarHelp
[nupkg]: https://www.nuget.org/api/v2/package/HtmlToFarHelp/
[make]: https://sourceforge.net/projects/ezwinports/files/
[Description List]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl
[BufferScroll]: https://forum.farmanager.com/viewtopic.php?t=8675
[LuaCheck]: https://forum.farmanager.com/viewtopic.php?f=15&t=9650
-- For hlf:
-- Make index page (out of pandoc-generated toc)
-- Add refs-block ("other help sections") to each article.
-- requires: meta.refsTemplate and --toc
-- optional: meta.refsLevel
--luacheck: globals Refs meta
local function Pandoc (doc)
meta = doc.meta
if not meta.refsTemplate then
--print("addRefs: no refsTemplate, exiting")
return
end
local insertRefs = require(meta.refsTemplate[1].text)
if not insertRefs then
return
end
-- add Index section
local toc = pandoc.structure.table_of_contents(doc.blocks):walk{Link=function(el)
el.attr = pandoc.Attr() -- remove auto-id's "toc-title"
return el
end}
doc.blocks:insert(pandoc.Header(1, "Index", pandoc.Attr("index", nil, {refsTitle="i"})))
doc.blocks:insert(toc)
--
Refs = pandoc.List{} -- list of linked articles
local refsLevel = meta.refsLevel
refsLevel = not refsLevel and 1 or refsLevel[1] and tonumber(refsLevel[1].text)
doc.blocks:walk { -- for refs pick all Headers with level<=meta.refsLevel (default 1)
Header=function(el)
if el.identifier~="" and not el.classes:includes("noref") then
if refsLevel and refsLevel<el.level then return end
local content, title = el.content
if el.attributes.refsTitle then
title = pandoc.utils.stringify(content)
content = el.attributes.refsTitle
end
Refs:insert(pandoc.Link(content, el.identifier, title))
end
end
}
--
require"sections"(doc.blocks, insertRefs)
return doc
end
return {
{ Pandoc=Pandoc },
{ Div=function(el) return el.content end }, -- HtmlToFarHelp does not support Divs
}
-- transform DL to BulletList
function DefinitionList(el)
local items = {}
for i,item in ipairs(el.content) do
local dt,blocks = table.unpack(item) --term,definitions
local first = blocks[1]
items[i] = first
for j=2,#blocks do
first:extend(blocks[j])
end
dt:extend{pandoc.Str" "} --actually needed not for gfm but for some other flavors
first:insert(1,pandoc.Plain(dt))
end
return pandoc.BulletList(items)
end
-- forum: replace <details> with bbcode [spoiler] tag
function RawBlock (el)
if el.format=="html" then
if el.text:match"</?details>" then
-- todo <summary>
return pandoc.RawBlock("markdown",el.text:gsub("<","["):gsub(">","]"):gsub("details","spoiler"))
end
end
end
-- Save space wrapping all (except first) sections content inside spoilers
-- Sections with header level > meta.foldLevel (when specified) will be absorbed
local function wrapSection (blocks, startIdx, endIdx)
-- transform headers into plain (but formatted) text
blocks[startIdx] = pandoc.Para {
pandoc.RawInline("markdown", "[u]"),
pandoc.Strong(blocks[startIdx].content),
pandoc.RawInline("markdown","[/u]")
}
blocks[startIdx].content:insert(pandoc.RawInline("markdown", " [spoiler]"))
blocks:insert(endIdx, pandoc.RawBlock("markdown", "[/spoiler]"))
return endIdx+1
end
local first = true
return {
{
Header=function(el)
if first then -- remove redundant first header, as forum topic already has own title
first = false
return {}
end
if el.classes:includes("noref") then --e.g. cfgscript note
return pandoc.HorizontalRule()
end
end
}, { -- add spoilers
Pandoc=function(doc)
local foldLevel = doc.meta.foldLevel
foldLevel = foldLevel and tonumber(foldLevel[1].text)
require"sections"(doc.blocks, wrapSection, function (header)
if foldLevel and header.level>foldLevel then
return "absorb"
end
end)
return doc
end
}
}
-- phpBB3 uses own markdown parser (https://s9etextformatter.readthedocs.io/Plugins/Litedown/Syntax/),
-- which differs in some minor details. That's why some fixes needed.
function Str (el)
-- normal way of escaping the asterisk is \*, but that does not work in Litedown.
if el.text=="*" then
return pandoc.RawInline("markdown","[i]*[/i]")
end
end
function Link (el)
-- replace by Emph because forum engine fails to parse such links
if el.target:match"^:" then
return pandoc.Emph(el.content)
end
-- remove links as forum engine does not create anchors for headers.
if el.target:match"^#" then
return pandoc.Strong(el.content)
end
end
-- Grab first header as title, but only if no explicit metadata specified (yaml)
local title
function Header(el)
if title then return end
title = pandoc.utils.stringify(el)
end
function Meta(el)
if not el.pagetitle then
el.pagetitle = title
return el
end
end
ifeq (1,$(words $(MAKEFILE_LIST))) # if not included
.DEFAULT_GOAL:=help
endif
define HELP
Makefile for documents converting using pandoc.
Source: *.text (extension defined by SOURCE_EXT).
Target(s): enumerated in commandline (otherwise can be set in .DEFAULT_GOAL)
Usage:
make some_doc.html
Convert some_doc.text to some_doc.html.
Instead of ".html" any other pandoc-supported file type can be used.
make html
Convert *.text to *.html.
Instead of ".html" any other supported extension can be used, incl. hlf (must be enumerated in TARGET_EXT).
make some_doc.hlf
make hlf
Convert to .hlf using pandoc and HtmlToFarHelp.
make some_doc.forum
make forum
Convert to phpBB-flavored markdown (Litedown).
make clean
Clean current directory from all files with extensions specified in TARGET_EXT.
make help
This help
make install
Copy *.lua to $(APPDATA)\pandoc\filters
Or to $$(DATA_DIR)\filters (if defined).
Notes:
1. pandoc.exe and HtmlToFarHelp.exe must be available.
It is also possible to define full paths in env variables PANDOC/HTMLTOFARHELP.
2. Some conversions may require additional lua filters, which must be present in current directory.
Alternatively they can reside in $(DATA_DIR)\filters
(See `make install`)
3. Some conversions may require lua writers, which must be present in current directory.
Alternatively they can reside in $(DATA_DIR)\custom
4. The best way to customize this Makefile is including it in own Makefile, adding new rules
and (re)defining corresponding variables.
endef
SOURCE_EXT:=text
SOURCE_FORMAT?=--from=markdown-auto_identifiers-raw_tex+autolink_bare_uris
TARGET_EXT+=hlfhtml htm html md plain native json forum hlf
ifneq (,$(filter $(TARGET_EXT),$(SOURCE_EXT)))
$(error TARGET_EXT cannot include SOURCE_EXT)
else ifneq (1,$(words $(SOURCE_EXT)))
$(error SOURCE_EXT must be single ext)
endif
#https://pandoc.org/installing.html
PANDOC?=pandoc.exe
TARGET_FORMAT:= # deduced from extension; defaulting to html
FLAGS+=--wrap=preserve
EXTRA+=--lua-filter=FarLinks.lua
#https://www.nuget.org/packages/HtmlToFarHelp
HTMLTOFARHELP?=HtmlToFarHelp.exe
SHELL:=$(ComSpec)
RM:=del
ifdef DATA_DIR
FLAGS+= --data-dir=$(DATA_DIR)
else
DATA_DIR:=$(APPDATA)\pandoc#default
endif
LUA_PATH:=$(LUA_PATH);$(DATA_DIR)\filters\?.lua;$(DATA_DIR)\filters\?\init.lua
# Far Manager help file
%.hlf: %.hlfhtml
@$(HTMLTOFARHELP) from="$<"; to="$@"
$(info $@)
# intermediate file for hlf
%.hlfhtml: TARGET_FORMAT:= --to=html --no-highlight
%.hlfhtml: FLAGS+= --lua-filter=ChmLinks.lua --lua-filter=unDetails.lua
%.hlfhtml: EXTRA:=
# full-featured html, with headers, styles, ... (otherwise use `htm`)
%.html: FLAGS+= --standalone --strip-comments --lua-filter=HeaderToTitle.lua
# github-flavored markdown (pandoc --list-extensions=gfm)
%.md: TARGET_FORMAT:= --to=gfm --lua-filter=DefinitionToBulletList.lua
# prepare text for posting on forum.farmanager.com
%.forum: TARGET_FORMAT:=--to=markdown_strict+fenced_code_blocks-raw_html --lua-filter=DefinitionToBulletList.lua
%.forum: FLAGS+= --lua-filter=fold.lua --lua-filter=forum.lua --lua-filter=DetailsToSpoiler.lua
%.forum: EXTRA:=--shift-heading-level-by=1 --strip-comments
#prevent circular dependencies
%.text %.lua: ;
$(MAKEFILE_LIST): ;
.SECONDEXPANSION: #https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html#Secondary-Expansion
%: $$(basename %).$(SOURCE_EXT) $(MAKEFILE_LIST) $(wildcard *.lua)
@$(PANDOC) $(SOURCE_FORMAT) $(TARGET_FORMAT) $(FLAGS) $(EXTRA) --output=$@ $<
$(info $@)
.PHONY: $(TARGET_EXT) clean help install
# targets like html md hlf...
SOURCES:=$(wildcard *.$(SOURCE_EXT))
NAMES:=$(basename $(SOURCES))
$(TARGET_EXT): %: $(addsuffix .%,$(NAMES))
TARGETS_MASK:=$(addprefix *.,$(TARGET_EXT))
clean:
$(info $(wildcard $(TARGETS_MASK)))
@$(RM) $(TARGETS_MASK)
help:
$(info $(HELP))
@rem
install: $(DATA_DIR)/filters $(addprefix $(DATA_DIR)/filters/, $(wildcard *.lua))
$(DATA_DIR)/filters:
mkdir $(subst /,\,$@)
$(DATA_DIR)/filters/%: %
@rem cmd /c mklink $(subst /,\, $@ $(realpath $<))
copy $< $(subst /,\,$@)
# required user setting
BASE:=D:\repo\Makefile
PANDOC:=C:\Apps\pandoc\pandoc.exe
HTMLTOFARHELP:=$(FARPROFILE)\tools\HtmlToFarHelp\HtmlToFarHelp.exe
# plug base Makefile
include $(BASE)\Makefile
.DEFAULT_GOAL:=hlf
# optional local customization
%.hlfhtml: FLAGS+=--lua-filter addRefs.lua
%: FLAGS+= --toc
--luacheck: globals meta Refs
local Label = meta.refsLabel
if not Label then -- default template of the links block
local doc = pandoc.read[[
---
refsLabel: >
- - -
Другие разделы справки:\
...
]]
Label = doc.meta.refsLabel
end
local function prepRefs (header) -- prepare refs section for current article
local refs = Label:walk{} -- copy
local inlines = refs[#refs].content
--
local links = Refs:filter(function (el) -- remove self from refs
return el.target~=header.identifier
end)
local last = links[#links]
for _,el in ipairs(links) do
inlines:extend{pandoc.Str"[", el, pandoc.Str"]"}
if el~=last then inlines:extend{pandoc.Str",", pandoc.Space()} end
end
return pandoc.Div(refs)
end
local function insertRefs (blocks, startIdx, endIdx)
local header = blocks[startIdx]
if header.identifier=="index" -- skip Index section
or header.classes:includes("noref") then --e.g. cfgscript note
return
end
local i = endIdx
if blocks[i-1].tag=="Div" then -- insert ref block before div
i = i-1
end
blocks:insert(i, prepRefs(header))
return endIdx+1
end
return insertRefs
-- helper script to execute same `callback` function for every section
-- to the same section belong blocks beginning by header and ending by last block before next header
-- it is possible to skip some header (= absorb it's content into current section),
-- with `condition` function, returning `true`
return function (blocks, callback, condition)
local start
local i = 1
repeat
local el = blocks[i]
if el.tag=="Header" and el.identifier~="" then
local skip = condition and condition(el, blocks, i)
if not skip then
if start then
i = callback(blocks, start, i) or i
assert(blocks[i]==el,"wrong index returned by callback")
end
start = i
end
end
i = i+1
until not blocks[i]
if start then
callback(blocks, start, i)
end
end
-- Remove <details> and <summary> tags
-- Useful for hlf, as HtmlToFarHelp does not support them
local REMOVE = {}
function RawBlock (el)
if el.format=="html" then
if el.text:match"^</?details>$" or el.text:match"^</?summary>$" then
-- todo summary=>strong
return REMOVE
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment