From f3bde26728f8e018e44d472181d2453ee3465081 Mon Sep 17 00:00:00 2001 From: osmarks Date: Tue, 17 Nov 2020 20:41:31 +0000 Subject: [PATCH] RPNCalcv4 and stuff --- experiments/apioform/index.html | 686 +++++++++++++++++++++++++++++ experiments/rpncalc-v4/builtin.mjs | 128 ++++++ experiments/rpncalc-v4/index.html | 48 ++ experiments/rpncalc-v4/main.js | 130 ++++++ experiments/rpncalc-v4/parse.mjs | 231 ++++++++++ experiments/rpncalc-v4/shiny.mjs | 158 +++++++ experiments/rpncalc-v4/token.mjs | 57 +++ openring.html | 4 +- src/global.json | 5 +- src/index.js | 2 +- style.sass | 3 - 11 files changed, 1443 insertions(+), 9 deletions(-) create mode 100644 experiments/apioform/index.html create mode 100644 experiments/rpncalc-v4/builtin.mjs create mode 100644 experiments/rpncalc-v4/index.html create mode 100644 experiments/rpncalc-v4/main.js create mode 100644 experiments/rpncalc-v4/parse.mjs create mode 100644 experiments/rpncalc-v4/shiny.mjs create mode 100644 experiments/rpncalc-v4/token.mjs diff --git a/experiments/apioform/index.html b/experiments/apioform/index.html new file mode 100644 index 0000000..4784c41 --- /dev/null +++ b/experiments/apioform/index.html @@ -0,0 +1,686 @@ +--- +title: Apioform Game +description: A game about... apioforms... by Heavpoot. +--- + +

Kills:

