!759 static PersistentMap theories; // name -> text static new MultiMap signMap; static new Map icCache; // lower case name -> text static class Sign { S user, theory, as; long since; } static new L signList; // v2 of signMap static boolean printTimings = false; p { theories = new PersistentMap("theories"); for (S name : getTheoryNames()) icCache.put(name.toLowerCase(), theories.get(name)); load("signMap"); load("signList"); } static synchronized L getTheoryNames() { ret new ArrayList(theories.keySet()); } static synchronized boolean hasTheory(S name) { ret icCache.containsKey(name.toLowerCase()); } static synchronized S getTheoryOpt(S name) { ret icCache.get(name.toLowerCase()); } static synchronized S getTheory(S name) { S text = getTheoryOpt(name); if (text == null) fail("Theory * not found", name); ret text; } static synchronized L getTheoriesSignedByAs(S user, S as) { new L l; for (Sign s : signList) if (eq(s.user, user) && eq(s.as, as)) l.add(s.theory); ret l; } static synchronized Sign findSigning(S user, S as, S theory) { for (Sign s : signList) if (eq(s.user, user) && eq(s.as, as) && eq(s.theory, theory)) ret s; ret null; } static synchronized void theoriesPut(S name, S text) { theories.put(name, text); icCache.put(name.toLowerCase(), text); } static synchronized void theoriesRemove(S name) { theories.remove(name); icCache.remove(name.toLowerCase()); } static S signTheory(S user, S theory, S as) { if (empty(user)) ret "go to slack please"; if (!hasTheory(theory)) ret "Theory not found"; if (getTheoriesSignedByAs(user, as).contains(theory)) ret "You have already signed this theory as " + as + "!"; new Sign sign; sign.user = user; sign.theory = theory; sign.as = as; sign.since = now(); signList.add(sign); save("signList"); ret format("OK, signed * as *!", theory, as); } static S unsignTheory(S user, S theory, S as) { if (empty(user)) ret "go to slack please"; if (!hasTheory(theory)) ret "Theory not found"; Sign sign = findSigning(user, as, theory); if (sign == null) ret "You have not signed this theory as " + as + "."; signList.remove(sign); save("signList"); ret format("OK, unsigned * as *.", theory, as); } synchronized answer { //if (!attn()) ret null; if "count theories" ret lstr(theories); if "list theories" ret structure(keys(theories)); if "show theory *" exceptionToUser { ret showTheories(litlist(m.unq(0))); } if (matchStart("show theories", s, m)) exceptionToUser { ret showTheories(codeTokensOnly(javaTok(m.rest()))); } if "modify theory * *" exceptionToUser { S name = m.unq(0), text = m.unq(1); ret saveTheory(name, text, true); } if "add theory * *" exceptionToUser { S name = m.unq(0), text = m.unq(1); ret saveTheory(name, text, false); } S line = firstLine(s).trim(); if (startsWithIgnoreCase(line, "theory ") && l(toLines(s)) > 1) exceptionToUser { line = dropPrefixIgnoreCase("theory ", line); line = dropSuffix(":", line).trim(); S name = unquote(line), text = dropFirstLine(s); ret saveTheory(name, text, false); } if (match("inc theory * *", s, m) || match("increment theory * *", s, m)) exceptionToUser { S name = m.unq(0), text = m.unq(1); ret incrementTheory(name, text); } if "sign theory *" exceptionToUser { S user = getUserName(); ret signTheory(user, m.unq(0), "default"); } if "unsign theory *" exceptionToUser { S user = getUserName(); ret unsignTheory(user, m.unq(0), "default"); } if "sign theory * as *" exceptionToUser { S user = getUserName(); ret signTheory(user, m.unq(0), m.unq(1)); } if "unsign theory * as *" exceptionToUser { S user = getUserName(); ret unsignTheory(user, m.unq(0), m.unq(1)); } if "my signed theories" exceptionToUser { S user = getUserName(); if (empty(user)) ret "go to slack please"; ret structure(getTheoriesSignedByAs(user, "default")); } if "my theories signed as *" exceptionToUser { S user = getUserName(); if (empty(user)) ret "go to slack please"; ret structure(getTheoriesSignedByAs(user, m.unq(0))); } if "theories signed by *" exceptionToUser { S user = m.unq(0); ret structure(getTheoriesSignedByAs(user, "default")); } if (match("theories page", s) || match("theories url", s)) ret getBotURL(); if (!master()) ret null; if "delete theory *" exceptionToUser { S name = m.unq(0); if (hasTheory(name)) { S text = getTheoryOpt(name); logMap("deleted", "name", name, "text", text, "time", now(), "signers", signMap.get(name)); logMap("deleted", "name", name, "signers", signMap.get(name)); theoriesRemove(name); signMap.remove(name); save("signMap"); ret format("Theory * backed up & deleted", name); } else ret format("Theory * not found", name); } if "rename theory * to *" exceptionToUser { S name = m.unq(0), newName = m.unq(1); if (hasTheory(newName)) ret format("A theory named * already exists", newName); if (hasTheory(name)) { S text = getTheoryOpt(name); theoriesPut(newName, text); theoriesRemove(name); // update signMap L signers = signMap.get(name); signMap.addAll(newName, signers); signMap.remove(name); save("signMap"); ret format("Theory * renamed to *", name, newName); } else ret format("Theory * not found", name); } } static S showTheories(L theoryNames) { new StringBuilder buf; for (S name: theoryNames) { if (!hasTheory(name)) buf.append(format("Theory * not found\n", name)); else { buf.append(quote(name) + "\n" + slackSnippet(getTheoryOpt(name)) + "\n\n"); } } ret str(buf).trim(); } static S html(S uri, Map params) { if (eq(uri, "/signList")) ret htmlencode(structure(signList)); if (eq(uri, "/theories")) ret htmlencode(structure(theories)); if (eq(uri, "/theoriesInc")) { int n = parseInt(or(params.get("n"), "0")); S id = params.get("id"); if (nempty(id) && neq(id, theories.id())) n = 0; int m = (int) theories.file.length(); S text = unnull(loadTextFileStartingFrom(theories.file, n)); //text = substr(text, n); ret theories.id() + "\n" + m + "\n" + htmlencode(text); } boolean showMulti = false; new L data; MultiMap signs = getSigningsByTheory(); for (S name : getTheoryNames()) { S text = unnull(getTheoryOpt(name)); boolean multi = false; Exception error = null; L signings = signs.get(name); new L bla; for (Sign sign : signings) bla.add(sign.user + " as " + sign.as); Map map = litmap( "Name", htmlencode(name), "Theory", pre(htmlencode(minitrim(rtrim(text)))), "Signed by", htmlencode(join(", ", bla))); if (showMulti) { map.put("Multiple rules?", multi); try { multi = nlIsMultipleStatements(text); } catch (Exception e) { error = e; } } if (error != null) map.put("Parse Error", str(error)); data.add(map); } ret h3("Theories!") + htmlTable(data, false); } // preserves formatting of first line static S minitrim(S s) { L l = toLines(s); while (!empty(l) && empty(trim(first(l)))) l.remove(0); ret autoUnindent(fromLines(l)); } static S saveTheory(S name, S text, boolean autoModify) { boolean has = hasTheory(name); S oldText = getTheoryOpt(name); S origName = name; int counter = 0; while (has && !autoModify) { name = origName + "." + (char) (((int) 'a') + (++counter)); has = hasTheory(name); } logMap(has ? "modified" : "added", "name", name, "text", text, "time", now(), "oldText", oldText); theoriesPut(name, text); ret format((has ? "OK, modified theory *" : "OK, added theory *"), name); } static synchronized long getTheoryCounter(S name) { S text = getTheoryOpt(name + ".count"); ret isEmpty(text) ? 0 : parseLong(text); } // returns inc'ed value static synchronized long incTheoryCounter(S name) { long c = getTheoryCounter(name); ++c; theoriesPut(name + ".count", str(c)); ret c; } // returned list might include null elements static L getIncrements(S name) { new L l; long n = getTheoryCounter(name); for (long i = 1; i <= n; i++) l.add(getTheoryOpt(name + "." + i)); ret l; } static synchronized S incrementTheory(S name, S newText) { long n = incTheoryCounter(name); ret saveTheory(name + "." + n, newText, false); } static synchronized MultiMap getSigningsByTheory() { new MultiMap map; for (Sign s : signList) map.put(s.theory, s); ret map; }