const path = require('path');
const ejs = require('ejs');
const FS = require('fs-extra');
const marked = require('marked');
const stylus = require('stylus');
const Prism = require('prismjs');
const loadLanguages = require('prismjs/components/');
const UglifyJS = require("uglify-js");
const colors = require('colors-cli/toxic');
const renderer = new marked.Renderer();
renderer.heading = (text, level) => {
if (/[\u4E00-\u9FA5]/i.test(text)) {
return `${text}`;
} else {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `${text}`;
}
}
marked.setOptions({
renderer: renderer,
pedantic: false,
gfm: true,
tables: true,
breaks: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false,
highlight: (code, lang, callback) => {
if (/(tex)$/.test(lang)) lang = 'latex';
if (/(h)$/.test(lang)) lang = 'c';
if (/(js)$/.test(lang)) lang = 'javascript';
if (/(tsx)$/.test(lang)) lang = 'jsx';
if (/(bat)$/.test(lang)) lang = 'batch';
if (/(py)$/.test(lang)) lang = 'python';
if (/(rb)$/.test(lang)) lang = 'ruby';
if (/(gitconfig|editorconfig|gitmodules)$/.test(lang)) lang = 'ini';
if (/(yml)$/.test(lang)) lang = 'yaml';
if (/(styl)$/.test(lang)) lang = 'stylus';
if (/(stylelintrc|postcssrc)$/.test(lang)) lang = 'json';
if (/(sh|shell|bash|bats|cgi|command|fcgi|ksh|sh.in|tmux|tool|zsh|bash_history|bash_logout|bash_profile|bashrc|cshrc|login|profile|zlogin|zlogout|zprofile|zshenv|zshrc)$/.test(lang)) lang = 'bash';
if (/(ps1|psm1)$/.test(lang)) lang = 'powershell';
if (/^(html|htm|xml|ejs)/.test(lang)) lang = 'html';
lang = lang ? lang : 'bash';
loadLanguages([lang]);
let html = code;
if (Prism.languages[lang]) {
html = Prism.highlight(code, Prism.languages[lang], lang);
html.toString();
html = html.replace(/\$/g, '$')
}
return callback('', html);
}
});
const deployDir = path.resolve(process.cwd(), '.deploy');
const faviconPath = path.resolve(process.cwd(), 'template', 'img', 'favicon.ico');
const rootIndexJSPath = path.resolve(process.cwd(), 'template', 'js', 'index.js');
const dataJsonPath = path.resolve(process.cwd(), 'dist', 'data.json');
const cssPath = path.resolve(deployDir, 'css', 'index.css');
let markdownIndexData = [];
mkdirs(deployDir)
.then(dir => emptyDir(dir))
.then(dir => {
ensureDir(path.resolve(dir, 'img'));
ensureDir(path.resolve(dir, 'js'));
ensureDir(path.resolve(dir, 'css'));
ensureDir(path.resolve(dir, 'c'));
})
.then(() => FS.copySync(faviconPath, path.resolve(deployDir, 'img', 'favicon.ico')))
.then(() => FS.readFileSync(rootIndexJSPath))
.then((data) => {
FS.outputFileSync(path.resolve(deployDir, 'js', 'index.js'), UglifyJS.minify(data.toString()).code)
})
.then(dir => readMarkdownPaths(path.resolve(process.cwd(), 'command')))
.then(dirs => createDataJSON(dirs))
.then(data => {
FS.outputFileSync(dataJsonPath, JSON.stringify(data.json));
FS.outputFileSync(path.resolve(deployDir, 'js', 'dt.js'), `var linux_commands=${JSON.stringify(data.data)}`);
markdownIndexData = data.data;
})
.then(() => createTmpToHTML(
path.resolve(process.cwd(), 'template', 'index.ejs'),
path.resolve(deployDir, 'index.html'),
{
p: '/index.html',
n: 'Linux命令搜索引擎',
d: '最专业的Linux命令大全,内容包含Linux命令手册、详解、学习,值得收藏的Linux命令速查手册。',
command_length: markdownIndexData.length
}
))
.then(() => createTmpToHTML(
path.resolve(process.cwd(), 'template', 'list.ejs'),
path.resolve(deployDir, 'list.html'),
{
p: '/list.html',
n: '搜索',
d: '最专业的Linux命令大全,命令搜索引擎,内容包含Linux命令手册、详解、学习,值得收藏的Linux命令速查手册。',
command_length: markdownIndexData.length
}
))
.then(() => createTmpToHTML(
path.resolve(process.cwd(), 'template', 'hot.ejs'),
path.resolve(deployDir, 'hot.html'),
{
p: '/hot.html',
n: '搜索',
d: '最专业的Linux命令大全,命令搜索引擎,内容包含Linux命令手册、详解、学习,值得收藏的Linux命令速查手册。',
arr: markdownIndexData,
command_length: markdownIndexData.length
}
))
.then(() => {
markdownIndexData.forEach(async (item, idx) => {
item.command_length = markdownIndexData.length;
await createTmpToHTML(
path.resolve(process.cwd(), 'template', 'details.ejs'),
path.resolve(deployDir, 'c', `${item.n}.html`),
item,
path.resolve(process.cwd(), 'command'),
);
})
})
.then(() => {
return createStylToCss(
path.resolve(process.cwd(), 'template', 'styl', 'index.styl'),
path.resolve(deployDir, 'css', 'index.css'),
);
})
.then((css) => FS.outputFileSync(cssPath, css))
.then(() => console.log(` ${'→'.green} ${markdownIndexData.length}`))
.catch((err) => {
if (err && err.message) {
console.log(`\n ERROR :> ${err.message.red_bt}\n`)
}
});
/**
* Create a directory
* @param {String} dir
*/
function mkdirs(dir) {
return new Promise((resolve, reject) => {
FS.ensureDir(dir, err => {
err ? reject(err) : resolve(dir);
})
});
}
/**
* Empty a directory
* @param {String} dir
*/
function emptyDir(dir) {
return new Promise((resolve, reject) => {
FS.emptyDir(dir, err => {
err ? reject(err) : resolve(dir);
})
});
}
/**
* Ensures that the directory exists.
* @param {String} dir
*/
function ensureDir(dir) {
return new Promise((resolve, reject) => {
try {
FS.ensureDirSync(dir);
resolve(dir);
} catch (err) {
reject(err);
}
});
}
/**
* [createStylToCss 生成CSS]
* @param {[type]} stylPath stylus path
* @param {[type]} cssPath css path
*/
function createStylToCss(stylPath) {
return new Promise((resolve, reject) => {
try {
const stylStr = FS.readFileSync(stylPath, 'utf8');
stylus(stylStr.toString())
.set('filename', stylPath)
.set('compress', true)
.render((err, css) => {
if (err) throw err;
resolve(css);
});
} catch (err) {
reject(err);
}
});
}
/**
*
* @param {String} fromPath ejs path
* @param {String} toPath html path
*/
function createTmpToHTML(fromPath, toPath, desJson, mdPath) {
return new Promise((resolve, reject) => {
try {
let relative_path = '';
const current_path = toPath.replace(new RegExp(`${deployDir}`), '');
const tmpStr = FS.readFileSync(fromPath);
let mdPathName = '';
if (mdPath) {
// CSS/JS 引用相对地址
relative_path = '../';
mdPathName = `/command/${desJson.n}.md`;
}
// 生成 HTML
let html = ejs.render(tmpStr.toString(), {
filename: fromPath,
relative_path, // 当前文件相对于根目录的相对路径
md_path: mdPathName || '', // markdown 路径
current_path, // 当前 html 路径
describe: desJson ? desJson : {}, // 当前 md 的描述
}, { filename: fromPath });
if (mdPath) {
const READMESTR = FS.readFileSync(path.resolve(mdPath, `${desJson.n}.md`));
marked(READMESTR.toString(), (err, mdhtml) => {
if (err) return reject(err);
html = html.replace(/{{content}}/, mdhtml);
FS.outputFileSync(toPath, html);
console.log(` ${'→'.green} ${toPath.replace(process.cwd(), '')}`);
resolve(html);
});
} else {
FS.outputFileSync(toPath, html);
console.log(` ${'→'.green} ${toPath.replace(process.cwd(), '')}`);
resolve(html);
}
} catch (err) {
reject(err);
}
});
}
/**
* Ensures that the directory exists.
* @param {String} pathArr
*/
function createDataJSON(pathArr) {
return new Promise((resolve, reject) => {
try {
const commandData = {};
const indexes = [];
pathArr.forEach((mdPath, i) => {
const json = {}
const con = FS.readFileSync(mdPath);
const str = con.toString();
let title = str.match(/[^===]+(?=[===])/g);
title = title[0] ? title[0].replace(/\n/g, '') : title[0];
title = title.replace(/\r/, '')
// 命令名称
json["n"] = title;
// 命令路径
json["p"] = `/${path.basename(mdPath, '.md').replace(/\\/g, '/')}`;
// 命令描述
let des = str.match(/\n==={1,}([\s\S]*?)##/i);
if (!des) {
throw `格式错误: ${mdPath}`;
}
des = des[1] ? des[1].replace(/\n/g, '') : des[1];
des = des.replace(/\r/g, '')
json["d"] = des;
indexes.push(json);
commandData[title] = json;
})
resolve({
json: commandData,
data: indexes
});
} catch (err) {
reject(err);
}
});
}
/**
* 返回 MD 所有路径的 Array
* @param {String} filepath
*/
function readMarkdownPaths(filepath) {
return new Promise((resolve, reject) => {
try {
let pathAll = [];
const files = FS.readdirSync(filepath);
for (let i = 0; i < files.length; i++) {
if (/\.md$/.test(files[i])) {
pathAll.push(path.join(filepath, files[i]));
}
}
resolve(pathAll);
} catch (err) {
reject(err);
}
});
}