💾 Archived View for soviet.circumlunar.space › oak › gmx.gmi captured on 2022-04-28 at 19:11:43. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-12-03)

-=-=-=-=-=-=-

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 )