+ \ No newline at end of file diff --git a/experiments/rpncalc-v4/builtin.mjs b/experiments/rpncalc-v4/builtin.mjs new file mode 100644 index 0000000..0c5ca5a --- /dev/null +++ b/experiments/rpncalc-v4/builtin.mjs @@ -0,0 +1,128 @@ +import {defnOp, makeOp, defn} from './shiny.mjs'; +import {parseExprs} from './parse.mjs'; +import {tokenize} from './token.mjs'; + +const objEq = (a, b) => { + if (typeof a !== 'object' && typeof b !== 'object') { + return a === b; + } else if (typeof a !== 'object' || typeof b !== 'object') { + return false; + } else { + const aprop = Object.getOwnPropertyNames(a); + const bprop = Object.getOwnPropertyNames(b); + + if (bprop.length !== aprop.length) { + return false; + } + + for (let i = 0; i < aprop.length; i++) { + if (!objEq(a[aprop[i]], b[aprop[i]])) { + return false; + } + } + + return true; + } +} + +export let scope = {}; + +export const customHandler = (ins) => { + return [ins]; +} + +const addRPNDefn = (name, def) => { + let toks = tokenize(def); + if (!toks) { + throw 'could not load builtin' + } + toks = toks.map(elem => { + elem.startPos = 0; + elem.endPos = 0; + return elem; + }); + let ast = parseExprs(toks); + if (!ast.parsed) { + throw 'could not load builtin' + } + scope = defn(name, ast.parsed.arr, scope); +} + +const assertType = (type) => (elem) => { + if (elem.type !== type) { + throw 'typeerror' + } +} + +const addDefn = (name, args, fn) => { + if (Array.isArray(args)) { + const nargs = [...Array(args.length).keys()]; + const liftFn = (scope) => { + let newscope = Object.create(scope); + for (let i = 0; i < args.length; i++) { + assertType(args[i])(scope[i][0]); + newscope[i] = scope[i][0].val; + } + return fn(newscope); + } + const op = makeOp(nargs, liftFn); + defnOp(name, op); + } else { + const nargs = [...Array(args).keys()]; + const liftFn = (scope) => { + let newscope = Object.create(scope); + for (let i = 0; i < args; i++) { + newscope[i] = scope[i][0]; + } + return fn(newscope); + } + const op = makeOp(nargs, liftFn); + defnOp(name, op); + } +} + +const add = (args) => [{type:"num", val:args[0] + args[1]}]; +const sub = (args) => [{type:"num", val:args[0] - args[1]}]; +const div = (args) => [{type:"num", val:args[0] / args[1]}]; +const mult = (args) => [{type:"num", val:args[0] * args[1]}]; +const pow = (args) => [{type:"num", val:Math.pow(args[0], args[1])}]; +const root = (args) => [{type:"num", val:Math.sqrt(args[0])}]; +const type = (args) => [{type:"type", val:args[0].type}]; +const pair = (args) => [{type:"pair", val:{fst:args[0], snd:args[1]}}]; +const fst = (args) => [args[0].fst]; +const snd = (args) => [args[0].snd]; +const tuple = (args) => makeOp([...Array(args[0]).keys()], (args) => {return [{type:"tuple", val:args}]}); +const index = (args) => args[0][args[1]]; +const len = (args) => [{type:"num", val:args[0].length}]; + +const eq = (args) => { + if (args[2].type === args[3].type && objEq(args[2].val, args[3].val)) { + return [args[0]]; + } else { + return [args[1]]; + } +} + +addDefn("+", ["num", "num"], add); +addDefn("-", ["num", "num"], sub); +addDefn("/", ["num", "num"], div); +addDefn("*", ["num", "num"], mult); +addDefn("^", ["num", "num"], pow); +addDefn("sqrt", ["num"], root); +addDefn("==", 4, eq); +addDefn("pair", 2, pair); +addDefn("fst", ["pair"], fst); +addDefn("snd", ["pair"], snd); +addDefn("tuple", ["num"], tuple); +addDefn("!!", ["tuple", "num"], index); +addDefn("len", ["tuple"], len); +addDefn("typeof", 1, type); +addRPNDefn("stop", "\"stop"); +addRPNDefn("inv", "(x -> 1 x /)"); +addRPNDefn("fold", "(x acc fn -> acc '(-> x acc fn 'fn fold) 'x stop ==)"); +addRPNDefn("range", "(x y -> x '(->x x 1 + y range) 'x y ==)"); +addRPNDefn("listthen", "(fn -> (internal; x acc -> '(->acc fn) '(->x acc pair internal) x stop ==) 0 tuple internal)"); +addRPNDefn("list", "'(a -> a) listthen"); +addRPNDefn("lmap", "(list fn -> list '(->list fst fn list snd 'fn lmap pair) list 0 tuple ==)"); +addRPNDefn("unlist", "(l -> (internal; list -> '(->) '(->list fst list snd internal) list 0 tuple ==) stop l internal)"); +addRPNDefn("map", "fn -> '(l->l 'fn lmap unlist) listthen"); \ No newline at end of file diff --git a/experiments/rpncalc-v4/index.html b/experiments/rpncalc-v4/index.html new file mode 100644 index 0000000..39d9045 --- /dev/null +++ b/experiments/rpncalc-v4/index.html @@ -0,0 +1,48 @@ +--- +title: RPNCalc v4 +slug: rpncalc4 +description: Reverse Polish Notation calculator, version 4 - increasingly esoteric and incomprehensible. Contributed by Aidan. +--- + + + + + + + +

+

+
+

documentation

+

use (name; value) to define something. the definition can be recursive. value is executed and name is set to the final state of the stack, i.e. (name; 1 3) is possible

+

use ' to push instead of apply to the stack, e.g. '(a -> a). This is useful for lazyness, i.e. '(->lazy evaluated thing)

+ +

stdlib

