From 3a57f3e3a7d7e55b9661a108104923dc4f0284d5 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Sun, 30 Jan 2022 21:56:05 +0200 Subject: [PATCH 01/13] add .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist From b939b16de92d6ae98ba3728ce2cf8686ca74bd18 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Sun, 30 Jan 2022 21:56:36 +0200 Subject: [PATCH 02/13] update: dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b8d71d4..6a1b7e1 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "devDependencies": { "gulp": "^4.0.2", "gulp-data": "^1.3.1", - "gulp-load-plugins": "^2.0.3", - "gulp-pug": "^4.0.1" + "gulp-load-plugins": "^2.0.7", + "gulp-pug": "^5.0.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From eaaff5f43929851b826992500cecf48b3195ac0d Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Sun, 30 Jan 2022 21:58:29 +0200 Subject: [PATCH 03/13] update: README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index aced19c..f7ea454 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,9 @@ block content Run command to build html page with data ```bash $ gulp html + +# Or +$ npx gulp html ``` ### TODO From f9c45c50d48e07cf1edcf808d38ec43a05def431 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Sun, 30 Jan 2022 22:11:18 +0200 Subject: [PATCH 04/13] update: indentations --- gulpfile.js | 22 +-- lib/gulp-json-loader.js | 202 ++++++++++++------------ src/data/imports/catalog/catalog_1.json | 60 +++---- src/data/imports/catalog/catalog_2.json | 60 +++---- src/data/imports/genres.json | 48 +++--- src/data/imports/menu.json | 60 +++---- src/data/pages/about.json | 16 +- src/data/pages/menu.json | 16 +- src/data/pages/partials/catalog.json | 18 +-- src/html/about.pug | 14 +- src/html/menu.pug | 22 +-- src/html/partials/catalog.pug | 38 ++--- src/html/without_data.pug | 16 +- 13 files changed, 296 insertions(+), 296 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 55a0bdf..3d74f2a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,20 +3,20 @@ const gulp = require('gulp'); const plugins = require('gulp-load-plugins')(); const jsonLoader = require('./lib/gulp-json-loader')({ - // sourcePath: __dirname, - pathHtml: 'src/html', - pathData: 'src/data', - report: true, + // sourcePath: __dirname, + pathHtml: 'src/html', + pathData: 'src/data', + report: true, }); function html() { - return gulp.src('src/html/**/*.pug') - .pipe(plugins.data(jsonLoader)) - .pipe(plugins.pug({ - pretty: true - })) - .pipe(gulp.dest('dist')) - ; + return gulp.src('src/html/**/*.pug') + .pipe(plugins.data(jsonLoader)) + .pipe(plugins.pug({ + pretty: true + })) + .pipe(gulp.dest('dist')) + ; } exports.html = html; diff --git a/lib/gulp-json-loader.js b/lib/gulp-json-loader.js index f2a10e8..7a38a2e 100644 --- a/lib/gulp-json-loader.js +++ b/lib/gulp-json-loader.js @@ -7,92 +7,92 @@ const asyncReadFile = Fs.promises.readFile; const BR = (process.platform === 'win32' ? '\r\n' : '\n'); async function loadJsonData(file) { - const { report, pathHtml, pathData } = this; - const pathJson = file.path.replace(pathHtml, `${pathData}/pages`).slice(0,-3) + 'json'; - const filename = Path.basename(file.path, '.pug'); - const pocket = { filename }; - - return Fs.promises.access(pathJson, Fs.constants.R_OK) - .then(async () => { - let jsonData = {}; - - try { - jsonData = await asyncReadFile(pathJson, 'utf8'); - jsonData = JSON.parse(jsonData); - } catch(err) { - process.stderr.write(err + BR); - return pocket; - } - - report && reportAction(pathJson, filename); - - if (jsonData.hasOwnProperty('imports')) { - if (!jsonData.hasOwnProperty('data')) { - jsonData.data = {}; - } - - Object.defineProperty(jsonData.data, 'imports', { - value: await loadImports.call(this, jsonData.imports), - enumerable: true, - // configurable: true - }); - delete jsonData.imports; - } - - return Object.assign(pocket, jsonData); - }) - .catch(() => pocket) - ; + const { report, pathHtml, pathData } = this; + const pathJson = file.path.replace(pathHtml, `${pathData}/pages`).slice(0, -3) + 'json'; + const filename = Path.basename(file.path, '.pug'); + const pocket = { filename }; + + return Fs.promises.access(pathJson, Fs.constants.R_OK) + .then(async () => { + let jsonData = {}; + + try { + jsonData = await asyncReadFile(pathJson, 'utf8'); + jsonData = JSON.parse(jsonData); + } catch (err) { + process.stderr.write(err + BR); + return pocket; + } + + report && reportAction(pathJson, filename); + + if (jsonData.hasOwnProperty('imports')) { + if (!jsonData.hasOwnProperty('data')) { + jsonData.data = {}; + } + + Object.defineProperty(jsonData.data, 'imports', { + value: await loadImports.call(this, jsonData.imports), + enumerable: true, + // configurable: true + }); + delete jsonData.imports; + } + + return Object.assign(pocket, jsonData); + }) + .catch(() => pocket) + ; } async function loadImports(imports) { - const { report, pathHtml, pathData } = this; - const data = {}; + const { report, pathHtml, pathData } = this; + const data = {}; - if (Array.isArray(imports) && imports.length) { - const pathFile = imports.shift(); - const pathJson = `${pathData}/imports/${pathFile}.json`; - const filename = Path.basename(pathJson, '.json'); + if (Array.isArray(imports) && imports.length) { + const pathFile = imports.shift(); + const pathJson = `${pathData}/imports/${pathFile}.json`; + const filename = Path.basename(pathJson, '.json'); - try { + try { - const jsonData = await asyncReadFile(pathJson, 'utf8'); - const parsedData = JSON.parse(jsonData); + const jsonData = await asyncReadFile(pathJson, 'utf8'); + const parsedData = JSON.parse(jsonData); - Object.defineProperty(data, filename, { - value: parsedData, - enumerable: true - }); + Object.defineProperty(data, filename, { + value: parsedData, + enumerable: true + }); - } catch(err) { + } catch (err) { - process.stderr.write(err + BR); + process.stderr.write(err + BR); - } + } - report && reportAction(pathJson, filename); + report && reportAction(pathJson, filename); - if (imports.length) { - // TODO: Find another solution with asynchronous queue. - // - // I know that recursion is a bad practice with its excessive - // consumption of resources but unfortunately, I didn't find - // any other solution by now - const jsonData = await loadImports.call(this, imports); - Object.assign(data, jsonData); - } + if (imports.length) { + // TODO: Find another solution with asynchronous queue. + // + // I know that recursion is a bad practice with its excessive + // consumption of resources but unfortunately, I didn't find + // any other solution by now + const jsonData = await loadImports.call(this, imports); + Object.assign(data, jsonData); } + } - return data; + return data; } function reportAction(pathJson, filename) { - const currTime = brush('grey', new Date().toLocaleTimeString()); - const relativePath = pathJson - .replace(process.cwd(), '.') - .replace(filename, brush('cyan', filename)) - ; - process.stdout.write(Format('[%s] Loaded %s%s', currTime, relativePath, BR)); + const currTime = brush('grey', new Date().toLocaleTimeString()); + const relativePath = pathJson + .replace(process.cwd(), '.') + .replace(filename, brush('cyan', filename)) + ; + process.stdout.write(Format('[%s] Loaded %s%s', currTime, relativePath, BR)); } /** @@ -104,52 +104,52 @@ function reportAction(pathJson, filename) { * @param {string} str Text data */ function brush(colorName, str) { - const colors = { - cyan: 6, - grey: 8 - } - const color = colors[colorName]; - return `\x1b[38;5;${color}m${str}\x1b[0m`; + const colors = { + cyan: 6, + grey: 8 + } + const color = colors[colorName]; + return `\x1b[38;5;${color}m${str}\x1b[0m`; } -function getAbsolutePath(sourcePath, postfix='') { - let absolutePath = sourcePath; +function getAbsolutePath(sourcePath, postfix = '') { + let absolutePath = sourcePath; - if (absolutePath.substr(0, 2) === './') { - absolutePath = absolutePath.slice(2); - } + if (absolutePath.substr(0, 2) === './') { + absolutePath = absolutePath.slice(2); + } - if (absolutePath[0] !== '/') { - absolutePath = process.cwd() + '/' + absolutePath; - } + if (absolutePath[0] !== '/') { + absolutePath = process.cwd() + '/' + absolutePath; + } - return absolutePath + postfix; + return absolutePath + postfix; } function manager(options) { - let opts = {}; + let opts = {}; - if (!(typeof(options) == 'object' && options !== null)) { - throw new Error('options is required'); - } + if (!(typeof (options) == 'object' && options !== null)) { + throw new Error('options is required'); + } - opts.report = options.hasOwnProperty('report') ? options.report : false; + opts.report = options.hasOwnProperty('report') ? options.report : false; - if (options.hasOwnProperty('pathHtml') && options.hasOwnProperty('pathData')) { - opts.pathHtml = getAbsolutePath(options.pathHtml); - opts.pathData = getAbsolutePath(options.pathData); - } + if (options.hasOwnProperty('pathHtml') && options.hasOwnProperty('pathData')) { + opts.pathHtml = getAbsolutePath(options.pathHtml); + opts.pathData = getAbsolutePath(options.pathData); + } - else if (options.hasOwnProperty('sourcePath')) { - opts.pathHtml = getAbsolutePath(options.sourcePath, '/src/html'); - opts.pathData = getAbsolutePath(options.sourcePath, '/src/data'); - } + else if (options.hasOwnProperty('sourcePath')) { + opts.pathHtml = getAbsolutePath(options.sourcePath, '/src/html'); + opts.pathData = getAbsolutePath(options.sourcePath, '/src/data'); + } - else { - throw new Error('required "sourcePath" parameter or pare of "pathHtml" and "pathData" parameters'); - } + else { + throw new Error('required "sourcePath" parameter or pare of "pathHtml" and "pathData" parameters'); + } - return loadJsonData.bind(opts); + return loadJsonData.bind(opts); } module.exports = manager; diff --git a/src/data/imports/catalog/catalog_1.json b/src/data/imports/catalog/catalog_1.json index 859bf18..79dbdf3 100644 --- a/src/data/imports/catalog/catalog_1.json +++ b/src/data/imports/catalog/catalog_1.json @@ -1,32 +1,32 @@ [ - { - "img": "book-01.png", - "title": "Duel", - "info": "Prose, 1986 р.", - "href": "#" - }, - { - "img": "book-02.png", - "title": "Vita Nova", - "info": "Prose, 1957 р.", - "href": "#" - }, - { - "img": "book-03.jpg", - "title": "The Ukrainian Folk Dance", - "info": "Folklore, 1980 р.", - "href": "#" - }, - { - "img": "book-04.jpg", - "title": "The Moscow Games of 1980", - "info": "Politology, 1981 р.", - "href": "#" - }, - { - "img": "book-05.jpg", - "title": "Young Ukraine", - "info": "Periodicals, 1965 р.", - "href": "#" - } + { + "img": "book-01.png", + "title": "Duel", + "info": "Prose, 1986 р.", + "href": "#" + }, + { + "img": "book-02.png", + "title": "Vita Nova", + "info": "Prose, 1957 р.", + "href": "#" + }, + { + "img": "book-03.jpg", + "title": "The Ukrainian Folk Dance", + "info": "Folklore, 1980 р.", + "href": "#" + }, + { + "img": "book-04.jpg", + "title": "The Moscow Games of 1980", + "info": "Politology, 1981 р.", + "href": "#" + }, + { + "img": "book-05.jpg", + "title": "Young Ukraine", + "info": "Periodicals, 1965 р.", + "href": "#" + } ] \ No newline at end of file diff --git a/src/data/imports/catalog/catalog_2.json b/src/data/imports/catalog/catalog_2.json index edda093..71767d3 100644 --- a/src/data/imports/catalog/catalog_2.json +++ b/src/data/imports/catalog/catalog_2.json @@ -1,32 +1,32 @@ [ - { - "img": "book-06.jpg", - "title": "Theophanes daughter", - "info": "Prose, 1946 р.", - "href": "#" - }, - { - "img": "book-07.jpg", - "title": "Love story", - "info": "Prose, 1947 р.", - "href": "#" - }, - { - "img": "book-08.jpg", - "title": "Old world nobility", - "info": "History, 1999 р.", - "href": "#" - }, - { - "img": "book-09.png", - "title": "Luzes na agua", - "info": "Prose, 1997 р.", - "href": "#" - }, - { - "img": "book-10.png", - "title": "In the arms of Melpomene", - "info": "Prose, 1954 р.", - "href": "#" - } + { + "img": "book-06.jpg", + "title": "Theophanes daughter", + "info": "Prose, 1946 р.", + "href": "#" + }, + { + "img": "book-07.jpg", + "title": "Love story", + "info": "Prose, 1947 р.", + "href": "#" + }, + { + "img": "book-08.jpg", + "title": "Old world nobility", + "info": "History, 1999 р.", + "href": "#" + }, + { + "img": "book-09.png", + "title": "Luzes na agua", + "info": "Prose, 1997 р.", + "href": "#" + }, + { + "img": "book-10.png", + "title": "In the arms of Melpomene", + "info": "Prose, 1954 р.", + "href": "#" + } ] \ No newline at end of file diff --git a/src/data/imports/genres.json b/src/data/imports/genres.json index 58871bb..b0ae7c4 100644 --- a/src/data/imports/genres.json +++ b/src/data/imports/genres.json @@ -1,26 +1,26 @@ [ - { - "href": "#", - "name": "Ucrainica" - }, - { - "href": "#", - "name": "Childrens literature" - }, - { - "href": "#", - "name": "History" - }, - { - "href": "#", - "name": "Religion" - }, - { - "href": "#", - "name": "Art" - }, - { - "href": "#", - "name": "Miscellaneous" - } + { + "href": "#", + "name": "Ucrainica" + }, + { + "href": "#", + "name": "Childrens literature" + }, + { + "href": "#", + "name": "History" + }, + { + "href": "#", + "name": "Religion" + }, + { + "href": "#", + "name": "Art" + }, + { + "href": "#", + "name": "Miscellaneous" + } ] \ No newline at end of file diff --git a/src/data/imports/menu.json b/src/data/imports/menu.json index 5e2b471..5fb300f 100644 --- a/src/data/imports/menu.json +++ b/src/data/imports/menu.json @@ -1,32 +1,32 @@ [ - { - "name": "Catalog", - "href": "catalog.html", - "visible": true, - "active": true - }, - { - "name": "About Us", - "href": "about-us.html", - "visible": true, - "active": false - }, - { - "name": "Donate", - "href": "#donate", - "visible": true, - "active": false - }, - { - "name": "Contacts", - "href": "#contacts", - "visible": false, - "active": false - }, - { - "name": "RSS", - "href": "http://domain.zone/feed/", - "visible": true, - "active": false - } + { + "name": "Catalog", + "href": "catalog.html", + "visible": true, + "active": true + }, + { + "name": "About Us", + "href": "about-us.html", + "visible": true, + "active": false + }, + { + "name": "Donate", + "href": "#donate", + "visible": true, + "active": false + }, + { + "name": "Contacts", + "href": "#contacts", + "visible": false, + "active": false + }, + { + "name": "RSS", + "href": "http://domain.zone/feed/", + "visible": true, + "active": false + } ] \ No newline at end of file diff --git a/src/data/pages/about.json b/src/data/pages/about.json index 55b13af..cf35ae0 100644 --- a/src/data/pages/about.json +++ b/src/data/pages/about.json @@ -1,10 +1,10 @@ { - "data": { - "name": "About Us", - "href": "about-us.html", - "visible": true - }, - "imports": [ - "genres" - ] + "data": { + "name": "About Us", + "href": "about-us.html", + "visible": true + }, + "imports": [ + "genres" + ] } \ No newline at end of file diff --git a/src/data/pages/menu.json b/src/data/pages/menu.json index b4e708e..129d395 100644 --- a/src/data/pages/menu.json +++ b/src/data/pages/menu.json @@ -1,10 +1,10 @@ { - "data": { - "name": "Menu", - "href": "menu.html", - "visible": true - }, - "imports": [ - "menu" - ] + "data": { + "name": "Menu", + "href": "menu.html", + "visible": true + }, + "imports": [ + "menu" + ] } \ No newline at end of file diff --git a/src/data/pages/partials/catalog.json b/src/data/pages/partials/catalog.json index 81de84f..a39204d 100644 --- a/src/data/pages/partials/catalog.json +++ b/src/data/pages/partials/catalog.json @@ -1,11 +1,11 @@ { - "data": { - "name": "Catalog", - "href": "#catalog", - "visible": true - }, - "imports": [ - "catalog/catalog_1", - "catalog/catalog_2" - ] + "data": { + "name": "Catalog", + "href": "#catalog", + "visible": true + }, + "imports": [ + "catalog/catalog_1", + "catalog/catalog_2" + ] } \ No newline at end of file diff --git a/src/html/about.pug b/src/html/about.pug index 91e197c..e7d53f9 100644 --- a/src/html/about.pug +++ b/src/html/about.pug @@ -1,10 +1,10 @@ block content - //- - console.log(data) + //- - console.log(data) - div= filename - div: a(href=data.href)= data.name + div= filename + div: a(href=data.href)= data.name - ul.genres - each g in data.imports.genres - li - a(href=g.href)= g.name + ul.genres + each g in data.imports.genres + li + a(href=g.href)= g.name diff --git a/src/html/menu.pug b/src/html/menu.pug index 2d1dead..ba81ab7 100644 --- a/src/html/menu.pug +++ b/src/html/menu.pug @@ -1,15 +1,15 @@ block content - //- - console.log(data) + //- - console.log(data) - div= filename - div: a(href=data.href)= data.name + div= filename + div: a(href=data.href)= data.name - ul.menu + ul.menu - each m in data.imports.menu - if m.active - li.nav-item.active - a.nav-link(href=m.href)= m.name - else - li.nav-item - a.nav-link(href=m.href)= m.name + each m in data.imports.menu + if m.active + li.nav-item.active + a.nav-link(href=m.href)= m.name + else + li.nav-item + a.nav-link(href=m.href)= m.name diff --git a/src/html/partials/catalog.pug b/src/html/partials/catalog.pug index e8b95ec..25fe474 100644 --- a/src/html/partials/catalog.pug +++ b/src/html/partials/catalog.pug @@ -1,25 +1,25 @@ block content - //- - console.log(data) + //- - console.log(data) - div= filename - div: a(href=data.href)= data.name + div= filename + div: a(href=data.href)= data.name - .catalog-1 + .catalog-1 - each c in data.imports.catalog_1 - .catalog-item - a.catalog-img-link(href=c.href) - img(src="assets/img/"+c.img) - .catalog-info - p.catalog-text= c.info - a.catalog-title(href=c.href)= c.title + each c in data.imports.catalog_1 + .catalog-item + a.catalog-img-link(href=c.href) + img(src="assets/img/"+c.img) + .catalog-info + p.catalog-text= c.info + a.catalog-title(href=c.href)= c.title - .catalog-2 + .catalog-2 - each c in data.imports.catalog_2 - .catalog-item - a.catalog-img-link(href=c.href) - img(src="assets/img/"+c.img) - .catalog-info - p.catalog-text= c.info - a.catalog-title(href=c.href)= c.title + each c in data.imports.catalog_2 + .catalog-item + a.catalog-img-link(href=c.href) + img(src="assets/img/"+c.img) + .catalog-info + p.catalog-text= c.info + a.catalog-title(href=c.href)= c.title diff --git a/src/html/without_data.pug b/src/html/without_data.pug index 7cbe449..6ae5034 100644 --- a/src/html/without_data.pug +++ b/src/html/without_data.pug @@ -1,11 +1,11 @@ block content - - var local_data = 'local data' + - var local_data = 'local data' - p This content is not provided of any external data from JSON files - p instead it provides a - strong #{local_data} + p This content is not provided of any external data from JSON files + p instead it provides a + strong #{local_data} - p. - To provide an external data from JSON file you have to create - a JSON file in "src/data/pages" with same name as that file - (src/data/pages/without_data.json) + p. + To provide an external data from JSON file you have to create + a JSON file in "src/data/pages" with same name as that file + (src/data/pages/without_data.json) From 34125b128810de419b9bcc6231399df324c26136 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 09:56:54 +0200 Subject: [PATCH 05/13] improve: core --- lib/gulp-json-loader.js | 275 ++++++++++++++++++++++++---------------- 1 file changed, 166 insertions(+), 109 deletions(-) diff --git a/lib/gulp-json-loader.js b/lib/gulp-json-loader.js index 7a38a2e..f9cc03a 100644 --- a/lib/gulp-json-loader.js +++ b/lib/gulp-json-loader.js @@ -1,155 +1,212 @@ 'use strict'; -const Format = require('util').format; +const Util = require('util'); const Path = require('path'); const Fs = require('fs'); -const asyncReadFile = Fs.promises.readFile; -const BR = (process.platform === 'win32' ? '\r\n' : '\n'); -async function loadJsonData(file) { - const { report, pathHtml, pathData } = this; - const pathJson = file.path.replace(pathHtml, `${pathData}/pages`).slice(0, -3) + 'json'; - const filename = Path.basename(file.path, '.pug'); - const pocket = { filename }; +const APP_PATH = process.cwd(); +const DEFAULT_SOURCE_PATH = './src'; +const BREAK_LINE = process.platform === 'win32' ? '\r\n' : '\n'; +const ERR_TOP_DIR = 'Working with top-level directories is prohibited!'; + +const CachedData = {}; +const BrushColors = { + red: 1, + cyan: 6, + grey: 8 +}; + +const getAbsolutePath = (path, subdir = '') => { + if (path.startsWith('/')) { + path = '.' + path; + } + + path = Path.join(APP_PATH, path); + + return subdir + ? Path.join(path, subdir) + : path; +} + +/** + * brush + * Adding the ANSI escape codes to a textual data + * @see https://en.wikipedia.org/wiki/ANSI_escape_code + * + * @param {string} colorName Color name + * @param {string} str Text data + */ +const brush = (colorName, str) => { + const color = BrushColors[colorName]; + return `\x1b[38;5;${color}m${str}\x1b[0m`; +} + +const reportAction = (ctx, pathJson, filename) => { + const currTime = new Date().toLocaleString(ctx.locales, { + timeStyle: 'medium', + }); + + const coloredCurrentTime = brush('grey', currTime); + const coloredRelativePath = pathJson + .replace(APP_PATH, '.') + .replace(filename, brush('cyan', filename)); - return Fs.promises.access(pathJson, Fs.constants.R_OK) - .then(async () => { - let jsonData = {}; - - try { - jsonData = await asyncReadFile(pathJson, 'utf8'); - jsonData = JSON.parse(jsonData); - } catch (err) { - process.stderr.write(err + BR); - return pocket; - } - - report && reportAction(pathJson, filename); - - if (jsonData.hasOwnProperty('imports')) { - if (!jsonData.hasOwnProperty('data')) { - jsonData.data = {}; - } - - Object.defineProperty(jsonData.data, 'imports', { - value: await loadImports.call(this, jsonData.imports), - enumerable: true, - // configurable: true - }); - delete jsonData.imports; - } - - return Object.assign(pocket, jsonData); - }) - .catch(() => pocket) - ; + const message = Util.format( + '[%s] Loaded %s%s', + coloredCurrentTime, + coloredRelativePath, + BREAK_LINE + ); + + process.stdout.write(message); } -async function loadImports(imports) { - const { report, pathHtml, pathData } = this; - const data = {}; +const loadImportsAsync = (ctx, imports) => { + return new Promise((resolve, reject) => { + if (!Array.isArray(imports)) { + reject(new Error('Imports should be an Array')); + } else { + const storage = {}; + loadImportsRecursively({ ctx, imports, storage, resolve, reject }); + } + }); +}; + +const loadImportsRecursively = async (options) => { + const { ctx, imports, storage, resolve, reject, idx = 0 } = options; + const { report, pathData } = ctx; - if (Array.isArray(imports) && imports.length) { - const pathFile = imports.shift(); - const pathJson = `${pathData}/imports/${pathFile}.json`; - const filename = Path.basename(pathJson, '.json'); + if (idx < imports.length) { + const jsonRelativePath = imports[idx] + '.json'; + const jsonFullPath = Path.join(pathData, 'imports', jsonRelativePath); + const jsonFilename = Path.basename(jsonFullPath, '.json'); + + if (!jsonFullPath.startsWith(APP_PATH)) { + reject(new Error(ERR_TOP_DIR)); + return; + } try { - const jsonData = await asyncReadFile(pathJson, 'utf8'); + const jsonData = await Fs.promises.readFile(jsonFullPath, 'utf8'); const parsedData = JSON.parse(jsonData); - Object.defineProperty(data, filename, { + Object.defineProperty(storage, jsonFilename, { value: parsedData, enumerable: true }); - } catch (err) { + } catch (error) { - process.stderr.write(err + BR); + reject(error); + return; } - report && reportAction(pathJson, filename); + report && reportAction(ctx, jsonFullPath, jsonFilename); - if (imports.length) { - // TODO: Find another solution with asynchronous queue. - // - // I know that recursion is a bad practice with its excessive - // consumption of resources but unfortunately, I didn't find - // any other solution by now - const jsonData = await loadImports.call(this, imports); - Object.assign(data, jsonData); - } - } + options.idx = idx + 1; + loadImportsRecursively(options); - return data; + } else { + resolve(storage); + } } -function reportAction(pathJson, filename) { - const currTime = brush('grey', new Date().toLocaleTimeString()); - const relativePath = pathJson - .replace(process.cwd(), '.') - .replace(filename, brush('cyan', filename)) - ; - process.stdout.write(Format('[%s] Loaded %s%s', currTime, relativePath, BR)); -} +async function loadJsonData(file) { + const { report, pathHtml, pathData, dataEntry } = this; -/** - * brush - * Adding the ANSI escape codes to a textual data - * @see https://en.wikipedia.org/wiki/ANSI_escape_code - * - * @param {string} colorName Color name - * @param {string} str Text data - */ -function brush(colorName, str) { - const colors = { - cyan: 6, - grey: 8 + const pathJson = file.path + .replace(pathHtml, `${pathData}/pages`) + .slice(0, -3) + 'json'; + + const filename = Path.basename(file.path, '.pug'); + const pocket = { filename }; + + if (CachedData.hasOwnProperty(pathJson)) { + report && reportAction(this, pathJson, filename); + return CachedData[pathJson]; } - const color = colors[colorName]; - return `\x1b[38;5;${color}m${str}\x1b[0m`; -} -function getAbsolutePath(sourcePath, postfix = '') { - let absolutePath = sourcePath; + let jsonData = ''; - if (absolutePath.substr(0, 2) === './') { - absolutePath = absolutePath.slice(2); + try { + jsonData = await Fs.promises.readFile(pathJson, 'utf8'); + jsonData = JSON.parse(jsonData); + } catch (err) { + return pocket; } - if (absolutePath[0] !== '/') { - absolutePath = process.cwd() + '/' + absolutePath; + report && reportAction(this, pathJson, filename); + + if (jsonData.hasOwnProperty('imports')) { + const { data = {}, imports = [] } = jsonData; + + try { + + const importedData = await loadImportsAsync(this, imports); + + Object.defineProperty(data, 'imports', { + value: importedData, + enumerable: true, + }); + + } catch (err) { + + const coloredErrorMesage = brush('red', err.message); + process.stderr.write(coloredErrorMesage + BREAK_LINE); + + return; + + } + + Object.defineProperty(pocket, dataEntry, { + value: data, + enumerable: true, + }); } - return absolutePath + postfix; + CachedData[pathJson] = pocket; + + return pocket; } -function manager(options) { - let opts = {}; +const factory = (options) => { + if (options === undefined) { + const packagePath = Path.join(APP_PATH, 'package.json'); + const Package = require(packagePath); - if (!(typeof (options) == 'object' && options !== null)) { - throw new Error('options is required'); + options = Package.hasOwnProperty(Package.name) + ? Package[Package.name] + : {}; } - opts.report = options.hasOwnProperty('report') ? options.report : false; + const { report = true, locales = 'en-EN', dataEntry = 'data' } = options; - if (options.hasOwnProperty('pathHtml') && options.hasOwnProperty('pathData')) { - opts.pathHtml = getAbsolutePath(options.pathHtml); - opts.pathData = getAbsolutePath(options.pathData); - } + const sourcePath = options.hasOwnProperty('sourcePath') + ? options.sourcePath + : DEFAULT_SOURCE_PATH; - else if (options.hasOwnProperty('sourcePath')) { - opts.pathHtml = getAbsolutePath(options.sourcePath, '/src/html'); - opts.pathData = getAbsolutePath(options.sourcePath, '/src/data'); - } + const pathHtml = options.hasOwnProperty('pathHtml') + ? getAbsolutePath(options.pathHtml) + : getAbsolutePath(sourcePath, 'html'); + + const pathData = options.hasOwnProperty('pathData') + ? getAbsolutePath(options.pathData) + : getAbsolutePath(sourcePath, 'data'); - else { - throw new Error('required "sourcePath" parameter or pare of "pathHtml" and "pathData" parameters'); + if (!pathHtml.startsWith(APP_PATH) || !pathData.startsWith(APP_PATH)) { + // Please put your source files into your project directory. + throw new Error(ERR_TOP_DIR); } - return loadJsonData.bind(opts); + return loadJsonData.bind({ + pathHtml, + pathData, + dataEntry, + locales, + report, + }); } -module.exports = manager; +module.exports = factory; From 695b5bc90de366d2374dea55ff57d66fe1808ecd Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 09:57:32 +0200 Subject: [PATCH 06/13] improve: demo --- gulpfile.js | 7 +------ src/html/about.pug | 8 ++++---- src/html/menu.pug | 12 ++++++------ src/html/partials/catalog.pug | 24 ++++++++++++------------ 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 3d74f2a..176ca4f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,12 +2,7 @@ const gulp = require('gulp'); const plugins = require('gulp-load-plugins')(); -const jsonLoader = require('./lib/gulp-json-loader')({ - // sourcePath: __dirname, - pathHtml: 'src/html', - pathData: 'src/data', - report: true, -}); +const jsonLoader = require('./lib/gulp-json-loader')(); function html() { return gulp.src('src/html/**/*.pug') diff --git a/src/html/about.pug b/src/html/about.pug index e7d53f9..af003d5 100644 --- a/src/html/about.pug +++ b/src/html/about.pug @@ -1,10 +1,10 @@ block content - //- - console.log(data) + //- - console.log($) div= filename - div: a(href=data.href)= data.name + div: a(href = $.href)= $.name ul.genres - each g in data.imports.genres + each $GenreItem in $.imports.genres li - a(href=g.href)= g.name + a(href = $GenreItem.href)= $GenreItem.name diff --git a/src/html/menu.pug b/src/html/menu.pug index ba81ab7..237bb06 100644 --- a/src/html/menu.pug +++ b/src/html/menu.pug @@ -1,15 +1,15 @@ block content - //- - console.log(data) + //- - console.log($) div= filename - div: a(href=data.href)= data.name + div: a(href = $.href)= $.name ul.menu - each m in data.imports.menu - if m.active + each $MenuItem in $.imports.menu + if $MenuItem.active li.nav-item.active - a.nav-link(href=m.href)= m.name + a.nav-link(href = $MenuItem.href)= $MenuItem.name else li.nav-item - a.nav-link(href=m.href)= m.name + a.nav-link(href = $MenuItem.href)= $MenuItem.name diff --git a/src/html/partials/catalog.pug b/src/html/partials/catalog.pug index 25fe474..d4c82ce 100644 --- a/src/html/partials/catalog.pug +++ b/src/html/partials/catalog.pug @@ -1,25 +1,25 @@ block content - //- - console.log(data) + //- - console.log($) div= filename - div: a(href=data.href)= data.name + div: a(href = $.href)= $.name .catalog-1 - each c in data.imports.catalog_1 + each $CatalogItem in $.imports.catalog_1 .catalog-item - a.catalog-img-link(href=c.href) - img(src="assets/img/"+c.img) + a.catalog-img-link(href = $CatalogItem.href) + img(src = "assets/img/" + $CatalogItem.img) .catalog-info - p.catalog-text= c.info - a.catalog-title(href=c.href)= c.title + p.catalog-text= $CatalogItem.info + a.catalog-title(href = $CatalogItem.href)= $CatalogItem.title .catalog-2 - each c in data.imports.catalog_2 + each $CatalogItem in $.imports.catalog_2 .catalog-item - a.catalog-img-link(href=c.href) - img(src="assets/img/"+c.img) + a.catalog-img-link(href = $CatalogItem.href) + img(src = "assets/img/" + $CatalogItem.img) .catalog-info - p.catalog-text= c.info - a.catalog-title(href=c.href)= c.title + p.catalog-text= $CatalogItem.info + a.catalog-title(href = $CatalogItem.href)= $CatalogItem.title From e16dbd599b7c40b52f0ad3ab28cc43374a9c85a0 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 09:57:58 +0200 Subject: [PATCH 07/13] goingto: 1.1.0 --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a1b7e1..68e13fe 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "gulp-json-loader", "description": "A little tool for the gulp-data plugin. It useful for data loading in the development of HTML or the pug pages", "author": "Alexander Yukal ", - "version": "1.0.0", + "version": "1.1.0", "main": "gulpfile.js", "devDependencies": { "gulp": "^4.0.2", @@ -13,6 +13,12 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "gulp-json-loader": { + "sourcePath": "src", + "dataEntry": "$", + "locales": "ru-UA", + "report": true + }, "repository": { "type": "git", "url": "git+https://github.com/yukal/gulp-json-loader.git" From 19dae429b7e2379fe1ff3c89bd1d43490dd155c2 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 10:36:02 +0200 Subject: [PATCH 08/13] add: changes.txt --- changes.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 changes.txt diff --git a/changes.txt b/changes.txt new file mode 100644 index 0000000..615b3b3 --- /dev/null +++ b/changes.txt @@ -0,0 +1,11 @@ +v1.1.0: +- Updated core + - Imposed restrictions on accessing external directories. + - There is no need to pass the config data to GulpJsonLoader, now it's optional + and defined from defaults. But if you want to change them, you might tune it in + both ways: using an old-style passing an object with options to GulpJsonLoader + factory or just pass the settings into a package.json. See package.json. + +- Updated dependencies + - gulp-load-plugins: ^2.0.7 + - gulp-pug: ^5.0.0 From cce8924a2287280557701abd01228736b5cd0e66 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 11:00:29 +0200 Subject: [PATCH 09/13] update: changes.txt --- changes.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/changes.txt b/changes.txt index 615b3b3..77c183a 100644 --- a/changes.txt +++ b/changes.txt @@ -9,3 +9,13 @@ v1.1.0: - Updated dependencies - gulp-load-plugins: ^2.0.7 - gulp-pug: ^5.0.0 + +- Improvement of variables names + - To distinguish variables from stylistic PAG syntax it is preferable to name + variables in a different way as opposed to decoration text. That's why I name + variables with a capital letter and a dollar sign at the beginning. + See PUG files to find out more about it. + + - The entry name of the Data loaded from the JSON file is defined in a global + space as a "data" entry, now you can change this name as you like. + See package.json and PUG files to find out more about it. From adc94e08aa79f2cbc3c56432fdb1381952eaa0c9 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 12:08:57 +0200 Subject: [PATCH 10/13] improve: caching data --- changes.txt | 3 +++ lib/gulp-json-loader.js | 48 ++++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/changes.txt b/changes.txt index 77c183a..fb5f279 100644 --- a/changes.txt +++ b/changes.txt @@ -19,3 +19,6 @@ v1.1.0: - The entry name of the Data loaded from the JSON file is defined in a global space as a "data" entry, now you can change this name as you like. See package.json and PUG files to find out more about it. + +- Implemented data caching + - The data that has already been loaded will be getting from the cache diff --git a/lib/gulp-json-loader.js b/lib/gulp-json-loader.js index f9cc03a..310fa95 100644 --- a/lib/gulp-json-loader.js +++ b/lib/gulp-json-loader.js @@ -41,7 +41,7 @@ const brush = (colorName, str) => { return `\x1b[38;5;${color}m${str}\x1b[0m`; } -const reportAction = (ctx, pathJson, filename) => { +const reportAction = (ctx, pathJson, filename, action) => { const currTime = new Date().toLocaleString(ctx.locales, { timeStyle: 'medium', }); @@ -51,9 +51,14 @@ const reportAction = (ctx, pathJson, filename) => { .replace(APP_PATH, '.') .replace(filename, brush('cyan', filename)); + if (action === 'Cached') { + action = brush('grey', action); + } + const message = Util.format( - '[%s] Loaded %s%s', + '[%s] %s %s%s', coloredCurrentTime, + action, coloredRelativePath, BREAK_LINE ); @@ -77,33 +82,40 @@ const loadImportsRecursively = async (options) => { const { report, pathData } = ctx; if (idx < imports.length) { + let action = 'Cached'; + const jsonRelativePath = imports[idx] + '.json'; const jsonFullPath = Path.join(pathData, 'imports', jsonRelativePath); const jsonFilename = Path.basename(jsonFullPath, '.json'); + const cacheKey = 'imports:' + jsonFullPath.replace(APP_PATH, ''); if (!jsonFullPath.startsWith(APP_PATH)) { reject(new Error(ERR_TOP_DIR)); return; } - try { + if (!CachedData.hasOwnProperty(cacheKey)) { + try { - const jsonData = await Fs.promises.readFile(jsonFullPath, 'utf8'); - const parsedData = JSON.parse(jsonData); + const jsonData = await Fs.promises.readFile(jsonFullPath, 'utf8'); - Object.defineProperty(storage, jsonFilename, { - value: parsedData, - enumerable: true - }); + CachedData[cacheKey] = JSON.parse(jsonData); + action = 'Loaded'; - } catch (error) { + } catch (error) { - reject(error); - return; + reject(error); + return; + } } - report && reportAction(ctx, jsonFullPath, jsonFilename); + report && reportAction(ctx, jsonFullPath, jsonFilename, action); + + Object.defineProperty(storage, jsonFilename, { + value: CachedData[cacheKey], + enumerable: true + }); options.idx = idx + 1; loadImportsRecursively(options); @@ -120,12 +132,14 @@ async function loadJsonData(file) { .replace(pathHtml, `${pathData}/pages`) .slice(0, -3) + 'json'; + const cacheKey = 'data:' + pathJson.replace(APP_PATH, ''); + const filename = Path.basename(file.path, '.pug'); const pocket = { filename }; if (CachedData.hasOwnProperty(pathJson)) { - report && reportAction(this, pathJson, filename); - return CachedData[pathJson]; + report && reportAction(this, pathJson, filename, 'Cached'); + return CachedData[cacheKey]; } let jsonData = ''; @@ -137,7 +151,7 @@ async function loadJsonData(file) { return pocket; } - report && reportAction(this, pathJson, filename); + report && reportAction(this, pathJson, filename, 'Loaded'); if (jsonData.hasOwnProperty('imports')) { const { data = {}, imports = [] } = jsonData; @@ -166,7 +180,7 @@ async function loadJsonData(file) { }); } - CachedData[pathJson] = pocket; + CachedData[cacheKey] = pocket; return pocket; } From b6021e97307d9fef7521d95ab91b3b5c4c3b54a7 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 12:09:10 +0200 Subject: [PATCH 11/13] update: README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index f7ea454..54ab2aa 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,28 @@ src src Somewhere in gulpfile.js: ```javascript +// It is optional now, but you able to tune it as you wish. +// You can pass the settings by an object, or you can pass it using package.json +const jsonLoaderSettings = { + // Chose where the source files are located. + // Use sourcePath or the pare of pathHtml and pathData + + // sourcePath: 'src', + pathHtml: 'src/html', + pathData: 'src/data', + + // The namespace where the Data is located. + // To get some loaded data from the JSON in a PUG context use syntax: + // $.href or $.imports.menu + dataEntry: '$', + + // It needs for the Date object to show a local date + locales: 'en-GB', + + // Will report about the loaded JSON files + report: true, +}; + const gulp = require('gulp'); const plugins = require('gulp-load-plugins')(); const jsonLoaderFactory = require('./lib/gulp-json-loader'); From 03add996d5e9fcbddfe7c389973ed2408e530e89 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 12:10:04 +0200 Subject: [PATCH 12/13] update: README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 54ab2aa..e1bfc66 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,7 @@ const jsonLoaderSettings = { const gulp = require('gulp'); const plugins = require('gulp-load-plugins')(); const jsonLoaderFactory = require('./lib/gulp-json-loader'); -const jsonLoader = jsonLoaderFactory({ - // sourcePath: __dirname, - pathHtml: 'src/html', - pathData: 'src/data', - report: true, -}); +const jsonLoader = jsonLoaderFactory(jsonLoaderSettings); function html() { return gulp.src('src/html/**/*.pug') From 2d978e92fce31512386951e83a908e27aecd5266 Mon Sep 17 00:00:00 2001 From: Alexander Yukal Date: Mon, 31 Jan 2022 12:13:30 +0200 Subject: [PATCH 13/13] update: README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1bfc66..1b2279f 100644 --- a/README.md +++ b/README.md @@ -125,12 +125,12 @@ block content //- - console.log(data) div= filename - div: a(href=data.href)= data.name + div: a(href = $.href)= $.name ul.genres - each Genre in data.imports.genres + each $GenreItem in $.imports.genres li - a(href=Genre.href)= Genre.name + a(href = $GenreItem.href)= $GenreItem.name ``` Run command to build html page with data