Skip to content

Commit 2621223

Browse files
committed
Lua: add new module pandoc.highlighting.
1 parent 68d80cc commit 2621223

File tree

6 files changed

+316
-1
lines changed

6 files changed

+316
-1
lines changed

doc/lua-filters.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5091,6 +5091,88 @@ Returns:
50915091

50925092
<!-- END: AUTOGENERATED CONTENT -->
50935093

5094+
<!-- BEGIN: AUTOGENERATED CONTENT for module pandoc.highlighting -->
5095+
5096+
# Module pandoc.highlighting
5097+
5098+
Code highlighting
5099+
5100+
## Fields {#pandoc.highlighting-fields}
5101+
5102+
### styles {#pandoc.highlighting.styles}
5103+
5104+
List of known code highlighting styles. ({string,\...})
5105+
5106+
## Functions {#pandoc.highlighting-functions}
5107+
5108+
### definitions {#pandoc.highlighting.definitions}
5109+
5110+
`definitions (style, format)`
5111+
5112+
Generate highlighting definitions for the given format. For
5113+
example, to generate CSS definitions for the *espresso* style, run
5114+
`pandoc.highlighting.toformat('espresso', 'css')`.
5115+
5116+
Parameters:
5117+
5118+
`style`
5119+
: style table or style name (table\|string)
5120+
5121+
`format`
5122+
: `'context'`, `'css'`, or `'latex'` (string)
5123+
5124+
Returns:
5125+
5126+
- style definitions (string)
5127+
5128+
*Since: 3.8*
5129+
5130+
### highlight {#pandoc.highlighting.highlight}
5131+
5132+
`highlight (code element, format[, wopts])`
5133+
5134+
Highlight code in the given format.
5135+
5136+
Parameters:
5137+
5138+
`code element`
5139+
: element that will be highlighted ([Inline]\|[Block])
5140+
5141+
`format`
5142+
: target format (`'ansi'`, `'context'`, `'html'`, or `'latex'`')
5143+
(string)
5144+
5145+
`wopts`
5146+
: ([WriterOptions])
5147+
5148+
Returns:
5149+
5150+
- highlighted code (string)
5151+
5152+
*Since: 3.8*
5153+
5154+
### style {#pandoc.highlighting.style}
5155+
5156+
`style (name)`
5157+
5158+
Returns the style definitions for a given style name. If the name
5159+
is a standard style, it is loaded amd returned; if it ends in
5160+
`.theme`, attemts to load a KDE theme from the file path
5161+
specified.
5162+
5163+
Parameters:
5164+
5165+
`name`
5166+
: style name or path to theme file (string)
5167+
5168+
Returns:
5169+
5170+
- style (table)
5171+
5172+
*Since: 3.8*
5173+
5174+
<!-- END: AUTOGENERATED CONTENT -->
5175+
50945176
<!-- BEGIN: AUTOGENERATED CONTENT for module pandoc.image -->
50955177

50965178
# Module pandoc.image

pandoc-lua-engine/pandoc-lua-engine.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ library
8888
, Text.Pandoc.Lua.Marshal.WriterOptions
8989
, Text.Pandoc.Lua.Module.CLI
9090
, Text.Pandoc.Lua.Module.Format
91+
, Text.Pandoc.Lua.Module.Highlighting
9192
, Text.Pandoc.Lua.Module.Image
9293
, Text.Pandoc.Lua.Module.JSON
9394
, Text.Pandoc.Lua.Module.Log
@@ -109,6 +110,7 @@ library
109110
, Text.Pandoc.Lua.Writer.Scaffolding
110111

111112
build-depends: aeson
113+
, blaze-html >= 0.9 && < 0.10
112114
, bytestring >= 0.9 && < 0.13
113115
, crypton >= 0.30 && < 1.1
114116
, citeproc >= 0.8 && < 0.10
@@ -133,7 +135,6 @@ library
133135
, parsec >= 3.1 && < 3.2
134136
, text >= 1.1.1 && < 2.2
135137

136-
137138
test-suite test-pandoc-lua-engine
138139
import: common-options
139140
type: exitcode-stdio-1.0

pandoc-lua-engine/src/Text/Pandoc/Lua/Module.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import qualified HsLua.Module.DocLayout as Module.Layout
2626
import qualified HsLua.Module.Zip as Module.Zip
2727
import qualified Text.Pandoc.Lua.Module.CLI as Pandoc.CLI
2828
import qualified Text.Pandoc.Lua.Module.Format as Pandoc.Format
29+
import qualified Text.Pandoc.Lua.Module.Highlighting as Pandoc.Highlighting
2930
import qualified Text.Pandoc.Lua.Module.Image as Pandoc.Image
3031
import qualified Text.Pandoc.Lua.Module.JSON as Pandoc.JSON
3132
import qualified Text.Pandoc.Lua.Module.Log as Pandoc.Log
@@ -80,6 +81,7 @@ submodules :: [Module PandocError]
8081
submodules =
8182
[ Pandoc.CLI.documentedModule
8283
, Pandoc.Format.documentedModule
84+
, Pandoc.Highlighting.documentedModule
8385
, Pandoc.Image.documentedModule
8486
, Pandoc.JSON.documentedModule
8587
, Pandoc.Log.documentedModule
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-|
4+
Module : Text.Pandoc.Lua.Module.Highlighting
5+
Copyright : © 2025 Albert Krewinkel
6+
License : MIT
7+
Maintainer : Albert Krewinkel <[email protected]>
8+
9+
Lua module for basic image operations.
10+
-}
11+
module Text.Pandoc.Lua.Module.Highlighting (
12+
-- * Module
13+
documentedModule
14+
15+
-- ** Functions
16+
, style
17+
)
18+
where
19+
20+
import Prelude hiding (null)
21+
import Control.Applicative ((<|>))
22+
import Data.Default (def)
23+
import Data.Maybe (fromMaybe)
24+
import Data.Version (makeVersion)
25+
import HsLua.Aeson (peekViaJSON, pushViaJSON)
26+
import HsLua.Core
27+
import HsLua.Marshalling
28+
import HsLua.Packaging
29+
import Text.Blaze.Html.Renderer.Text (renderHtml)
30+
import Text.Pandoc.Definition (Block(CodeBlock), Inline(Code))
31+
import Text.Pandoc.Error (PandocError)
32+
import Text.Pandoc.Highlighting
33+
( Style
34+
, formatANSI
35+
, formatConTeXtBlock
36+
, formatConTeXtInline
37+
, formatHtmlBlock
38+
, formatHtmlInline
39+
, formatLaTeXBlock
40+
, formatLaTeXInline
41+
, highlightingStyles
42+
, lookupHighlightingStyle
43+
, pygments
44+
, styleToConTeXt
45+
, styleToCss
46+
, styleToLaTeX
47+
)
48+
import Text.Pandoc.Lua.Marshal.AST (peekBlockFuzzy, peekInlineFuzzy)
49+
import Text.Pandoc.Lua.Marshal.WriterOptions (peekWriterOptions)
50+
import Text.Pandoc.Lua.PandocLua (unPandocLua)
51+
import Text.Pandoc.Options
52+
( WriterOptions (writerHighlightStyle, writerSyntaxMap) )
53+
54+
import qualified Data.Text as T
55+
import qualified Data.Text.Lazy as TL
56+
import qualified Text.Pandoc.Highlighting as HL
57+
58+
-- | The @pandoc.image@ module specification.
59+
documentedModule :: Module PandocError
60+
documentedModule = Module
61+
{ moduleName = "pandoc.highlighting"
62+
, moduleDescription = "Code highlighting"
63+
, moduleFields = fields
64+
, moduleFunctions =
65+
[ definitions `since` makeVersion [3, 8]
66+
, highlight `since` makeVersion [3, 8]
67+
, style `since` makeVersion [3, 8]
68+
]
69+
, moduleOperations = []
70+
, moduleTypeInitializers = []
71+
}
72+
73+
--
74+
-- Fields
75+
--
76+
77+
-- | Exported fields.
78+
fields :: LuaError e => [Field e]
79+
fields =
80+
[ Field
81+
{ fieldName = "styles"
82+
, fieldType = "{string,...}"
83+
, fieldDescription = "List of known code highlighting styles."
84+
, fieldPushValue = pushList (pushText . fst) highlightingStyles
85+
}
86+
]
87+
88+
--
89+
-- Functions
90+
--
91+
92+
-- | Gets a highlighting style of the given name.
93+
style :: DocumentedFunction PandocError
94+
style = defun "style"
95+
### (unPandocLua . lookupHighlightingStyle)
96+
<#> stringParam "name" "style name or path to theme file"
97+
=#> functionResult pushViaJSON "table" "style"
98+
#? "Returns the style definitions for a given style name.\
99+
\\
100+
\ If the name is a standard style, it is loaded amd returned;\
101+
\ if it ends in `.theme`, attemts to load a KDE theme from the \
102+
\ file path specified."
103+
104+
definitions :: DocumentedFunction PandocError
105+
definitions = defun "definitions"
106+
### (\sty format -> case T.toLower format of
107+
"context" -> pure $ styleToConTeXt sty
108+
"css" -> pure $ T.pack $ styleToCss sty
109+
"latex" -> pure $ styleToLaTeX sty
110+
_ -> failLua $ "Unsupported format: " <> T.unpack format)
111+
<#> parameter peekStyle "table|string" "style" "style table or style name"
112+
<#> textParam "format" "`'context'`, `'css'`, or `'latex'`"
113+
=#> functionResult pushText "string" "style definitions"
114+
#? "Generate highlighting definitions for the given format.\
115+
\ For example, to generate CSS definitions for the *espresso* style,\
116+
\ run `pandoc.highlighting.toformat('espresso', 'css')`."
117+
118+
highlight :: DocumentedFunction PandocError
119+
highlight = defun "highlight"
120+
### (\codeElement format mwopts -> do
121+
(attr, code, inline) <-
122+
case codeElement of
123+
Left (Code a c) -> pure (a, c, True)
124+
Right (CodeBlock a c) -> pure (a, c, False)
125+
_ -> failLua "Cannot highlight element"
126+
let wopts = fromMaybe def mwopts
127+
let sty = fromMaybe pygments (writerHighlightStyle wopts)
128+
(inlineFormatter, blockFormatter) <- case T.toLower format of
129+
"ansi" -> pure ( \opts lns -> formatANSI opts sty lns
130+
, \opts lns -> formatANSI opts sty lns )
131+
"context" -> pure (formatConTeXtInline, formatConTeXtBlock)
132+
"html" -> let htmlToText fn = \opts src ->
133+
TL.toStrict $ renderHtml (fn opts src)
134+
in pure ( htmlToText formatHtmlInline
135+
, htmlToText formatHtmlBlock )
136+
"latex" -> pure (formatLaTeXInline, formatLaTeXBlock)
137+
_ -> failLua $
138+
"Unsupported highlighting format: " <> T.unpack format
139+
let syntaxMap = writerSyntaxMap wopts
140+
let formatter = if inline then inlineFormatter else blockFormatter
141+
case HL.highlight syntaxMap formatter attr code of
142+
Left err -> failLua $ T.unpack err
143+
Right result -> pure result)
144+
<#> parameter
145+
(\idx ->
146+
(Left <$> peekInlineFuzzy idx) <|>
147+
(Right <$> peekBlockFuzzy idx))
148+
"Inline|Block" "code element" "element that will be highlighted"
149+
<#> textParam "format"
150+
"target format (`'ansi'`, `'context'`, `'html'`, or `'latex'`')"
151+
<#> opt (parameter peekWriterOptions "WriterOptions" "wopts" "")
152+
=#> functionResult pushText "string" "highlighted code"
153+
#? "Highlight code in the given format."
154+
155+
-- | Retrieves a highlighting style; accepts a string, themepath, or style
156+
-- table.
157+
peekStyle :: Peeker PandocError Style
158+
peekStyle idx = do
159+
liftLua (ltype idx) >>= \case
160+
TypeTable -> peekViaJSON idx
161+
TypeString -> do
162+
name <- peekString idx
163+
liftLua $ unPandocLua $ lookupHighlightingStyle name
164+
_type -> failPeek "Can't retrieve style."