+ \ No newline at end of file diff --git a/experiments/rpncalc-v4/main.js b/experiments/rpncalc-v4/main.js new file mode 100644 index 0000000..57d2814 --- /dev/null +++ b/experiments/rpncalc-v4/main.js @@ -0,0 +1,130 @@ +import {execRPN, step} from './shiny.mjs'; +import {parseExprs} from './parse.mjs'; +import {tokenize} from './token.mjs'; +import {scope, customHandler} from './builtin.mjs'; + +//const scope = {}; +//const customHandler = a => [a]; + +const inbox = document.getElementById("inbox") +const insbox = document.getElementById("insbox") +const outbox = document.getElementById("outbox") +const stepb = document.getElementById("step") +const play = document.getElementById("play") +const load = document.getElementById("load") + +const usestd = document.getElementById("use-std") + +let state = null; +let input = null; + +const highlight = (str, start, end, color) => { + return str.slice(0, start) + `` + str.slice(start, end) + "" + str.slice(end); +} + +const loadState = () => { + input = inbox.value; + let toks = tokenize(input); + let ast = parseExprs(toks); + console.log(ast); + if (ast.parsed === null) { + insbox.innerHTML = "could not parse AST!" + } else if (ast.stream.length !== 0) { + insbox.innerHTML = highlight(input, ast.stream[0].startPos, ast.stream[0].endPos, "red"); + insbox.innerHTML += "
unexpected token" + input = null; + state = null; + return; + } + insbox.innerHTML = input; + outbox.innerHTML = ""; + if (usestd.checked) { + state = {scopes:[scope], stacks:[[]], calls:[ast.parsed.arr]}; + } else { + state = {scopes:[{}], stacks:[[]], calls:[ast.parsed.arr]}; + } +} + +const showIns = (ins) => { + if (ins.val) { + return ins.val; + } else if (ins.ident) { + return ins.ident; + } else if (ins.name) { + return ins.name; + } else { + return 'anon'; + } +} + +load.onclick = _ => { + loadState(); +} + +play.onclick = _ => { + if (state === null) { + loadState(); + } + insbox.innerHTML = ""; + while (state.calls[0].length > 0 || state.calls.length > 1) { + try { + step(state, customHandler, showIns); + } catch (err) { + insbox.innerHTML = err; + state = null; + input = null; + return; + } + } + outbox.innerHTML = prettyprint(state.stacks[0]); + state = null; + input = null; +} + +stepb.onclick = _ => { + if (state === null) { + return; + } + if (state.calls[0].length > 0 || state.calls.length > 1) { + let pos; + try { + pos = step(state, customHandler, showIns); + } catch (err) { + insbox.innerHTML = err; + state = null; + input = null; + return; + } + if (!(pos.start === 0 && pos.end === 0)) { + insbox.innerHTML = highlight(input, pos.start, pos.end, "green"); + } + if (state.stacks.length > 1) { + outbox.innerHTML = "... " + prettyprint(state.stacks[state.stacks.length-1]); + } else { + outbox.innerHTML = prettyprint(state.stacks[0]); + } + } +} + +const show = (elem) => { + if (elem.type === "pair") { + return "{" + show(elem.val.fst) + ", " + show(elem.val.snd) + "}" + } else if (elem.type === "closure") { + return "(needs " + elem.val.args.length + ")" + } else if (elem.type === "array") { + return "[" + prettyprint(elem.val) + "]" + } else { + return "(" + elem.val + ": " + elem.type + ")" + } +} + +const prettyprint = (out) => { + let str = ""; + for (let i = 0; i < out.length; i++) { + str += show(out[i]); + if (i < out.length - 1) { + str += " "; + } + } + return str; +} \ No newline at end of file diff --git a/experiments/rpncalc-v4/parse.mjs b/experiments/rpncalc-v4/parse.mjs new file mode 100644 index 0000000..2d2ddfe --- /dev/null +++ b/experiments/rpncalc-v4/parse.mjs @@ -0,0 +1,231 @@ +/* make parser safe */ +const debug = false; + +const attempt = (parser) => (stream) => { + if (debug) { + console.log("attempt"); + console.log(stream); + } + let streamclone = [...stream]; + try { + let out = parser(stream); + return out; + } catch(err) { + return {parsed:null,stream:streamclone}; + } +} + +/* chain */ +const or = (a, b) => (stream) => { + let streamclone = [...stream]; + if (debug) { + console.log("or"); + console.log(stream); + } + let aout = a(stream); + if (aout.parsed === null) { + return b(streamclone); + } else { + return aout; + } +} + +/* (parser) */ +const parens = (parser) => (stream) => { + if (debug) { + console.log("parens"); + console.log(stream); + } + let a = parseSyntax("(")(stream); + if (a.parsed === null) { + return {parsed:null, stream:a.stream}; + } + let dat = parser(a.stream); + let b = parseSyntax(")")(dat.stream); + if (b.parsed === null) { + throw 'mismatched parens!'; + } + dat.parsed.pos.start = a.parsed.pos.start; + dat.parsed.pos.end = b.parsed.pos.end; + return {parsed:dat.parsed, stream:b.stream}; +} + +/* [parser] */ +const many = (parser) => (stream) => { + if (debug) { + console.log("many"); + console.log(stream); + } + let parsed = []; + while (true) { + let i = parser(stream); + stream = i.stream; + if (i.parsed === null) { + break; + } else { + parsed.push(i.parsed); + } + } + let startPos = 0; + let endPos = 0; + if (parsed.length > 1) { + startPos = parsed[0].pos.start; + endPos = parsed[parsed.length-1].pos.start; + } + return {parsed:{arr:parsed, pos:{start:startPos, end:endPos}}, stream:stream}; +} + +/* takes in stream, outputs parsed item or null */ +const parseIdent = (stream) => { + if (debug) { + console.log("ident"); + console.log(stream); + } + let e = stream[0]; + if (e === undefined) { + return {parsed:null, stream:stream}; + } + if (e.type !== "ident") { + return {parsed:null, stream:stream}; + } else { + stream.shift(); + return {parsed:{type:e.type, val:e.name, pos:{start:e.startPos, end:e.endPos}}, stream:stream}; + } +} + +/* takes in stream, outputs parsed item or null */ +const parseNum = (stream) => { + if (debug) { + console.log("num"); + console.log(stream); + } + let e = stream[0]; + if (e === undefined) { + return {parsed:null, stream:stream}; + } + if (e.type !== "num") { + return {parsed:null, stream:stream}; + } else { + stream.shift(); + return {parsed:{type:e.type, val:e.val, pos:{start:e.startPos, end:e.endPos}}, stream:stream}; + } +} + +/* takes in stream, outputs parsed item or null */ +const parseSyntax = (syntax) => (stream) => { + if (debug) { + console.log("syntax", syntax); + console.log(stream); + } + let e = stream[0]; + if (e === undefined) { + return {parsed:null, stream:stream}; + } + if (e.type !== "syntax") { + return {parsed:null, stream:stream}; + } + if (e.val !== syntax) { + return {parsed:null, stream:stream}; + } else { + stream.shift(); + return {parsed:{type:"syntax", val:syntax, pos:{start:e.startPos, end:e.endPos}}, stream:stream}; + } +} + +/* takes in stream, outputs string or null - FAILABLE */ +const parseName = (stream) => { + if (debug) { + console.log("name"); + console.log(stream); + } + let id = parseIdent(stream); + if (id.parsed === null) { + return {parsed:null, stream:id.stream}; + } + let syn = parseSyntax(";")(id.stream); + if (syn.parsed === null) { + throw 'could not parse name!' + } + return {parsed:{name:id.parsed.val, pos:{start:id.parsed.pos.start, end:syn.parsed.pos.end}}, stream:syn.stream}; +} + +const parseType = (stream) => { + if (debug) { + console.log("type"); + console.log(stream); + } + let syn = attempt(parseSyntax("\""))(stream); + if (syn.parsed === null) { + return {parsed:null, stream:syn.stream}; + } + let id = or(parseIdent, parseNum)(syn.stream); + if (id.parsed === null) { + return {parsed:null, stream:id.stream}; + } + return {parsed:{type:"type", val:id.parsed.val, pos:{start:syn.parsed.pos.start, end:id.parsed.pos.end}}, stream:id.stream}; +} + +/* takes in stream, outputs parsed item or null - FAILABLE */ +const parsePush = (stream) => { + if (debug) { + console.log("push"); + console.log(stream); + } + let syn = attempt(parseSyntax("'"))(stream); + if (syn.parsed === null) { + return {parsed:null, stream:syn.stream}; + } + let id = parseExpr(syn.stream); + if (id.parsed === null) { + return {parsed:null, stream:id.stream}; + } + return {parsed:{type:"push", elem:id.parsed, pos:{start:syn.parsed.pos.start, end:id.parsed.pos.end}}, stream:id.stream}; +} + +const parseDefn = (stream) => { + if (debug) { + console.log("defn"); + console.log(stream); + } + let name = parseName(stream); + if (name.parsed === null) { + throw 'no name found!' + } + let expr = parseExprs(stream); + if (expr.parsed === null) { + throw 'no body found!' + } + return {parsed:{type:"defn", ident:name.parsed.name, defn:expr.parsed.arr, pos:{start:name.parsed.pos.start, end:expr.parsed.pos.end}}, stream:expr.stream} +} + +/* takes in stream, outputs parsed item or null - FAILABLE */ +const parseLambda = (stream) => { + if (debug) { + console.log("lambda"); + console.log(stream); + } + let args = many(parseIdent)(stream); + let syn = parseSyntax("->")(args.stream); + if (syn.parsed === null) { + throw 'no lambda body found!'; + } + let body = parseExprs(syn.stream); // .parsed should never be null, but anyway... + if (body.parsed === null) { + throw 'no lambda body found!'; + } + return {parsed:{type:"func", args:args.parsed.arr.map(x => x.val), body:body.parsed.arr, pos:{start:args.parsed.pos.start, end:body.parsed.pos.end}}, stream:body.stream}; +} + +/* takes in stream, outputs parsed item or null */ +const parseExpr = or( + attempt(parens(parseDefn)), or( + attempt(parens(parseLambda)), or( + attempt(parseLambda), or( + parseType, or( + parseIdent, or( + parseNum, + parsePush + )))))); + +/* takes in stream, outputs parsed items */ +export const parseExprs = many(parseExpr); \ No newline at end of file diff --git a/experiments/rpncalc-v4/shiny.mjs b/experiments/rpncalc-v4/shiny.mjs new file mode 100644 index 0000000..fb5ae80 --- /dev/null +++ b/experiments/rpncalc-v4/shiny.mjs @@ -0,0 +1,158 @@ +/* +EVAL.js +------- + +reads exprs from AST and executes them + +types of elem: +all have type +all have val + +TYPE VAL +"closure" {scope:{}, args:['x', 'y'], defn:} + +: +{type:"ins", ins:[]} +{type:"builtin", fn:(scope) => {stack}} + +exported functionality: +defnOp(name, function) - define a built-in operator +defn(name, ins, scope) - extend scope with AST +makeOp(name, args, fn) - lift a function to an operator +evalRPN(scope, ast) +*/ + +let builtins = {}; + +export const defnOp = (name, data) => { + builtins[name] = data; +} + +export const defn = (name, ins, scope) => { + let defscope = Object.create(scope); + let fnForm = {type:"closure", val:{scope:defscope, args:[], defn:{type:"ins", ins:ins}}}; + defscope[name] = [fnForm]; + let out = execRPN(defscope, ins); + defscope[name] = out.stacks[0]; + return defscope; +} + +export const makeOp = (args, fn) => { + return [{type:"closure", val:{scope:{}, args:args, defn:{type:"builtin", fn:fn}}}]; +} + +const lookup = (name, scope) => { + let n = scope[name]; + if (n) { + return n; + } + n = builtins[name]; + if (n) { + return n; + } + console.log(scope); + throw '"' + name + '" not in scope' +} + +const extend = (scope, name, elems) => { + let o = Object.create(scope); + o[name] = elems; + return o; +} + +const runFn = (defn, state) => { + if (defn.type === "ins") { + state.calls.push(defn.ins); + state.stacks.push([]); + } else if (defn.type === "builtin") { + let scope = state.scopes[state.scopes.length-1]; + let out = defn.fn(scope); + state.calls.push([]); + state.stacks.push(out); + } +} + +const giveArg = (arg, elem) => { + let argN = elem.val.args[elem.val.args.length-1]; + let newscope = extend(elem.val.scope, argN, [arg]); + return {type:elem.type, val:{scope:newscope, args:elem.val.args.slice(0,-1), defn:elem.val.defn}}; +} + +const apply = (elem, state) => { + if (elem.type === "closure") { + if (elem.val.args.length === 0) { + state.scopes.push(elem.val.scope); + runFn(elem.val.defn, state); + } else if (state.stacks[state.stacks.length-1].length > 0) { + apply(giveArg(state.stacks[state.stacks.length-1].pop(), elem), state); + } else { + state.stacks[state.stacks.length-1].push(elem); + } + } else { + state.stacks[state.stacks.length-1].push(elem); + } +} + +const applyMany = (elems, state) => { + for (let i = 0; i < elems.length; i++) { + apply(elems[i], state); + } +} + +const makeStackElems = (ins, state, handler) => { + if (ins.type === "push") { + throw 'nested push error' + } else if (ins.type === "ident") { + return lookup(ins.val, state.scopes[state.scopes.length-1]); + } else if (ins.type === "func") { + return [{type:"closure", val:{scope:state.scopes[state.scopes.length-1], args:ins.args, defn:{type:"ins", ins:ins.body}}}]; + } else { + return handler(ins); + } +} + +const doIns = (ins, state, handler) => { + if (ins.type === "push") { + state.stacks[state.stacks.length-1] = state.stacks[state.stacks.length-1].concat(makeStackElems(ins.elem, state, handler)); + } else if (ins.type === "defn") { + state.scopes[state.scopes.length-1] = defn(ins.ident, ins.defn, state.scopes[state.scopes.length-1]); + } else { + applyMany(makeStackElems(ins, state, handler), state); + } +} + +export const step = (state, handler, showIns, maxdepth) => { + if (state.stacks.length > maxdepth) { + throw 'max recursion depth exceeded' + } + if (state.calls[state.calls.length-1].length === 0) { + if (state.calls.length === 1) { + throw 'finished execution' + } + if (state.stacks.length < 2) { + throw 'nothing to return' + } + state.calls.pop(); + state.scopes.pop(); + let out = state.stacks.pop(); + applyMany(out, state); + return {start:0, end:0}; + } else { + let ins = state.calls[state.calls.length-1][0]; + state.calls[state.calls.length-1] = state.calls[state.calls.length-1].slice(1); + try { + doIns(ins, state, handler); + } catch (error) { + throw error + ' while executing "' + showIns(ins) + '"' + } + return ins.pos; + } +} + +export const execRPN = (scope, ins, handler=(x)=>[x], showIns=(x)=>x.name, maxdepth=16384) => { + let state = {scopes:[scope], stacks:[[]], calls:[ins]}; + while (state.calls[0].length > 0 || state.calls.length > 1) { + step(state, handler, showIns, maxdepth); + } + return state; +} \ No newline at end of file diff --git a/experiments/rpncalc-v4/token.mjs b/experiments/rpncalc-v4/token.mjs new file mode 100644 index 0000000..b1c0ad9 --- /dev/null +++ b/experiments/rpncalc-v4/token.mjs @@ -0,0 +1,57 @@ +const tokens = (stream) => { + let toks = []; + let currTok = {startPos:0, val:"", type:"str", endPos:0}; + for (let i = 0; i < stream.length; i++) { + if ("()';\"".includes(stream[i])) { + if (currTok.val !== "") { + currTok.endPos = i; + toks.push(currTok); + } + toks.push({startPos:i, endPos:i+1, val:stream[i], type:"syntax"}); + currTok = {startPos:i+1, val:"", type:"str"}; + } else if (stream[i] === "-") { + if (stream[i+1] === ">") { + if (currTok.val !== "") { + currTok.endPos = i; + toks.push(currTok); + } + toks.push({startPos:i, endPos:i+1, val:"->", type:"syntax"}); + i++; + currTok = {startPos:i+1, val:"", type:"str"}; + } else { + currTok.val += "-"; + } + } else if (/\s/.test(stream[i])) { + if (currTok.val !== "") { + currTok.endPos = i; + toks.push(currTok); + } + currTok = {startPos:i+1, val:"", type:"str"}; + } else { + currTok.val += stream[i]; + } + } + if (currTok.val !== "") { + currTok.endPos = stream.length; + toks.push(currTok); + } + return toks; +} + +const classify = (tokens) => { + return tokens.map(tok => { + if (tok.type === "str") { + if (!isNaN(tok.val)) { + return {startPos:tok.startPos, endPos:tok.endPos, val:Number(tok.val), type:"num"}; + } else { + return {startPos:tok.startPos, endPos:tok.endPos, name:tok.val, type:"ident"}; + } + } else { + return tok; + } + }); +} + +export const tokenize = (stream) => { + return classify(tokens(stream)); +} \ No newline at end of file diff --git a/openring.html b/openring.html index 388f2fc..83d9a70 100644 --- a/openring.html +++ b/openring.html @@ -2,9 +2,7 @@
{{range .Articles}}
-

- {{.Title}} -

+ {{.Title}}

{{.Summary}}

via {{.SourceTitle}} diff --git a/src/global.json b/src/global.json index 13d18f0..f0fc8fb 100644 --- a/src/global.json +++ b/src/global.json @@ -8,7 +8,8 @@ "https://slatestarcodex.com/feed/", "https://www.rifters.com/crawl/?feed=rss2", "https://drewdevault.com/feed.xml", - "https://www.giantitp.com/comics/oots.rss", - "https://qntm.org/rss.php" + "https://qntm.org/rss.php", + "https://aphyr.com/posts.atom", + "https://os.phil-opp.com/rss.xml" ] } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 21a96f2..a979ba7 100644 --- a/src/index.js +++ b/src/index.js @@ -178,7 +178,7 @@ const runOpenring = async () => { console.log(arg) const out = await util.promisify(childProcess.exec)(arg) console.log(out.stderr) - return out.stdout + return minifyHTML(out.stdout) } const run = async () => { diff --git a/style.sass b/style.sass index 3765f0e..2e9cf29 100644 --- a/style.sass +++ b/style.sass @@ -76,9 +76,6 @@ ul flex-wrap: wrap margin: -0.5rem - h4 - margin: 0 - .art flex: 1 1 0 display: flex