/*
This is the web builder script that generates the README file.
Run using `npm run webber`.
*/
// Load modules
const fs = require('fs-extra'),
path = require('path'),
chalk = require('chalk'),
md = require('markdown-it')(),
minify = require('html-minifier').minify;
const util = require('./util');
var Prism = require('prismjs');
const unescapeHTML = str =>
str.replace(
/&|<|>|'|"/g,
tag =>
({
'&': '&',
'<': '<',
'>': '>',
''': "'",
'"': '"'
}[tag] || tag)
);
if(util.isTravisCI() && /^Travis build: \d+/g.test(process.env['TRAVIS_COMMIT_MESSAGE'])) {
console.log(`${chalk.green('NOBUILD')} index build terminated, parent commit is a Travis build!`);
process.exit(0);
}
// Compile the mini.css framework and custom CSS styles, using `node-sass`.
const sass = require('node-sass');
sass.render(
{
file: path.join('docs', 'mini', 'flavor.scss'),
outFile: path.join('docs', 'mini.css'),
outputStyle: 'compressed'
},
function(err, result) {
if (!err) {
fs.writeFile(path.join('docs', 'mini.css'), result.css, function(err2) {
if (!err2) console.log(`${chalk.green('SUCCESS!')} mini.css file generated!`);
else console.log(`${chalk.red('ERROR!')} During mini.css file generation: ${err}`);
});
} else {
console.log(`${chalk.red('ERROR!')} During mini.css file generation: ${err}`);
}
}
);
// Set variables for paths
const snippetsPath = './snippets',
staticPartsPath = './static-parts',
docsPath = './docs';
// Set variables for script
let snippets = {},
beginnerSnippetNames = ['everyNth', 'filterNonUnique', 'last', 'maxN', 'minN', 'nthElement', 'sample', 'similarity', 'tail', 'currentURL', 'hasClass', 'getMeridiemSuffixOfInteger', 'factorial', 'fibonacci', 'gcd', 'isDivisible', 'isEven', 'isPrime', 'lcm', 'randomIntegerInRange', 'sum', 'reverseString', 'truncateString'],
startPart = '',
endPart = '',
output = '',
beginnerStartPart = '',
beginnerEndPart = '',
beginnerOutput = '',
pagesOutput = [];
tagDbData = {};
// Start the timer of the script
console.time('Webber');
// Synchronously read all snippets and sort them as necessary (case-insensitive)
snippets = util.readSnippets(snippetsPath);
// Load static parts for the index.html file
try {
startPart = fs.readFileSync(path.join(staticPartsPath, 'page-start.html'), 'utf8');
endPart = fs.readFileSync(path.join(staticPartsPath, 'page-end.html'), 'utf8');
beginnerStartPart = fs.readFileSync(path.join(staticPartsPath, 'beginner-page-start.html'), 'utf8');
beginnerEndPart = fs.readFileSync(path.join(staticPartsPath, 'beginner-page-end.html'), 'utf8');
} catch (err) {
// Handle errors (hopefully not!)
console.log(`${chalk.red('ERROR!')} During static part loading: ${err}`);
process.exit(1);
}
// Load tag data from the database
tagDbData = util.readTags();
// Create the output for the index.html file
try {
// Add the start static part
output += `${startPart + '\n'}`;
// Loop over tags and snippets to create the table of contents
for (let tag of [...new Set(Object.entries(tagDbData).map(t => t[1][0]))]
.filter(v => v)
.sort((a, b) => util.capitalize(a, true) === 'Uncategorized' ? 1 : util.capitalize(b, true) === 'Uncategorized' ? -1 : a.localeCompare(b))) {
output += `
` +
md
.render(`${util.capitalize(tag, true)}\n`)
.replace(/
/g, '')
.replace(/<\/p>/g, '') +
``;
for (let taggedSnippet of Object.entries(tagDbData).filter(v => v[1][0] === tag))
output += md
.render(`[${taggedSnippet[0]}](./${tag}.html#${taggedSnippet[0].toLowerCase()})\n`)
.replace(/
/g, '')
.replace(/<\/p>/g, '')
.replace(/`;
output += ` `;
// Loop over tags and snippets to create the list of snippets
for (let tag of [...new Set(Object.entries(tagDbData).map(t => t[1][0]))]
.filter(v => v)
.sort((a, b) => util.capitalize(a, true) === 'Uncategorized' ? 1 : util.capitalize(b, true) === 'Uncategorized' ? -1 : a.localeCompare(b))) {
let localOutput = output.replace(/\$tag/g, util.capitalize(tag)).replace(new RegExp(`./${tag}.html#`,'g'),'#');
localOutput += md
.render(`## ${util.capitalize(tag, true)}\n`)
.replace(//g, '');
for (let taggedSnippet of Object.entries(tagDbData).filter(v => v[1][0] === tag))
localOutput +=
'' +
md
.render(`\n${snippets[taggedSnippet[0] + '.md']}`)
.replace(/
/g, `${taggedSnippet[1].includes('advanced')?'advanced':''}
`)
.replace(/<\/h3>/g, '
')
.replace(/
([^\0]*?)<\/code><\/pre>/gm, (match, p1) => `${Prism.highlight(unescapeHTML(p1), Prism.languages.javascript)}`)
.replace(/<\/pre>\s+📋 Copy to clipboard' +
'
';
// Add the ending static part
localOutput += `\n${endPart + '\n'}`;
localOutput = util.optimizeNodes(localOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
// Optimize operator nodes
localOutput = util.optimizeNodes(localOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
// Optimize keyword nodes
localOutput = util.optimizeNodes(localOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
pagesOutput.push({'tag': tag,'content': localOutput});
}
// Minify output
pagesOutput.forEach(page => {
page.content = minify(page.content, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: false,
minifyCSS: true,
minifyJS: true,
keepClosingSlash: true,
processConditionalComments: true,
removeAttributeQuotes: false,
removeComments: true,
removeEmptyAttributes: false,
removeOptionalTags: false,
removeScriptTypeAttributes: false,
removeStyleLinkTypeAttributes: false,
trimCustomFragments: true
});
fs.writeFileSync(path.join(docsPath, page.tag+'.html'), page.content);
console.log(`${chalk.green('SUCCESS!')} ${page.tag}.html file generated!`);
})
// Write to the index.html file
//fs.writeFileSync(path.join(docsPath, 'index.html'), output);
} catch (err) {
// Handle errors (hopefully not!)
console.log(`${chalk.red('ERROR!')} During category page generation: ${err}`);
process.exit(1);
}
// Create the output for the beginner.html file
try {
// Add the static part
beginnerOutput += `${beginnerStartPart + '\n'}`;
// Filter begginer snippets
const filteredBeginnerSnippets = Object.keys(snippets)
.filter(key => beginnerSnippetNames.map(name => name+'.md').includes(key))
.reduce((obj, key) => {
obj[key] = snippets[key];
return obj;
}, {});
for (let snippet of Object.entries(filteredBeginnerSnippets))
beginnerOutput +=
'' +
'
' +
'
' +
md
.render(`\n${snippets[snippet[0]]}`)
.replace(/
/g, `${snippet[1].includes('advanced')?'advanced':''}
`)
.replace(/<\/h3>/g, '
')
.replace(/
([^\0]*?)<\/code><\/pre>/gm, (match, p1) => `${Prism.highlight(unescapeHTML(p1), Prism.languages.javascript)}`)
.replace(/<\/pre>\s+📋 Copy to clipboard' +
'
';
beginnerOutput = util.optimizeNodes(beginnerOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
// Optimize operator nodes
beginnerOutput = util.optimizeNodes(beginnerOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
// Optimize keyword nodes
beginnerOutput = util.optimizeNodes(beginnerOutput, /([^\0<]*?)<\/span>([\n\r\s]*)([^\0]*?)<\/span>/gm, (match, p1, p2, p3) => `${p1}${p2}${p3}`);
beginnerOutput += `${beginnerEndPart}`;
// Generate 'beginner.html' file
fs.writeFileSync(path.join(docsPath, 'beginner.html'), beginnerOutput);
console.log(`${chalk.green('SUCCESS!')} beginner.html file generated!`);
} catch (err) {
console.log(`${chalk.red('ERROR!')} During beginner.html generation: ${err}`);
process.exit(1);
}
// Log the time taken
console.timeEnd('Webber');