gmx
Experimental filter to process .gmi files for use with the gemini protocol.
Useful for static site generation or runtime server side templating / dynamic content inclusion (ymmv and experimental nature is stressed).
We use the alt text of gemtext preformatted blocks to generate inline expansion of arbitary code and script blocks for arbitary languages.
Best demonstrated by some examples..
The follow is a file named test.gmi (note the alt text near the ``` marker, this tells us we want to run the block using bash).
# Hello This is an example of using gmx. ``` bash:gmx cowsay "Hi from gmx!" ``` See you!
Now run gmx against test.gmi - the preformated block will be run using bash and its output inserted into the output in the expected location.
>./gmx test.gmi
# Hello
This is an example of using gmx.
```
______________
< Hi from gmx! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
```
See you!
You are not limited to using bash. For example, you can run Lua code snippets if you like (or python, or perl, or ..).
# Example 2
This is an example of using gmx and Lua.
``` lua:gmx
opt = {}
opt[ #opt + 1] = "one"
opt[ #opt + 1] = "two"
for i,v in ipairs(opt) do
print(i,v)
end
```
See you!
Gives
>./gmx test2.gmi # Example 2 This is an example of using gmx and Lua. ``` 1 one 2 two ``` See you!
If you prefer the output to not be within a preformatted blog, use the inline tag:
# Example 3
This is an example of using gmx and Lua.
``` lua:gmx:inline
opt = {}
opt[ #opt + 1] = "one"
opt[ #opt + 1] = "two"
for i,v in ipairs(opt) do
print(i,v)
end
```
See you!
Gives
>./gmx test2.gmi # Example 2 This is an example of using gmx and Lua. 1 one 2 two See you!
If process exits with an error then this gives a flavour of what happens (stderr is captured and inserted inline together with a line numbered output of the script/code block).
Error running inline script: exit 1
Command executed was: lua script.lua >output.tmp 2>error.tmp
lua: script.lua:3: attempt to concatenate a nil value (global 'free')
stack traceback:
script.lua:3: in main chunk
[C]: in ?
Script was:
1: print( "hello world from inside a lua script" )
2:
3: x = "" .. free
4:
5: tt = {}
6: tt[ #tt + 1 ] = "one"
7: tt[ #tt + 1 ] = "two"
8:
9: for i,v in ipairs(tt) do
10: print( i,v )
11: end
12:
13: print( "thats all folks" )
14:
Preformatted blocks without gmx blocks are processed in the normal way and not run as scripts.
Spec Reference
text/gemini supports ``` preformated blocks.
The gemini specification says
Any text following the leading "```" of a preformat toggle line which toggles preformatted mode on MAY be interpreted by the client as "alt text" pertaining to the preformatted text lines which follow the toggle line. Use of alt text is at the client's discretion, and simple clients may ignore it. Alt text is recommended for ASCII art or similar non-textual content which, for example, cannot be meaningfully understood when rendered through a screen reader or usefully indexed by a search engine. Alt text may also be used for computer source code to identify the programming language which advanced clients may use for syntax highlighting.
Source code
gmx
Just a quick hack but works for me! ;-)
Keep smiling
#!/usr/local/bin/lua
-- gmx 0.1
-- oak@soviet.circumlunar.space
function file_exists(file)
local f = io.open(file, "rb")
if f then f:close() end
return f ~= nil
end
-- get all lines from a file, returns an empty
-- list/table if the file does not exist
function lines_from(file)
if not file_exists(file) then return {} end
lines = {}
for line in io.lines(file) do
lines[#lines + 1] = line
end
return lines
end
function to_file( filename, lines )
local f = io.open( filename, "w+" )
if f then
for i,v in ipairs(lines) do
f:write( v .. "\n" )
end
f:close()
end
end
function show( gmi )
for i,v in ipairs(gmi) do
print( v )
end
end
function append( output, new, withLineNumbers )
if new then
if type(new) == "table" then
for i,v in ipairs(new) do
if withLineNumbers then
output[ #output + 1] = string.format( "%3d: %s", i, v )
else
output[ #output + 1] = v
end
end
else
output[ #output + 1 ] = "" .. new
end
end
end
function execute( lang, script, opts )
local script_name = "script.lua"
local stdout = "output.tmp"
local errout = "error.tmp"
local cmdline = lang .. " " .. script_name .. " >" .. stdout .. " 2>" .. errout
to_file( "script.lua", script )
local success, label, status = os.execute( cmdline )
local tmp = lines_from( stdout )
local output = {}
if not success then
append( output, "```" )
append( output, "Error running inline script: " .. label .. " " .. status )
append( output, "Command executed was: " .. cmdline )
append( output, "" )
append( output, lines_from( errout ) )
append( output, "" )
append( output, "Script was:" )
append( output, "" )
append( output, script, true )
append( output, "```" )
else
if opts == "inline" then
append( output, tmp )
else
append( output, "```" )
append( output, tmp )
append( output, "```" )
end
end
return output
end
function filter( gmi )
local output = {}
local script = {}
local capturing = false
local lang, opts
for i,line in ipairs(gmi) do
if string.sub(line,1,3) == "```" then
capturing = not capturing
if not capturing then
if lang then
local script_output = execute( lang, script, opts )
append( output, script_output )
else
append( output, "```" )
append( output, script )
append( output, "```" )
end
else
lang = string.match( line, "```%s+([%w%._]+):gmx" )
opts = string.match( line, "```%s+[%w%._]+:gmx:(%g+)" )
script = {}
end
else
if capturing then
script[ #script + 1 ] = line
else
output[ #output + 1] = line
end
end
end
return output
end
gmi = lines_from( arg[1] )
gmix = filter( gmi )
show( gmix )