pandoc-lua-engine/test/Tests/Lua/Module.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ tests =
2525
("lua" </> "module" </> "pandoc-list.lua")
2626
, testPandocLua "pandoc.format"
2727
("lua" </> "module" </> "pandoc-format.lua")
28+
, testPandocLua "pandoc.highlighting"
29+
("lua" </> "module" </> "pandoc-highlighting.lua")
2830
, testPandocLua "pandoc.image"
2931
("lua" </> "module" </> "pandoc-image.lua")
3032
, testPandocLua "pandoc.json"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
local tasty = require 'tasty'
2+
3+
local test = tasty.test_case
4+
local group = tasty.test_group
5+
local assert = tasty.assert
6+
7+
local pandoc = require 'pandoc'
8+
local highlighting = require 'pandoc.highlighting'
9+
10+
return {
11+
group 'styles' {
12+
test('is a table', function ()
13+
assert.are_equal('table', type(highlighting.styles))
14+
end),
15+
},
16+
17+
group 'definitions' {
18+
test('returns a string', function ()
19+
local defs = highlighting.definitions('espresso', 'css')
20+
assert.are_equal('string', type(defs))
21+
end),
22+
test('errors when presented with an unknown style name', function ()
23+
assert.error_matches(
24+
function ()
25+
highlighting.definitions('unknown-style', 'css')
26+
end,
27+
'Unknown highlight%-style'
28+
)
29+
end),
30+
test('errors when asked to converto to an unsupported format', function ()
31+
local kate = highlighting.style('kate')
32+
assert.error_matches(
33+
function ()
34+
highlighting.definitions(kate, 'markdown')
35+
end,
36+
'Unsupported format'
37+
)
38+
end),
39+
},
40+
41+
group 'highlight' {
42+
test('produces highlighted code', function ()
43+
local espresso = highlighting.style 'espresso'
44+
local codeblock = pandoc.CodeBlock('print(42, "answer")', {class='lua'})
45+
local highlighted = highlighting.highlight(codeblock, 'html')
46+
assert.are_equal('string', type(highlighted))
47+
end)
48+
},
49+
50+
group 'style' {
51+
test('returns a table for a default style', function ()
52+
local style = highlighting.style('espresso')
53+
assert.are_equal('table', type(style))
54+
end),
55+
test('errors when presented with an unknown style name', function ()
56+
assert.error_matches(
57+
function ()
58+
highlighting.style('unknown-style')
59+
end,
60+
'Unknown highlight%-style unknown%-style'
61+
)
62+
end)
63+
},
64+
}

0 commit comments

Comments
 (0)