#!/usr/bin/env lua5.1 local marigold = require 'marigold-cgi.marigold' local h = marigold.h local b64 = require '3rdparty.base64' local json = require '3rdparty.json' -- get statistics local metavars = marigold.get_metavars() local query = marigold.decode_query(metavars.query_string or '') local function query_retrieve(key, default) local key = 'stat_'..key if query[key] ~= '' then return query[key] else return default end end local stat = { name = query_retrieve('name', 'Name'), alignment = query_retrieve('alignment', 'neutral'), size = query_retrieve('size', 'medium'), type = query_retrieve('type', 'creature'), ac = query_retrieve('ac', '0'), hp = query_retrieve('hp', '0'), speed = query_retrieve('speed', '0 ft.'), str = query_retrieve('str', '10'), dex = query_retrieve('dex', '10'), con = query_retrieve('con', '10'), int = query_retrieve('int', '10'), wis = query_retrieve('wis', '10'), cha = query_retrieve('cha', '10'), saving_throws = (query.stat_saving_throws ~= '' and query.stat_saving_throws) or '', vulnerabilities = (query.stat_vulnerabilities ~= '' and query.stat_vulnerabilities) or '', resistances = (query.stat_reistances ~= '' and query.stat_reistances) or '', immunities = (query.stat_immunities ~= '' and query.stat_immunities) or '', condition_immunities = (query.stat_condition_immunities ~= '' and query.stat_condition_immunities) or '', languages = (query.stat_languages ~= '' and query.stat_languages) or '', cr = (query.stat_cr ~= '' and query.stat_cr) or '', traits = (query.stat_traits ~= '' and query.stat_traits) or [[@New Trait Add a named trait with an at sign. All lines following until the next at sign will be part of the trait description! @Second Trait This is a different trait. ]], actions = (query.stat_actions ~= '' and query.stat_actions) or [[ @New Action Actions (and reactions and legendary actions) use the same syntax as traits. As well, in all of them you can add *italics* and **bold** text! ]], reactions = (query.stat_reactions ~= '' and query.stat_reactions) or '', legendary_description = (query.stat_legendary_description ~= '' and query.stat_legendary_description) or '', legendary = (query.stat_legendary ~= '' and query.stat_legendary) or '', } -- stats yarrow query conversion local function clean(str) local str = string.gsub(str, '^%s+', '') local str = string.gsub(str, '%s+$', '') return str end local function parse_traits(str) local tbl = {} for name, value in string.gmatch(str, '@([^@]-)\n([^@]+)') do table.insert(tbl, {name=clean(name), value=clean(value)}) end return tbl end local function convert(stats) local tbl = {} for k, v in pairs(stats) do if v ~= '' then tbl[k] = v end end tbl.traits = parse_traits(stats.traits) tbl.actions = parse_traits(stats.actions) tbl.reactions = parse_traits(stats.reactions) if stats.legendary ~= '' then tbl.legendary = {} tbl.legendary.description = stats.legendary_description tbl.legendary.actions = parse_traits(stats.legendary) end return json.encode(tbl) end -- form helpers local function text_input(label, name, value) local tbl = {} tbl['for'] = name return h('div', { class='inputdiv', h('label', label, tbl), h('input', { type='text', id=name, name=name, value=value }), }) end local function textarea(label, name, value, height) local tbl = {} tbl['for'] = name return h('div', { class='inputdiv', h('label', label, tbl), h('textarea', value, { id=name, name=name, style=(height and 'height:'..height) or nil }), }) end -- stats form local form_basic = h('form', { style="width:100%; max-width: 30em; margin: auto; padding: 3em 0;", text_input('Name', 'stat_name', stat.name), text_input('Alignment', 'stat_alignment', stat.alignment), text_input('Size', 'stat_size', stat.size), text_input('Type', 'stat_type', stat.type), h('br'), text_input('Armor Class', 'stat_ac', stat.ac), text_input('Hit Points', 'stat_hp', stat.hp), text_input('Speed', 'stat_speed', stat.speed), h('br'), text_input('STR', 'stat_str', stat.str), text_input('DEX', 'stat_dex', stat.dex), text_input('CON', 'stat_con', stat.dex), text_input('INT', 'stat_int', stat.int), text_input('WIS', 'stat_wis', stat.wis), text_input('CHA', 'stat_cha', stat.cha), h('br'), text_input('Saving Throws', 'stat_saving_throws', stat.saving_throws), text_input('Damage Vulnerabilities', 'stat_vulnerabilities', stat.vulnerabilities), text_input('Damage Resistances', 'stat_resistances', stat.resistances), text_input('Damage Immunities', 'stat_immunities', stat.immunities), text_input('Condition Immunities', 'stat_condition_immunities', stat.condition_immunities), text_input('Senses', 'stat_senses', stat.senses), text_input('Languages', 'stat_languages', stat.languages), text_input('Challenge', 'stat_cr', stat.cr), h('br'), textarea('Additional Traits', 'stat_traits', stat.traits, '20em'), h('br'), textarea('Actions', 'stat_actions', stat.actions, '20em'), h('br'), textarea('Reactions', 'stat_reactions', stat.reactions, '20em'), h('br'), textarea('Legendary Action Text', 'stat_legendary_description', stat.legendary_description, '5em'), textarea('Legendary Actions', 'stat_legendary', stat.legendary, '20em'), h('br'), h('div', { style='text-align:center', h('button', 'Update', { type='submit' }), }) }) local img = h('div', { style='text-align: center', h('img', { style="max-width:500px", width='90%', src='yarrow.cgi?statistics='..b64.encode(convert(stat)) }), h('br'), h('a', 'image permalink', { id="permalink", href='yarrow.cgi?statistics='..b64.encode(convert(stat)) }), }) local head = h('head', { h('meta', { charset='utf-8' }), h('meta', { name='viewport', content='width=device-width, initial-scale=1' }), h('title', 'yarrow config'), h('link', { rel='stylesheet', href='style.css' }), h('script', string.format([[ window.onload = () => { const link = document.getElementById('permalink'); const br = document.createElement('br'); const button = document.createElement('button'); button.textContent = 'copy link'; button.addEventListener('click', () => { navigator.clipboard.writeText('%s'); button.textContent = 'copied!'; setTimeout(() => button.textContent = 'copy link', 1000); }); link.after(br, button); }; ]], 'https://sanine.net/utils/yarrow/yarrow.cgi?statistics='..b64.encode(convert(stat))), { type='text/javascript' }), }) local traits = parse_traits(stat.traits) local traits_elements = {} for _, t in ipairs(traits) do table.insert(traits_elements, h('p', string.format('%s -> %s', t.name, t.value))) end local body = h('body', { form_basic, img, h('hr'), h('a', 'source code', { href='https://sanine.net/git/yarrow' }), }) print('Content-type: text/html\n') print(marigold.html(h('html', { head, body })))