Skip to content

Instantly share code, notes, and snippets.

@xddxdd
Last active March 24, 2021 15:46
Show Gist options
  • Save xddxdd/99170395595e339ed5735cdf9b9b2cd1 to your computer and use it in GitHub Desktop.
Save xddxdd/99170395595e339ed5735cdf9b9b2cd1 to your computer and use it in GitHub Desktop.
Gophermaps generation code for Hexo
Here's my code to generate Gophermaps for posts on a Hexo site.
I cannot guarantee this will work on your Hexo site. I had custom multilingual support on my site,
and manually removed all language related stuff from this code snippet. I did not test that the
code still functions after this modification.
Usage:
Put gopher.js into "scripts" folder of your theme, e.g. "themes/lantian/scripts/gopher.js".
Run "hexo clean" followed by "hexo generate".
var unified = require('unified');
var remark_parse = require('remark-parse');
var remark_stringify = require('remark-stringify');
var remark_gfm = require('remark-gfm');
var remark_frontmatter = require('remark-frontmatter');
var remark_inline_links = require('remark-inline-links');
var prettier = require('prettier');
var fs = require('hexo-fs');
var path = require('path');
const isFullwidthCodePoint = require('is-fullwidth-code-point');
const crlf = '\r\n';
const gopherBefore = 'i';
const gopherBeforeLink = '1';
const gopherBeforeImage = 'I';
const gopherAfter = '\t\t{{server_addr}}\t{{server_port}}' + crlf;
const gopherEOF = '.' + crlf;
function slice_width(str, start, length) {
var idx_first = 0;
while (true) {
var w = isFullwidthCodePoint(str.codePointAt(idx_first)) ? 2 : 1;
if (start < w) break;
idx_first++;
start -= w;
}
var idx_last = idx_first;
while (true) {
var w = isFullwidthCodePoint(str.codePointAt(idx_last)) ? 2 : 1;
if (length < w) break;
idx_last++;
length -= w;
}
return str.slice(idx_first, idx_last);
}
function markdown_formatter(rel_path, md) {
const markdownRegex = /([^!]?)(!?)\[([^\]]+)\]\(([^)]+)\)(.?)/g;
var rows = md.split('\n');
for (var i = 0; i < rows.length; i++) {
// Link regex can also match images
if (rows[i].match(markdownRegex)) {
var replace_at_beginning = false,
replace_at_end = false;
var replace_fn = (
match,
prefix,
img_marker,
label,
href,
suffix,
) => {
// Don't touch external links
if (href.match("://")) {
return match;
}
if (prefix !== null) {
replace_at_beginning = true;
}
if (suffix !== null) {
replace_at_end = true;
}
href = path.join('/', rel_path, href);
return (
(prefix ? prefix + gopherAfter : '') +
(img_marker === '!'
? gopherBeforeImage
: gopherBeforeLink) +
label +
'\t' +
href +
'\t{{server_addr}}\t{{server_port}}' +
crlf +
(suffix ? gopherBefore + suffix : '')
);
};
rows[i] = rows[i].replaceAll(markdownRegex, replace_fn);
rows[i] =
(replace_at_beginning ? '' : gopherBefore) +
rows[i] +
(replace_at_end ? '' : gopherAfter);
} else {
rows[i] = gopherBefore + rows[i] + gopherAfter;
}
}
return rows.join('') + gopherEOF;
}
var markdown_to_gopher = (result, data) => {
if (data.page.raw) {
unified()
.use(remark_parse)
.use(remark_frontmatter)
.use(remark_gfm)
.use(remark_inline_links)
.use(remark_stringify, {
bullet: '-',
fences: true,
listItemIndent: 'one',
resourceLink: false,
})
.process(data.page.raw)
.then((file) => {
var md = String(file);
if (!md) return;
md = prettier.format(md, {
parser: 'markdown',
printWidth: 70,
tabWidth: 2,
proseWrap: 'always',
endOfLine: 'lf',
});
if (!md) return;
md = markdown_formatter(path.dirname(data.path), md);
var target_path = data.path;
target_path = target_path.replace(/index\.html$/, 'gophermap');
target_path = target_path.replace(/.html$/, '.gopher');
var promise = fs.writeFile(
path.join(hexo.public_dir, target_path),
md,
);
hexo.log.info('[LT Gopher] Generated: ' + target_path);
return promise;
});
}
return result;
};
var gophermap_index_generator = (locals) => {
var data = '';
data += gopherBefore + '#' + gopherAfter;
data += gopherBefore + '# ' + hexo.config.title + gopherAfter;
data += gopherBefore + '#' + gopherAfter;
data += gopherBefore + gopherAfter;
data += gopherBefore + 'Posts:' + gopherAfter;
locals.posts.sort('date', 'desc').each((post) => {
data +=
gopherBeforeLink +
'- ' +
slice_width(post.title, 0, 56) +
' (' +
new Date(post.date).toISOString().replace('T', ' ').substr(0, 19) +
')' +
'\t/' +
post.path.replace(/index\.html$/g, '') +
'\t{{server_addr}}\t{{server_port}}' +
crlf;
var summary = post.content.trim().replace(EXCERPT_REGEX, '');
data +=
gopherBefore +
' ' +
slice_width(summary, 0, 68) +
gopherAfter;
data +=
gopherBefore +
' ' +
slice_width(summary, 68, 68) +
gopherAfter;
data +=
gopherBefore +
' ' +
slice_width(summary, 136, 68) +
gopherAfter;
data += gopherBefore + gopherAfter;
});
data += gopherEOF;
var path = '/gophermap';
hexo.log.info('[LT Gopher] Generated: ' + path);
return {
path: path,
data: data,
};
};
hexo.extend.filter.register('after_render:html', markdown_to_gopher, 1);
hexo.extend.generator.register(
'gophermap_index_generator',
gophermap_index_generator,
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment