169 lines
4.5 KiB
JavaScript
169 lines
4.5 KiB
JavaScript
const path = require("path");
|
|
const fs = require("fs"),
|
|
readline = require("readline");
|
|
|
|
let preformattedText = false;
|
|
//take input
|
|
const args = process.argv.slice(2);
|
|
const inputFile = args[0];
|
|
const outputFile = args[1];
|
|
let toMarkdown = false;
|
|
|
|
const header =
|
|
`
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="generator" content="geminut" />
|
|
<link rel="stylesheet" href="gmi.css">
|
|
|
|
<title>` +
|
|
outputFile +
|
|
`</title>
|
|
|
|
</head>
|
|
<body>
|
|
`;
|
|
|
|
const footer = `
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
const inputExtension = path.extname(inputFile);
|
|
const outputExtension = path.extname(outputFile);
|
|
if (outputExtension == ".md") {
|
|
toMarkdown = true;
|
|
}
|
|
|
|
if (!inputFile || !outputFile) {
|
|
console.log("USAGE: node geminut.js <input.gmi> {output.html | output.md}");
|
|
process.exit(1);
|
|
}
|
|
if (inputExtension !== ".gmi") {
|
|
console.log("Input must be a gemini .gmi file");
|
|
console.log("USAGE: node geminut.js <input.gmi> {output.html | output.md}");
|
|
process.exit(1);
|
|
}
|
|
if (!(outputExtension == ".html" || outputExtension == ".md")) {
|
|
console.log("Output must be a .html HTML or .md Markdown file");
|
|
console.log("USAGE: node geminut.js <input.gmi> {output.html | output.md}");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (fs.existsSync(outputFile)) {
|
|
//if already exists we remove first
|
|
fs.unlinkSync(outputFile);
|
|
}
|
|
|
|
if (!toMarkdown) {
|
|
fs.appendFileSync(outputFile, header);
|
|
}
|
|
|
|
const rd = readline.createInterface({
|
|
input: fs.createReadStream(inputFile),
|
|
//output: process.stdout,
|
|
console: false
|
|
});
|
|
|
|
rd.on("line", function(line) {
|
|
// console.log(line);
|
|
let data;
|
|
|
|
if (toMarkdown) {
|
|
data = parseGemtextToMarkdown(line);
|
|
fs.appendFileSync(outputFile, data + "\n");
|
|
} else {
|
|
data = parseGemtextToHTML(line);
|
|
fs.appendFileSync(outputFile, data + "\n");
|
|
}
|
|
});
|
|
|
|
rd.on("close", function() {
|
|
if (!toMarkdown) {
|
|
fs.appendFileSync(outputFile, footer);
|
|
}
|
|
});
|
|
|
|
function parseGemtextToHTML(gmiText) {
|
|
let htmlText;
|
|
|
|
//CODE BLOCK SECTION PARSER / CONVERTER
|
|
if (gmiText.match(/^```*(.*$)/gim)) {
|
|
//We are triggering a pre/codeblock to start or end
|
|
//check for code block toggle
|
|
if (!preformattedText) {
|
|
htmlText = gmiText.replace(/^```*(.*$)/gim, "<pre>$1");
|
|
} else {
|
|
htmlText = gmiText.replace(/^```*(.*$)/gim, "</pre>$1");
|
|
}
|
|
preformattedText = !preformattedText; //toggle!
|
|
//END CODE BLOCK SECTION
|
|
} else {
|
|
//we are not on a line with pre/codeblock
|
|
|
|
if (!preformattedText) {
|
|
//outside of a codeblock
|
|
htmlText = gmiText
|
|
.replace(/^### (.*$)/gim, "<h3>$1</h3>")
|
|
.replace(/^## (.*$)/gim, "<h2>$1</h2>")
|
|
.replace(/^# (.*$)/gim, "<h1>$1</h1>")
|
|
.replace(/^\* (.*$)/gim, "<ul><li>$1</li></ul>") //hack, would be better to add to ul list
|
|
.replace(/^\> (.*$)/gim, "<blockquote>$1</blockquote>")
|
|
.replace(
|
|
/^=>\s+(\S+(.jpg|.jpeg|.png|.gif|.svg))\s*(.*)/gim,
|
|
"<figure>\n<img src='$1' alt='$3'>\n<figcaption>$3</figcaption>\n</figure>"
|
|
) //for images, must be before links replacement below or else will be captured and converted
|
|
.replace(/^=>\s+(\S+)\s*(.*)/gim, "<p><a href='$1'>$2</a></p>") //close, need to remove gmi
|
|
.replace(".gmi", ".html") //a hack. will break for remote *.gmi url's
|
|
.replace(/^([a-zA-Z0-9].*$)/gim, "<p>$1</p>"); //lines starting with text or number are paragraph
|
|
} else {
|
|
//we are inside a pre/codeblock
|
|
|
|
htmlText = gmiText;
|
|
}
|
|
}
|
|
|
|
return htmlText;
|
|
}
|
|
|
|
function parseGemtextToMarkdown(gmiText) {
|
|
let mdText;
|
|
|
|
//CODE BLOCK SECTION PARSER / CONVERTER
|
|
if (gmiText.match(/^```*(.*$)/gim)) {
|
|
//We are triggering a pre/codeblock to start or end
|
|
//check for code block toggle
|
|
if (!preformattedText) {
|
|
mdText = gmiText.replace(/^```*(.*$)/gim, "```$1");
|
|
} else {
|
|
mdText = gmiText.replace(/^```*(.*$)/gim, "```$1");
|
|
}
|
|
preformattedText = !preformattedText; //toggle!
|
|
//END CODE BLOCK SECTION
|
|
} else {
|
|
//we are not on a line with pre/codeblock
|
|
|
|
if (!preformattedText) {
|
|
//outside of a codeblock
|
|
mdText = gmiText
|
|
.replace(
|
|
/^=>\s+(\S+(.jpg|.jpeg|.png|.gif|.svg))\s*(.*)/gim,
|
|
"![$3]($1)"
|
|
) //for images, must be before links replacement below or else will be captured and converted
|
|
.replace(/^\* (.*$)/gim, "* $1\n") //hack, would be better to add to ul list
|
|
.replace(/^=>\s+(\S+)\s*(.*)/gim, "[$2]($1)\n") //close, need to remove gmi, add break
|
|
.replace(".gmi", ".md"); //a hack. will break for remote *.gmi url's
|
|
} else {
|
|
//we are inside a pre/codeblock
|
|
|
|
mdText = gmiText;
|
|
}
|
|
}
|
|
|
|
return mdText;
|
|
}
|