const { watch, series, src, dest } = require('gulp'); var sass = require('gulp-sass')(require('sass')); var del = require('del'); var fs = require('fs'); var frontMatter = require('gulp-front-matter'); var toc = require('gulp-markdown-toc'); var markdown = require('gulp-markdown'); var wrap = require('gulp-wrap'); var swig = require('gulp-swig'); var data = require('gulp-data'); var browserSync = require('browser-sync').create(); const { DateTime } = require("luxon"); var yaml = require('js-yaml'); const nunjucks = require('nunjucks'); var tutorials = {}; var links = {}; var link_map = new Map(); var link_title_map = new Map(); var authorsMap = new Map(); var authorsArray = yaml.load(fs.readFileSync('app/data/authors.yaml', 'utf8')) for (const author of authorsArray) { authorsMap[getAuthorID(author.name)] = author; authorsMap[getAuthorID(author.name)].tutorials = []; } var javadoc = require('./app/data/javadoc.json') var javafxdoc = require('./app/data/javafxdoc.json') function getAuthorID(name) { return name.replace(/\s/g, '') } function copy_assets() { return src("app/assets/**/*.*") .pipe(dest("site/assets")); } function sassify() { return src("app/scss/*.scss") .pipe(sass()) .pipe(dest("site/assets/css")); } function pre_process_pages() { tutorials = { "tutorials": {} } links = { "links": {} } return src('app/pages/**/*.md', { silent: false, debug: false }) .pipe(frontMatter({ property: 'fm', remove: true })) .pipe(data(function (file) { if (file.fm.title) file.fm.title_html = markdown.marked.parseInline(file.fm.title) if (file.fm.description) file.fm.description_html = markdown.marked(file.fm.description) })) .pipe(data(function (file) { const type = file.fm.type; if (is_tutorial(file)) { const category = file.fm.category const category_order = file.fm.category_order const title = file.fm.title const title_html = file.fm.title_html const description = file.fm.description const description_html = file.fm.description_html const group = file.fm.group const id = file.fm.id if (!file.fm.slug) { console.log("Tutorial page " + id + " doesn't have a slug"); } const path = generate_url_structure(file).newrelative.replace(".md", ".html") const tutorial_json = { title, title_html, path, description, description_html, group, category_order } if (file.fm.id) { link_map.set(file.fm.id, path); } link_title_map.set(path, title); elements = path.split("/").filter(element => element.length > 0); if (path.endsWith("/")) { elements[elements.length - 1] += "/"; } links = []; for (i = 0; i < elements.length - 1; i++) { link = elements.slice(0, i + 1).join("/") + "/"; links.push(link); } link_titles = []; for (link of links) { link_title = link_title_map.get(link); if (link_title) { link_json = { link, link_title }; link_titles.push(link_json); } } if (type == "tutorial") { if (tutorials["tutorials"][category]) { tutorials["tutorials"][category].push(tutorial_json); } else { tutorials["tutorials"][category] = [tutorial_json]; } } if (group) { if (!tutorials["tutorials"][group]) { tutorials["tutorials"][group] = []; } if (type == "tutorial-group") { tutorials["tutorials"][group].push(tutorial_json); } else if (type == "tutorial") { tutorials["tutorials"][group]["parent_link"] = path; tutorials["tutorials"][group]["parent_title"] = file.fm.title; tutorials["tutorials"][group]["parent_title_html"] = file.fm.title_html; } tutorials["tutorials"][group][id] = []; tutorials["tutorials"][group][id]["breadcrumb"] = link_titles; } } if (file.fm.other_links) { const group = file.fm.group if (!tutorials["tutorials"][group]["doc_links"]) { tutorials["tutorials"][group]["doc_links"] = []; } if (!tutorials["tutorials"][group]["related_pages"]) { tutorials["tutorials"][group]["related_pages"] = []; } for (let link of file.fm.other_links) { let link_id = link.match(/doc:([\w|\.|-]+)/); if (link_id != null && javadoc[link_id[1]]) { let processedLink = processDocLink(javadoc[link_id[1]]["link"]); let text = javadoc[link_id[1]]["text"] tutorials["tutorials"][group]["doc_links"].push({ "path": processedLink, "title_html": text }) } else if (link_id != null && javafxdoc[link_id[1]]) { console.log(javafxdoc[link_id[1]]); let processedLink = processDocLink(javafxdoc[link_id[1]]["link"]); let text = javafxdoc[link_id[1]]["text"] tutorials["tutorials"][group]["doc_links"].push({ "path": processedLink, "title_html": text }) } let page_id = link.match(/related_page:([\w|\.|-]+)/); if (page_id != null && link_map.get(page_id[1])) { let linkPath = link_map.get(page_id[1]) let title = link_title_map.get(linkPath) tutorials["tutorials"][group]["related_pages"].push({ "path": linkPath, "title_html": title }) } } } })) .pipe(data(function (file) { if (file.fm.author) { if (file.fm.author.length > 1) { console.log("Adding tutorial " + file.fm.id + " to multiple authors: " + file.fm.author); for (const author of file.fm.author) { authorsMap[author].tutorials.push(file.fm.id); } } else { console.log("Adding tutorial " + file.fm.id + " to single author: " + file.fm.author); authorsMap[file.fm.author].tutorials.push(file.fm.id); } } })) } function pages() { const renderer = { link(href, title, text) { let processedHref = href; if (href.includes("id:")) { let page_id = href.match(/id:([\w|\.|-]+)/); if (page_id != null) { let anchor = href.match(/#[\w|-]+/); var link = ''; let page_link = link_map.get(page_id[1]); processedHref = "/" + page_link; if (processedHref.includes("undefined")) { console.log("Page " + page_id[1] + " resolved to undefined"); } if (anchor) { processedHref = processedHref + anchor; } } else { console.log("Null page id link for href " + href); } } if (href.includes("javadoc:")) { let javadoc_id = href.match(/javadoc:([\w\.\-(),]+)/) if (javadoc_id != null) { processedHref = processDocLink(javadoc["javadoc_root"] + javadoc[javadoc_id[1]]); if (processedHref.includes("undefined")) { console.log("Javadoc " + javadoc_id[1] + " resolved to undefined"); } return `${text}`; } } if (href.includes("javafxdoc:")) { let javafxdoc_id = href.match(/javafxdoc:([\w\.\-(),]+)/) if (javafxdoc_id != null) { processedHref = processDocLink(javafxdoc["javafxdoc_root"] + javafxdoc[javafxdoc_id[1]]); if (processedHref.includes("undefined")) { console.log("Javadoc " + javafxdoc_id[1] + " resolved to undefined"); } return `${text}`; } } if (href.includes("doc:")) { let doc_id = href.match(/doc:([\w\.\-(),]+)/); if (doc_id != null) { if (javadoc[doc_id[1]]["text"] != null) { processedHref = processDocLink("" + javadoc[doc_id[1]]["link"]) } else { processedHref = processDocLink("" + javadoc[doc_id[1]]) } if (processedHref.includes("undefined")) { console.log("Doc " + doc_id[1] + " resolved to undefined"); } return `${text}`; } } if (title) { link = `${text}`; } else { link = `${text}`; } return link; }, heading(text, level, raw, slugger) { const richHeader = parseRichHeader(text) text = richHeader.text if (this.options.headerIds) { const slug = richHeader.slug ?? this.options.headerPrefix + slugger.slug(raw) return '' + text + '\n'; } return '' + text + '\n'; } }; markdown.marked.use({ headerIds: true, headerPrefix: '' }); markdown.marked.use({ renderer }); var mystream = src('app/pages/**/*.md', { silent: false, debug: false }) .pipe(frontMatter({ property: 'fm', remove: true })) .pipe(data(function (file) { if (file.fm.title) file.fm.title_html = markdown.marked.parseInline(file.fm.title) if (file.fm.description) file.fm.description_html = markdown.marked(file.fm.description) })) .pipe(data(process_last_update)) .pipe(data(function (file) { generate_url_structure(file); })) .pipe(data(function () { return tutorials })) .pipe(toc()) .pipe(data(function (file) { if (file.fm.toc) { const section_name_regex = /^[\s\w.,|]+[^{|^\s{]+/; const anchor_regex = /{([^{|^}]+)}/; file.fm.toc = file.fm.toc .map(entry => ({ sectionName: entry.match(section_name_regex)[0], anchorsArray: entry.match(anchor_regex) })) .map(({ sectionName, anchorsArray }, index) => ({ text: sectionName, html: sectionName, slug: (anchorsArray == null || anchorsArray.length == 0) ? `anchor_${index + 1}` : anchorsArray[1] })) } else { file.fm.toc = file.toc.json .filter(entry => entry.lvl === 2) .map(entry => { const richHeader = parseRichHeader(entry.content) return { text: richHeader.text, html: richHeader.html, slug: richHeader.slug ?? entry.slug } }) } if (file.fm.more_learning) { file.fm.toc.push({ text: "More Learning", html: "More Learning", slug: "more-learning" }); } })) .pipe(data(function () { let data = {} data.env = process.env; data.authorsMap = authorsMap; data.authorsArray = authorsArray; data.linkMap = link_map; data.linkTitleMap = link_title_map; return data; })) .pipe(swig({ defaults: { cache: false } })) .pipe(markdown()) .pipe(wrap(function (data) { return fs.readFileSync('app/templates/pages/' + data.file.fm.layout).toString() }, null, { engine: 'nunjucks' })) .pipe(data(function (file) { if (file.relative == "author/index.html") { var template = fs.readFileSync('app/templates/pages/authors/author.html').toString(); for (const author of authorsArray) { let nameId = getAuthorID(author.name); let authorPage = nunjucks.renderString(template, { author: author, file: file }); let newFilePath = file.cwd + "/site/author/" + nameId + "/index.html"; writeFile(newFilePath, authorPage, err => { if (err) { console.error(err); } }); } } })) .pipe(dest('site')) .pipe(browserSync.stream()); return mystream; } var getDirName = require('path').dirname; function writeFile(path, contents, cb) { fs.mkdir(getDirName(path), { recursive: true }, function (err) { if (err) return cb(err); fs.writeFile(path, contents, cb); }); } function processDocLink(link) { return link.replace("@@CURRENT_RELEASE@@", javadoc[`current_release`]); } function is_tutorial(file) { return file.fm.type == "tutorial" || file.fm.type == "tutorial-group"; } function serve(done) { browserSync.init({ server: { baseDir: './site/' } }); watch("app/**/*.md", {}, series(build)); watch("app/**/*.html", {}, series(build)); watch("app/scss/*.scss", {}, series(build)); watch("site/**/", {}, browserSync.reload()); done(); } function cleanup() { return del([ 'site' ]); } function process_last_update(file) { const update = file.fm.last_update ?? new Date("2021-09-14") const review = file.fm.last_review const showReview = review && update < review const type = showReview ? "review" : "update" const date = DateTime .fromJSDate(showReview ? review : update) .setLocale('en-us') .toLocaleString(DateTime.DATE_FULL) file.fm.last_update = { type, date } } function parseRichHeader(header) { const parsed = header.match(/^(?.*[^\}]) ?(?\{#(?.+)\})?$/) if (parsed === null) throw new Error(`Illegal header "${header}" - check gulpfile.js for details.`) return { ...parsed.groups, html: markdown.marked.parseInline(parsed.groups.text) } } function generate_url_structure(file) { if (file.fm.slug) { file.basename = "index.html"; file.dirname = file.cwd + "/app/pages/" + file.fm.slug; file.newrelative = file.fm.slug + "/"; } else { const reg = /^\d+_/g; file.basename = file.basename.replace(reg, ""); const olddir = file.dirname.split('\\').join('/'); const newdir = olddir.split('/').map(element => element.replace(reg, "")).join('/'); file.dirname = newdir; const oldrelative = file.relative.split('\\').join('/') const newrelative = oldrelative.split('/').map(element => element.replace(reg, "")).join('/'); file.newrelative = newrelative } return file; } var build = series(cleanup, copy_assets, sassify, pre_process_pages, pages); var deploy = series(cleanup, build); // local development only (by typing `gulp`) exports.default = series(build, serve); // Export for use at the command line exports.serve = serve; exports.pages = pages; exports.clean = cleanup; exports.build = build; exports.deploy = deploy;