Skip to content

Instantly share code, notes, and snippets.

@kuuote

kuuote/scrap.ts Secret

Created February 18, 2021 15:14
Show Gist options
  • Save kuuote/52a88f809d9e36890dd99e776967e92f to your computer and use it in GitHub Desktop.
Save kuuote/52a88f809d9e36890dd99e776967e92f to your computer and use it in GitHub Desktop.
Scrapbox風リンク解決
type PageRelation = {
relate: string;
pages: Page[];
};
const getOrCreate = <T>(map: Map<string, T>, key: string, func: () => T) => {
const result = map.get(key);
if (!result) {
const newValue = func();
map.set(key, newValue);
return newValue;
}
return result;
};
class Page {
static comparator = (a: Page, b: Page) =>
a.mtime === b.mtime ? (a.name < b.name ? -1 : 1) : b.mtime - a.mtime;
name: string;
links = new Map<string, Page>();
fromLinks = new Map<string, Page>();
mtime = 0;
constructor(name: string) {
this.name = name;
}
getLinks(): Array<PageRelation> {
const links = {
relate: "links",
pages: Array.from(this.links.values()).sort(Page.comparator),
};
const fromLinks = {
relate: "fromLinks",
pages: Array.from(this.fromLinks.values()).sort(Page.comparator),
};
const twoHop = links.pages.map((
p,
) => ({
relate: p.name,
pages: Array.from(p.fromLinks.values()).sort(Page.comparator),
}));
const visit = new Map<string, void>();
// ignore this at link destination
visit.set(this.name);
return [links, fromLinks, ...twoHop].map(({ relate, pages }) => ({
relate,
pages: pages.filter((p) => {
if (visit.has(p.name)) {
return false;
}
visit.set(p.name);
return true;
}),
})).filter(({ pages }) => pages.length !== 0);
}
}
class Project {
path: string;
pages = new Map<string, Page>();
constructor(path: string) {
this.path = path;
}
getOrCreatePage(name: string) {
return getOrCreate(this.pages, name, () => new Page(name));
}
readAll() {
for (const entry of Deno.readDirSync(this.path)) {
if (entry.name.endsWith(".scp")) {
this.resolve(entry.name.slice(0, -4), `${this.path}/${entry.name}`);
}
}
}
resolve(name: string, path: string) {
const page = this.getOrCreatePage(name);
// 既存のリンクの削除
for (const linkedPage of page.links.values()) {
linkedPage.fromLinks.delete(name);
}
// テキストを読んで `[link]` 形式のリンクを抽出し
const match = (Deno.readTextFileSync(path).match(/\[[^\]]+\]/g) ?? []).map((
l,
) => l.slice(1, -1));
// ページを関連付けする
for (const link of match) {
const linkToPage = this.getOrCreatePage(link);
page.links.set(link, linkToPage);
linkToPage.fromLinks.set(name, page);
}
}
}
const projects = new Map<string, Project>();
export const getOrCreateProject = (path: string): Project =>
getOrCreate(projects, path, () => new Project(path));
import {
assertEquals,
} from "https://deno.land/std@0.87.0/testing/asserts.ts";
import { getOrCreateProject } from "./scrap.ts";
const hoge = getOrCreateProject("./test");
hoge.readAll();
const a = hoge.getOrCreatePage('a');
const b = hoge.getOrCreatePage('b');
const c = hoge.getOrCreatePage('c');
// resolve link
console.log('# resolve links');
console.log('a -> b');
assertEquals(Array.from(a.links.keys()).sort(), ["b"]);
console.log('b <- a, c');
assertEquals(Array.from(b.fromLinks.keys()).sort(), ["a", "c"]);
console.log('c -> b');
assertEquals(Array.from(c.links.keys()).sort(), ["b"]);
// get links, backlinks, and two hop links
console.log('# get links');
console.log('a');
console.log('links -> b | b -> c')
const aLinks = a.getLinks();
assertEquals(aLinks.length, 2); // links, b
assertEquals(aLinks[0].relate, "links");
assertEquals(aLinks[0].pages.map((p) => p.name), ["b"]);
assertEquals(aLinks[1].relate, "b");
assertEquals(aLinks[1].pages.map((p) => p.name), ["c"]); // ignore self
console.log('b');
console.log('fromLinks -> a, c')
const bLinks = b.getLinks();
assertEquals(bLinks.length, 1); // fromLinks
assertEquals(bLinks[0].relate, "fromLinks");
assertEquals(bLinks[0].pages.map((p) => p.name).sort(), ["a", "c"]);
console.log('c');
console.log('links -> b | b -> a')
const cLinks = c.getLinks();
assertEquals(cLinks.length, 2); // links, b
assertEquals(cLinks[0].relate, "links");
assertEquals(cLinks[0].pages.map((p) => p.name), ["b"]);
assertEquals(cLinks[1].relate, "b");
assertEquals(cLinks[1].pages.map((p) => p.name), ["a"]); // ignore self
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment