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!');
});