diff --git a/prestart.js b/prestart.js index eee84d4..6aa0db5 100644 --- a/prestart.js +++ b/prestart.js @@ -11,13 +11,10 @@ const showdown = require('showdown'), mkdirp = require('mkdirp'), projectInputDir = './project_writeups/', projectOutputDir = './views/partials/md/projects/', - recipeInputDir = './recipes/', - recipeOutputDir = './views/partials/md/recipes/', - recipeListGeneratedOutputDir = './views/partials/generated/', projectClassMap = { h1: 'display-1' //tag type : class to add to all tags of that type (class="display-1" added to all

) }; -const { assert } = require('console'); +var recipeTools = require('./recipe_generator'); function addClassToTag(text, classMap) { @@ -78,185 +75,14 @@ function convertMarkdownInDirWithShowdown(inputDir, outputDir, converter) { }); } -function convertRecipeMarkdown(inputDir, outputDir) { - var md = require('markdown-it')() - .use(require('markdown-it-hashtag')); - - md.renderer.rules.hashtag_open = function (tokens, idx) { - var tagName = tokens[idx].content.toLowerCase(); - return ''; - } - - md.renderer.rules.hashtag_close = function () { return ''; } - - // This is a hardcoded markdown header section number to html file name - // - // Example.md: - // """ - // ... maybe some other header info here -| - exported as filename-title.ejs - // # Delicious Recipe Name -| - // Catch phrase or yield -| - // | - exported as filename-subtitle - // image of the food | - // -| - // ## Ingredients -| - // ... ingredients table, etc | - exported as filename-ingredients.ejs - // -| - // ## Instructions - // """ - // - // NOTE: these titles are HARDCODED in recipe_template.ejs! - const mdSectionHtmlTitles = [ - 'title', - // 'subtitle', - 'ingredients', - 'instructions', - ] - - mkdirp.sync(outputDir); +function convertRecipeMarkdown(inputDir) { fs.readdir(inputDir, (err, files) => { files.forEach(file => { - if (!file.endsWith('.md')) { - return; - } - let fileNameNoExtension = file.slice(0, -3); - console.log('converting: ' + fileNameNoExtension); - fs.readFile(inputDir + file, 'utf8', (err, data) => { - if (err) { - console.error(err); - return; - } - - var ingredientTableRegex = new RegExp(`^(\\|?.*?)\\|(.*?)(\\|.*?\\|.*?)\n`, `gm`); - var ingredientDashCheck = new RegExp("^\-+$"); - ingredientTableMatcher = ingredientTableRegex.exec(data); - while (ingredientTableMatcher != null) { - meas = ingredientTableMatcher[1]; - let unit = ingredientTableMatcher[2].toLowerCase().trim(); - if (unit != "unit" && unit && !ingredientDashCheck.test(unit)) { - meas += unit + " "; - } - data = data.replace(ingredientTableMatcher[0], `${meas}${ingredientTableMatcher[3]}\n`); - ingredientTableMatcher = ingredientTableRegex.exec(data); - } - ingredientTableMatcher = ingredientTableRegex.exec(data); - while (ingredientTableMatcher != null) { - meas = ingredientTableMatcher[1]; - let unit = ingredientTableMatcher[2].toLowerCase().trim(); - if (unit != "unit" && unit && !ingredientDashCheck.test(unit)) { - meas += unit + " "; - } - data = data.replace(ingredientTableMatcher[0], `${meas}${ingredientTableMatcher[3]}\n`); - ingredientTableMatcher = ingredientTableRegex.exec(data); - } - - let tokens = md.parse(data) - - let sections = [] - sections.push([]); // start off the array and put everything before and including the first header in title - let numSections = 0; - for (const token of tokens) { - if (token.type === 'heading_open') { - if (numSections == 0) { - numSections++; - } - else if (numSections < mdSectionHtmlTitles.length) { - numSections++; - sections.push([]); - } - } - sections[sections.length - 1].push(token) - } - - assert(sections.length <= mdSectionHtmlTitles.length); - - // hardcode bootstrap class attribute to add to tag in ingredients - for (let ii = 0; ii < sections[1].length; ii++) { - if (sections[1][ii].type == 'table_open') { - sections[1][ii].attrs = [["class", "table table-striped table-sm table-hover"]]; - break; - } - } - - for (let ii = 0; ii < sections.length; ii++) { - let html = md.renderer.render(sections[ii], md.options); - // hardcode making images in the title section larger - if (ii == 0) { - var regex = new RegExp(``, `g`); - matcher = regex.exec(html); - while (matcher != null && !matcher[1].includes("w-100")) { - var restOfTag = matcher[1]; - html = html.replace(matcher[0], ``); - matcher = regex.exec(html); - } - } - fs.writeFileSync(outputDir + fileNameNoExtension + '-' + mdSectionHtmlTitles[ii] + '.ejs', html, 'utf8'); - } - }); + recipeTools.generateRecipePartials(file); }); }); } -function generateRecipeNavigatorList(recipeSrcDir, generatedOutputDir) { - // generate a list of recipe links. While doing so generate an array - // of unique hashtags found in all recipes - mkdirp.sync(generatedOutputDir); - let recipeListPartialOut = ""; - let allRecipeHashtags = []; - fs.readdir(recipeSrcDir, (err, files) => { - files.sort().forEach(file => { - if (!file.endsWith('.md')) { - return; - } - let fileNameNoExtension = file.slice(0, -3); - - const data = fs.readFileSync(recipeSrcDir + file, { encoding: 'utf8', flag: 'r' }); - - // find all hashtags in the file - var hashtagRegex = new RegExp(`#(\\w+)`, `g`); - hashtagMatcher = hashtagRegex.exec(data); - var recipeTags = []; // hashtags of the current recipe only - while (hashtagMatcher != null) { - var hashtag = hashtagMatcher[1].toLowerCase(); - if (!allRecipeHashtags.includes(hashtag)) { - allRecipeHashtags.push(hashtag); - } - if (!recipeTags.includes(hashtag)) { - recipeTags.push(hashtag); - } - hashtagMatcher = hashtagRegex.exec(data); - } - - let combinedRecipeTags = ""; - if (recipeTags.length > 0) { - combinedRecipeTags = recipeTags.join(","); - } - - // get first recipe title from document - var titleRegex = new RegExp(`#\\s+(.+)\\n`, `g`); - titleMatcher = titleRegex.exec(data); - var recipeTitle = fileNameNoExtension; - if (titleMatcher != null) { - recipeTitle = titleMatcher[1]; - } - - recipeListPartialOut += `${recipeTitle}\n`; - }); - - // writeout the link list partial - fs.writeFileSync(generatedOutputDir + "recipe-links.ejs", recipeListPartialOut, "utf-8"); - - // now generate the hashtag button list partial - // TODO: in the future sort the list by number of hashtag hits (most -> least common) - // instead of alphabetically - let tagListPartialOut = ""; - allRecipeHashtags.sort().forEach(hashtag => { - tagListPartialOut += `\n`; - }); - fs.writeFileSync(generatedOutputDir + "recipe-tags.ejs", tagListPartialOut, "utf-8"); - }); -} - convertMarkdownInDirWithShowdown(projectInputDir, projectOutputDir, projectsConverter); -convertRecipeMarkdown(recipeInputDir, recipeOutputDir); -generateRecipeNavigatorList(recipeInputDir, recipeListGeneratedOutputDir); +convertRecipeMarkdown("./recipes"); +recipeTools.generateRecipeNavPartials(); diff --git a/recipe_generator.js b/recipe_generator.js new file mode 100644 index 0000000..e50f32f --- /dev/null +++ b/recipe_generator.js @@ -0,0 +1,178 @@ +const fs = require('fs'), + mkdirp = require('mkdirp'), + recipeInputDir = './recipes/', + recipeOutputDir = './views/partials/md/recipes/', + recipeListGeneratedOutputDir = './views/partials/generated/'; +const { assert } = require('console'); + +var mdIt = require('markdown-it')() + .use(require('markdown-it-hashtag')); + +mdIt.renderer.rules.hashtag_open = function (tokens, idx) { + var tagName = tokens[idx].content.toLowerCase(); + return ''; +} +mdIt.renderer.rules.hashtag_close = function () { return ''; } + + +// This is a hardcoded markdown header section number to html file name +// +// Example.md: +// """ +// ... maybe some other header info here -| - exported as filename-title.ejs +// # Delicious Recipe Name -| +// Catch phrase or yield -| +// | - exported as filename-subtitle +// image of the food | +// -| +// ## Ingredients -| +// ... ingredients table, etc | - exported as filename-ingredients.ejs +// -| +// ## Instructions +// """ +// +// NOTE: these titles are HARDCODED in recipe_template.ejs! +const mdSectionHtmlTitles = [ + 'title', + // 'subtitle', + 'ingredients', + 'instructions', +] + +/// @param: fileName: the markdown file to open +/// and generate into EJS partials +function generateRecipePartials(fileName) { + mkdirp.sync(recipeOutputDir); + if (!fileName.endsWith('.md')) { + return; + } + let fileNameNoExtension = fileName.slice(0, -3); + console.log('converting: ' + fileNameNoExtension); + fs.readFile(recipeInputDir + fileName, 'utf8', (err, data) => { + if (err) { + console.error(err); + return; + } + + var ingredientTableRegex = new RegExp(`^(\\|?.*?)\\|(.*?)(\\|.*?\\|.*?)\r?\n`, `m`); + var ingredientDashCheck = new RegExp("^\-+$"); + ingredientTableMatcher = ingredientTableRegex.exec(data); + while (ingredientTableMatcher != null) { + meas = ingredientTableMatcher[1]; + let unit = ingredientTableMatcher[2].toLowerCase().trim(); + if (unit != "unit" && unit && !ingredientDashCheck.test(unit)) { + meas += unit + " "; + } + data = data.replace(ingredientTableMatcher[0], `${meas}${ingredientTableMatcher[3]}\n`); + ingredientTableMatcher = ingredientTableRegex.exec(data); + } + + let tokens = mdIt.parse(data) + + let sections = [] + sections.push([]); // start off the array and put everything before and including the first header in title + let numSections = 0; + for (const token of tokens) { + if (token.type === 'heading_open') { + if (numSections == 0) { + numSections++; + } + else if (numSections < mdSectionHtmlTitles.length) { + numSections++; + sections.push([]); + } + } + sections[sections.length - 1].push(token) + } + + assert(sections.length <= mdSectionHtmlTitles.length); + + // hardcode bootstrap class attribute to add to
tag in ingredients + for (let ii = 0; ii < sections[1].length; ii++) { + if (sections[1][ii].type == 'table_open') { + sections[1][ii].attrs = [["class", "table table-striped table-sm table-hover"]]; + break; + } + } + + for (let ii = 0; ii < sections.length; ii++) { + let html = mdIt.renderer.render(sections[ii], mdIt.options); + // hardcode making images in the title section larger + if (ii == 0) { + var regex = new RegExp(``, `g`); + matcher = regex.exec(html); + while (matcher != null && !matcher[1].includes("w-100")) { + var restOfTag = matcher[1]; + html = html.replace(matcher[0], ``); + matcher = regex.exec(html); + } + } + fs.writeFileSync(recipeOutputDir + fileNameNoExtension + '-' + mdSectionHtmlTitles[ii] + '.ejs', html, 'utf8'); + } + }); +} + +function generateRecipeNavPartials() { + // generate a list of recipe links. While doing so generate an array + // of unique hashtags found in all recipes + mkdirp.sync(recipeListGeneratedOutputDir); + let recipeListPartialOut = ""; + let allRecipeHashtags = []; + fs.readdir(recipeInputDir, (err, files) => { + files.sort().forEach(file => { + if (!file.endsWith('.md')) { + return; + } + let fileNameNoExtension = file.slice(0, -3); + + const data = fs.readFileSync(recipeInputDir + file, { encoding: 'utf8', flag: 'r' }); + + // find all hashtags in the file + var hashtagRegex = new RegExp(`#(\\w+)`, `g`); + hashtagMatcher = hashtagRegex.exec(data); + var recipeTags = []; // hashtags of the current recipe only + while (hashtagMatcher != null) { + var hashtag = hashtagMatcher[1].toLowerCase(); + if (!allRecipeHashtags.includes(hashtag)) { + allRecipeHashtags.push(hashtag); + } + if (!recipeTags.includes(hashtag)) { + recipeTags.push(hashtag); + } + hashtagMatcher = hashtagRegex.exec(data); + } + + let combinedRecipeTags = ""; + if (recipeTags.length > 0) { + combinedRecipeTags = recipeTags.join(","); + } + + // get first recipe title from document + var titleRegex = new RegExp(`#\\s+(.+)\\n`, `g`); + titleMatcher = titleRegex.exec(data); + var recipeTitle = fileNameNoExtension; + if (titleMatcher != null) { + recipeTitle = titleMatcher[1]; + } + + recipeListPartialOut += `${recipeTitle}\n`; + }); + + // writeout the link list partial + fs.writeFileSync(recipeListGeneratedOutputDir + "recipe-links.ejs", recipeListPartialOut, "utf-8"); + + // now generate the hashtag button list partial + // TODO: in the future sort the list by number of hashtag hits (most -> least common) + // instead of alphabetically + let tagListPartialOut = ""; + allRecipeHashtags.sort().forEach(hashtag => { + tagListPartialOut += `\n`; + }); + fs.writeFileSync(recipeListGeneratedOutputDir + "recipe-tags.ejs", tagListPartialOut, "utf-8"); + }); +} + +module.exports = { + generateRecipePartials: generateRecipePartials, + generateRecipeNavPartials: generateRecipeNavPartials, +} diff --git a/server.js b/server.js index f906d35..5915275 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,6 @@ const PORT = 8090; var express = require('express'); +var recipeTools = require('./recipe_generator'); // formidable helps get JSON POST request content const formidable = require('express-formidable'); var compile = require('es6-template-strings/compile'), @@ -22,6 +23,7 @@ app.set('view engine', 'ejs'); // add folder for static content: app.use(express.static(__dirname + '/assets')); +// Get method for all pages app.get(/\/.*/, function (req, res) { let pathname = 'pages' + req.path; let page = pathname.substr(pathname.lastIndexOf('/') + 1); @@ -63,7 +65,7 @@ function toCamelCase(str) { app.post("/new_recipe", function (req, res) { let dataIn = req.fields; - filename = recipeMarkdownDir + toCamelCase(dataIn.recipe_name_input) + ".md"; + filename = toCamelCase(dataIn.recipe_name_input) + ".md"; if (fs.existsSync(recipeMarkdownDir + filename)) { res.status(409).send(`Already have a recipe with name "${dataIn.recipe_name_input}"`); return; @@ -79,7 +81,10 @@ app.post("/new_recipe", function (req, res) { dataIn.tags = formatted_tags formattedMarkdownRecipe = resolveToString(compiledRecipeTemplate, dataIn); - fs.writeFileSync(filename, formattedMarkdownRecipe, 'utf-8'); + fs.writeFileSync(recipeMarkdownDir + filename, formattedMarkdownRecipe, 'utf-8'); + + recipeTools.generateRecipePartials(filename); + recipeTools.generateRecipeNavPartials() res.send('Successfully added!'); });