diff --git a/README.md b/README.md index b08ba7e..15c9804 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@ Image resizing done by [sharp](http://sharp.dimens.io/) ([github](https://github - use some lightbox on the prepared directory - further development - integration with other projects (iframe? ajax? web component? Polymer?) - - config.json support (for specifying conversion settings) + - ~~config.json support (for specifying conversion settings)~~ diff --git a/package.json b/package.json index ef9d26a..eb61a36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gallerymaker", - "version": "0.0.0", + "version": "0.0.1", "description": "A static html gallery maker", "keywords": [ "gallery", diff --git a/src/index.js b/src/index.js index b34c95c..fa90cea 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ var fs = require('fs'); var path = require('path'); -var util = require('util'); +//var util = require('util'); var sharp = require('sharp'); var utils = require('./utils.js'); @@ -13,15 +13,27 @@ var odir = sanitize(process.argv[4] || idir + '.web'); var clientDir = path.join(__dirname, 'client'); -var conf = { - width: 700, - quality: 80, - debug: true, - template: 'index.html' -}; -var EOL = "\n"; -var DEPTH = 2; -var SORT_DESC = true; +var conf; +try { conf = require(path.join(__dirname, '..', 'config.json')); } +catch (e) { conf = {}; } +if (!conf.width) conf.width = 700; +if (!conf.quality) conf.quality = 80; +if (!conf.fnames) conf.fnames = {}; // odir +if (!conf.fnames.list) conf.fnames.list = 'list.html'; +if (!conf.fnames.gallery) conf.fnames.gallery = 'gallery.html'; +if (!conf.templates) conf.templates = {}; // clientDir +if (!conf.templates.index) conf.templates.index = 'index.html'; +if (!conf.assets) conf.assets = {}; // clientDir +if (!conf.assets.common) conf.assets.common = ['index.js', 'index.css']; +if (!conf.ignored_files || !conf.ignored_files.push) conf.ignored_files = []; +if (conf.debug === undefined) conf.debug = true; + +for (var ak in conf.assets) conf.ignored_files.push.apply(conf.ignored_files, conf.assets[ak]); +for (var fk in conf.fnames) conf.ignored_files.push(conf.fnames[fk]); + +var EOL = conf.eol ? conf.eol : "\n"; +var DEPTH = conf.html_init_depth ? conf.html_init_depth : 2; +var SORT_DESC = conf.sort_desc ? conf.sort_desc : true; var log = conf.debug ? console.log.bind(console) : function () {}; var logi = function () { @@ -31,17 +43,17 @@ var logi = function () { var ind = depth => utils.indent(depth, DEPTH); var cmds = { - prepare: prepareImages, + prepare: () => prepareImages().then(copyAssets('common')), list: generateList, gallery: generateGallery, - all: () => { - prepareImages() + all: () => prepareImages() .then(cont => Promise.all([ + copyAssets('common'), + ['content.json'].concat(cont.cont.map(c => c.dir)), generateList(cont), generateGallery(cont) ])) - .then(res => log('All tasks finished!', res.map(r => { return { type: typeof r, lenght: r.length }; }))); - } + .then(res => log('All tasks finished! Files:', res.reduce((c, n) => c.concat(n), []).join(', '))) }; if (!cmd || !idir) { @@ -68,7 +80,11 @@ else if (!cmds[cmd]) { process.exit(); } else { - cmds[cmd](); + cmds[cmd]() + .catch(err => { + console.log(err); + process.exit(); + }); } function prepareImages() { @@ -79,38 +95,20 @@ function prepareImages() { var name = file.replace(/[.][^.]*$/, ''); return sharp(path.join(srcDir, file)) - .resize(conf.width) - .quality(conf.quality) - .toFile(path.join(destDir, fname)) - .then(() => { - return { file: fname, name: name }; - }, err => { - console.log('Resizing "' + file + '" ' + err); - return new Promise((resolve) => { - var rs = fs.createReadStream(path.join(srcDir, file)); - var ws = fs.createWriteStream(path.join(destDir, fname)); - ws.on('error', e => { - resolve({ file: '', name: name, error: 'Writing file failed: ' + e + '; ' + err }); - }); - rs.on('end', () => { - resolve({ file: fname, name: name, error: '' + err }); - ws.end(); - }); - rs.pipe(ws, { end: false }); - }); - }); + .resize(conf.width) + .quality(conf.quality) + .toFile(path.join(destDir, fname)) + .then(() => ({ file: fname, name: name })) + .catch(sharpErr => utils.copyFile(srcDir, file, destDir, fname) + .then(() => ({ file: fname, name: name, error: '' + sharpErr })) + .catch(writeErr => ({ file: '', name: name, error: writeErr })) + ); }) + .then(cont => utils.writeFile(cont, 'content.json', odir)) .then(cont => { - log('Image preparation done!'); - return utils.writeFile(JSON.stringify(cont), 'content.json', odir); - }) - .then(cont => { - log('Content:', util.inspect(cont, { showHidden: false, depth: null })); + //log('Preparing images finished. Content:', util.inspect(cont, { showHidden: false, depth: null })); + log('Preparing images finished:', ['content.json'].concat(cont.cont.map(c => c.dir)).join(', ')); return cont; - }) - .catch(err => { - console.log(err); - process.exit(); }); } @@ -118,25 +116,113 @@ function generateList(cont) { log('Generating file list'); return getContent(cont) - .then(cont => { - //log('content:', util.inspect(cont, { showHidden: false, depth: null })); - return cont - }) .then(cont => getListEntryHtml(cont.cont)) - .then(html => getPageHtml(html)) + .then(html => getPageHtml(html, conf.templates.index)) .then(html => Promise.all([ - utils.writeFile(html, 'list.html', odir), - utils.copyFile(clientDir, 'index.css', odir), - utils.copyFile(clientDir, 'index.js', odir) + utils.writeFile(html, conf.fnames.list, odir, true) ])) .then(res => { - log('Generated files:', res); - return res[0]; + log('Generating file list finished:', res.join(', ')); + return res; + }); +} + +function generateGallery(cont) { + log('Generating gallery'); + + return getContent(cont) + .then(cont => getGalleryEntryHtml(cont.cont)) + .then(html => getPageHtml(html, conf.templates.index)) + .then(html => Promise.all([ + utils.writeFile(html, conf.fnames.gallery, odir, true) + ])) + .then(files => { + log('Generating gallery finished:', files.join(', ')); + return files; + }); +} + +function copyAssets(asskey) { + log('Copying', asskey, 'assets'); + var fnames = conf.assets[asskey] ? conf.assets[asskey] : asskey && asskey.map ? asskey : []; + + return Promise.all(fnames.map(fname => utils.copyFile(clientDir, fname, odir))) + .then(files => { + log('Copying', asskey, 'assets finished:', files.join(', ')); + return files; + }); +} + +/** + * Creates sanitized mirror of a directory tree and grants access to its files + * + * @param {string} dir Path of the directory to be processed + * @param {function({string}, {string}, {string})} cb(file, srcDir, destDir) File transformator + * @param {boolean} nomkdir Disable (sanitized) mkdir of the directory being processed + * @param {int} [depth=0] Indents log messages according to an actual depth + * @returns {void} + */ +function mirrorDirTree(dir, cb, nomkdir, depth) { + depth = depth === undefined ? 0 : depth += 1; + var destDir = dir === idir ? odir : path.join(odir, sanitize(dir.replace(idir, ''))); + + return new Promise((resolve, reject) => fs.readdir(dir, (err, files) => { + if (err) reject('Reading directory "' + dir + '" failed: ' + err); + if (files === undefined) reject('Directory "' + dir +'" does not exist!'); + if (files.length && !fs.existsSync(destDir) && !nomkdir) fs.mkdirSync(destDir); + if (files.filter) files = files.filter(file => conf.ignored_files.indexOf(file) === -1); + if (SORT_DESC) files.reverse(); + resolve(files); + })) + .then(files => Promise.all(files.map(file => + new Promise((resolve, reject) => fs.stat(path.join(dir, file), (err, stat) => { + if (stat.isDirectory()) + resolve(mirrorDirTree(path.join(dir, file), cb, nomkdir, depth)); + else if (typeof cb === 'function') + resolve(cb(file, dir, destDir, depth + 1)); + else + reject('No file processing callback provided.'); + })) + ))) + .then(items => { + logi(depth, '[dir]', dir); + items.forEach(item => logi(depth + 1, item.error ? '[error] ' + item.file + ' ' + item.error + : item.file ? '[file] ' + item.file : item.dir ? '[dir] ' + item.dir : item)); + return { dir: destDir.replace(/^.*\//, ''), name: dir.replace(/^.*\//, ''), cont: items }; + }); +} + +function getContent(cont) { + return new Promise((resolve, reject) => { + if (cont) resolve(cont); + fs.readFile(path.join(odir, 'content.json'), (err, data) => { + if (err) reject(err); + else if (!data) reject('File empty: ' + path.join(odir, 'content.json')); + try { resolve(JSON.parse(data)); } + catch (e) { reject(path.join(odir, 'content.json') + ' parsing failed: ' + e); } + }); }) .catch(err => { - console.log(err); - process.exit(); - }); + console.log('Content cache reading failed: ' + err + '; Re-reading directory tree'); + return mirrorDirTree(odir, file => ({ file: file }), true); + }) +} + +function sanitize(path) { + return utils.diaStrip(path).replace(/\ /g, '_') + .replace(/([.][^.]*)$/, match => String.prototype.toLowerCase.call(match)); +} + +function getPageHtml(htmlPart, pageFname) { + var pageFile = path.join(clientDir, pageFname); + + return new Promise((resolve, reject) => fs.readFile(pageFile, 'utf-8', (err, data) => { + if (err) reject(err); + else if (!data) reject('File empty: ' + pageFile); + resolve(data); + })) + .then((pageHtml) => pageHtml.substring(0, pageHtml.search('[ ]*')) + + htmlPart + pageHtml.substr(pageHtml.search('[ ]*'))); } function getListEntryHtml(cont, dir, depth) { @@ -156,34 +242,6 @@ function getListEntryHtml(cont, dir, depth) { return html; } -function getPageHtml(htmlPart, pageFname) { - pageFname = pageFname || conf.template; - return new Promise((resolve, reject) => { - var pageFile = path.join(clientDir, pageFname); - fs.readFile(pageFile, 'utf-8', (err, data) => { - if (err) reject(err); - else if (!data) reject('File empty: ' + pageFile); - resolve(data); - }); - }) - .then((pageHtml) => { - return pageHtml.substring(0, pageHtml.search('[ ]*')) + htmlPart - + pageHtml.substr(pageHtml.search('[ ]*')); - }); -} - -function generateGallery(cont) { - log('Generating gallery'); - - return getContent(cont) - .then(cont => getGalleryEntryHtml(cont.cont)) - .then(cont => { - log('Gallery prepared!'); - log('content:', util.inspect(cont, { showHidden: false, depth: null })); - return cont; - }); -} - function getGalleryEntryHtml(cont) { var html = ''; @@ -192,67 +250,3 @@ function getGalleryEntryHtml(cont) { return html; } - -/** - * Creates sanitized mirror of a directory tree and grants access to its files - * - * @param {string} dir Path of the directory to be processed - * @param {function({string}, {string}, {string})} cb(file, srcDir, destDir) File transformator - * @param {boolean} nomkdir Disable (sanitized) mkdir of the directory being processed - * @param {int} [depth=0] Indents log messages according to an actual depth - * @returns {void} - */ -function mirrorDirTree(dir, cb, nomkdir, depth) { - depth = depth === undefined ? 0 : depth += 1; - - return new Promise((resolve, reject) => fs.readdir(dir, (err, files) => { - if (err) reject('Reading directory "' + dir + '" failed: ' + err); - //logi(depth, '[dir]', dir.replace(/^.*\//, '')); - - var destDir = dir === idir ? odir : path.join(odir, sanitize(dir.replace(idir, ''))); - if (!nomkdir && files.length && !fs.existsSync(destDir)) { - logi(depth, '[mkdir]', destDir); - fs.mkdirSync(destDir); - } - - if (SORT_DESC) - files.reverse(); - - return Promise.all(files.map(file => { - return new Promise((resolve) => fs.stat(path.join(dir, file), (err, stat) => { - if (stat.isDirectory()) - resolve(mirrorDirTree(path.join(dir, file), cb, nomkdir, depth)); - else if (typeof cb === 'function') - resolve(cb(file, dir, destDir, depth + 1)); - })); - })) - .then(items => { - logi(depth, 'finished:'); - items.forEach(item => logi(depth + 1, item.error ? '[error] ' + item.file + ' ' + item.error - : item.file ? '[file] ' + item.file : item.dir ? '[dir] ' + item.dir : item)); - resolve({ dir: destDir.replace(/^.*\//, ''), name: dir.replace(/^.*\//, ''), cont: items }); - }); - })); -} - -function getContent(cont) { - return new Promise((resolve, reject) => { - if (cont) resolve(cont); - else - fs.readFile(path.join(odir, 'content.json'), (err, data) => { - if (err) reject(err); - else if (!data) reject('File empty: ' + path.join(odir, 'content.json')); - try { resolve(JSON.parse(data)); } - catch (e) { reject(path.join(odir, 'content.json') + ' parsing failed: ' + e); } - }); - }) - .catch(err => { - console.log('Content reading failed: ' + err + '; Re-reading directory tree'); - return mirrorDirTree(odir, file => { return { file: file }; }, true); - }) -} - -function sanitize(path) { - return utils.diaStrip(path).replace(/\ /g, '_') - .replace(/([.][^.]*)$/, match => String.prototype.toLowerCase.call(match)); -} diff --git a/src/utils.js b/src/utils.js index fb5aa55..b162fe8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -121,20 +121,21 @@ function diaStrip(str) { return str; } -function writeFile(data, fname, dir) { - return new Promise((resolve, reject) => fs.writeFile(path.join(dir, fname), data, err => { +function writeFile(data, fname, dir, returnFileName) { + var serialized = typeof data === 'object' ? JSON.stringify(data) : data; + return new Promise((resolve, reject) => fs.writeFile(path.join(dir, fname), serialized, err => { if (err) reject('Writing content file failed: ' + err) - resolve(fname); + resolve(returnFileName ? fname : data); })); } -function copyFile(srcDir, fname, destDir) { +function copyFile(sdir, sfname, ddir, dfname) { return new Promise((resolve, reject) => { - var rs = fs.createReadStream(path.join(srcDir, fname)); - var ws = fs.createWriteStream(path.join(destDir, fname)); + var rs = fs.createReadStream(path.join(sdir, sfname)); + var ws = fs.createWriteStream(path.join(ddir, dfname || sfname)); rs.on('error', err => reject('File reading failed: ' + err)); ws.on('error', err => reject('File writing failed: ' + err)); - ws.on('finish', () => resolve(fname)); + ws.on('finish', () => resolve(dfname || sfname)); rs.pipe(ws); }); }