Skip to content

Instantly share code, notes, and snippets.

@tmcw
Created September 25, 2018 01:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmcw/1e2c4a16a27ebee36f8a0295aef7ff57 to your computer and use it in GitHub Desktop.
Save tmcw/1e2c4a16a27ebee36f8a0295aef7ff57 to your computer and use it in GitHub Desktop.

hypertext-doctor

  1. point it at a directory of markdown files
  2. it finds all links, identifies the ones that link to redirects
  3. it re-links those links to the eventual destination
#!/usr/bin/env node
const Remark = require("remark");
const frontmatter = require("remark-frontmatter");
const select = require("unist-util-select");
const fs = require("fs");
const path = require("path");
const MagicString = require("magic-string");
const got = require("got");
const { extname, basename } = require("path");
const pAll = require("p-all");
const REDIRECT = Symbol("REDIRECT");
const OK = Symbol("OK");
const NOT_FOUND = Symbol("NOT_FOUND");
async function localizeFile(dirname) {
const files = fs.readdirSync(dirname).filter(f => path.extname(f) === ".md");
await pAll(
files.map(filename => async () => {
filename = path.join(dirname, filename);
const remark = Remark().use(frontmatter, "yaml");
const text = fs.readFileSync(filename, "utf8");
const base = basename(filename, ".md");
const ast = remark.parse(text);
const links = select(ast, "link");
const s = new MagicString(text);
const linkStatuses = await pAll(
links.map(link => async () => {
try {
const resp = await got.head(link.url, { timeout: 1000 });
if (resp.redirectUrls && resp.redirectUrls.length) {
return {
type: REDIRECT,
link: {
...link,
url: resp.redirectUrls[resp.redirectUrls.length - 1]
}
};
}
return { type: OK };
} catch {
return { type: NOT_FOUND };
}
}),
{ concurrency: 10 }
);
let modified = false;
for (let { type, link } of linkStatuses) {
if (type === REDIRECT) {
s.overwrite(
link.position.start.offset,
link.position.end.offset,
Remark().stringify(link)
);
modified = true;
}
}
console.error(`${filename}
${linkStatuses.length} links
| ${linkStatuses.filter(l => l.type === OK).length} OK
| ${linkStatuses.filter(l => l.type === NOT_FOUND).length} NOT_FOUND
| ${linkStatuses.filter(l => l.type === REDIRECT).length} REDIRECT`);
if (modified) {
fs.writeFileSync(filename, s.toString());
}
}),
{ concurrency: 10 }
);
}
localizeFile(process.argv[2]);
{
"name": "hypertext-doctor",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"got": "^9.2.2",
"magic-string": "^0.25.1",
"p-all": "^1.0.0",
"remark": "^9.0.0",
"remark-frontmatter": "^1.3.0",
"unist-util-select": "^1.5.0"
},
"devDependencies": {
"prettier": "^1.14.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment