Overview
Comment: | add first parvan revision |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
bf5f4fd9caf55cc1b83781b7d889d294 |
User & Date: | lexi on 2022-04-25 21:01:22 |
Other Links: | manifest | tags |
Context
2022-04-26
| ||
02:02 | add syn check-in: 0f6a5bda23 user: lexi tags: trunk | |
2022-04-25
| ||
21:01 | add first parvan revision check-in: bf5f4fd9ca user: lexi tags: trunk | |
2021-09-11
| ||
22:18 | add rd-key check-in: 4e2b17fce2 user: lexi tags: trunk | |
Changes
Modified newtab.c from [c8163da01b] to [ef6aaa6ad1].
1 1 /* [ʞ] newtab.c 2 2 * ~ lexi hale <lexi@hale.su> 3 3 * $ cc -Ofast newtab.c -onewtab \ 4 - * [-D_default_qutebrowser_location=/...] 4 + * [-D_default_qutebrowser_location=/...] \ 5 + * [-D_enable_vblank] 5 6 * $ ./newtab [example.net] 6 7 * © AGPLv3 7 8 * ? may god have mercy on my soul. 8 9 * i wrote this because qutebrowser, being a python 9 10 * abomination, takes an absurdly fucking long time 10 11 * to load, even when there's already an instance 11 12 * running and i just want a new goddamn tab. it ................................................................................ 90 91 } ; { 91 92 /* fuck this fuck this fuck this fuck this */ 92 93 char* end = stpncpy(srv.sun_path, run, sizeof srv.sun_path); 93 94 end = stpncpy(end, ssz("/qutebrowser/")); 94 95 DIR* qb = opendir(srv.sun_path); 95 96 if (!qb) return start_instance; 96 97 struct dirent* ent; 97 - while (ent = readdir(qb)) { 98 + while ((ent = readdir(qb))) { 98 99 if (ent == NULL) return start_instance; 99 100 if (strncmp(ent -> d_name, "ipc-", 4) == 0) break; 100 101 } 101 102 if (ent == NULL) return start_instance; 102 103 end = stpncpy(end, ent->d_name, 103 104 (sizeof srv.sun_path) - (end - srv.sun_path)); 104 105 closedir(qb); ................................................................................ 134 135 if (argc > 2) { 135 136 printf("\x1b[1musage:\x1b[m %s [uri]\n", argv[0]); 136 137 } else { 137 138 const char* uri = argc < 2 ? NULL : argv[1]; 138 139 enum status st = transmit(uri); 139 140 if (st == start_instance) { 140 141 if (!fork()) { 142 +# ifndef _enable_vblank 143 + setenv("vblank_mode","0",0); 144 +# endif 141 145 execl(dupl(_default_qutebrowser_location), uri, NULL); 142 146 execl(dupl("/usr/local/bin/qutebrowser"), uri, NULL); 143 147 execlp(dupl("qutebrowser"), uri, NULL); 144 148 st = fail_find; 145 149 } else { 146 150 return start_instance; 147 151 } 148 152 } 149 153 if (errors[st] != NULL) printf("\x1b[1;31m(error)\x1b[m %s\n", errors[st]); 150 154 return st; 151 155 } 152 156 }
Added parvan.lua version [760cd798bc].
1 +-- [ʞ] parvan.lua 2 +-- ? tool for maintaining and searching dictionaries 3 +-- [ CONTROL CLASS: GREEN ] 4 +-- [ CODEWORDS: - GENERAL ACCESS - ] 5 +-- [ CONTROLLING AUTHORITY: - INTERDIRECTORIAL - 6 +-- < Commission for Defense Communication > 7 +-- +WCO Worlds Culture Overdirectorate 8 +-- SSD Social Sciences Directorate 9 +-- ELS External Linguistics Subdirectorate 10 +-- +WSO Worlds Security Overdirectorate 11 +-- EID External Influence Directorate ] 12 + 13 +local function implies(a,b) return a==b or not(a) end 14 + 15 +local ansi = { 16 + levels = { 17 + plain = 0; 18 + ansi = 1; 19 + color = 2; 20 + color8b = 3; 21 + color24b = 4; 22 + }; 23 +} 24 + 25 +ansi.seqs = { 26 + br = {ansi.levels.ansi, "[1m", "[21m"}; 27 + hl = {ansi.levels.ansi, "[7m", "[27m"}; 28 + ul = {ansi.levels.ansi, "[4m", "[24m"}; 29 + em = {ansi.levels.ansi, "[3m", "[23m"}; 30 +}; 31 + 32 +function ansi.termclass(fd) -- awkwardly emulate isatty 33 + if fd:seek('cur',0) then 34 + return ansi.levels.plain 35 + end 36 + 37 + if os.getenv('COLORTERM') == 'truecolor' then 38 + return ansi.levels.color24b 39 + end 40 + 41 + local term = os.getenv('TERM') 42 + if term then 43 + if term:match '-256color' then 44 + return ansi.levels.color8b 45 + elseif term:match '-color' then 46 + return ansi.levels.color 47 + else 48 + return ansi.levels.ansi 49 + end 50 + end 51 + 52 + return ansi.levels.plain 53 +end 54 + 55 +function ansi.formatter(fd) 56 + local cl = ansi.termclass(fd) 57 + local id = function(...) return ... end 58 + local esc = '\27' 59 + local f = {} 60 + for k,v in pairs(ansi.seqs) do 61 + local lvl, on, off = table.unpack(v) 62 + if lvl <= cl then 63 + f[k] = function(s) 64 + return esc..on .. s .. esc..off 65 + end 66 + else f[k] = id end 67 + end 68 + local function ftoi(r,g,b) 69 + return math.ceil(r*0xff), 70 + math.ceil(g*0xff), 71 + math.ceil(b*0xff) 72 + end 73 + local reset = "\27[39m" 74 + function f.color(str, n, br) 75 + return string.format("\27[%s%cm", 76 + (bg and 4 or 3) + 77 + (br and 6 or 0), 0x30+n) 78 + .. str .. reset 79 + end 80 + function f.resetLine() 81 + return '\27[1K\13' 82 + end 83 + if cl == ansi.levels.color24b then 84 + function f.rgb(str, r,g,b, bg) 85 + return string.format("\27[%c8;2;%u;%u;%um", bg and 0x34 or 0x33, 86 + ftoi(r,g,b)) .. str .. reset 87 + end 88 + elseif cl == ansi.levels.color8b then 89 + function f.rgb(str, r,g,b, bg) 90 + local code = 16 + (r * 5)*36 + (g * 5)*6 + (b * 6) 91 + return string.format("\27[%c8;5;%um", bg and 0x34 or 0x33, code) 92 + .. str .. reset 93 + end 94 + elseif cl == ansi.levels.color then 95 + function f.rgb(str, r,g,b, bg) 96 + local code = 0x30 + 1 -- TODO 97 + return string.format("\27[%c%cm", bg and 0x34 or 0x33, code) 98 + .. str .. reset 99 + end 100 + else 101 + function f.rgb(s) return s end 102 + function f.color(s) return s end 103 + function f.resetLine() return '' end 104 + end 105 + return f 106 +end 107 + 108 + 109 +local function dump(v,pfx,cyc,ismeta) 110 + pfx = pfx or '' 111 + cyc = cyc or {} 112 + local np = pfx .. ' ' 113 + 114 + if type(v) == 'table' then 115 + if cyc[v] then return '<...>' else cyc[v] = true end 116 + end 117 + 118 + if type(v) == 'string' then 119 + return string.format('%q', v) 120 + elseif type(v) == 'table' then 121 + local str = '' 122 + for k,v in pairs(v) do 123 + local tkey, tval = dump(k,np,cyc), dump(v,np,cyc) 124 + str = str .. string.format('%s[%s] = %s\n', np, tkey,tval) 125 + end 126 + local meta = '' 127 + if getmetatable(v) then 128 + meta = dump(getmetatable(v),pfx,cyc,true) .. '::' 129 + end 130 + if ismeta then 131 + return string.format('%s<|\n%s%s|>',meta,str,pfx) 132 + else 133 + return meta..'{\n' .. str .. pfx .. '}\n' 134 + end 135 + else 136 + return string.format('%s', v) 137 + end 138 +end 139 + 140 +local struct = { 141 + __call = function(s,...) return s:mk(...) end; 142 +} 143 +function struct:mk(s) 144 + function s.is(o) return getmetatable(o) == s end 145 + return setmetatable(s, self) 146 +end 147 +setmetatable(struct, struct) 148 + 149 +local stream = struct { 150 + __index = { 151 + next = function(self, f) 152 + local flds = {string.unpack('<'..f, self.data, self.index)} 153 + self.index = flds[#flds] 154 + flds[#flds] = nil 155 + return table.unpack(flds) 156 + end; 157 + }; 158 + mk = function(self, str) 159 + return setmetatable({ 160 + data = str; 161 + index = 1; 162 + }, self) 163 + end; 164 +} 165 + 166 +local fmt = {} 167 + 168 +local userError = struct { 169 + __tostring = function(self) return self.msg end; 170 + mk = function(self, s) return setmetatable({msg=s},self) end; 171 +} 172 + 173 +local function id10t(...) 174 + error(userError(string.format(...)),0) 175 +end 176 + 177 +local packer,unpacker = 178 + function(f) return function(...) return string.pack ("<"..f, ...) end end, 179 + function(f) return function( s ) return s:next (f) end end 180 +local qpack = function(f) return { 181 + encode = packer(f); 182 + decode = unpacker(f); 183 +} end 184 + 185 +local parse, marshal 186 +fmt.string = qpack "s4" 187 +fmt.u8 = qpack "I1" 188 +fmt.u16 = qpack "I2" 189 +fmt.u24 = qpack "I3" 190 +fmt.u32 = qpack "I4" 191 +fmt.list = function(t,ty) ty = ty or fmt.u32 192 + return { 193 + encode = function(a) 194 + local vals = {marshal(ty, #a)} 195 + for i=1,#a do 196 + table.insert(vals, marshal(t, a[i])) 197 + end 198 + return table.concat(vals) 199 + end; 200 + decode = function(s) 201 + local n = parse(ty, s) 202 + local vals = {} 203 + for i=1,n do 204 + table.insert(vals, parse(t, s)) 205 + end 206 + return vals 207 + end; 208 + } 209 +end 210 + 211 +fmt.map = function(from,to,ity) 212 + local ent = fmt.list({ 213 + {'key', from}, 214 + {'val', to} 215 + }, ity) 216 + return { 217 + encode = function(a) 218 + local m = {} 219 + for k,v in pairs(a) do 220 + table.insert(m, {key=k, val=v}) 221 + end 222 + return ent.encode(m) 223 + end; 224 + decode = function(s) 225 + local lst = ent.decode(s) 226 + local m = {} 227 + for _,p in pairs(lst) do m[p.key] = p.val end 228 + return m 229 + end; 230 + } 231 +end 232 + 233 +fmt.form = { 234 + {'form', fmt.u16}; 235 + {'text', fmt.string}; 236 +} 237 + 238 +fmt.note = { 239 + {'kind', fmt.string}; 240 + {'paras', fmt.list(fmt.string)}; 241 +} 242 + 243 +fmt.meaning = { 244 + {'lit', fmt.string}; 245 + {'notes', fmt.list(fmt.note,fmt.u8)}; 246 +} 247 + 248 +fmt.def = { 249 + {'part', fmt.u8}; 250 + {'branch', fmt.list(fmt.string,fmt.u8)}; 251 + {'means', fmt.list(fmt.meaning,fmt.u8)}; 252 + {'forms', fmt.list(fmt.form,fmt.u16)}; 253 +} 254 + 255 +fmt.word = { 256 + {'defs', fmt.list(fmt.def,fmt.u8)}; 257 +} 258 + 259 +fmt.dictHeader = { 260 + {'lang', fmt.string}; 261 + {'meta', fmt.string}; 262 + {'partsOfSpeech', fmt.list(fmt.string,fmt.u16)}; 263 +} 264 + 265 +fmt.dict = { 266 + {'header', fmt.dictHeader}; 267 + {'words', fmt.map(fmt.string,fmt.word)}; 268 +} 269 + 270 +function marshal(ty, val) 271 + if ty.encode then 272 + return ty.encode(val) 273 + end 274 + local ac = {} 275 + 276 + for idx,fld in ipairs(ty) do 277 + local name, fty = table.unpack(fld) 278 + table.insert(ac, marshal(fty, assert(val[name]))) 279 + end 280 + 281 + return table.concat(ac) 282 +end 283 + 284 +function parse(ty, stream) 285 + if ty.decode then 286 + return ty.decode(stream) 287 + end 288 + 289 + local obj = {} 290 + for idx,fld in ipairs(ty) do 291 + local name, fty = table.unpack(fld) 292 + obj[name] = parse(fty, stream) 293 + end 294 + return obj 295 +end 296 + 297 +local function 298 +atomizer() 299 + local map = {} 300 + local i = 1 301 + return function(v) 302 + if map[v] then return map[v] else 303 + map[v] = i 304 + i=i+1 305 + return i-1 306 + end 307 + end, map 308 +end 309 + 310 +local function 311 +writeDict(d) 312 + local atomizePoS, posMap = atomizer() 313 + for lit,w in pairs(d.words) do 314 + for j,def in ipairs(w.defs) do 315 + def.part = atomizePoS(def.part) 316 + end 317 + end 318 + d.header.partsOfSpeech = {} 319 + for v,i in pairs(posMap) do 320 + d.header.partsOfSpeech[i] = v 321 + end 322 + return marshal(fmt.dict, d) 323 +end 324 + 325 +local function 326 +readDict(file) 327 + local d = parse(fmt.dict, stream(file)) 328 + -- handle atoms 329 + for lit,w in pairs(d.words) do 330 + for j,def in ipairs(w.defs) do 331 + def.part = d.header.partsOfSpeech[def.part] 332 + end 333 + end 334 + return d 335 +end 336 + 337 +local function strwords(str) -- here be dragons 338 + local wds = {} 339 + local w = {} 340 + local state, d, quo, dquo = 0,0 341 + local function flush(n) 342 + if next(w) then 343 + table.insert(wds, utf8.char(table.unpack(w))) 344 + w = {} 345 + end 346 + state = n 347 + quo = nil 348 + dquo = nil 349 + d = 0 350 + end 351 + local function isws(c) 352 + return c == 0x20 or c == 0x09 or c == 0x0a 353 + end 354 + for p,cp in utf8.codes(str) do 355 + if state == 0 then -- begin 356 + if not(isws(cp)) then 357 + if cp == 0x22 or cp == 0x27 then 358 + quo = cp 359 + elseif cp == 0x5b then -- boxquote 360 + quo = 0x5d 361 + dquo = 0x5b 362 + elseif cp == 0x7b then -- curlquote 363 + quo = 0x7d 364 + dquo = 0x7b 365 + elseif cp == 0x201c then -- fancyquote 366 + quo = 0x201d 367 + dquo = 0x201c 368 + end 369 + if quo then 370 + state = 2 371 + d = 1 372 + elseif cp == 0x5c then -- escape 373 + state = 11 374 + else 375 + state = 1 376 + table.insert(w, cp) 377 + end 378 + end 379 + elseif state == 1 then -- word 380 + if isws(cp) then flush(0) 381 + elseif cp == 0x5c then state = 11 else 382 + table.insert(w,cp) 383 + end 384 + elseif state == 2 then -- (nested?) quote 385 + if cp == 0x5c then state = 12 386 + elseif cp == quo then 387 + d = d - 1 388 + if d == 0 then 389 + flush(0) 390 + else 391 + table.insert(w,cp) 392 + end 393 + else 394 + if cp == dquo then d = d + 1 end 395 + table.insert(w,cp) 396 + end 397 + elseif state == 11 or state == 12 then -- escape 398 + -- 12 = quote escape, 11 = raw escape 399 + if cp == 0x63 then --n 400 + table.insert(w,0x0a) 401 + else 402 + table.insert(w,cp) 403 + end 404 + state = state - 10 405 + end 406 + end 407 + flush() 408 + return wds 409 +end 410 + 411 +local predicates 412 +local function parsefilter(str) 413 + local f = strwords(str) 414 + if #f == 1 then return function(e) return predicates.lit.fn(e,f[1]) end end 415 + if not predicates[f[1]] then 416 + id10t('no such predicate %s',f[1]) 417 + else 418 + local p = predicates[f[1]].fn 419 + return function(e) 420 + return p(e, table.unpack(f,2)) 421 + end 422 + end 423 +end 424 + 425 +do 426 + local function p_all(e,pred,...) 427 + if pred == nil then return true end 428 + pred = parsefilter(pred) 429 + if not pred(e) then return false end 430 + return p_all(e,...) 431 + end; 432 + local function p_any(e,pred,...) 433 + if pred == nil then return false end 434 + pred = parsefilter(pred) 435 + if pred(e) then return true end 436 + return p_any(e,...) 437 + end; 438 + local function p_none(e,pred,...) 439 + if pred == nil then return true end 440 + pred = parsefilter(pred) 441 + if pred(e) then return false end 442 + return p_none(e,...) 443 + end; 444 + local function p_some(e,count,pred,...) 445 + if count == 0 then return true end 446 + if pred == nil then return false end 447 + pred = parsefilter(pred) 448 + if pred(e) then 449 + count = count-1 450 + end 451 + return p_some(e,count,...) 452 + end; 453 + 454 + local function prepScan(...) 455 + local map = {} 456 + local tgt = select('#',...) 457 + for _,v in pairs{...} do map[v] = true end 458 + return map,tgt 459 + end 460 + predicates = { 461 + all = { 462 + fn = p_all; 463 + syntax = '<pred>…'; 464 + help = 'every sub-<pred> matches' 465 + }; 466 + any = { 467 + fn = p_any; 468 + syntax = '<pred>…'; 469 + help = 'any sub-<pred> matches' 470 + }; 471 + none = { 472 + fn = p_none; 473 + syntax = '<pred>…'; 474 + help = 'no sub-<pred> matches' 475 + }; 476 + some = { 477 + fn = p_some; 478 + syntax = '<count> <pred>…'; 479 + help = '<count> or more sub-<pred>s match' 480 + }; 481 + def = { 482 + help = 'word has at least one definition that contains all <keyword>s'; 483 + syntax = '<keyword>…'; 484 + fn = function(e,...) 485 + local kw = {...} 486 + for i,d in ipairs(e.word.defs) do 487 + for j,m in ipairs(d.means) do 488 + for k,n in ipairs(kw) do 489 + if not string.find(m.lit, n, 1, true) then 490 + goto notfound 491 + end 492 + end 493 + do return true end 494 + ::notfound:: 495 + end 496 + end 497 + return false 498 + end; 499 + }; 500 + lit = { 501 + help = 'word is, begins with, or ends with <word>'; 502 + syntax = '<word> [(pfx|sfx)]'; 503 + fn = function(e,val,op) 504 + if not op then 505 + return e.lit == val 506 + elseif op == 'pfx' then 507 + return val == string.sub(e.lit,1,#val) 508 + elseif op == 'sfx' then 509 + return val == string.sub(e.lit,(#e.lit) - #val + 1) 510 + else 511 + id10t('[lit %s %s] is not a valid filter, “%s” should be either “pfx” or “sfx”',val,op,op) 512 + end 513 + end; 514 + }; 515 + form = { 516 + help = 'match against word\'s inflected forms'; 517 + syntax = '(<inflect> | <form> (set | is <inflect> | pfx <prefix> | sfx <suffix>))'; 518 + fn = function(e, k, op, v) 519 + end; 520 + }; 521 + part = { 522 + help = 'word has definitions for every <part> of speech'; 523 + syntax = '<part>…'; 524 + fn = function(e,...) 525 + local map, tgt = prepScan(...) 526 + local matches = 0 527 + for i,d in ipairs(e.word.defs) do 528 + if map[d.part] then matches = matches + 1 end 529 + end 530 + return matches == tgt 531 + end 532 + }; 533 + root = { 534 + help = 'match a word that derives from every <word>'; 535 + syntax = '<word>…'; 536 + fn = function(e,...) 537 + local map, tgt = prepScan(...) 538 + for i,d in ipairs(e.word.defs) do 539 + local matches = 0 540 + for j,r in ipairs(d.branch) do 541 + if map[r] then matches = matches + 1 end 542 + end 543 + if matches == tgt then return true end 544 + end 545 + end 546 + }; 547 + } 548 +end 549 + 550 +local function 551 +safeopen(file,...) 552 + if type(file) == 'string' then 553 + local fd = io.open(file,...) 554 + if not fd then error(userError("cannot open file " .. file),2) end 555 + return fd 556 + else 557 + return file 558 + end 559 +end 560 + 561 +local function 562 +safeNavWord(ctx, word, dn, mn, nn) 563 + local w = ctx.dict.words[word] 564 + if not w then id10t 'bad word' end 565 + if dn == nil then return w end 566 + 567 + local d = w.defs[tonumber(dn)] 568 + if not d then id10t('no definition #%u',dn) end 569 + if mn == nil then return w,d end 570 + 571 + local m = d.means[tonumber(mn)] 572 + if not m then id10t('no meaning #%u',mn) end 573 + if nn == nil then return w,d,m end 574 + 575 + local n = m.notes[tonumber(nn)] 576 + if not n then id10t('no note #%u',nn) end 577 + return w,d,m,n 578 +end 579 + 580 +local function copy(tab) 581 + local new = {} 582 + for k,v in pairs(tab) do new[k] = v end 583 + return new 584 +end 585 + 586 +local function parsePath(p) 587 + local w,dn,mn,nn = p:match('^(.+)@([0-9]+)/([0-9]+):([0-9]+)$') 588 + if not w then w,dn,mn = p:match('^(.+)@([0-9]+)/([0-9]+)$') end 589 + if not w then w,dn = p:match('^(.+)@([0-9]+)$') end 590 + if not w then w=p:match('^(.-)%.?$') end 591 + return {w = w, dn = tonumber(dn), mn = tonumber(mn), nn = tonumber(nn)} 592 +end 593 + 594 +local cmds = { 595 + create = { 596 + help = "initialize a new dictionary file"; 597 + syntax = "<lang>"; 598 + raw = true; 599 + exec = function(ctx, lang) 600 + if not lang then 601 + id10t 'for what language?' 602 + end 603 + local fd = safeopen(ctx.file,"wb") 604 + local new = { 605 + header = { 606 + lang = lang; 607 + meta = ""; 608 + partsOfSpeech = {}; 609 + branch = {}; 610 + }; 611 + words = {}; 612 + } 613 + local o = writeDict(new); 614 + fd:write(o) 615 + fd:close() 616 + end; 617 + }; 618 + coin = { 619 + help = "add a new word"; 620 + syntax = "<word>"; 621 + write = true; 622 + exec = function(ctx,word) 623 + if ctx.dict.words[word] then 624 + id10t "word already coined" 625 + end 626 + ctx.dict.words[word] = {defs={}} 627 + end; 628 + }; 629 + def = { 630 + help = "define a word"; 631 + syntax = "<word> <part-of-speech> [<meaning> [<root>…]]"; 632 + write = true; 633 + exec = function(ctx,word,part,means,...) 634 + local etym = {...} 635 + if (not word) or not part then 636 + id10t 'bad definition' 637 + end 638 + if not ctx.dict.words[word] then 639 + ctx.dict.words[word] = {defs={}} 640 + end 641 + local n = #(ctx.dict.words[word].defs)+1 642 + ctx.dict.words[word].defs[n] = { 643 + part = part; 644 + branch = etym; 645 + means = {means and {lit=means,notes={}} or nil}; 646 + forms = {}; 647 + } 648 + ctx.log('info', string.format('added definition #%u to “%s”', n, word)) 649 + end; 650 + }; 651 + mean = { 652 + help = "add a meaning to a definition"; 653 + syntax = "<word> <def#> <meaning>"; 654 + write = true; 655 + exec = function(ctx,word,dn,m) 656 + local _,d = safeNavWord(ctx,word,dn) 657 + table.insert(d.means, {lit=m,notes={}}) 658 + end; 659 + }; 660 + mod = { 661 + help = "move, merge, split, or delete words or definitions"; 662 + syntax = { 663 + "<path> (drop | [move|merge|clobber] <path> | out [<part> [<root>…]])"; 664 + "path ::= <word>[(@<def#>[/<meaning#>[:<note#>]]|.)]"; 665 + }; 666 + write = true; 667 + }; 668 + note = { 669 + help = "add a note to a definition or a paragraph to a note"; 670 + syntax = {"(<m-path> (add|for) <kind> | <m-path>:<note#>) <para>…"; 671 + "m-path ::= <word>@<def#>/<meaning#>"}; 672 + write = true; 673 + exec = function(ctx,path,...) 674 + local paras, mng 675 + local dest = parsePath(path) 676 + local _,_,m = safeNavWord(ctx,dest.w,dest.dn,dest.mn) 677 + if dest.nn then 678 + paras = {...} 679 + else 680 + local op, kind = ... 681 + paras = { select(3, ...) } 682 + if op == 'add' then 683 + dest.nn = #(m.notes) + 1 684 + m.notes[dest.nn] = {kind=kind, paras=paras} 685 + return 686 + elseif op == 'for' then 687 + for i,nn in ipairs(m.notes) do 688 + if nn.kind == kind then 689 + dest.nn = i break 690 + end 691 + end 692 + if not dest.nn then 693 + id10t('no note of kind %s in %s',kind,path) 694 + end 695 + end 696 + end 697 + local dpa = m.notes[dest.nn].paras 698 + local top = #dpa 699 + for i,p in ipairs(paras) do 700 + dpa[top+i] = p 701 + end 702 + end 703 + }; 704 + shell = { 705 + help = "open an interactive prompt"; 706 + raw = true; 707 + }; 708 + help = { 709 + help = "show help"; 710 + nofile = true; 711 + syntax = "[<command>]"; 712 + }; 713 + predicates = { 714 + help = "show available filter predicates"; 715 + nofile = true; 716 + syntax = "[<predicate>]"; 717 + }; 718 + dump = { 719 + exec = function(ctx) print(dump(ctx.dict)) end 720 + }; 721 + ls = { 722 + help = "list all words that meet any given <filter>"; 723 + syntax = {"[<filter>…]"; 724 + "filter ::= (<word>|<pred> <arg>…)"; 725 + "arg ::= (<atom>|'['(<string>|<pred> <arg>…)']')"}; 726 + } 727 +} 728 + 729 +function cmds.predicates.exec(ctx, pred) 730 + local list = predicates 731 + if pred then list = {predicates[pred]} end 732 + local f = ctx.sty[io.stderr] 733 + for k,p in pairs(predicates) do 734 + if p.help then 735 + io.stderr:write( 736 + f.br(' - ' .. 737 + f.rgb('[',.8,.3,1) .. 738 + k .. ' ' .. 739 + (f.color(p.syntax,5) or '…') .. 740 + f.rgb(']',.8,.3,1)) .. ': ' .. 741 + f.color(p.help,4,true) .. '\n') 742 + end 743 + end 744 +end 745 + 746 +function cmds.ls.exec(ctx,...) 747 + local filter = nil 748 + local out = {} 749 + for i,f in ipairs{...} do 750 + local fn = parsefilter(f) 751 + local of = filter or function() return false end 752 + filter = function(e) 753 + return fn(e) or of(e) 754 + end 755 + end 756 + for lit,w in pairs(ctx.dict.words) do 757 + local e = {lit=lit, word=w} 758 + if filter == nil or filter(e) then 759 + table.insert(out, e) 760 + end 761 + end 762 + table.sort(out, function(a,b) return a.lit < b.lit end) 763 + local fo = ctx.sty[io.stdout] 764 + local function meanings(d,md,n) 765 + local start = md and 2 or 1 766 + local part = string.format('(%s)', d.part) 767 + local pad = md and string.rep(' ', #part) or '' 768 + local function note(n,insert) 769 + if not next(n.paras) then return end 770 + local pad = string.rep(' ',#(n.kind) + 9) 771 + insert(' ' .. fo.hl(' ' .. n.kind .. ' ') .. ' ' .. n.paras[1]) 772 + for i=2,#n.paras do 773 + insert(pad..n.paras[2]) 774 + end 775 + end 776 + local m = { (function() 777 + if d.means[1] then 778 + if md then return 779 + string.format(" %s 1. %s", fo.em(part), d.means[1].lit) 780 + end 781 + else return 782 + fo.em(string.format(' %s [empty definition #%u]', part,n)) 783 + end 784 + end)() } 785 + for i=start,#d.means do local v = d.means[i] 786 + table.insert(m, string.format(' %s %u. %s', pad, i, v.lit)) 787 + for j,n in ipairs(v.notes) do 788 + note(n, function(v) table.insert(m, v) end) 789 + end 790 + end 791 + return table.concat(m,'\n') 792 + end 793 + for i, w in ipairs(out) do 794 + local d = fo.ul(w.lit) 795 + if #w.word.defs == 1 then 796 + d=d .. ' ' .. fo.em('('..(w.word.defs[1].part)..')') ..'\n' 797 + .. meanings(w.word.defs[1],false,1) 798 + else 799 + for j, def in ipairs(w.word.defs) do 800 + d=d .. '\n' .. meanings(def,true,j) 801 + end 802 + end 803 + io.stdout:write(d..'\n') 804 + end 805 +end 806 + 807 +function cmds.mod.exec(ctx, orig, oper, dest, ...) 808 + if (not orig) or not oper then 809 + id10t '`mod` requires at least an origin and an operation' 810 + end 811 + local op, dp = parsePath(orig) 812 + local w,d,m,n = safeNavWord(ctx, op.w,op.dn,op.mn,op.nn) 813 + if oper == 'drop' then 814 + if not d then 815 + ctx.dict.words[op.w] = nil 816 + elseif not m then 817 + table.remove(w.defs, op.dn) 818 + elseif not n then 819 + table.remove(d.means, op.mn) 820 + else 821 + table.remove(m.notes, op.nn) 822 + end 823 + elseif oper == 'out' then 824 + if n or not m then 825 + id10t '`mod out` must target a meaning' 826 + end 827 + if not dest then id10t '`mod out` requires at least a part of speech' end 828 + local newdef = { 829 + part = dest; 830 + branch = {...}; 831 + forms = {}; 832 + means = {m}; 833 + } 834 + table.insert(w.defs,op.dn+1, newdef) 835 + table.remove(d.means,op.mn) 836 + elseif oper == 'move' or oper == 'merge' or oper == 'clobber' then 837 + if dest 838 + then dp = parsePath(dest) 839 + else id10t('`mod %s` requires a target',oper) 840 + end 841 + if n then 842 + if not dp.mn then 843 + id10t '`mod` on a note requires a note or meaning destination' 844 + end 845 + local _,_,dm = safeNavWord(ctx, dp.w,dp.dn,dp.mn) 846 + if dp.nn then 847 + if oper == 'move' then 848 + table.insert(dm.notes, dp.nn, n) 849 + elseif oper == 'merge' then 850 + local top = #(dm.notes[dp.nn].paras) 851 + for i, v in ipairs(n.paras) do 852 + dm.notes[dp.nn].paras[i+top] = v 853 + end 854 + elseif oper == 'clobber' then 855 + dm.notes[dp.nn] = n 856 + end 857 + else 858 + if oper ~= 'move' then 859 + id10t('`mod note %s` requires a note target', oper) 860 + end 861 + table.insert(dm.notes, n) 862 + end 863 + if oper == 'move' and dp.nn and dm == m and op.nn > dp.nn then 864 + table.remove(m.notes,op.nn+1) 865 + else 866 + table.remove(m.notes,op.nn) 867 + end 868 + elseif m then 869 + if not dp.dn then 870 + local newdef = { 871 + part = d.part; 872 + branch = copy(d.branch); 873 + forms = copy(d.forms); 874 + means = {m}; 875 + } 876 + if ctx.dict.words[dp.w] then 877 + table.insert(ctx.dict.words[dp.w].defs, newdef) 878 + else 879 + ctx.dict.words[dp.w] = { 880 + defs = {newdef}; 881 + } 882 + end 883 + table.remove(d.means,dp.mn) 884 + else 885 + local dw, dd = safeNavWord(ctx, dp.w, dp.dn) 886 + if dp.mn then 887 + if dd.means[dp.mn] and (oper == 'merge' or oper=='clobber') then 888 + if oper == 'merge' then 889 + dd.means[dp.mn] = dd.means[dp.mn] .. '; ' .. m 890 + elseif oper == 'clobber' then 891 + dd.means[dp.mn] = m 892 + end 893 + else 894 + if oper == clobber then dd.means = {} end 895 + table.insert(dd.means, dp.mn, m) 896 + end 897 + else 898 + table.insert(dd.means, m) 899 + end 900 + if oper == 'move' and dp.mn and dd.means == d.means and op.mn > dp.mn then 901 + table.remove(d.means,op.mn+1) 902 + else 903 + table.remove(d.means,op.mn) 904 + end 905 + end 906 + elseif d then 907 + local ddefs = safeNavWord(ctx, dp.w).defs 908 + if dp.dn then 909 + if oper == 'merge' then 910 + local top = #(ddefs[dp.dn].means) 911 + for i,om in ipairs(d.means) do 912 + ddefs[dp.dn].means[top+i] = om 913 + end 914 + for k,p in pairs(d.forms) do 915 + ddefs[dp.dn].forms[k] = p -- clobbers! 916 + end 917 + else 918 + table.insert(ddefs, dp.dn, d) 919 + end 920 + else 921 + table.insert(ddefs, d) 922 + end 923 + if oper == 'move' and dp.mn and w.defs == ddefs and op.mn > dp.mn then 924 + table.remove(w.defs,op.dn+1) 925 + else 926 + table.remove(w.defs,op.dn) 927 + end 928 + else 929 + if ctx.dict.words[dp.w] then 930 + if oper ~= 'merge' then 931 + id10t('the word “%s” already exists; use `merge` if you want to merge the words together', dp.w) 932 + end 933 + for i,def in ipairs(w.defs) do 934 + if dp.dn then 935 + table.insert(ctx.dict.words[dp.w].defs, dp.dn+i-1, def) 936 + else 937 + table.insert(ctx.dict.words[dp.w].defs, def) 938 + end 939 + end 940 + else 941 + ctx.dict.words[dp.w] = w 942 + end 943 + ctx.dict.words[op.w] = nil 944 + end 945 + end 946 +end 947 + 948 +local function fileLegible(file) 949 + -- check if we can access the file 950 + local fd = io.open(file,"rb") 951 + local ret = false 952 + if fd then ret = true end 953 + fd:close() 954 + return ret 955 +end 956 + 957 +local function map(fn,lst) 958 + local new = {} 959 + for k,v in pairs(lst) do 960 + local nv, nk = fn(v,k) 961 + new[nk or k] = nv 962 + end 963 + return new 964 +end 965 +local function mapD(fn,lst) --destructive 966 + -- WARNING: this will not work if nk names an existing key! 967 + for k,v in pairs(lst) do 968 + local nv, nk = fn(v,k) 969 + if nk == nil or k == nk then 970 + lst[k] = nv 971 + else 972 + lst[k] = nil 973 + lst[nk] = nv 974 + end 975 + end 976 + return lst 977 +end 978 + 979 +local function 980 +prompt(p,multiline) 981 + -- returns string if successful, nil if EOF, false if ^C 982 + io.stderr:write(p) 983 + local ok, res = pcall(function() 984 + return io.stdin:read(multiline and 'a' or 'l') 985 + end) 986 + if ok then return res end 987 + return false 988 +end 989 + 990 +function cmds.shell.exec(ctx) 991 + if not fileLegible(ctx.file) then 992 + -- avoid accidentally creating a file without the 993 + -- proper document structure and metadata 994 + id10t("file %s must already exist and be at least readable", ctx.file) 995 + end 996 + 997 + local fd, rw = io.open(ctx.file,"r+b"), true 998 + if not fd then -- not writable 999 + ctx.log('warn',string.format('file %s is not writable', ctx.file)) 1000 + fd, rw = io.open(ctx.file, "rb"), false 1001 + end 1002 + ctx.fd = fd 1003 + ctx.dict = readDict(fd:read 'a') 1004 + fd:close() 1005 + 1006 + local written = false 1007 + local fo = ctx.sty[io.stdout] 1008 + local fe = ctx.sty[io.stderr] 1009 + repeat 1010 + local cmd = prompt(fe.br(string.format('(parvan %s) ', ctx.file))) 1011 + if cmd == false then 1012 + io.stderr:write(fe.resetLine()) 1013 + if written then 1014 + ctx.log('warn', 'abandoning changes!') 1015 + end 1016 + return 0 1017 + end 1018 + if cmd and cmd ~= '' then 1019 + local words = strwords(cmd) 1020 + if next(words) then 1021 + if words[1] == 'bail' or 1022 + words[1] == 'abandon' or 1023 + words[1] == 'q!' then 1024 + if written then 1025 + ctx.log('warn', 'abandoning changes!') 1026 + end 1027 + return 0 1028 + end 1029 + local c = cmds[words[1]] 1030 + if c then 1031 + if c.raw then 1032 + ctx.log('fatal', words[1] .. ' cannot be run from `shell`') 1033 + elseif not implies(c.write, rw) then 1034 + ctx.log('fatal', ctx.file .. ' is not writable') 1035 + else 1036 + local ok = ctx.try(c.exec, ctx, table.unpack(words,2)) 1037 + if ok then written = written or c.write end 1038 + end 1039 + elseif cmd == 'save' or cmd == 'wq' then 1040 + if not written then 1041 + ctx.log('info', 'no changes to save') 1042 + end 1043 + cmd = nil 1044 + elseif cmd == 'quit' or cmd == 'q' then 1045 + if not written then cmd = nil else 1046 + ctx.log('fatal', 'dictionary has unsaved changes') 1047 + end 1048 + else 1049 + ctx.log('fatal', words[1] .. ' is not a command') 1050 + end 1051 + end 1052 + end 1053 + until cmd == nil 1054 + 1055 + if written then 1056 + ctx.log('info', 'saving file') 1057 + local out = writeDict(ctx.dict) 1058 + local fd = io.open(ctx.file,'w+b') 1059 + fd:write(out) 1060 + fd:close() 1061 + end 1062 +end 1063 + 1064 +local function 1065 +showHelp(ctx,k,v) 1066 + if not v then 1067 + id10t 'no such command' 1068 + end 1069 + 1070 + if v.help then 1071 + local fe = ctx.sty[io.stderr] 1072 + local defs, synt = '' 1073 + if type(v.syntax) == 'table' then 1074 + synt = v.syntax[1] 1075 + local pad = string.rep(' ', #k+5) 1076 + for i=2,#v.syntax do 1077 + defs = defs .. pad .. fe.color(v.syntax[i],5) .. '\n' 1078 + end 1079 + else synt = v.syntax end 1080 + 1081 + io.stderr:write(string.format( 1082 + " > %s %s\n" .. defs .. 1083 + " %s\n", 1084 + fe.br(k), synt and fe.br(fe.color(synt,5)) or '', 1085 + fe.em(fe.color(v.help,4,true)))) 1086 + end 1087 +end 1088 + 1089 +function cmds.help.exec(ctx,cmd) 1090 + if cmd then 1091 + showHelp(ctx, cmd, cmds[cmd]) 1092 + else 1093 + for cmd,c in pairs(cmds) do 1094 + showHelp(ctx, cmd, c) 1095 + end 1096 + end 1097 +end 1098 + 1099 +local function 1100 +usage(me,ctx) 1101 + local ln = 0 1102 + local ct = {} 1103 + local fe = ctx.sty[io.stderr] 1104 + io.stderr:write(string.format(fe.br"usage:".." %s <file> [<command> [args…]]\n",me)) 1105 + --[[ 1106 + for k,v in pairs(cmds) do 1107 + local n = 1 + utf8.len(k) + utf8.len(v.syntax) 1108 + ct[k] = n 1109 + if n > ln then ln = n end 1110 + end 1111 + for k,v in pairs(cmds) do 1112 + local pad = string.rep(" ", ln - ct[k] + 3) 1113 + io.stderr:write(string.format(" "..fe.br'%s %s'.."%s%s\n", 1114 + k, v.syntax, pad, v.help)) 1115 + end]] 1116 + for k,v in pairs(cmds) do 1117 + showHelp(ctx,k,v) 1118 + end 1119 + return 64 1120 +end 1121 + 1122 +local function 1123 +dispatch(argv, ctx) 1124 + local ferr = ctx.sty[io.stderr] 1125 + local file, cmd = table.unpack(argv) 1126 + if cmd and cmds[cmd] then 1127 + local c,fd,dict = cmds[cmd] 1128 + if (not c.raw) and not c.nofile then 1129 + fd = safeopen(file, "rb") 1130 + dict = readDict(fd:read 'a') 1131 + fd:close() 1132 + -- lua io has no truncate method, so we must 1133 + -- rely on the clobbering behavior of the open() 1134 + -- call instead :( 1135 + end 1136 + 1137 + cmds[cmd].exec({ 1138 + sty = ctx.sty; 1139 + try = ctx.try; 1140 + log = ctx.log; 1141 + 1142 + file = file; 1143 + fd = fd; 1144 + dict = dict; 1145 + }, table.unpack(argv,3)) 1146 + 1147 + if (not c.raw) and c.write then 1148 + local output = writeDict(dict) 1149 + -- writeDict should always be given a chance to 1150 + -- bail before the previous file is destroyed!! 1151 + -- you don't want one bug to wipe out your entire 1152 + -- dictionary in one fell swoop 1153 + fd = safeopen(file,'w+b') 1154 + fd:write(output) 1155 + fd:close() 1156 + end 1157 + 1158 + return 0 1159 + else 1160 + return usage(argv[0], ctx) 1161 + end 1162 +end 1163 + 1164 + 1165 +local argv if arg 1166 + then argv = arg 1167 + else argv = {[0] = 'parvan', ...} 1168 +end 1169 + 1170 +local sty = { 1171 + [io.stdout] = ansi.formatter(io.stdout); 1172 + [io.stderr] = ansi.formatter(io.stderr); 1173 +}; 1174 + 1175 +local function log(lvl, msg) 1176 + local colors = {fatal=1,warn=3,info=4,debug=2} 1177 + local ferr = sty[io.stderr] 1178 + io.stderr:write(string.format( 1179 + ferr.color(ferr.br("(%s)"),colors[lvl]).." %s\n", lvl, msg)) 1180 +end 1181 +local function try(...) 1182 + -- a wrapper around pcall that produces a standard error 1183 + -- message format when an error occurs 1184 + local res = { pcall(...) } 1185 + if not res[1] then 1186 + log('fatal', res[2]) 1187 + end 1188 + return table.unpack(res) 1189 +end 1190 + 1191 +local function stacktrace(err) 1192 + return debug.traceback(err,3) 1193 +end 1194 +local ok, res = xpcall(dispatch, stacktrace, argv, { 1195 + try = try, sty = sty, log = log 1196 +}) 1197 + 1198 +if not ok then 1199 + log('fatal', res) 1200 + os.exit(1) 1201 +end 1202 + 1203 +os.exit(res)
Modified readme.md from [855194b85a] to [b037845c63].
8 8 * **safekill.c**: utility to help keep from accidentally killing important windows; compile with `cc -Ofast safekill.c -lX11 -lc -osafekill` 9 9 * **newtab.c**: a "open a new tab if there's already an instance running or launch a new instance otherwise" utility for qutebrowser 10 10 * **fabulist.scm**: a work-in-progress communal fiction server 11 11 * **bgrd.c**: it’s… a long story. just read the header. 12 12 * **mkpw.c** an extremely fast mass random password generator 13 13 * **kpw**: an extremely simple, lightweight, secure password manager for POSIX OSes written in C. depends on libsodium for crypto primitives. compile with `make kpw`. 14 14 * **rosshil.ml**: tool to convert between the various calendars of the [Spirals](https://ʞ.cc/fic/spirals/) setting 15 +* **parvan.lua**: a script for creating and querying dictionaries, intended as a conlanging tool. no dependencies, just run it with `lua`