I'm trying to set up a simple custom writer going from pandoc's markdown to latex. Here's what I have so far:
test.md
# A section
## A subsection
Heres a paragraph.
Heres another
custom_writer.lua
function Header(lev, s, attr)
level_sequences = {
"section",
"subsection",
"subsubsection",
"subsubsubsection"
}
return string.format("\\%s{%s}", level_sequences[lev], s)
end
function Para(s)
return s.."\\parskip"
end
function Str(s)
return s
end
function Space()
return " "
end
Question
As far as I understand from the docs
A writer using the classic style defines rendering functions for each element of the pandoc AST
I checked the resulting JSON from my markdown file and the only the following elements occur:
Header
Para
Str
Space
It seems to my that I've covered all the necessary elements in the AST, so I'm not sure why pandoc complains with Error running lua: attempt to call a nil value when I do the following:
pandoc test.md -t custom_writer.lua
Does anyone know what I'm missing in custom_writer.lua?
I was missing a few things which are not documented:
function Header(lev, s, attr)
level_sequences = {
"section",
"subsection",
"subsubsection",
"subsubsubsection"
}
return string.format("\\%s{%s}", level_sequences[lev], s)
end
function Blocksep()
return "\\parskip"
end
function Para(s)
return s.."\\parskip"
end
function Str(s)
return s
end
function Space()
return " "
end
function Doc(body, metadata, variables)
return body
end
Related
I am writing a Lua filter for pandoc that adds a glossary function to HTML output of a markdown file. The goal is to add mouseover text to each occurrence of an acronym or key definition in the document.
However, I don't want this to occur for text in headings.
My MWE works on most* text in the document:
-- Parse glossary file (summarised here for brevity)
local glossary = {CO = "Cardiac Output", DBP = "Diastolic Blood Pressure", SBP = "Systolic Blood Pressure"}
-- Substitute glossary term for span with a mouseover link
function Str(elem)
for key, value in next, glossary do
if elem.text == key then
return pandoc.Span (key, {title = value, class = "glossary"})
end
end
end
My understanding from the documentation and poking at the AST suggests to me I need to use a block-level function first and then walk_block to alter the inline elements.
function Pandoc(doc)
for i, el in pairs(doc.blocks) do
if (el.t ~= "Header") then
return pandoc.walk_block(el, {
Str = function (el)
for key, value in next, glossary do
if el.text == key then
return pandoc.Span (key, {title = value, class = "glossary"})
end
end
end })
end
end
end
However, this attempt isn't working and returns the error: "Error while trying to get a filter's return value from Lua stack.
PandocLuaError "Could not get Pandoc value: expected table, got 'nil' (nil)". I think my return structure is wrong, but I haven't been able to debug it.
My test markdown file contains:
# Acronyms: SBP, DBP & CO
Spaced acronyms: CO and SBP and DBP.
In a comma-separated list: CO, SBP, DBP; with backslashes; CO/DBP/SBP, and in bullet points:
* CO
* SBP
* DBP
*It fails on terms with non-space adjacent characters, such as punctuation.
After a couple more days, I have found a partial solution which may help anyone else with a similar problem.
I think (but am not certain) that the Pandoc(doc) requires a return of both a list of block elements and the doc.meta, which I wasn't doing above.
My solution has been to separate the glossary function out and then call it individually for each desired block element. This works, even though it is a little clunky.
function glos_sub (el)
return pandoc.walk_block(el, {
Str = function (el)
for key, value in next, glossary do
if el.text == key then
return pandoc.Span (key, {title = value, class = "glossary"})
end
end
end
})
end
-- Run on desired elements
return {
{BulletList = glos_sub},
{Para = glos_sub},
{Table = glos_sub}
}
In the manual I found this example of an pandoc lua-filter:
return {
{
Str = function (elem)
if elem.text == "{{helloworld}}" then
return pandoc.Emph {pandoc.Str "Hello, World"}
else
return elem
end
end,
}
}
I want to replace {{helloworld}} with <div>abc</div>. My try:
return {
{
Str = function (elem)
if elem.text == "{{helloworld}}" then
return pandoc.RawInline('html','<div>abc</div>')
else
return elem
end
end,
}
}
...but this give me the following output:
<p></p>
<div>abc</div>
<p></p>
How can I get rid of the empty p-tags?
Additional information
I convert from markdown to html and my markdown file looks like this:
The manual says:
The function’s output must result in an element of the same type as
the input. This means a filter function acting on an inline element
must return either nil, an inline, or a list of inlines, and a
function filtering a block element must return one of nil, a block, or
a list of block elements. Pandoc will throw an error if this condition
is violated.
You want your output to be rendered as a block (<div>abc</div>) but your input (Str) is inline. That's why it doesn't work. Change Str (Inline) to Para (Block), elem.text to element.content[1].text and RawInline to RawBlock and it will work:
return {
{
Para = function (elem)
if elem.content[1].text == "{{helloworld}}" then
return pandoc.RawBlock('html','<div>abc</div>')
else
return elem
end
end,
}
}
I'm trying to apply a LUA filter that would only alter the body of a document, leaving the Metadata untouched. And it's harder than I thought.
The filter should prepend and append text to inline elements as well as block elements. If it works for the inline element, here Code, it fails for the block element CodeBlock.
function Pandoc(doc)
blocks = {}
for k,block in pairs(doc.blocks) do
table.insert(blocks, pandoc.walk_block(block, {
-- Doesn't work!?
CodeBlock = function(el)
return {
pandoc.Para({pandoc.Str("Before")}),
el,
pandoc.Para({pandoc.Str("After")})}
end,
-- Works!
Code = function(el)
return {pandoc.Str("Before"), el, pandoc.Str("After")}
end,
}))
end
return pandoc.Pandoc(blocks, doc.meta)
end
What am I missing? Cheers,
The issue here is that walk_block and walk_inline process the content of an element, not the element itself.
If wrapper is your filter table, this should do what you want:
function Pandoc (doc)
local div = pandoc.Div(doc.blocks)
local blocks = pandoc.walk_block(div, wrapper).content
return pandoc.Pandoc(blocks, doc.meta)
end
An alternative solution would be to save and restore the metadata, like so:
local meta = {}
return {
{ Meta = function(m) meta = m; return {} end },
wrapper,
{ Meta = function(_) return meta; end },
}
This is probably more efficient, as serializing/deserializing only metadata and Code/CodeBlock elements is likely faster than doing the same for the full document.
Good evening
Will you help me solve this problem?
ERROR: race/util_server.lua:440: attempt to index local 'self' (a nil value)
function string:split(separator)
if separator == '.' then
separator = '%.'
end
local result = {}
for part in self:gmatch('(.-)' .. separator) do
result[#result+1] = part
end
result[#result+1] = self:match('.*' .. separator .. '(.*)$') or self
return result
end
You're probably calling it wrong.
function string:split(separator)
Is short hand for:
function string.split(self, separator)
Given a string and separator:
s = 'This is a test'
separator = ' '
You need to call it like this:
string.split(s, separator)
Or:
s:split(separator)
If you call it like this:
s.split(separator)
You're failing to provide a value for the self argument.
Side note, you can write split more simply like this:
function string:split(separators)
local result = {}
for part in self:gmatch('[^'..separators..']+') do
result[#result + 1] = part
end
return result
end
This has the disadvantage that you can't used multi-character strings as delimiters, but the advantage that you can specify more than one delimiter. For instance, you could strip all the punctuation from a sentence and grab just the words:
s = 'This is an example: homemade, freshly-baked pies are delicious!'
for _,word in pairs(s:split(' :.,!-')) do
print(word)
end
Output:
This
is
an
example
homemade
freshly
baked
pies
are
delicious
I am following a tutorial on Lua, specifically for making a gamemode in the game Garry's Mod. I've been looking at this for a while and I simply can't find what's wrong.
function ply:databaseFolders()
return "server/example/players/" .. self:ShortSteamID() .. "/" --ref. A
end
function ply:databasePath()
return self:databaseFolders() .. "database.txt" --ERROR line here, goes up
end
function ply:databaseExists()
local f = file.Exists(self.databasePath(), "DATA") --goes up here
return f
end
function ply:databaseCheck()
self.database = {}
local f = self:databaseExists() --goes up here
...
end
function GM:PlayerAuthed(ply, steamID, uniqueID)
ply:databaseCheck() --goes up here
print("Player: " .. ply:Nick() .. " has gotten authed.")
end
Summary of code: I want to create a database.txt file at the directory above.
Edit1: When all players leave the game, ref. A is reached, but no file created in directory.
When you are calling the function databasePath, you are not using the OOP syntax; and therefore self is not implicitly passed to the function. Henceforth, the error. Change the following:
function ply:databaseExists()
local f = file.Exists(self:databasePath(), "DATA")
-- notice the use of ---> : <--- here
return f
end