106 lines · 3.8 KB · JavaScript
RawDownload
const QUIET = ["idle", "exited", "stopped", "orphanTlc"];const going = (v) => v.pc.filter((p) => !QUIET.includes(p)).length;const changed = (before, after) =>  after.pc.findIndex(    (p, i) => p !== before.pc[i] || after.aside[i].exists !== before.aside[i].exists,  ) + 1; export const readerActions = ["Start", "Edit", "Note", "Stop", "KillNode"]; function context(v) {  if (v.pc.every((p) => p === "idle")) return "Before any run";  if (going(v) === 2) return "Two runs going";  return going(v) === 1 ? "One run going" : "Done";} const SPECS = { 1: "the spec", 2: "the edited spec", "1,2": "both specs" }; function contents(v) {  const kept = v.aside.filter((a) => a.exists);  const aside = kept.some((a) => a.notes)    ? ", your notes moved aside"    : kept.some((a) => a.by)      ? ", the old chart moved aside"      : "";  const parts = [    v.out.by && `chart of ${SPECS[v.out.read.join()]}`,    v.out.notes && "your notes",  ].filter(Boolean);  if (!v.out.exists) return `no folder${aside}`;  return `${parts.join(" and ") || "empty folder"}${aside}`;} export function state(v) {  return `${context(v)}: ${contents(v)}`;} export function group(v) {  if (v.pc.every((p) => p === "idle")) return "Not started";  return going(v) ? "Running" : "Finished";} export function step(action, before, after) {  const r = changed(before, after);  switch (action) {    case "Start": {      const where = going(before) ? " in a second shell" : "";      const how = after.inputOk[r - 1] ? "" : " with a typo";      return `you start run ${r}${where}${how}`;    }    case "Refuse":      return before.inputOk[r - 1]        ? `run ${r} says the folder has a file that it did not write`        : `run ${r} says the input is wrong`;    case "Tlc":      return `run ${r}'s TLC reads the spec`;    case "Draw":      return `run ${r} draws the chart in its own folder`;    case "Stale":      return `run ${r} says the spec changed while it read it`;    case "Foreign":      return `run ${r} says the folder has a file that it did not write`;    case "Swap":    case "Checked":      return "";    case "MoveIn":      return `run ${r} moves its folder into place`;    case "MoveAside":      return `run ${r} moves the old folder aside`;    case "Restore":      return `run ${r} puts the folder with your notes back and stops`;    case "Supersede":      return `run ${r} finds another run's folder in place and deletes the old folder`;    case "KeepAside":      return `run ${r} keeps the folder with your notes aside and says where`;    case "RenameFails":      if (after.aside[r - 1].exists)        return `run ${r} cannot rename a folder and says where the old folder is`;      return after.out.exists && !before.out.exists        ? `run ${r} cannot rename a folder, puts the old folder back, and stops`        : `run ${r} cannot rename a folder and stops`;    case "Clean":      return `run ${r} deletes the old folder and finishes`;    case "Edit":      return "you edit the spec";    case "Note":      return "you save notes in the folder";    case "Stop":      return `you stop run ${r} with Ctrl-C`;    case "KillNode":      return `you kill run ${r}'s Node, and its TLC keeps running`;    case "OrphanEnd":      return `run ${r}'s orphaned TLC finishes`;    default:      return action;  }} export function cause(v, facts, invariant) {  return {    ChartsOneSpec: "the run read the spec before and after an edit, and still wrote its chart",    KeepsYourFiles: "a run deleted a folder that had your notes in it",    NeverLosesChart: "a run deleted the last chart before another chart was in place",    ExitTwoLeavesFolder: "a run wrote the folder and then said it could not finish",    OverlapNeverRefuses: "a run stopped only because another run overlapped it",  }[invariant];}