parsav  Check-in [6f17de4767]

Overview
Comment:more boilerplate, add template framework
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 6f17de476745e01bc24c082213de56561e17aaf4b06cec660319e6b1b1acb1f4
User & Date: lexi on 2020-12-14 14:40:34
Other Links: manifest | tags
Context
2020-12-16
08:46
iterating check-in: 59e1d7d56a user: lexi tags: trunk
2020-12-14
14:40
more boilerplate, add template framework check-in: 6f17de4767 user: lexi tags: trunk
2020-12-10
05:28
initial commit check-in: e18c9de34d user: lexi tags: trunk
Changes

Added cmdparse.t version [8abc7a6fc7].

            1  +return function(tbl)
            2  +	local options = terralib.types.newstruct('options') do
            3  +		local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
            4  +		local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
            5  +		options.entries = {
            6  +			{field = 'arglist', type = lib.mem.ptr(rawstring)}
            7  +		}
            8  +		local shortcases, longcases, init = {}, {}, {}
            9  +		local self = symbol(&options)
           10  +		local arg = symbol(rawstring)
           11  +		local skip = label()
           12  +		for o,desc in pairs(tbl) do
           13  +			options.entries[#options.entries + 1] = {
           14  +				field = o, type = bool
           15  +			}
           16  +			helpstr = helpstr .. string.format('    -%s --%s: %s\n',
           17  +				desc[1], o, desc[2])
           18  +		end
           19  +		for o,desc in pairs(tbl) do
           20  +			local flag = desc[1]
           21  +			init[#init + 1] = quote [self].[o] = false end
           22  +			shortcases[#shortcases + 1] = quote
           23  +				case [int8]([string.byte(flag)]) then [self].[o] = true end
           24  +			end
           25  +			longcases[#longcases + 1] = quote
           26  +				if lib.str.cmp([arg]+2, o) == 0 then
           27  +					[self].[o] = true
           28  +					goto [skip]
           29  +				end
           30  +			end
           31  +		end
           32  +		options.methods.parse = terra([self], argc: int, argv: &rawstring)
           33  +			[init]
           34  +			var parseopts = true
           35  +			self.arglist = lib.mem.heapa(rawstring, argc)
           36  +			var finalargc = 0
           37  +			for i=0,argc do
           38  +				var [arg] = argv[i]
           39  +				if arg[0] == ('-')[0] and parseopts then
           40  +					if arg[1] == ('-')[0] then -- long option
           41  +						if arg[2] == 0 then -- last option
           42  +							parseopts = false
           43  +						else [longcases] end
           44  +					else -- short options
           45  +						var j = 1
           46  +						while arg[j] ~= 0 do
           47  +							switch arg[j] do [shortcases] end
           48  +							j = j + 1
           49  +						end
           50  +					end
           51  +				else
           52  +					self.arglist.ptr[finalargc] = arg
           53  +					finalargc = finalargc + 1
           54  +				end
           55  +				::[skip]::
           56  +			end
           57  +			if finalargc == 0 then self.arglist:free()
           58  +							  else self.arglist:resize(finalargc) end
           59  +		end
           60  +		options.helptxt = helpstr
           61  +	end
           62  +	return options
           63  +end

Modified common.lua from [9dc63595c4] to [3d397d42e6].

    20     20   	if val then return os.execute(cmd) else
    21     21   		local fd = io.popen(cmd,'r')
    22     22   		local t = fd:read('*a')
    23     23   		return chomp(t), fd:close()
    24     24   	end
    25     25   end
    26     26   
    27         -local function dump(v,pfx)
           27  +local function dump(v,pfx,cyc)
    28     28   	pfx = pfx or ''
           29  +	cyc = cyc or {}
    29     30   	local np = pfx .. '  '
           31  +
           32  +	if type(v) == 'table' then
           33  +		if cyc[v] then return '<...>' else cyc[v] = true end
           34  +	end
           35  +
    30     36   	if type(v) == 'string' then
    31     37   		return string.format('%q', v)
    32     38   	elseif type(v) == 'table' then
    33     39   		local str = ''
    34     40   		for k,v in pairs(v) do
    35         -			str = str .. string.format('%s[%s] = %s\n', np, dump(k,np), dump(v,np))
           41  +			local tkey, tval = dump(k,np,cyc), dump(v,np,cyc)
           42  +			str = str .. string.format('%s[%s] = %s\n', np, tkey,tval)
    36     43   		end
    37     44   		return '{\n' .. str .. pfx .. '}\n'
    38     45   	else
    39     46   		return string.format('%s', v)
    40     47   	end
    41     48   end
    42     49   local ping = function(path)
................................................................................
    50     57   	s = ({
    51     58   		yes = true, ['true'] = true,   on = true,   ['1'] = true;
    52     59   		no = false, ['false'] = false, off = false, ['0'] = false;
    53     60   	})[string.lower(s)]
    54     61   	if not s then return false end
    55     62   	return true
    56     63   end
           64  +
           65  +if ping '/dev/urandom' then
           66  +	local r = io.open('/dev/urandom')
           67  +	local ent = r:read(8) r:close()
           68  +	local seed = 1 for i = 1, 8 do
           69  +		seed = seed * string.byte(string.sub(ent,i,i))
           70  +	end
           71  +	math.randomseed(seed)
           72  +else math.randomseed(os.time()) end
           73  +
    57     74   return {
    58     75   	exec = exec;
    59     76   	dump = dump;
    60     77   	ping = ping;
    61     78   	chomp = chomp;
    62     79   	map = map;
    63     80   	tobool = tobool;
           81  +	rndstr = function(len)
           82  +		local s = ''
           83  +		for i=1,len do
           84  +			local ofs = math.random(0,10 + 26)
           85  +			if ofs >= 10 then ofs = 0x60 + (ofs - 10)
           86  +						 else ofs = 0x30 + ofs end
           87  +			s = s .. string.char(ofs) 
           88  +		end
           89  +		return s
           90  +	end;
    64     91   	append = function(a,b)
    65     92   		for _, v in pairs(b) do a[#a+1] = v end
    66     93   	end;
    67     94   	has = function(haystack,needle,eq)
    68     95   		eq = eq or function(a,b) return a == b end
    69     96   		for k,v in pairs(haystack) do
    70     97   			if eq(needle,v) then return k end
    71     98   		end
           99  +	end;
          100  +	ingest = function(f)
          101  +		local h = io.open(f, 'r')
          102  +		if h == nil then return nil end
          103  +		local txt = f:read('*a') f:close()
          104  +		return chomp(txt)
    72    105   	end;
    73    106   	parseargs = function(a)
    74    107   		local raw = false
    75    108   		local opts, args = {}, {}
    76    109   		for i,v in ipairs(a) do
    77    110   			if v == '--' then
    78    111   				raw = true 

Modified config.lua from [f14c1e8df4] to [85408f7c8a].

     1      1   local u = dofile('common.lua')
     2      2   local default = function(var,def)
     3      3   	local v = os.getenv(var)
     4      4   	if v then return v else return def end
            5  +end
            6  +local function coalesce(x,...)
            7  +	if x ~= nil then return x else
            8  +		if select('#',...) == 0 then return nil end
            9  +		return coalesce(...)
           10  +	end
     5     11   end
     6     12   local posixes = {
     7     13   	linux = true; osx = true;
     8     14   	android = true; haiku = true;
     9     15   }
           16  +local default_os = 'linux'
    10     17   local conf = {
    11     18   	pkg = {};
    12         -	os        = default('parsav_target_os', 'linux');
    13         -	dist      = default('parsav_dist', os.getenv('NIX_PATH') and 'nixos');
           19  +	dist      = default('parsav_dist', coalesce(
           20  +		os.getenv('NIX_PATH')  and 'nixos',
           21  +		os.getenv('NIX_STORE') and 'nixos',
           22  +	''));
    14     23   	tgttrip   = default('parsav_arch_triple'); -- target triple, used in xcomp
    15     24   	tgtcpu    = default('parsav_arch_cpu'); -- target cpu, used in xcomp
    16     25   	tgthf     = u.tobool(default('parsav_arch_armhf',true)); 
           26  +	build     = {
           27  +		id = u.rndstr(6);
           28  +		release = u.ingest('release');
           29  +		when = os.date();
           30  +	};
           31  +	feat = {};
    17     32   }
           33  +if u.ping '.fslckout' or u.ping '_FOSSIL_' then
           34  +	if u.ping '_FOSSIL_' then default_os = 'windows' end
           35  +	conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
           36  +	conf.build.checkout = (u.exec { 'fossil', 'sql',
           37  +		[[select value from localdb.vvar where name = 'checkout-hash']]
           38  +	}):gsub("^'(.*)'$", '%1')
           39  +end
           40  +conf.os    = default('parsav_host_os', default_os);
           41  +conf.tgtos = default('parsav_target_os', default_os);
    18     42   conf.posix = posixes[conf.os]
    19     43   conf.exe   = u.tobool(default('parsav_link',not conf.tgttrip)); -- turn off for partial builds
           44  +conf.build.origin = coalesce(
           45  +	os.getenv('parsav_builder'),
           46  +	string.format('%s@%s', coalesce (
           47  +		os.getenv('USER'),
           48  +		u.exec{'whoami'}
           49  +	), u.exec{'hostname'}) -- whoami and hostname are present on both windows & unix
           50  +)
           51  +if conf.build.release then
           52  +	local v = conf.build.release
           53  +	conf.build.str = string.format('release %s', conf.build.release)
           54  +else
           55  +	conf.build.str = string.format('build %s-%s', conf.build.id, conf.build.origin)
           56  +	if conf.build.branch then
           57  +		conf.build.str = conf.build.str .. string.format(':%s(%s)',
           58  +			conf.build.branch, string.sub(conf.build.checkout,1,10))
           59  +	end
           60  +end
           61  +
           62  +if conf.tgtos == 'linux' then
           63  +	conf.feat.randomizer = default('parsav_feat_randomizer', 'kern')
           64  +else
           65  +	conf.feat.randomizer = default('parsav_feat_randomizer', 'libc')
           66  +end
    20     67   
           68  +local choplib = function(l)
           69  +	if string.sub(l,1,3) == 'lib' then return string.sub(l,4) end
           70  +	return l
           71  +end
    21     72   local fallback = dofile('pkgdata.lua')
    22     73   local libfind do local mkf = function(p)
    23     74   		return function(l) return string.format(p,l) end
    24     75   	end
    25     76   	local unx = function(p)
    26     77   		return function(l)
    27     78   			if string.sub(l,1,3) == 'lib'
................................................................................
    28     79   				then return string.format('%s.%s',l,p)
    29     80   				else return string.format('lib%s.%s',l,p)
    30     81   			end
    31     82   		end
    32     83   	end
    33     84   
    34     85   	libfind = {
    35         -		linux = {
    36         -			static = unx 'a';
    37         -			dynamic = unx 'so';
    38         -		};
    39         -		osx = { dynamic = mkf '%s.dylib'; };
    40         -		win = { dynamic = mkf '%s.dll'; };
           86  +		linux = { static = unx 'a', dynamic = unx 'so'; };
           87  +		osx   = { dynamic = mkf '%s.dylib'; };
           88  +		win   = { dynamic = mkf '%s.dll'; };
    41     89   	}
    42     90   end
    43     91   local pkg = function(name)
    44         -	local fbo = fallback[name] and fallback[name].osvars
    45         -		and fallback[name].osvars[conf.os .. '_' .. conf.dist]
           92  +	local fbo = fallback[name] and fallback[name].osvars and coalesce(
           93  +		fallback[name].osvars[conf.os .. '_' .. conf.dist],
           94  +		fallback[name].osvars[conf.os]
           95  +	)
    46     96   	local fbv = fallback[name] and fallback[name].vars
           97  +	local fb = fallback[name]
    47     98   	local pkgenv = function(e)
    48     99   		return os.getenv(string.format('parsav_pkg_%s_%s',name,e))
    49    100   	end
    50         -	name = pkgenv('override') or name
    51    101   	local nul = function() end
    52    102   	local pkc, pkv = nul, nul
          103  +	local locdep = false
    53    104   	local cnfvar = function(v,e)
    54    105   		local eval = function(e)
    55    106   			if type(e) == 'function'
    56    107   				then return e(conf)
    57    108   				else return e
    58    109   			end
    59    110   		end
    60         -		return pkgenv(v) or pkv(e or v) or (fbo and eval(fbo[v])) or (fbv and eval(fbv[v]))
          111  +		return coalesce(
          112  +			pkgenv(v),
          113  +			pkv(e or v),
          114  +			(fbo and eval(fbo[v])),
          115  +			(fbv and eval(fbv[v])))
    61    116   	end
          117  +	local name = cnfvar('override') or name
          118  +	local pcname = coalesce(cnfvar('pcname'), name)
    62    119   	if conf.posix then
    63         -		pkc  = function(...) return u.exec { 'pkg-config'; name; ...  } end
    64         -		local pkcv = function(...) return u.exec({ 'pkg-config'; name; ...  }, true) end
          120  +		pkc  = function(...) if locdep then return nil end
          121  +			return u.exec { 'pkg-config'; pcname; ...  } end
          122  +		local pkcv = function(...) return u.exec({ 'pkg-config'; pcname; ...  }, true) end
    65    123   		if pkcv('--exists') == 0 then
    66         -			 pkv = function(v)
          124  +			pkv = function(v)
          125  +				if locdep then return nil end
    67    126   				return pkc('--variable', v)
    68    127   			end
    69    128   		else pkc = nul end
    70    129   	else
    71    130   		print '(warn) configuring on non-POSIX OS, all relevant paths must be specified manually in environment variables or build will fail!'
    72    131   	end
    73         -	local locdep = u.ping('./lib/' .. name)
    74         -	local prefix
          132  +	locdep = u.ping('./lib/' .. name)
          133  +	local incdir, libdir, prefix
    75    134   	if locdep then
    76    135   		prefix = './lib/' .. name
    77         -	end
    78         -	prefix = prefix or cnfvar('prefix')
    79         -	local libdir = cnfvar('libdir')
    80         -	if not libdir then
    81         -		if locdep
    82         -			then libdir = prefix .. (cnfvar('builddir') or cnfvar('libbuilddir')) or ''
    83         -			else libdir = prefix .. '/lib'
    84         -		end
          136  +		libdir = prefix .. coalesce(cnfvar('libbuilddir'),cnfvar('builddir'),'')
          137  +		incdir = prefix .. coalesce(cnfvar('srcincdir'),cnfvar('builddir'),'/include')
          138  +	else
          139  +		prefix = coalesce(cnfvar('prefix'), '/usr')
          140  +		libdir = cnfvar('libdir')
          141  +		incdir = cnfvar('incdir','includedir')
    85    142   	end
    86         -	local incdir = cnfvar('incdir','includedir') or (prefix .. '/include')
          143  +	libdir = libdir or prefix .. '/lib'
          144  +	incdir = incdir or prefix .. '/include'
          145  +
    87    146   	local libstr = pkc '--libs-only-l' -- (--static is not reliable)
    88         -	local libs = fallback[name] and fallback[name].libs or {}
          147  +	local libs = fb and fb.libs or {}
    89    148   	local linkstatic = locdep
    90    149   	if (not locdep) and libstr then
    91    150   		libs = {}
    92    151   		for m in string.gmatch(libstr, '-l(%g+)') do
    93    152   			libs[#libs + 1] = m
    94    153   		end
    95    154   	else
................................................................................
   112    171   		local ldf = {}
   113    172   		for _,v in pairs(me.statlibs) do
   114    173   			local fl = libdir .. '/' .. v
   115    174   			if u.ping(fl) then ldf[#ldf+1] = fl end
   116    175   		end
   117    176   		me.linkargs = ldf
   118    177   	else
   119         -		local arg = name
   120         -		if string.sub(arg,1,3) == 'lib' then arg = string.sub(arg,4) end
   121         -		local linkline = libstr or string.format('-l%s', arg)
          178  +		local arg = choplib(name)
          179  +		local linkline = libstr
          180  +		if not linkline and #libs == 0 then
          181  +			linkline = string.format('-l%s', arg)
          182  +		elseif not linkline then linkline = '' end
   122    183   		me.linkargs = {
   123    184   			string.format('-L%s', me.libdir);
   124    185   		}
   125    186   		for m in string.gmatch(linkline, '(%g+)') do
   126    187   			me.linkargs[#me.linkargs+1] = m
   127    188   		end
   128    189   
   129    190   		-- siiiiigh
   130    191   		for _,l in pairs(libs) do
   131         -			local sw = string.format('-l%s', l) 
          192  +			local sw = string.format('-l%s', choplib(l)) 
   132    193   			local found = false
   133    194   			for _,a in pairs(me.linkargs) do
   134    195   				if a == sw then found = true break end
   135    196   			end
   136    197   			if not found then
   137    198   				me.linkargs[#me.linkargs+1] = sw
   138    199   			end
   139    200   		end
   140    201   	end
   141    202   end
   142    203   
   143    204   pkg('mbedtls')
   144         -pkg('libhttp')
          205  +pkg('mongoose')
   145    206   pkg('json-c')
          207  +pkg('libc')
          208  +pkg('libpq')
   146    209   
   147    210   return conf

Added crypt.t version [340864c560].

            1  +-- vim: ft=terra
            2  +local const = {
            3  +	keybits = 2048;
            4  +	sighash = lib.md.MBEDTLS_MD_SHA256;
            5  +}
            6  +local err = {
            7  +	rawcode = terra(code: int)
            8  +		if code < 0 then code = -code end
            9  +		return code and 0xFF80
           10  +	end;
           11  +	toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE;
           12  +}
           13  +const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to
           14  +
           15  +local ctx = lib.pk.mbedtls_pk_context
           16  +
           17  +local m = {
           18  +	pemfile = uint8[const.maxpemsz];
           19  +}
           20  +local callbacks = {}
           21  +if config.feat.randomizer == 'kern' then
           22  +	local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
           23  +	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
           24  +		return rnd(dest, sz, 0)
           25  +	end
           26  +elseif config.feat.randomizer == 'devfs' then
           27  +	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
           28  +		var gen = lib.io.open("/dev/urandom",0)
           29  +		lib.io.read(gen, dest, sz)
           30  +		lib.io.close(gen)
           31  +		return sz
           32  +	end
           33  +elseif config.feat.randomizer == 'libc' then
           34  +	local rnd = terralib.externfunction('rand', {} -> int);
           35  +	local srnd = terralib.externfunction('srand', uint -> int);
           36  +	local time = terralib.includec 'time.h'
           37  +	lib.init[#lib.init + 1] = quote srnd(time.time(nil)) end
           38  +	print '(warn) using libc soft-rand function for cryptographic purposes, this is very bad!'
           39  +	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
           40  +		for i=0,sz do dest[i] = [uint8](rnd()) end
           41  +		return sz
           42  +	end
           43  +end
           44  +
           45  +terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool
           46  +	if pub then
           47  +		return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
           48  +	else
           49  +		return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
           50  +	end
           51  +end
           52  +
           53  +m.destroy = lib.dispatch {
           54  +	[ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end;
           55  +
           56  +	[false] = function(ptr) return `ptr:free() end;
           57  +}
           58  +
           59  +terra m.genkp(): lib.pk.mbedtls_pk_context
           60  +	lib.dbg('generating new keypair')
           61  +
           62  +	var pk: ctx
           63  +	lib.pk.mbedtls_pk_init(&pk)
           64  +	lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA))
           65  +	var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx)
           66  +	lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537)
           67  +
           68  +	return pk
           69  +end
           70  +
           71  +terra m.loadpriv(buf: &uint8, len: intptr): ctx
           72  +	lib.dbg('parsing saved keypair')
           73  +
           74  +	var pk: ctx
           75  +	lib.pk.mbedtls_pk_init(&pk)
           76  +	lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0)
           77  +	return pk
           78  +end
           79  +
           80  +terra m.sign(pk: &ctx, txt: rawstring, len: intptr)
           81  +	lib.dbg('signing message')
           82  +	var osz: intptr = 0
           83  +	var sig = lib.mem.heapa(int8, 2048)
           84  +	var hash: uint8[32]
           85  +	
           86  +	lib.dbg('(1/2) hashing message')
           87  +	if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(const.sighash), [&uint8](txt), len, hash) ~= 0 then
           88  +		lib.bail('could not hash message')
           89  +	end
           90  +
           91  +	lib.dbg('(2/2) signing hash')
           92  +	var ret = lib.pk.mbedtls_pk_sign(pk, const.sighash, hash, 0, [&uint8](sig.ptr), &osz, callbacks.randomize, nil)
           93  +	if ret ~= 0 then lib.bail('could not sign message hash')
           94  +	else sig:resize(osz) end
           95  +
           96  +	return sig
           97  +end
           98  +
           99  +terra m.verify(pk: &ctx, txt: rawstring, len: intptr,
          100  +                        sig: rawstring, siglen: intptr): {bool, uint8}
          101  +	lib.dbg('verifying signature')
          102  +	var osz: intptr = 0
          103  +	var hash: uint8[64]
          104  +
          105  +	-- there does not appear to be any way to extract the hash algorithm
          106  +	-- from the message, so we just have to try likely algorithms until
          107  +	-- we find one that fits or give up. a security level is attached
          108  +	-- to indicate our relative confidence in the hash; 0 == 'likely forgeable'
          109  +	var algs = array(
          110  +		-- common hashes
          111  +		{lib.md.MBEDTLS_MD_SHA256, 'sha256', 2},
          112  +		{lib.md.MBEDTLS_MD_SHA512, 'sha512', 3},
          113  +		{lib.md.MBEDTLS_MD_SHA1,   'sha1',   1},
          114  +		-- uncommon hashes
          115  +		{lib.md.MBEDTLS_MD_SHA384, 'sha384', 2},
          116  +		{lib.md.MBEDTLS_MD_SHA224, 'sha224', 2},
          117  +		-- bad hashes
          118  +		{lib.md.MBEDTLS_MD_MD5,   'md5', 0},
          119  +		{lib.md.MBEDTLS_MD_MD4,   'md4', 0},
          120  +		{lib.md.MBEDTLS_MD_MD2,   'md2', 0}
          121  +	)
          122  +	
          123  +	for i = 0, [algs.type.N] do
          124  +		var hk, aname, secl = algs[i]
          125  +
          126  +		lib.dbg('(1/2) trying hash algorithm ',aname)
          127  +		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then
          128  +			lib.bail('could not hash message')
          129  +		end
          130  +
          131  +		lib.dbg('(2/2) verifying hash')
          132  +		if lib.pk.mbedtls_pk_verify(pk, hk, hash, 0, [&uint8](sig), siglen) == 0 then
          133  +			return true, secl
          134  +		end
          135  +	end
          136  +	lib.dbg('all hash algorithms failed')
          137  +	return false, 0
          138  +end
          139  +
          140  +return m

Added http.t version [6d6c40fc94].

            1  +-- vim: ft=terra
            2  +local m = {}
            3  +local util = dofile('common.lua')
            4  +
            5  +struct m.header {
            6  +	key: rawstring
            7  +	value: rawstring
            8  +}
            9  +struct m.page {
           10  +	respcode: uint16
           11  +	body: lib.mem.ptr(int8)
           12  +	headers: lib.mem.ptr(m.header)
           13  +}
           14  +
           15  +local resps = {
           16  +	[200] = 'OK';
           17  +	[201] = 'Created';
           18  +	[301] = 'Moved Permanently';
           19  +	[302] = 'Found';
           20  +	[303] = 'See Other';
           21  +	[307] = 'Temporary Redirect';
           22  +	[404] = 'Not Found';
           23  +	[401] = 'Unauthorized';
           24  +	[403] = 'Forbidden';
           25  +	[418] = 'I\'m a teapot';
           26  +	[405] = 'Method Not Allowed';
           27  +	[500] = 'Internal Server Error';
           28  +}
           29  +local resptext = symbol(rawstring)
           30  +local resplen = symbol(intptr)
           31  +local respbranches = {}
           32  +for k,v in pairs(resps) do
           33  +	local txt = string.format('%u %s\r\n',k,v)
           34  +	respbranches[#respbranches + 1] = quote
           35  +		case [uint16](k) then resptext = [txt] resplen = [#txt] end
           36  +	end
           37  +end
           38  +m.codestr = terra(code: uint16)
           39  +	var [resptext] var [resplen]
           40  +	switch code do [respbranches] end
           41  +	return resptext, resplen
           42  +end
           43  +m.page.methods = {
           44  +	free = terra(self: &m.page)
           45  +		self.body:free()
           46  +		self.headers:free()
           47  +	end;
           48  +	send = terra(self: &m.page, con: &lib.net.mg_connection)
           49  +		var code: rawstring
           50  +		var [resptext] var [resplen]
           51  +		switch self.respcode do [respbranches] end
           52  +		lib.net.mg_send(con, "HTTP/1.1 ", 9)
           53  +		lib.net.mg_send(con, resptext, resplen)
           54  +		for i = 0, self.headers.ct do
           55  +			var h = self.headers.ptr[i]
           56  +			lib.net.mg_send(con, h.key, lib.str.sz(h.key))
           57  +			lib.net.mg_send(con, ": ", 2)
           58  +			lib.net.mg_send(con, h.value, lib.str.sz(h.value))
           59  +			lib.net.mg_send(con, "\r\n", 2)
           60  +		end
           61  +		lib.net.mg_printf(con, 'Content-Length: %llu\r\n\r\n', self.body.ct)
           62  +		lib.net.mg_send(con,self.body.ptr,self.body.ct)
           63  +	end;
           64  +}
           65  +return m

Modified makefile from [b49975ddb0] to [0f98e9b572].

     5      5   	terra $(dbg-flags) $<
     6      6   parsav.o: parsav.t config.lua pkgdata.lua
     7      7   	env parsav_link=no terra $(dbg-flags) $<
     8      8   
     9      9   clean:
    10     10   	rm parsav parsav.o
    11     11   
    12         -dep: dep.mbedtls dep.libhttp dep.json-c
           12  +install: parsav
           13  +	mkdir $(prefix)/bin
           14  +	cp $< $(prefix)/bin/
           15  +
           16  +dep: dep.mbedtls dep.mongoose dep.json-c
    13     17   dep.mbedtls: lib/mbedtls/library/libmbedtls.a \
    14     18   	lib/mbedtls/library/libmbedcrypto.a \
    15     19   	lib/mbedtls/library/libmbedx509.a
    16         -dep.libhttp: lib/libhttp/lib/libhttp.a
    17         -dep.json-c: lib/libhttp/json-c.a
           20  +dep.mongoose: lib/mongoose/libmongoose.a
           21  +dep.json-c: lib/json-c/libjson-c.a
    18     22   
    19     23   lib:
    20     24   	mkdir $@
    21     25   # parsav is designed to be fronted by a real web
    22     26   # server like nginx if SSL is to be used
    23         -# caveat: libhttp is a mess. the docs are completely
    24         -# full of shit. there is no lua support as far as i
    25         -# can tell.
    26         -lib/libhttp/lib/libhttp.a: lib/libhttp
    27         -	$(MAKE) -C $< lib/libhttp.a \
    28         -		RM='rm -f' \
    29         -		CC="$(CC) -Wno-unused-result" \
    30         -		DFLAGS="-DNO_SSL -DNO_FILES -DNO_CGI -DUSE_STACK_SIZE=102400 -DUSE_IPV6"
           27  +# generate a shim static library so mongoose cooperates
           28  +# with the build apparatus
           29  +lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h
           30  +	$(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \
           31  +		-DMG_ENABLE_THREADS \
           32  +		-DMG_ENABLE_IPV6 \
           33  +		-DMG_ENABLE_HTTP_WEBDAV \
           34  +		-DMG_ENABLE_HTTP_WEBSOCKET=0
           35  +	ar rcs $@ lib/mongoose/*.o
           36  +	ranlib $@
    31     37   
    32     38   lib/json-c/Makefile: lib/json-c lib/json-c/CMakeLists.txt
    33     39   	cd lib/json-c && cmake .
    34     40   lib/json-c/libjson-c.a: lib/json-c/Makefile
    35     41   	$(MAKE) -C lib/json-c
    36     42   lib/mbedtls/library/%.a: lib/mbedtls 
    37     43   	$(MAKE) -C lib/mbedtls/library $*.a
    38     44   
    39     45   ifeq ($(dl), git)
    40         -lib/libhttp: lib
    41         -	cd lib && git clone https://github.com/lammertb/libhttp.git
           46  +lib/mongoose: lib
           47  +	cd lib && git clone https://github.com/cesanta/mongoose
    42     48   lib/mbedtls: lib
    43     49   	cd lib && git clone https://github.com/ARMmbed/mbedtls.git
    44     50   lib/json-c: lib
    45     51   	cd lib && git clone https://github.com/json-c/json-c.git
    46     52   else
    47     53   lib/%: lib/%.tar.gz
    48     54   	cd lib && tar zxf $*.tar.gz
................................................................................
    52     58       dlfile = wget "$(1)" -O "$(2)"
    53     59   endif
    54     60   
    55     61   ifeq ($(dl), curl)
    56     62       dlfile = curl "$(1)" -o "$(2)"
    57     63   endif
    58     64   
    59         -lib/libhttp.tar.gz: lib
    60         -	$(call dlfile,https://api.github.com/repos/lammertb/libhttp/tarball/master,$@)
           65  +lib/mongoose.tar.gz: lib
           66  +	$(call dlfile,https://api.github.com/repos/cesanta/mongoose/tarball/master,$@)
    61     67   lib/mbedtls.tar.gz: lib
    62     68   	$(call dlfile,https://api.github.com/repos/ARMmbed/mbedtls/tarball/master,$@)
    63     69   lib/json-c.tar.gz: lib
    64     70   	$(call dlfile,https://api.github.com/repos/json-c/json-c/tarball/master,$@)
    65     71   endif

Modified parsav.t from [d2f35b212c] to [889180c92d].

     1      1   -- vim: ft=terra
     2      2   
     3      3   local util = dofile('common.lua')
     4      4   local buildopts, buildargs = util.parseargs{...}
            5  +config = dofile('config.lua')
     5      6   
     6         -local config = dofile('config.lua')
     7         -
     8         -local lib = {
            7  +lib = {
            8  +	init = {};
     9      9   	loadlib = function(name,hdr)
    10     10   		local p = config.pkg[name]
    11     11   		-- for _,v in pairs(p.dylibs) do
    12     12   		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
    13     13   		-- end
    14     14   		return terralib.includec(p.incdir .. '/' .. hdr)
    15     15   	end;
    16         -	randomize = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
           16  +	dispatch = function(tbl)
           17  +		return macro(function(v,...)
           18  +			for ty,fn in pairs(tbl) do
           19  +				if v.tree.type == ty then return fn(v,...) end
           20  +			end
           21  +			return (tbl[false])(v,...)
           22  +		end)
           23  +	end;
           24  +	emit = function(...)
           25  +		local code = {}
           26  +		for i,v in ipairs{...} do
           27  +			if type(v) == 'string' or type(v) == 'number' then
           28  +				local str = tostring(v)
           29  +				code[#code+1] = `lib.io.send(2, str, [#str])
           30  +			elseif v.tree:is 'constant' then
           31  +				local str = tostring(v:asvalue())
           32  +				code[#code+1] = `lib.io.send(2, str, [#str])
           33  +			else
           34  +				code[#code+1] = quote var n = v in
           35  +					lib.io.send(2, n, lib.str.sz(n)) end
           36  +			end
           37  +		end
           38  +		code[#code+1] = `lib.io.send(2, '\n', 1)
           39  +		return code
           40  +	end;
           41  +	proc = {
           42  +		exit = terralib.externfunction('exit', int -> {});
           43  +		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
           44  +	};
           45  +	io = {
           46  +		open = terralib.externfunction('open', {rawstring, int} -> int);
           47  +		close = terralib.externfunction('close',  {int} -> int);
           48  +		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
           49  +		recv = terralib.externfunction('read',  {int, rawstring, intptr} -> ptrdiff);
           50  +		say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
           51  +		fmt = terralib.externfunction('printf',
           52  +			terralib.types.funcpointer({rawstring},{int},true));
           53  +	};
           54  +	str = {
           55  +		sz = terralib.externfunction('strlen', rawstring -> intptr);
           56  +		cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
           57  +		cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
           58  +		ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
           59  +		fmt = terralib.externfunction('asprintf',
           60  +			terralib.types.funcpointer({&rawstring},{int},true));
           61  +	};
           62  +	mem = {
           63  +		zero = macro(function(r)
           64  +			return quote
           65  +				for i = 0, [r.tree.type.N] do r[i] = 0 end
           66  +			end
           67  +		end);
           68  +		heapa_raw = terralib.externfunction('malloc', intptr -> &opaque);
           69  +		heapr_raw = terralib.externfunction('realloc', {&opaque, intptr} -> &opaque);
           70  +		heapf = terralib.externfunction('free', &opaque -> {});
           71  +		cpy = terralib.externfunction('mempcpy',{&opaque, &opaque, intptr} -> &opaque);
           72  +		heapa = macro(function(ty, sz)
           73  +			local p = lib.mem.ptr(ty:astype())
           74  +			return `p {
           75  +				ptr = [&ty:astype()](lib.mem.heapa_raw(sizeof(ty) * sz));
           76  +				ct = sz;
           77  +			}
           78  +		end)
           79  +	};
    17     80   }
           81  +
           82  +local noise = global(uint8,1)
           83  +local defrep = function(level,n,code)
           84  +	return macro(function(...)
           85  +		local q = lib.emit("\27["..code..";1m(parsav "..n..")\27[m ", ...)
           86  +		return quote
           87  +			if noise >= level then [q] end
           88  +		end
           89  +	end);
           90  +end
           91  +lib.dbg = defrep(3,'debug', '32')
           92  +lib.report = defrep(2,'info', '35')
           93  +lib.warn = defrep(1,'warn', '33')
           94  +lib.bail = macro(function(...)
           95  +	local q = lib.emit("\27[31;1m(parsav fatal)\27[m ", ...)
           96  +	return quote
           97  +		[q]
           98  +		lib.proc.exit(1)
           99  +	end
          100  +end);
          101  +lib.mem.ptr = terralib.memoize(function(ty)
          102  +	local t = terralib.types.newstruct(string.format('ptr<%s>', ty))
          103  +	t.entries = {
          104  +		{'ptr', &ty};
          105  +		{'ct', intptr};
          106  +	}
          107  +	local recurse = false
          108  +	if ty:isstruct() then
          109  +		if ty.methods.free then recurse = true end
          110  +	end
          111  +	t.methods = {
          112  +		free = terra(self: &t): bool
          113  +			[recurse and quote
          114  +				self.ptr:free()
          115  +			end or {}]
          116  +			if self.ct > 0 then
          117  +				lib.mem.heapf(self.ptr)
          118  +				self.ct = 0
          119  +				return true
          120  +			end
          121  +			return false
          122  +		end;
          123  +		init = terra(self: &t, newct: intptr): bool
          124  +			var nv = [&ty](lib.mem.heapa_raw(sizeof(ty) * newct))
          125  +			if nv ~= nil then
          126  +				self.ptr = nv
          127  +				self.ct = newct
          128  +				return true
          129  +			else return false end
          130  +		end;
          131  +		resize = terra(self: &t, newct: intptr): bool
          132  +			var nv: &ty
          133  +			if self.ct > 0
          134  +				then nv = [&ty](lib.mem.heapr_raw(self.ptr, sizeof(ty) * newct))
          135  +				else nv = [&ty](lib.mem.heapa_raw(sizeof(ty) * newct))
          136  +			end
          137  +			if nv ~= nil then
          138  +				self.ptr = nv
          139  +				self.ct = newct
          140  +				return true
          141  +			else return false end
          142  +		end;
          143  +	}
          144  +	return t
          145  +end)
          146  +
          147  +lib.err = lib.loadlib('mbedtls','mbedtls/error.h')
    18    148   lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h')
    19    149   lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
          150  +lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
          151  +lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
          152  +lib.net = lib.loadlib('mongoose','mongoose.h')
          153  +lib.pq = lib.loadlib('libpq','libpq-fe.h')
          154  +lib.crypt = terralib.loadfile('crypt.t')()
          155  +lib.http = terralib.loadfile('http.t')()
          156  +lib.tpl = terralib.loadfile('tpl.t')()
          157  +lib.string = terralib.loadfile('string.t')()
          158  +lib.store = terralib.loadfile('store.t')()
          159  +lib.cmdparse = terralib.loadfile('cmdparse.t')()
    20    160   
    21         -local callbacks = {
    22         -	randomize = terra(ctx: &opaque, dest: &uint8, sz: intptr): int
    23         -		return lib.randomize(dest, sz, 0)
          161  +do local collate = function(path,f, ...)
          162  +	return loadfile(path..'/'..f..'.lua')(path, ...)
          163  +end
          164  +data = {
          165  +	view = collate('view','load');
          166  +} end
          167  +
          168  +-- slightly silly -- because we're using plain lua to gather up
          169  +-- the template sources, they have to be actually turned into
          170  +-- templates in the terra code, so we "mutate" them here
          171  +for k,v in pairs(data.view) do
          172  +	local t = lib.tpl.mk { body = v, id = 'view/'..k }
          173  +	data.view[k] = t
          174  +end
          175  +
          176  +local pemdump = macro(function(pub, kp)
          177  +	local msg = (pub:asvalue() and ' * emitting public key\n') or ' * emitting private key\n'
          178  +	return quote
          179  +		var buf: lib.crypt.pemfile
          180  +		lib.mem.zero(buf)
          181  +		lib.crypt.pem(pub, &kp, buf)
          182  +		lib.io.send(1, msg, [#msg])
          183  +		lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf)))
          184  +		lib.io.send(1, '\n', 1)
          185  +	end
          186  +end)
          187  +
          188  +local handle = {
          189  +	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
          190  +		switch event do
          191  +			case lib.net.MG_EV_HTTP_MSG then
          192  +				lib.dbg('routing HTTP request')
          193  +				var msg = [&lib.net.mg_http_message](p)
          194  +
          195  +			end
          196  +		end
    24    197   	end;
    25    198   }
          199  +do
          200  +	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
          201  +	terra version() lib.io.send(1, p, [#p]) end
          202  +end
          203  +terra noise_init()
          204  +	var n = lib.proc.getenv('parsav_noise')
          205  +	if n ~= nil then
          206  +		if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
          207  +			noise = n[0] - 0x30
          208  +			return
          209  +		end
          210  +	end
          211  +	noise = 1
          212  +end
          213  +
          214  +local options = lib.cmdparse {
          215  +	version = {'V', 'display information about the binary build and exit'};
          216  +	quiet = {'q', 'do not print to standard out'};
          217  +	help = {'h', 'display this list'}
          218  +}
          219  +
          220  +terra entry(argc: int, argv: &rawstring): int
          221  +	noise_init()
          222  +	[lib.init]
          223  +
          224  +	var mode: options
          225  +	mode:parse(argc,argv)
          226  +	if mode.version then
          227  +		version()
          228  +		return 0
          229  +	end
          230  +	if mode.help then
          231  +		lib.io.send(1,  [options.helptxt], [#options.helptxt])
          232  +		return 0
          233  +	end
          234  +
          235  +	var bind = lib.proc.getenv('parsav_bind')
          236  +	if bind == nil then bind = '[::]:10917' end
          237  +
          238  +	var nm: lib.net.mg_mgr
          239  +	lib.net.mg_mgr_init(&nm)
          240  +
          241  +	var nmc = lib.net.mg_http_listen(&nm, bind, handle.http, nil)
          242  +
          243  +	while true do
          244  +		lib.net.mg_mgr_poll(&nm,1000)
          245  +	end
    26    246   
    27         -terra entry(): int
    28         -	var pk: lib.pk.mbedtls_pk_context
    29         -	lib.pk.mbedtls_pk_init(&pk)
    30         -	lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA))
    31         -	var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx)
    32         -	lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, 2048, 65537)
          247  +	lib.net.mg_mgr_free(&nm)
    33    248   	return 0
    34    249   end
    35    250   
    36    251   local bflag = function(long,short)
    37    252   	if short and util.has(buildopts, short) then return true end
    38    253   	if long and util.has(buildopts, long) then return true end
    39    254   	return false
................................................................................
    45    260   end
    46    261   
    47    262   local emit = print
    48    263   if bflag('quiet','q') then emit = function() end end
    49    264   
    50    265   local out = config.exe and 'parsav' or 'parsav.o'
    51    266   local linkargs = {}
          267  +if config.posix then
          268  +	linkargs[#linkargs+1] = '-pthread'
          269  +end
    52    270   for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
    53    271   emit('linking with args',util.dump(linkargs))
    54    272   terralib.saveobj(out, {
    55    273   		main = entry
    56    274   	},
    57    275   	linkargs,
    58    276   	config.tgttrip and terralib.newtarget {
    59    277   		Triple = config.tgttrip;
    60    278   		CPU = config.tgtcpu;
    61    279   		FloatABIHard = config.tgthf;
    62    280   	} or nil)

Modified pkgdata.lua from [b4c0388029] to [8c50f1b4b0].

     1      1   local util = dofile('common.lua')
     2      2   local sthunk = function(...) local a = {...} return function() return util.exec(a) end end
            3  +
     3      4   return {
     4      5   	mbedtls = { 
     5      6   		libs = {'mbedtls', 'mbedcrypto', 'mbedx509'};
     6      7   		osvars = {
     7      8   			linux_nixos = { -- lacks a *.pc on nixos systems
     8      9   				prefix = sthunk('nix', 'path-info', 'nixos.mbedtls');
     9     10   			}
    10     11   		};
    11     12   		vars = { builddir = '/library' };
    12     13   	};
    13         -	libhttp = { vars = { builddir = '/lib' }; };
           14  +	mongoose = { vars = { builddir = '' } };
           15  +	libpq = {
           16  +		osvars = {
           17  +			linux_nixos = {
           18  +				prefix = sthunk('nix', 'path-info', 'nixos.postgresql.lib');
           19  +				incdir = function()
           20  +					local a = {'nix', 'path-info', 'nixos.postgresql'}
           21  +					return (util.exec(a)) .. '/include';
           22  +				end;
           23  +			};
           24  +		};
           25  +		vars = {pcname = 'postgresql';}
           26  +	};
           27  +	libc = {
           28  +		libs = {'dl'}; -- libc.so does not need explicit mention
           29  +		osvars = {
           30  +			linux_nixos = {
           31  +				prefix = sthunk('nix', 'path-info', 'nixos.glibc');
           32  +				override = 'glibc';
           33  +			};
           34  +			linux = { override = 'glibc'; };
           35  +		}
           36  +	};
    14     37   }

Added store.t version [a0814e5c61].

            1  +-- vim: ft=terra
            2  +local m = {}
            3  +
            4  +local backend = {
            5  +	pgsql = {
            6  +	};
            7  +}
            8  +
            9  +struct m.user {
           10  +	uid: rawstring
           11  +	nym: rawstring
           12  +	handle: rawstring
           13  +
           14  +	localuser: bool
           15  +}

Added string.t version [a0d1de486b].

            1  +-- vim: ft=terra
            2  +-- string.t: string classes
            3  +
            4  +local m = {}
            5  +
            6  +struct m.acc {
            7  +	buf: rawstring
            8  +	sz: intptr
            9  +	run: intptr
           10  +	space: intptr
           11  +}
           12  +
           13  +local terra biggest(a: intptr, b: intptr)
           14  +	if a > b then return a else return b end
           15  +end
           16  +
           17  +m.acc.methods = {
           18  +	init = terra(self: &m.acc, run: intptr)
           19  +		lib.dbg('initializing string accumulator')
           20  +		self.buf = [rawstring](lib.mem.heapa_raw(run))
           21  +		self.run = run
           22  +		self.space = run
           23  +		self.sz = 0
           24  +	end;
           25  +	free = terra(self: &m.acc)
           26  +		lib.dbg('freeing string accumulator')
           27  +		if self.buf ~= nil and self.space > 0 then
           28  +			lib.mem.heapf(self.buf)
           29  +		end
           30  +	end;
           31  +	crush = terra(self: &m.acc)
           32  +		lib.dbg('crushing string accumulator')
           33  +		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
           34  +		self.space = self.sz
           35  +		return self
           36  +	end;
           37  +}
           38  +
           39  +m.acc.methods.finalize = terra(self: &m.acc)
           40  +	lib.dbg('finalizing string accumulator')
           41  +	self:crush()
           42  +	var pt: lib.mem.ptr(int8)
           43  +	pt.ptr = self.buf
           44  +	pt.ct = self.sz
           45  +	self.buf = nil
           46  +	self.sz = 0
           47  +	return pt
           48  +end;
           49  +m.acc.methods.push = terra(self: &m.acc, str: rawstring, len: intptr)
           50  +	lib.dbg('pushing "',str,'" onto accumulator')
           51  +	if self.buf == nil then self:init(self.run) end
           52  +	if len == 0 then len = lib.str.sz(str) end
           53  +	if len >= self.space - self.sz then
           54  +		self.space = self.space + biggest(self.run,len + 1)
           55  +		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
           56  +	end
           57  +	lib.mem.cpy(self.buf + self.sz, str, len)
           58  +	self.sz = self.sz + len
           59  +	self.buf[self.sz] = 0
           60  +	return self
           61  +end;
           62  +m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
           63  +	self:push(str.ptr, str.ct)            return self end;
           64  +m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
           65  +	self:push(str.ptr, str.ct) str:free() return self end;
           66  +
           67  +return m

Added tpl.t version [ad44dd6129].

            1  +-- vim: ft=terra
            2  +-- string template generator:
            3  +-- returns a function that fills out a template
            4  +-- with the strings given
            5  +
            6  +local util = dofile 'common.lua'
            7  +local m = {}
            8  +function m.mk(tplspec)
            9  +	local str
           10  +	if type(tplspec) == 'string'
           11  +		then str = tplspec tplspec = {}
           12  +		else str = tplspec.body
           13  +	end
           14  +	local tplchar_o = tplspec.sigil or '@'
           15  +	local tplchar = tplchar_o
           16  +	local magic = {
           17  +		['%'] = true, ['$'] = true, ['^'] = true;
           18  +		['.'] = true, ['*'] = true, ['+'] = true;
           19  +		['-'] = true, ['?'] = true;
           20  +	}
           21  +	tplchar_o = string.gsub(tplchar_o,'%%','%%%%')
           22  +	tplchar = string.gsub(tplchar, '.', function(c)
           23  +		if magic[c] then return '%' .. c end
           24  +	end)
           25  +	local last = 1
           26  +	local fields = {}
           27  +	local segs = {}
           28  +	local constlen = 0
           29  +	-- strip out all irrelevant whitespace to tidy things up
           30  +	-- TODO: find way to exclude <pre> tags?
           31  +	str = str:gsub('[\n^]%s+','')
           32  +	str = str:gsub('%s+[\n$]','')
           33  +	str = str:gsub('\n','')
           34  +	for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do
           35  +		if string.sub(str,start-1,start-1) ~= '\\' then
           36  +			segs[#segs+1] = string.sub(str,last,start-1)
           37  +			fields[#segs] = key
           38  +			last = stop
           39  +		end
           40  +	end
           41  +	segs[#segs+1] = string.sub(str,last)
           42  +	for i, s in ipairs(segs) do
           43  +		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)
           44  +		constlen = constlen + string.len(segs[i])
           45  +	end
           46  +
           47  +	local runningtally = symbol(intptr)
           48  +	local tallyup = {quote
           49  +		var [runningtally] = 1 + constlen
           50  +	end}
           51  +	local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
           52  +	local symself = symbol(&rec)
           53  +	do local kfac = {}
           54  +		for afterseg,key in pairs(fields) do
           55  +			if not kfac[key] then
           56  +				rec.entries[#rec.entries + 1] = {
           57  +					field = key;
           58  +					type = rawstring;
           59  +				}
           60  +			end
           61  +			kfac[key] = (kfac[key] or 0) + 1
           62  +		end
           63  +		for key, fac in pairs(kfac) do
           64  +			tallyup[#tallyup + 1] = quote
           65  +				[runningtally] = [runningtally] + lib.str.sz([symself].[key])*fac
           66  +			end
           67  +		end
           68  +	end
           69  +
           70  +	local copiers = {}
           71  +	local senders = {}
           72  +	local symtxt = symbol(lib.mem.ptr(int8))
           73  +	local cpypos = symbol(&opaque)
           74  +	local destcon = symbol(&lib.net.mg_connection)
           75  +	for idx, seg in ipairs(segs) do
           76  +		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
           77  +		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
           78  +		if fields[idx] then
           79  +			copiers[#copiers+1] = quote
           80  +				[cpypos] = lib.mem.cpy([cpypos],
           81  +					[&opaque](symself.[fields[idx]]),
           82  +					lib.str.sz(symself.[fields[idx]]))
           83  +			end
           84  +			senders[#senders+1] = quote
           85  +				lib.net.mg_send([destcon],
           86  +					symself.[fields[idx]],
           87  +					lib.str.sz(symself.[fields[idx]]))
           88  +			end
           89  +		end
           90  +	end
           91  +
           92  +	local tid = tplspec.id or '<anonymous>'
           93  +	rec.methods.tostr = terra([symself])
           94  +		lib.dbg(['compiling template ' .. tid])
           95  +		[tallyup]
           96  +		var [symtxt] = lib.mem.heapa(int8, [runningtally])
           97  +		var [cpypos] = [&opaque](symtxt.ptr)
           98  +		[copiers]
           99  +		@[&int8](cpypos) = 0
          100  +		return symtxt
          101  +	end
          102  +	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
          103  +		lib.dbg(['transmitting template ' .. tid])
          104  +		[tallyup]
          105  +		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
          106  +		for i = 0, hd.ct do
          107  +			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
          108  +		end
          109  +		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)
          110  +		[senders]
          111  +		lib.net.mg_send([destcon],'\r\n',2)
          112  +	end
          113  +
          114  +	return rec
          115  +end
          116  +
          117  +return m

Added view/docskel.tpl version [1d38dac966].

            1  +<!doctype html>
            2  +<html>
            3  +	<head>
            4  +		<title>@instance :: @title</title>
            5  +		<link rel="stylesheet" href="/style.css">
            6  +	</head>
            7  +	<body>
            8  +		<h1>@title</h1>
            9  +		@body
           10  +	</body>
           11  +</html>

Added view/load.lua version [f2a65d7b61].

            1  +-- because lua can't scan directories, we need a
            2  +-- file that indexes the templates manually, and
            3  +-- copies them into a data structure we can then
            4  +-- create templates from when we return to terra
            5  +local path = ...
            6  +local sources = {
            7  +	'docskel';
            8  +	'tweet';
            9  +}
           10  +
           11  +local ingest = function(filename)
           12  +	local hnd = io.open(path..'/'..filename)
           13  +	local txt = hnd:read('*a')
           14  +	io.close(hnd)
           15  +	txt = txt:gsub('([^\\])!%b[]', '%1')
           16  +	txt = txt:gsub('([^\\])!!.-\n', '%1')
           17  +	txt = txt:gsub('\\(!%b[])', '%1')
           18  +	txt = txt:gsub('\\(!!)', '%1')
           19  +	return txt
           20  +end
           21  +
           22  +
           23  +local views = {}
           24  +for _,n in pairs(sources) do views[n] = ingest(n .. '.tpl') end
           25  +return views

Added view/tweet.tpl version [59ae4e4ad9].

            1  +<div class="post">
            2  +	<div class="detail">
            3  +		<a class="username" href="@link">
            4  +			<span class="nym">@nym</span> <span class="handle">[@handle]</span>
            5  +		</div>
            6  +		<div class="when">@when</div>
            7  +	</div>
            8  +	<div class="content">
            9  +		<img class="avatar" src="@avatar">
           10  +		<div class="text">@text</div>
           11  +	</div>
           12  +</div>