mirror of
https://github.com/GenderDysphoria/GenderDysphoria.fyi.git
synced 2025-02-07 10:16:16 +00:00
More burndown.
This commit is contained in:
parent
2df7574697
commit
e95f2cf3db
@ -1,72 +1,29 @@
|
|||||||
|
|
||||||
const { pick } = require('lodash');
|
|
||||||
const actions = require('./actions');
|
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const { pick } = require('lodash');
|
||||||
|
const actions = require('./actions');
|
||||||
|
const File = require('./file');
|
||||||
|
const { TYPE } = require('./resolve');
|
||||||
const getImageDimensions = require('../lib/dimensions');
|
const getImageDimensions = require('../lib/dimensions');
|
||||||
const getVideoDimensions = require('get-video-dimensions');
|
const getVideoDimensions = require('get-video-dimensions');
|
||||||
|
|
||||||
const JPG = '.jpg';
|
|
||||||
const JPEG = '.jpeg';
|
|
||||||
const PNG = '.png';
|
|
||||||
const GIF = '.gif';
|
|
||||||
const MP4 = '.mp4';
|
|
||||||
const M4V = '.m4v';
|
|
||||||
|
|
||||||
const FILETYPE = {
|
|
||||||
[JPG]: 'jpeg',
|
|
||||||
[JPEG]: 'jpeg',
|
|
||||||
[PNG]: 'png',
|
|
||||||
[GIF]: 'gif',
|
|
||||||
[MP4]: 'mp4',
|
|
||||||
[M4V]: 'mp4',
|
|
||||||
};
|
|
||||||
|
|
||||||
const RESOLUTIONS = [ 2048, 1024, 768, 576, 300, 100 ];
|
const RESOLUTIONS = [ 2048, 1024, 768, 576, 300, 100 ];
|
||||||
|
|
||||||
module.exports = exports = class Asset {
|
module.exports = exports = class Asset extends File {
|
||||||
|
|
||||||
constructor (filepath) {
|
constructor (filepath) {
|
||||||
const file = path.parse(filepath);
|
super(filepath);
|
||||||
let { base: basename, name } = file;
|
|
||||||
|
|
||||||
this.preprocessed = false;
|
this.serializable.push(
|
||||||
if (name[0] === '_') {
|
'dimensions',
|
||||||
this.preprocessed = true;
|
'sizes',
|
||||||
file.name = name = name.slice(1);
|
);
|
||||||
file.basename = basename = basename.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.type = FILETYPE[file.ext] || file.ext.slice(1);
|
|
||||||
if ([ JPG, JPEG, PNG, GIF ].includes(file.ext)) {
|
|
||||||
this.kind = 'image';
|
|
||||||
} else if ([ MP4, M4V ].includes(file.ext)) {
|
|
||||||
this.kind = 'video';
|
|
||||||
} else {
|
|
||||||
this.kind = 'raw';
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the pages root and any _images segment from the dir
|
|
||||||
const dir = file.dir.split('/');
|
|
||||||
if (dir[0] === 'pages') dir.shift();
|
|
||||||
const i = dir.indexOf('_images');
|
|
||||||
if (i > -1) dir.splice(i, 1);
|
|
||||||
|
|
||||||
this.input = filepath; // pages/file.ext
|
|
||||||
this.base = path.join(...dir); // '', 'folder', 'folder/subfolder'
|
|
||||||
this.dir = path.join('/', ...dir); // /, /folder, /folder/subfolder
|
|
||||||
this.name = name; // index, fileA, fileB
|
|
||||||
this.basename = basename; // index.ext, fileA.ext, fileB.ext
|
|
||||||
this.ext = file.ext;
|
|
||||||
|
|
||||||
this.out = path.join(this.base, `${this.name}${this.preprocessed ? this.ext : '.' + this.type}`);
|
|
||||||
this.url = path.join(this.dir, `${this.name}${this.preprocessed ? this.ext : '.' + this.type}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load () {
|
load () {
|
||||||
switch (this.kind) {
|
switch (this.type) {
|
||||||
case 'video': return this.loadVideo();
|
case TYPE.VIDEO: return this.loadVideo();
|
||||||
case 'image': return this.loadImage();
|
case TYPE.IMAGE: return this.loadImage();
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +68,7 @@ module.exports = exports = class Asset {
|
|||||||
|
|
||||||
for (const w of RESOLUTIONS) {
|
for (const w of RESOLUTIONS) {
|
||||||
if (w > width) continue;
|
if (w > width) continue;
|
||||||
const name = `${this.name}.${w}w.${this.type}`;
|
const name = `${this.name}.${w}w${this.ext}`;
|
||||||
this.sizes.push({
|
this.sizes.push({
|
||||||
output: path.join(this.base, name),
|
output: path.join(this.base, name),
|
||||||
url: path.join(this.dir, name),
|
url: path.join(this.dir, name),
|
||||||
@ -156,27 +113,12 @@ module.exports = exports = class Asset {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson () {
|
|
||||||
return pick(this, [
|
|
||||||
'preprocessed',
|
|
||||||
'type',
|
|
||||||
'kind',
|
|
||||||
'input',
|
|
||||||
'base',
|
|
||||||
'dir',
|
|
||||||
'name',
|
|
||||||
'basename',
|
|
||||||
'ext',
|
|
||||||
'dimensions',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
webready () {
|
webready () {
|
||||||
const { kind, name } = this;
|
const { type, name, sizes } = this;
|
||||||
return {
|
return {
|
||||||
kind,
|
type,
|
||||||
name,
|
name,
|
||||||
sizes: this.sizes.map((s) => pick(s, [ 'url', 'width', 'height' ])),
|
sizes: sizes.map((s) => pick(s, [ 'url', 'width', 'height' ])),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,19 +126,10 @@ module.exports = exports = class Asset {
|
|||||||
return this.sizes.map(({ output, width }) => ({
|
return this.sizes.map(({ output, width }) => ({
|
||||||
input: this.input,
|
input: this.input,
|
||||||
output,
|
output,
|
||||||
format: this.preprocessed ? undefined : this.type,
|
format: this.preprocessed ? undefined : this.ext.slice(1),
|
||||||
width: this.preprocessed ? undefined : width,
|
width: this.preprocessed ? undefined : width,
|
||||||
action: this.preprocessed ? actions.copy : actions.image,
|
action: this.preprocessed ? actions.copy : actions.image,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.JPG = JPG;
|
|
||||||
exports.JPEG = JPEG;
|
|
||||||
exports.PNG = PNG;
|
|
||||||
exports.GIF = GIF;
|
|
||||||
exports.MP4 = MP4;
|
|
||||||
exports.M4V = M4V;
|
|
||||||
exports.FILETYPE = FILETYPE;
|
|
||||||
exports.RESOLUTIONS = RESOLUTIONS;
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
const glob = require('../lib/glob');
|
|
||||||
const { keyBy, filter, get, set, memoize } = require('lodash');
|
|
||||||
const { relative, ROOT } = require('./resolve');
|
|
||||||
const Asset = require('./asset');
|
|
||||||
|
|
||||||
module.exports = exports = async function createAssetFinder () {
|
|
||||||
const files = await glob('pages/**/*.{jpeg,jpg,png,gif,mp4}', { cwd: ROOT });
|
|
||||||
const map = {};
|
|
||||||
const assets = (await Promise.all(files.map(async (filepath) => {
|
|
||||||
const asset = new Asset(relative(filepath));
|
|
||||||
await asset.load();
|
|
||||||
set(map, [ ...asset.base.split('/'), asset.name ], asset);
|
|
||||||
return asset;
|
|
||||||
}))).filter(Boolean);
|
|
||||||
|
|
||||||
Object.freeze(map);
|
|
||||||
|
|
||||||
function within (dir) {
|
|
||||||
const subset = filter(assets, { dir });
|
|
||||||
return {
|
|
||||||
get titlecard () {
|
|
||||||
return get(filter(subset, { name: 'titlecard' }), [ 0, 'url' ]);
|
|
||||||
},
|
|
||||||
get assets () {
|
|
||||||
return keyBy(subset.map((a) => a.webready()), 'name');
|
|
||||||
},
|
|
||||||
get all () {
|
|
||||||
return [ ...subset ];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
map,
|
|
||||||
for: memoize(within),
|
|
||||||
get tasks () {
|
|
||||||
return assets.map((a) => a.tasks()).flat(1);
|
|
||||||
},
|
|
||||||
get all () {
|
|
||||||
return [ ...assets ];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Asset = Asset;
|
|
@ -4,7 +4,7 @@ const path = require('path');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const log = require('fancy-log');
|
const log = require('fancy-log');
|
||||||
const { minify } = require('html-minifier-terser');
|
const { minify } = require('html-minifier-terser');
|
||||||
const { resolve, readFile } = require('./resolve');
|
const { resolve, readFile, ENGINE } = require('./resolve');
|
||||||
|
|
||||||
const handlebars = require('handlebars');
|
const handlebars = require('handlebars');
|
||||||
const HandlebarsKit = require('hbs-kit');
|
const HandlebarsKit = require('hbs-kit');
|
||||||
@ -121,18 +121,15 @@ module.exports = exports = async function (prod) {
|
|||||||
const shrink = (input) => (prod ? minify(input, MINIFY_CONFIG) : input);
|
const shrink = (input) => (prod ? minify(input, MINIFY_CONFIG) : input);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
hbs: (source, env) => {
|
[ENGINE.HANDLEBARS]: (source, env) => {
|
||||||
const template = handlebars.compile(source);
|
const template = handlebars.compile(source);
|
||||||
return shrink(template(env));
|
return shrink(template(env));
|
||||||
},
|
},
|
||||||
md: (source, env) => shrink(pageTemplate({ ...env, contents: markdown('full', source, env) })),
|
[ENGINE.MARKDOWN]: (source, env) => shrink(pageTemplate({ ...env, contents: markdown('full', source, env) })),
|
||||||
raw: (source) => shrink(source),
|
[ENGINE.OTHER]: (source) => shrink(source),
|
||||||
preview: (source, env) => markdown('preview', source, env),
|
PREVIEW: (source, env) => markdown('preview', source, env),
|
||||||
};
|
};
|
||||||
|
|
||||||
// result.handlebars.engine = handlebars;
|
|
||||||
// result.markdown.engine = markdownEngines.full;
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,11 +150,11 @@ class Injectables {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_template (tpath, make) {
|
_template (tpath, make) {
|
||||||
|
if (!tpath) throw new Error('Received an empty template path: ' + tpath);
|
||||||
if (this.injections[tpath]) return this.injections[tpath];
|
if (this.injections[tpath]) return this.injections[tpath];
|
||||||
|
|
||||||
if (!fs.existsSync(tpath)) {
|
if (!fs.existsSync(tpath)) {
|
||||||
log.error('Injectable does not exist: ' + tpath);
|
throw new Error('Injectable does not exist: ' + tpath);
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents;
|
let contents;
|
||||||
@ -226,7 +223,7 @@ class Injectables {
|
|||||||
const contents = self._template(tpath, handlebars.compile)(context);
|
const contents = self._template(tpath, handlebars.compile)(context);
|
||||||
return new handlebars.SafeString(contents);
|
return new handlebars.SafeString(contents);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Could not execute import template ' + path.relative(ROOT, tpath), e);
|
log.error('Could not execute import template ' + tpath, e);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -245,7 +242,7 @@ class Injectables {
|
|||||||
|
|
||||||
return new handlebars.SafeString(contents);
|
return new handlebars.SafeString(contents);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('Could not execute import template ' + path.relative(ROOT, tpath), e);
|
log.error('Could not execute import template ' + tpath, e);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,4 +1,4 @@
|
|||||||
const { sortBy } = require('lodash');
|
const { sortBy, uniqBy } = require('lodash');
|
||||||
const { resolve } = require('./resolve');
|
const { resolve } = require('./resolve');
|
||||||
const log = require('fancy-log');
|
const log = require('fancy-log');
|
||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird');
|
||||||
@ -16,7 +16,10 @@ const LOG = {
|
|||||||
module.exports = exports = async function process (tasks, cache) {
|
module.exports = exports = async function process (tasks, cache) {
|
||||||
const lastSeen = new Date();
|
const lastSeen = new Date();
|
||||||
|
|
||||||
await Promise.map(sortBy(tasks, [ 'input', 'output' ]), async (task) => {
|
tasks = uniqBy(tasks, 'output');
|
||||||
|
tasks = sortBy(tasks, [ 'input', 'output' ]);
|
||||||
|
|
||||||
|
await Promise.map(tasks, async (task) => {
|
||||||
let result;
|
let result;
|
||||||
let status = await cache.get(task);
|
let status = await cache.get(task);
|
||||||
const { input, output } = task;
|
const { input, output } = task;
|
||||||
|
91
gulp/content/file.js
Normal file
91
gulp/content/file.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const { pick } = require('lodash');
|
||||||
|
const {
|
||||||
|
normalizedExt,
|
||||||
|
kind,
|
||||||
|
type,
|
||||||
|
} = require('./resolve');
|
||||||
|
const actions = require('./actions');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = exports = class File {
|
||||||
|
|
||||||
|
constructor (filepath) {
|
||||||
|
if (filepath && typeof filepath === 'object') {
|
||||||
|
// we've been passed a json object, treat as serialized Page
|
||||||
|
Object.assign(this, filepath);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = path.parse(filepath);
|
||||||
|
let { base: basename, name } = file;
|
||||||
|
|
||||||
|
this.preprocessed = false;
|
||||||
|
if (name[0] === '_') {
|
||||||
|
this.preprocessed = true;
|
||||||
|
file.name = name = name.slice(1);
|
||||||
|
file.basename = basename = basename.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the public root and any _images segment from the dir
|
||||||
|
const dir = file.dir.split('/');
|
||||||
|
if (dir[0] === 'public') dir.shift();
|
||||||
|
const i = dir.indexOf('_images');
|
||||||
|
if (i > -1) dir.splice(i, 1);
|
||||||
|
|
||||||
|
this.kind = kind(filepath);
|
||||||
|
this.type = type(filepath);
|
||||||
|
this.cwd = file.dir;
|
||||||
|
this.ext = this.preprocessed ? file.ext : normalizedExt(file.ext);
|
||||||
|
this.input = filepath; // public/file.ext
|
||||||
|
this.base = path.join(...dir); // '', 'folder', 'folder/subfolder'
|
||||||
|
this.dir = path.join('/', ...dir); // /, /folder, /folder/subfolder
|
||||||
|
this.name = name; // index, fileA, fileB
|
||||||
|
this.basename = basename; // index.ext, fileA.ext, fileB.ext
|
||||||
|
this.ext = file.ext;
|
||||||
|
|
||||||
|
this.out = path.join(this.base, `${this.name}${this.ext}`);
|
||||||
|
this.url = path.join(this.dir, `${this.name}${this.ext}`);
|
||||||
|
|
||||||
|
this.serializable = [
|
||||||
|
'kind',
|
||||||
|
'type',
|
||||||
|
'ext',
|
||||||
|
'input',
|
||||||
|
'base',
|
||||||
|
'dir',
|
||||||
|
'name',
|
||||||
|
'basename',
|
||||||
|
'ext',
|
||||||
|
'out',
|
||||||
|
'url',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
load () {}
|
||||||
|
|
||||||
|
tasks () {
|
||||||
|
return [ {
|
||||||
|
input: this.input,
|
||||||
|
output: this.out,
|
||||||
|
action: actions.copy,
|
||||||
|
} ];
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson () {
|
||||||
|
return pick(this.serializable, [
|
||||||
|
'preprocessed',
|
||||||
|
'type',
|
||||||
|
'kind',
|
||||||
|
'input',
|
||||||
|
'base',
|
||||||
|
'dir',
|
||||||
|
'name',
|
||||||
|
'basename',
|
||||||
|
'ext',
|
||||||
|
'dimensions',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
@ -1,69 +1,52 @@
|
|||||||
|
|
||||||
const createAssetFinder = require('./assets');
|
const loadPublicFiles = require('./public');
|
||||||
const Cache = require('./cache');
|
const Cache = require('./cache');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
const primeTweets = require('./page-tweets');
|
||||||
|
const pageWriter = require('./page-writer');
|
||||||
const evaluate = require('./evaluate');
|
const evaluate = require('./evaluate');
|
||||||
|
const { resolve } = require('./resolve');
|
||||||
|
|
||||||
const pages = require('./pages');
|
|
||||||
|
|
||||||
const twitter = require('./twitter');
|
|
||||||
const favicon = require('./favicon');
|
const favicon = require('./favicon');
|
||||||
const assets = () => createAssetFinder().then(({ tasks }) => tasks);
|
const svg = require('./svg');
|
||||||
|
|
||||||
exports.everything = function (prod = false) {
|
exports.everything = function (prod = false) {
|
||||||
const fn = async () => {
|
const fn = async () => {
|
||||||
|
|
||||||
const AssetFinder = await createAssetFinder();
|
// load a directory scan of the public folder
|
||||||
|
const PublicFiles = await loadPublicFiles();
|
||||||
|
|
||||||
await pages.parse(AssetFinder);
|
// load data for all the files in that folder
|
||||||
|
await Promise.map(PublicFiles.all, (p) => p.load(PublicFiles));
|
||||||
|
|
||||||
|
// prime tweet data for all pages
|
||||||
|
const pages = await primeTweets(PublicFiles.pages);
|
||||||
|
|
||||||
|
// compile all tasks to be completed
|
||||||
const tasks = await Promise.all([
|
const tasks = await Promise.all([
|
||||||
AssetFinder.tasks,
|
PublicFiles.tasks,
|
||||||
twitter(prod),
|
svg(prod),
|
||||||
favicon(prod),
|
favicon(prod),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!tasks.length) return;
|
async function crankTasks () {
|
||||||
|
if (!tasks.length) return;
|
||||||
|
const cache = new Cache({ prod });
|
||||||
|
await cache.load();
|
||||||
|
await evaluate(tasks.flat(), cache);
|
||||||
|
await cache.save();
|
||||||
|
}
|
||||||
|
|
||||||
const cache = new Cache({ prod });
|
await Promise.all([
|
||||||
await cache.load();
|
fs.writeFile(resolve('pages.json'), JSON.stringify(pages.map((p) => p.toJson()), null, 2)),
|
||||||
await evaluate(tasks.flat(), cache);
|
pageWriter(pages, prod),
|
||||||
await cache.save();
|
crankTasks(),
|
||||||
|
]);
|
||||||
await pages.write(prod);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ret = () => fn().catch((err) => { console.log(err.trace || err); throw err; });
|
const ret = () => fn().catch((err) => { console.log(err.trace || err); throw err; });
|
||||||
ret.displayName = prod ? 'generateEverythingForProd' : 'generateEverything';
|
ret.displayName = prod ? 'generateEverythingForProd' : 'generateEverything';
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.task = function (action, prod = false) {
|
|
||||||
let fn;
|
|
||||||
|
|
||||||
if (action === 'parse') {
|
|
||||||
fn = () => pages.parse();
|
|
||||||
} else if (action === 'pages') {
|
|
||||||
fn = () => pages.write(prod);
|
|
||||||
} else {
|
|
||||||
fn = async () => {
|
|
||||||
const tasks = await {
|
|
||||||
twitter,
|
|
||||||
favicon,
|
|
||||||
assets,
|
|
||||||
}[action](prod);
|
|
||||||
|
|
||||||
if (!tasks.length) return;
|
|
||||||
|
|
||||||
const cache = new Cache({ prod });
|
|
||||||
await cache.load();
|
|
||||||
await evaluate(tasks, cache);
|
|
||||||
await cache.save();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = () => fn().catch((err) => { console.log(err.trace || err); throw err; });
|
|
||||||
ret.displayName = prod ? action + 'ForProd' : action;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
89
gulp/content/page-tweets.js
Normal file
89
gulp/content/page-tweets.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
const { chunk, uniq, difference } = require('lodash');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const { resolve } = require('./resolve');
|
||||||
|
const log = require('fancy-log');
|
||||||
|
const tweetparse = require('../lib/tweetparse');
|
||||||
|
const Twitter = require('twitter-lite');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = exports = async function tweets (pages) {
|
||||||
|
const [ twitter, twitterBackup, twitterCache ] = await Promise.all([
|
||||||
|
fs.readJson(resolve('twitter-config.json')).catch(() => null)
|
||||||
|
.then(getTwitterClient),
|
||||||
|
fs.readJson(resolve('twitter-backup.json')).catch(() => ({})),
|
||||||
|
fs.readJson(resolve('twitter-cache.json')).catch(() => ({})),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let tweetsNeeded = [];
|
||||||
|
const tweetsPresent = Object.keys(twitterCache);
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
if (!page.tweets || !page.tweets.length) continue;
|
||||||
|
|
||||||
|
const missing = difference(page.tweets, tweetsPresent);
|
||||||
|
tweetsNeeded.push(...missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
tweetsNeeded = uniq(tweetsNeeded);
|
||||||
|
|
||||||
|
/* Load Missing Tweets **************************************************/
|
||||||
|
|
||||||
|
if (tweetsNeeded.length) {
|
||||||
|
log('Fetching tweets: ' + tweetsNeeded.join(', '));
|
||||||
|
const arriving = await Promise.all(chunk(tweetsNeeded, 99).map(twitter));
|
||||||
|
const loaded = [];
|
||||||
|
for (const tweet of arriving.flat(1)) {
|
||||||
|
if (!twitterBackup[tweet.id_str]) twitterBackup[tweet.id_str] = tweet;
|
||||||
|
twitterCache[tweet.id_str] = tweetparse(tweet);
|
||||||
|
loaded.push(tweet.id_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
const absent = difference(tweetsNeeded, loaded);
|
||||||
|
for (const id of absent) {
|
||||||
|
if (twitterBackup[id]) {
|
||||||
|
log('Pulled tweet from backup ' + id);
|
||||||
|
twitterCache[id] = tweetparse(twitterBackup[id]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.error('Could not find tweet ' + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply Tweets to Pages **************************************************/
|
||||||
|
|
||||||
|
const twitterMedia = [];
|
||||||
|
|
||||||
|
// now loop through pages and substitute the tweet data for the ids
|
||||||
|
for (const page of pages) {
|
||||||
|
if (!page.tweets || !page.tweets.length) continue;
|
||||||
|
|
||||||
|
page.tweets = page.tweets.reduce((dict, tweetid) => {
|
||||||
|
const tweet = twitterCache[tweetid];
|
||||||
|
if (!tweet) {
|
||||||
|
log.error(`Tweet ${tweetid} is missing from the cache.`);
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
dict[tweetid] = tweet;
|
||||||
|
twitterMedia.push( ...tweet.media );
|
||||||
|
return dict;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(resolve('twitter-media.json'), JSON.stringify(twitterMedia, null, 2)),
|
||||||
|
fs.writeFile(resolve('twitter-cache.json'), JSON.stringify(twitterCache, null, 2)),
|
||||||
|
fs.writeFile(resolve('twitter-backup.json'), JSON.stringify(twitterBackup, null, 2)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Utility Functions **************************************************/
|
||||||
|
|
||||||
|
function getTwitterClient (config) {
|
||||||
|
if (!config) return () => [];
|
||||||
|
const client = new Twitter(config);
|
||||||
|
return (tweetids) => client
|
||||||
|
.get('statuses/lookup', { id: tweetids.join(','), tweet_mode: 'extended' })
|
||||||
|
.catch((e) => { log.error(e); return []; });
|
||||||
|
}
|
56
gulp/content/page-writer.js
Normal file
56
gulp/content/page-writer.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const getEngines = require('./engines');
|
||||||
|
const { resolve, ROOT } = require('./resolve');
|
||||||
|
const { siteInfo } = require(resolve('package.json'));
|
||||||
|
|
||||||
|
module.exports = exports = async function writePageContent (pages, prod) {
|
||||||
|
const engines = await getEngines(prod);
|
||||||
|
|
||||||
|
await Promise.map(pages, async (page) => {
|
||||||
|
// page = new Page(page);
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
...page,
|
||||||
|
meta: { ...page.meta, ...page },
|
||||||
|
page: {
|
||||||
|
domain: siteInfo.domain,
|
||||||
|
title: page.meta.title
|
||||||
|
? (page.meta.title + (page.meta.subtitle ? ', ' + page.meta.subtitle : '') + ' :: ' + siteInfo.title)
|
||||||
|
: siteInfo.title,
|
||||||
|
description: page.meta.description || siteInfo.description,
|
||||||
|
},
|
||||||
|
site: siteInfo,
|
||||||
|
local: {
|
||||||
|
cwd: resolve(page.cwd),
|
||||||
|
root: ROOT,
|
||||||
|
basename: page.basename,
|
||||||
|
},
|
||||||
|
pages,
|
||||||
|
};
|
||||||
|
|
||||||
|
const html = String(engines[page.engine](data.source, data));
|
||||||
|
const json = page.json && {
|
||||||
|
url: page.fullurl,
|
||||||
|
title: page.meta.title,
|
||||||
|
subtitle: page.meta.subtitle,
|
||||||
|
description: page.meta.description,
|
||||||
|
tweets: page.tweets,
|
||||||
|
images: page.images,
|
||||||
|
dateCreated: page.dateCreated,
|
||||||
|
dateModified: page.dateModified,
|
||||||
|
titlecard: page.titlecard,
|
||||||
|
preview: page.engine === 'md' && String(engines.preview(data.source, data)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = resolve('dist', page.output);
|
||||||
|
await fs.ensureDir(path.dirname(output));
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(output, Buffer.from(html)),
|
||||||
|
json && fs.writeFile(resolve('dist', page.json), Buffer.from(
|
||||||
|
prod ? JSON.stringify(json) : JSON.stringify(json, null, 2),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}, { concurrency: 1 });
|
||||||
|
};
|
@ -3,74 +3,42 @@ const path = require('path');
|
|||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const log = require('fancy-log');
|
const log = require('fancy-log');
|
||||||
const frontmatter = require('front-matter');
|
const File = require('./file');
|
||||||
|
const actions = require('./actions');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
const { pick, omit } = require('lodash');
|
const { resolve, readFile, isCleanUrl, ENGINE } = require('./resolve');
|
||||||
const { resolve, readFile } = require('./resolve');
|
const { isObject } = require('../lib/util');
|
||||||
|
|
||||||
const pkg = require(resolve('package.json'));
|
const pkg = require(resolve('package.json'));
|
||||||
|
const frontmatter = require('front-matter');
|
||||||
|
|
||||||
|
module.exports = exports = class Page extends File {
|
||||||
/* Utility Functions **************************************************/
|
|
||||||
|
|
||||||
const MD = '.md';
|
|
||||||
const HBS = '.hbs';
|
|
||||||
const HTML = '.html';
|
|
||||||
const XML = '.xml';
|
|
||||||
|
|
||||||
const tweeturl = /https?:\/\/twitter\.com\/(?:#!\/)?(?:\w+)\/status(?:es)?\/(\d+)/i;
|
|
||||||
const tweetidcheck = /^\d+$/;
|
|
||||||
function parseTweetId (tweetid) {
|
|
||||||
// we can't trust an id that isn't a string
|
|
||||||
if (typeof tweetid !== 'string') return false;
|
|
||||||
|
|
||||||
const match = tweetid.match(tweeturl);
|
|
||||||
if (match) return match[1];
|
|
||||||
if (tweetid.match(tweetidcheck)) return tweetid;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = exports = class Page {
|
|
||||||
|
|
||||||
constructor (filepath) {
|
constructor (filepath) {
|
||||||
if (filepath && typeof filepath === 'object') {
|
super(filepath);
|
||||||
// we've been passed a json object, treat as serialized Page
|
|
||||||
Object.assign(this, filepath);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = path.parse(filepath);
|
this.serializable.push(
|
||||||
const { base: basename, name, ext } = file;
|
'fullurl',
|
||||||
|
'engine',
|
||||||
|
'source',
|
||||||
|
'meta',
|
||||||
|
'images',
|
||||||
|
'titlecard',
|
||||||
|
'tweets',
|
||||||
|
'dateCreated',
|
||||||
|
'dateModified',
|
||||||
|
'classes',
|
||||||
|
'flags',
|
||||||
|
);
|
||||||
|
|
||||||
// this file is an include, skip it.
|
var isIndexPage = (this.name === 'index');
|
||||||
if (name[0] === '_') return false;
|
var isClean = isCleanUrl(this.ext);
|
||||||
|
|
||||||
// this is not a page file
|
if (isClean && isIndexPage) {
|
||||||
if (![ MD, HBS, HTML, XML ].includes(ext)) return false;
|
|
||||||
|
|
||||||
// remove the pages root and any _images segment from the dir
|
|
||||||
const dir = file.dir.split('/');
|
|
||||||
if (dir[0] === 'pages') dir.shift();
|
|
||||||
const i = dir.indexOf('_images');
|
|
||||||
if (i > -1) dir.splice(i, 1);
|
|
||||||
|
|
||||||
this.input = filepath; // /local/path/to/pages/file.ext
|
|
||||||
this.cwd = file.dir; // /local/path/to/pages/, pages/folder, pages/folder/subfolder
|
|
||||||
this.base = path.join(...dir); // '', 'folder', 'folder/subfolder'
|
|
||||||
this.dir = path.join('/', ...dir); // /, /folder, /folder/subfolder
|
|
||||||
this.name = name; // index, fileA, fileB
|
|
||||||
this.basename = basename; // index.ext, fileA.ext, fileB.ext
|
|
||||||
this.ext = file.ext;
|
|
||||||
|
|
||||||
var isIndexPage = (name === 'index');
|
|
||||||
var isCleanUrl = [ HBS, MD ].includes(ext);
|
|
||||||
|
|
||||||
if (isCleanUrl && isIndexPage) {
|
|
||||||
this.output = path.join(this.base, 'index.html');
|
this.output = path.join(this.base, 'index.html');
|
||||||
this.json = path.join(this.base, 'index.json');
|
this.json = path.join(this.base, 'index.json');
|
||||||
this.url = this.dir;
|
this.url = this.dir;
|
||||||
} else if (isCleanUrl) {
|
} else if (isClean) {
|
||||||
this.output = path.join(this.base, this.name, 'index.html');
|
this.output = path.join(this.base, this.name, 'index.html');
|
||||||
this.json = path.join(this.base, this.name + '.json');
|
this.json = path.join(this.base, this.name + '.json');
|
||||||
this.url = path.join(this.dir, this.name);
|
this.url = path.join(this.dir, this.name);
|
||||||
@ -88,23 +56,16 @@ module.exports = exports = class Page {
|
|||||||
url.pathname = this.url;
|
url.pathname = this.url;
|
||||||
this.fullurl = url.href;
|
this.fullurl = url.href;
|
||||||
|
|
||||||
if ([ HBS, HTML, XML ].includes(ext)) {
|
this.engine = ENGINE[this.type] || ENGINE.COPY;
|
||||||
this.engine = 'hbs';
|
|
||||||
} else if (ext === MD) {
|
|
||||||
this.engine = 'md';
|
|
||||||
} else {
|
|
||||||
this.engine = 'raw';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load ({ Assets }) {
|
async load (PublicFiles) {
|
||||||
const [ raw, { ctime, mtime } ] = await Promise.all([
|
const [ raw, { ctime, mtime } ] = await Promise.all([
|
||||||
readFile(this.input).catch(() => null),
|
readFile(this.input).catch(() => null),
|
||||||
fs.stat(this.input).catch(() => ({})),
|
fs.stat(this.input).catch(() => ({})),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { titlecard, assets } = Assets.for(this.dir);
|
const { titlecard, assets } = PublicFiles.for(this.dir);
|
||||||
|
|
||||||
// empty file
|
// empty file
|
||||||
if (!raw || !ctime) {
|
if (!raw || !ctime) {
|
||||||
@ -137,36 +98,27 @@ module.exports = exports = class Page {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson () {
|
tasks () {
|
||||||
const j = pick(this, [
|
if (!isObject(this.tweets)) return [];
|
||||||
'input',
|
|
||||||
'output',
|
|
||||||
'json',
|
|
||||||
'dateCreated',
|
|
||||||
'dateModified',
|
|
||||||
'cwd',
|
|
||||||
'base',
|
|
||||||
'dir',
|
|
||||||
'name',
|
|
||||||
'ext',
|
|
||||||
'basename',
|
|
||||||
'dest',
|
|
||||||
'out',
|
|
||||||
'url',
|
|
||||||
'fullurl',
|
|
||||||
'engine',
|
|
||||||
'source',
|
|
||||||
'images',
|
|
||||||
'assets',
|
|
||||||
'titlecard',
|
|
||||||
'tweets',
|
|
||||||
'classes',
|
|
||||||
'flags',
|
|
||||||
]);
|
|
||||||
|
|
||||||
j.meta = omit(this.meta, [ 'date', 'classes', 'tweets' ]);
|
return Object.values(this.tweets)
|
||||||
|
.map((t) => t.media)
|
||||||
return j;
|
.flat()
|
||||||
|
.map((m) => ({ ...m, action: actions.fetch, output: m.output }));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Utility Functions **************************************************/
|
||||||
|
|
||||||
|
const tweeturl = /https?:\/\/twitter\.com\/(?:#!\/)?(?:\w+)\/status(?:es)?\/(\d+)/i;
|
||||||
|
const tweetidcheck = /^\d+$/;
|
||||||
|
function parseTweetId (tweetid) {
|
||||||
|
// we can't trust an id that isn't a string
|
||||||
|
if (typeof tweetid !== 'string') return false;
|
||||||
|
|
||||||
|
const match = tweetid.match(tweeturl);
|
||||||
|
if (match) return match[1];
|
||||||
|
if (tweetid.match(tweetidcheck)) return tweetid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -1,161 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const glob = require('../lib/glob');
|
|
||||||
const { chunk, uniq, difference } = require('lodash');
|
|
||||||
const Promise = require('bluebird');
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
const log = require('fancy-log');
|
|
||||||
const tweetparse = require('../lib/tweetparse');
|
|
||||||
const getEngines = require('./renderers');
|
|
||||||
const Twitter = require('twitter-lite');
|
|
||||||
const Page = require('./page');
|
|
||||||
const createAssetFinder = require('./assets');
|
|
||||||
const { resolve, ROOT } = require('./resolve');
|
|
||||||
|
|
||||||
exports.parse = async function parsePageContent (assetFinder) {
|
|
||||||
const [ files, twitter, twitterBackup, twitterCache, Assets ] = await Promise.all([
|
|
||||||
glob('pages/**/*.{md,hbs,html,xml}', { cwd: ROOT }),
|
|
||||||
fs.readJson(resolve('twitter-config.json')).catch(() => null)
|
|
||||||
.then(getTwitterClient),
|
|
||||||
fs.readJson(resolve('twitter-backup.json')).catch(() => ({})),
|
|
||||||
fs.readJson(resolve('twitter-cache.json')).catch(() => ({})),
|
|
||||||
assetFinder || createAssetFinder(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
let tweetsNeeded = [];
|
|
||||||
const tweetsPresent = Object.keys(twitterCache);
|
|
||||||
|
|
||||||
let pages = await Promise.map(files, async (filepath) => {
|
|
||||||
const page = new Page(filepath);
|
|
||||||
if (!page.input) return;
|
|
||||||
await page.load({ Assets });
|
|
||||||
|
|
||||||
if (page.tweets.length) {
|
|
||||||
const missing = difference(page.tweets, tweetsPresent);
|
|
||||||
tweetsNeeded.push(...missing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return page;
|
|
||||||
});
|
|
||||||
|
|
||||||
pages = pages.filter(Boolean);
|
|
||||||
tweetsNeeded = uniq(tweetsNeeded);
|
|
||||||
|
|
||||||
/* Load Missing Tweets **************************************************/
|
|
||||||
|
|
||||||
if (tweetsNeeded.length) {
|
|
||||||
log('Fetching tweets: ' + tweetsNeeded.join(', '));
|
|
||||||
const arriving = await Promise.all(chunk(tweetsNeeded, 99).map(twitter));
|
|
||||||
const loaded = [];
|
|
||||||
for (const tweet of arriving.flat(1)) {
|
|
||||||
if (!twitterBackup[tweet.id_str]) twitterBackup[tweet.id_str] = tweet;
|
|
||||||
twitterCache[tweet.id_str] = tweetparse(tweet);
|
|
||||||
loaded.push(tweet.id_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
const absent = difference(tweetsNeeded, loaded);
|
|
||||||
for (const id of absent) {
|
|
||||||
if (twitterBackup[id]) {
|
|
||||||
log('Pulled tweet from backup ' + id);
|
|
||||||
twitterCache[id] = tweetparse(twitterBackup[id]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
log.error('Could not find tweet ' + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply Tweets to Pages **************************************************/
|
|
||||||
|
|
||||||
const twitterMedia = [];
|
|
||||||
|
|
||||||
// now loop through pages and substitute the tweet data for the ids
|
|
||||||
for (const page of pages) {
|
|
||||||
if (!page.tweets || !page.tweets.length) continue;
|
|
||||||
|
|
||||||
page.tweets = page.tweets.reduce((dict, tweetid) => {
|
|
||||||
const tweet = twitterCache[tweetid];
|
|
||||||
if (!tweet) {
|
|
||||||
log.error(`Tweet ${tweetid} is missing from the cache.`);
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
dict[tweetid] = tweet;
|
|
||||||
twitterMedia.push( ...tweet.media );
|
|
||||||
return dict;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
fs.writeFile(resolve('pages.json'), JSON.stringify(pages.map((p) => p.toJson()), null, 2)),
|
|
||||||
fs.writeFile(resolve('twitter-media.json'), JSON.stringify(twitterMedia, null, 2)),
|
|
||||||
fs.writeFile(resolve('twitter-cache.json'), JSON.stringify(twitterCache, null, 2)),
|
|
||||||
fs.writeFile(resolve('twitter-backup.json'), JSON.stringify(twitterBackup, null, 2)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.write = async function writePageContent (prod) {
|
|
||||||
const [ pages, { siteInfo }, engines ] = await Promise.all([
|
|
||||||
fs.readJson(resolve('pages.json')),
|
|
||||||
fs.readJson(resolve('package.json')),
|
|
||||||
getEngines(prod),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await Promise.map(pages, async (page) => {
|
|
||||||
// page = new Page(page);
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
...page,
|
|
||||||
meta: { ...page.meta, ...page },
|
|
||||||
page: {
|
|
||||||
domain: siteInfo.domain,
|
|
||||||
title: page.meta.title
|
|
||||||
? (page.meta.title + (page.meta.subtitle ? ', ' + page.meta.subtitle : '') + ' :: ' + siteInfo.title)
|
|
||||||
: siteInfo.title,
|
|
||||||
description: page.meta.description || siteInfo.description,
|
|
||||||
},
|
|
||||||
site: siteInfo,
|
|
||||||
local: {
|
|
||||||
cwd: page.cwd,
|
|
||||||
root: ROOT,
|
|
||||||
basename: page.basename,
|
|
||||||
},
|
|
||||||
pages,
|
|
||||||
};
|
|
||||||
|
|
||||||
const html = String(engines[page.engine](data.source, data));
|
|
||||||
const json = page.json && {
|
|
||||||
url: page.fullurl,
|
|
||||||
title: page.meta.title,
|
|
||||||
subtitle: page.meta.subtitle,
|
|
||||||
description: page.meta.description,
|
|
||||||
tweets: page.tweets,
|
|
||||||
images: page.images,
|
|
||||||
dateCreated: page.dateCreated,
|
|
||||||
dateModified: page.dateModified,
|
|
||||||
titlecard: page.titlecard,
|
|
||||||
preview: page.engine === 'md' && String(engines.preview(data.source, data)),
|
|
||||||
};
|
|
||||||
|
|
||||||
const output = resolve('dist', page.output);
|
|
||||||
await fs.ensureDir(path.dirname(output));
|
|
||||||
await Promise.all([
|
|
||||||
fs.writeFile(output, Buffer.from(html)),
|
|
||||||
json && fs.writeFile(resolve('dist', page.json), Buffer.from(
|
|
||||||
prod ? JSON.stringify(json) : JSON.stringify(json, null, 2),
|
|
||||||
)),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* Utility Functions **************************************************/
|
|
||||||
|
|
||||||
function getTwitterClient (config) {
|
|
||||||
if (!config) return () => [];
|
|
||||||
const client = new Twitter(config);
|
|
||||||
return (tweetids) => client
|
|
||||||
.get('statuses/lookup', { id: tweetids.join(','), tweet_mode: 'extended' })
|
|
||||||
.catch((e) => { log.error(e); return []; });
|
|
||||||
}
|
|
62
gulp/content/public.js
Normal file
62
gulp/content/public.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
const glob = require('../lib/glob');
|
||||||
|
const { groupBy, keyBy, filter, find, get, memoize } = require('lodash');
|
||||||
|
const { ROOT, kind, KIND } = require('./resolve');
|
||||||
|
const File = require('./file');
|
||||||
|
const Asset = require('./asset');
|
||||||
|
const Page = require('./page');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
|
const KIND_MAP = {
|
||||||
|
[KIND.PAGE]: Page,
|
||||||
|
[KIND.ASSET]: Asset,
|
||||||
|
[KIND.OTHER]: File,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exports = async function loadPublicFiles () {
|
||||||
|
const files = await Promise.map(glob('public/**/*', { cwd: ROOT, nodir: true }), (filepath) => {
|
||||||
|
const k = kind(filepath);
|
||||||
|
const F = KIND_MAP[k];
|
||||||
|
const f = new F(filepath);
|
||||||
|
if (f.kind === KIND.PAGE && f.preprocessed) return false;
|
||||||
|
return f;
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
const {
|
||||||
|
[KIND.PAGE]: pages,
|
||||||
|
[KIND.ASSET]: assets,
|
||||||
|
} = groupBy(files, 'kind');
|
||||||
|
|
||||||
|
function within (dir) {
|
||||||
|
const subset = filter(files, { dir });
|
||||||
|
|
||||||
|
const getTitlecard = memoize(() =>
|
||||||
|
get(find(files, { name: 'titlecard' }), [ 0, 'url' ]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
[KIND.PAGE]: subpages,
|
||||||
|
[KIND.ASSET]: subassets,
|
||||||
|
} = groupBy(subset, 'kind');
|
||||||
|
|
||||||
|
return {
|
||||||
|
all: subset,
|
||||||
|
get titlecard () { return getTitlecard; },
|
||||||
|
get pages () {
|
||||||
|
return subpages;
|
||||||
|
},
|
||||||
|
get assets () {
|
||||||
|
return keyBy(subassets, 'name');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
all: files,
|
||||||
|
pages,
|
||||||
|
assets,
|
||||||
|
for: memoize(within),
|
||||||
|
get tasks () {
|
||||||
|
return files.map((a) => a.tasks()).flat(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -2,6 +2,129 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const ROOT = path.resolve(__dirname, '../..');
|
const ROOT = path.resolve(__dirname, '../..');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const { is: _is, re } = require('../lib/util');
|
||||||
|
|
||||||
|
function is (...args) {
|
||||||
|
const fn = _is(...args);
|
||||||
|
const ret = (ext) => fn(normalizedExt(ext));
|
||||||
|
ret.matching = args;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dictMatch (dict, def) {
|
||||||
|
const arr = Object.entries(dict);
|
||||||
|
|
||||||
|
return (tok) => {
|
||||||
|
for (const [ key, fn ] of arr) {
|
||||||
|
// console.log({ key, tok, r: fn(tok), matching: fn.matching })
|
||||||
|
if (fn(tok)) return key;
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXT = exports.EXT = {
|
||||||
|
JPG: '.jpg',
|
||||||
|
JPEG: '.jpeg',
|
||||||
|
PNG: '.png',
|
||||||
|
GIF: '.gif',
|
||||||
|
MP4: '.mp4',
|
||||||
|
M4V: '.m4v',
|
||||||
|
MD: '.md',
|
||||||
|
HBS: '.hbs',
|
||||||
|
HTML: '.html',
|
||||||
|
XML: '.xml',
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
JPG,
|
||||||
|
JPEG,
|
||||||
|
PNG,
|
||||||
|
GIF,
|
||||||
|
MP4,
|
||||||
|
M4V,
|
||||||
|
MD,
|
||||||
|
HBS,
|
||||||
|
HTML,
|
||||||
|
XML,
|
||||||
|
} = EXT;
|
||||||
|
|
||||||
|
exports.RE = {
|
||||||
|
JPG: re(/.jpg$/),
|
||||||
|
JPEG: re(/.jpeg$/),
|
||||||
|
PNG: re(/.png$/),
|
||||||
|
GIF: re(/.gif$/),
|
||||||
|
MP4: re(/.mp4$/),
|
||||||
|
M4V: re(/.m4v$/),
|
||||||
|
MD: re(/.md$/),
|
||||||
|
HBS: re(/.hbs$/),
|
||||||
|
HTML: re(/.html$/),
|
||||||
|
XML: re(/.xml$/),
|
||||||
|
};
|
||||||
|
|
||||||
|
const NORMALIZE_EXT = {
|
||||||
|
[JPG]: JPEG,
|
||||||
|
[M4V]: MP4,
|
||||||
|
[HBS]: HTML,
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizedExt = exports.normalizedExt = (ext) => {
|
||||||
|
if (ext[0] !== '.') ext = '.' + ext.split('.').pop();
|
||||||
|
return NORMALIZE_EXT[ext] || ext;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isVideo = exports.isVideo = is(MP4, M4V);
|
||||||
|
const isImage = exports.isImage = is(JPG, JPEG, PNG, GIF);
|
||||||
|
const isHandlebars = exports.isHandlebars = is(XML, HBS, HTML);
|
||||||
|
const isMarkdown = exports.isMarkdown = is(MD);
|
||||||
|
const isPage = exports.isPage = is(isHandlebars, isMarkdown);
|
||||||
|
const isAsset = exports.isAsset = is(isImage, isVideo);
|
||||||
|
exports.isCleanUrl = is(HBS, HTML, MD);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const TYPE = exports.TYPE = {
|
||||||
|
IMAGE: 'IMAGE',
|
||||||
|
VIDEO: 'VIDEO',
|
||||||
|
HANDLEBARS: 'HANDLEBARS',
|
||||||
|
MARKDOWN: 'MARKDOWN',
|
||||||
|
OTHER: 'OTHER',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.type = dictMatch({
|
||||||
|
[TYPE.IMAGE]: isImage,
|
||||||
|
[TYPE.HANDLEBARS]: isHandlebars,
|
||||||
|
[TYPE.MARKDOWN]: isMarkdown,
|
||||||
|
[TYPE.VIDEO]: isVideo,
|
||||||
|
}, TYPE.OTHER);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const KIND = exports.KIND = {
|
||||||
|
PAGE: 'PAGE',
|
||||||
|
ASSET: 'ASSET',
|
||||||
|
OTHER: 'OTHER',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.kind = dictMatch({
|
||||||
|
[KIND.ASSET]: isAsset,
|
||||||
|
[KIND.PAGE]: isPage,
|
||||||
|
}, KIND.OTHER);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const ENGINE = exports.ENGINE = {
|
||||||
|
HANDLEBARS: 'HANDLEBARS',
|
||||||
|
MARKDOWN: 'MARKDOWN',
|
||||||
|
COPY: 'COPY',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.engine = dictMatch({
|
||||||
|
[ENGINE.HANDLEBARS]: is(XML, HBS, HTML),
|
||||||
|
[ENGINE.MARKDOWN]: is(MD),
|
||||||
|
}, ENGINE.COPY);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports.readFile = function readFile (fpath) {
|
exports.readFile = function readFile (fpath) {
|
||||||
fpath = exports.resolve(fpath);
|
fpath = exports.resolve(fpath);
|
||||||
@ -15,7 +138,7 @@ exports.resolve = function resolve (...args) {
|
|||||||
let fpath = args.shift();
|
let fpath = args.shift();
|
||||||
if (!fpath) return ROOT;
|
if (!fpath) return ROOT;
|
||||||
if (fpath[0] === '/') throw new Error('Did you mean to resolve this? ' + fpath);
|
if (fpath[0] === '/') throw new Error('Did you mean to resolve this? ' + fpath);
|
||||||
if (fpath[0] === '/') fpath = fpath.slice(1);
|
// if (fpath[0] === '/') fpath = fpath.slice(1);
|
||||||
return path.resolve(ROOT, fpath, ...args);
|
return path.resolve(ROOT, fpath, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
15
gulp/content/svg.js
Normal file
15
gulp/content/svg.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const glob = require('../lib/glob');
|
||||||
|
const { ROOT } = require('./resolve');
|
||||||
|
const actions = require('./actions');
|
||||||
|
|
||||||
|
module.exports = exports = async function svgIcons () {
|
||||||
|
const files = await glob('svg/**/*.svg', { cwd: ROOT });
|
||||||
|
|
||||||
|
const tasks = files.map((f) => ({
|
||||||
|
input: f,
|
||||||
|
output: 'images/' + f,
|
||||||
|
action: actions.copy,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
};
|
@ -1,48 +0,0 @@
|
|||||||
|
|
||||||
const path = require('path');
|
|
||||||
const { src, dest } = require('gulp');
|
|
||||||
const rev = require('gulp-rev');
|
|
||||||
const asyncthrough = require('./lib/through');
|
|
||||||
const changed = require('gulp-changed');
|
|
||||||
const merge = require('merge-stream');
|
|
||||||
|
|
||||||
const ROOT = path.dirname(__dirname);
|
|
||||||
const DEST = 'dist';
|
|
||||||
|
|
||||||
module.exports = exports = function fileCopy () {
|
|
||||||
const pageFiles = src([ 'pages/**/*', '!pages/**/*.{md,hbs,xml,html,jpeg,jpg,png,gif,mp4}' ])
|
|
||||||
.pipe(changed(DEST))
|
|
||||||
.pipe(dest(DEST))
|
|
||||||
;
|
|
||||||
|
|
||||||
const svgs = src('svg/**/*.svg')
|
|
||||||
// .pipe(changed(DEST))
|
|
||||||
.pipe(dest(path.join(DEST, 'images/svg')))
|
|
||||||
.pipe(asyncthrough(async (stream, file) => {
|
|
||||||
file.base = path.resolve(file.base, '../..');
|
|
||||||
stream.push(file);
|
|
||||||
}))
|
|
||||||
;
|
|
||||||
|
|
||||||
return merge(pageFiles, svgs);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.prod = function fileCopyForProd () {
|
|
||||||
return exports()
|
|
||||||
.pipe(rev())
|
|
||||||
.pipe(dest(DEST))
|
|
||||||
.pipe(asyncthrough(async (stream, file) => {
|
|
||||||
// Change rev's original base path back to the public root so that it uses the full
|
|
||||||
// path as the original file name key in the manifest
|
|
||||||
var base = path.resolve(ROOT, DEST);
|
|
||||||
file.revOrigBase = base;
|
|
||||||
file.base = base;
|
|
||||||
|
|
||||||
stream.push(file);
|
|
||||||
}))
|
|
||||||
.pipe(rev.manifest({
|
|
||||||
merge: true, // Merge with the existing manifest if one exists
|
|
||||||
}))
|
|
||||||
.pipe(dest('.'))
|
|
||||||
;
|
|
||||||
};
|
|
@ -5,22 +5,10 @@ const { series, parallel, watch } = require('gulp');
|
|||||||
|
|
||||||
var content = require('./content');
|
var content = require('./content');
|
||||||
|
|
||||||
const parse = exports.parse = content.task('parse');
|
|
||||||
const pages = exports.pages = content.task('pages');
|
|
||||||
exports.twitter = content.task('twitter');
|
|
||||||
exports.favicon = content.task('favicon');
|
|
||||||
exports.assets = content.task('assets');
|
|
||||||
|
|
||||||
exports.content = series(parse, pages);
|
|
||||||
|
|
||||||
const everything = content.everything();
|
const everything = content.everything();
|
||||||
everything.prod = content.everything(true);
|
everything.prod = content.everything(true);
|
||||||
|
|
||||||
|
exports.everything = everything;
|
||||||
|
|
||||||
const filesTask = require('./files');
|
|
||||||
exports.files = filesTask;
|
|
||||||
exports['files-prod'] = filesTask.prod;
|
|
||||||
|
|
||||||
var scssTask = require('./scss');
|
var scssTask = require('./scss');
|
||||||
exports.scss = scssTask;
|
exports.scss = scssTask;
|
||||||
@ -42,14 +30,12 @@ exports.cloudfront = cloudfront;
|
|||||||
var prodBuildTask = parallel(
|
var prodBuildTask = parallel(
|
||||||
scssTask.prod,
|
scssTask.prod,
|
||||||
jsTask.prod,
|
jsTask.prod,
|
||||||
filesTask.prod,
|
|
||||||
everything.prod,
|
everything.prod,
|
||||||
);
|
);
|
||||||
|
|
||||||
var devBuildTask = parallel(
|
var devBuildTask = parallel(
|
||||||
scssTask,
|
scssTask,
|
||||||
jsTask,
|
jsTask,
|
||||||
filesTask,
|
|
||||||
everything,
|
everything,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -68,11 +54,9 @@ exports.testpush = pushToProd.dryrun;
|
|||||||
function watcher () {
|
function watcher () {
|
||||||
|
|
||||||
watch([
|
watch([
|
||||||
'pages/**/*.{md,hbs,html}',
|
'public/**/*',
|
||||||
'templates/*.{md,hbs,html}',
|
'templates/*.{md,hbs,html}',
|
||||||
], series(exports.parse, exports.twitter, exports.pages));
|
], everything);
|
||||||
|
|
||||||
watch('page/**/*.{jpeg,jpg,png,gif}', series(exports.assets, exports.parse, exports.pages));
|
|
||||||
|
|
||||||
watch('scss/*.scss', scssTask);
|
watch('scss/*.scss', scssTask);
|
||||||
watch('js/*.js', jsTask);
|
watch('js/*.js', jsTask);
|
||||||
@ -92,7 +76,7 @@ function server () {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.watch = series(exports.parse, exports.pages, watcher);
|
exports.watch = series(everything, watcher);
|
||||||
exports.uat = series(cleanTask, prodBuildTask, server);
|
exports.uat = series(cleanTask, prodBuildTask, server);
|
||||||
|
|
||||||
/** **************************************************************************************************************** **/
|
/** **************************************************************************************************************** **/
|
||||||
|
@ -22,6 +22,6 @@ module.exports = exports = function (iteratees) {
|
|||||||
stream.push(file);
|
stream.push(file);
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
198
gulp/lib/util.js
198
gulp/lib/util.js
@ -31,6 +31,35 @@
|
|||||||
|
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
|
||||||
|
function equals (value) {
|
||||||
|
value = uc(value);
|
||||||
|
return (tok) => uc(tok) === value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function re (pattern) {
|
||||||
|
if (isString(pattern)) pattern = new RegExp(pattern);
|
||||||
|
return (tok) => !!String(tok).match(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
function anyOf (...args) {
|
||||||
|
args = args.flat().map(uc);
|
||||||
|
if (!anyBy(args, isFunction)) {
|
||||||
|
// arguments do not contain a function, so we can optimize
|
||||||
|
if (args.length === 1) return (tok) => uc(tok) === args[0];
|
||||||
|
return (tok) => args.includes(uc(tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args.map((a) => isFunction(a) && a || equals(a));
|
||||||
|
if (args.length === 1) return (tok) => args[0](tok);
|
||||||
|
return (tok) => anyBy(args, (check) => check(tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
function allOf (...args) {
|
||||||
|
args = args.flat().map((a) => isFunction(a) && a || equals(a));
|
||||||
|
if (args.length === 1) return (tok) => args[0](tok);
|
||||||
|
return (tok) => allBy(args, (check) => check(tok));
|
||||||
|
}
|
||||||
|
|
||||||
function isNumber (input) { return typeof input === 'number' && !isNaN(input); }
|
function isNumber (input) { return typeof input === 'number' && !isNaN(input); }
|
||||||
function isString (input) { return typeof input === 'string'; }
|
function isString (input) { return typeof input === 'string'; }
|
||||||
function isBoolean (input) { return typeof input === 'boolean'; }
|
function isBoolean (input) { return typeof input === 'boolean'; }
|
||||||
@ -38,6 +67,12 @@ function isFunction (input) { return typeof input === 'function'; }
|
|||||||
function isUndefined (input) { return typeof input === 'undefined'; }
|
function isUndefined (input) { return typeof input === 'undefined'; }
|
||||||
function isMap (input) { return input instanceof Map; }
|
function isMap (input) { return input instanceof Map; }
|
||||||
function isSet (input) { return input instanceof Set; }
|
function isSet (input) { return input instanceof Set; }
|
||||||
|
function isDate (input) { return input instanceof Date; }
|
||||||
|
function isRegExp (input) { return input instanceof RegExp; }
|
||||||
|
function isTruthy (input) { return !!input; }
|
||||||
|
function isFalsey (input) { return !input; }
|
||||||
|
function isNull (input) { return input === null; }
|
||||||
|
const isArray = Array.isArray;
|
||||||
|
|
||||||
function isPrimitive (input) {
|
function isPrimitive (input) {
|
||||||
switch (typeof input) {
|
switch (typeof input) {
|
||||||
@ -50,8 +85,6 @@ function isPrimitive (input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNull (input) { return input === null; }
|
|
||||||
|
|
||||||
function isObject (input) {
|
function isObject (input) {
|
||||||
if (!input) return false;
|
if (!input) return false;
|
||||||
if (typeof input !== 'object') return false;
|
if (typeof input !== 'object') return false;
|
||||||
@ -61,13 +94,72 @@ function isObject (input) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isArray = Array.isArray;
|
|
||||||
|
const IS_LOOKUP = new Map([
|
||||||
|
[ Array, isArray ],
|
||||||
|
[ Number, isNumber ],
|
||||||
|
[ String, isString ],
|
||||||
|
[ Boolean, isBoolean ],
|
||||||
|
[ Map, isMap ],
|
||||||
|
[ Set, isSet ],
|
||||||
|
[ Function, isFunction ],
|
||||||
|
[ Date, isDate ],
|
||||||
|
[ undefined, isUndefined ],
|
||||||
|
[ true, isTruthy ],
|
||||||
|
[ false, isFalsey ],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function is (...args) {
|
||||||
|
args = args.flat().map((a) =>
|
||||||
|
IS_LOOKUP.get(a)
|
||||||
|
|| (isFunction(a) && a)
|
||||||
|
|| (isRegExp(a) && re(a))
|
||||||
|
|| equals(a),
|
||||||
|
);
|
||||||
|
if (args.length === 1) return (tok) => args[0](tok);
|
||||||
|
return (tok) => anyBy(args, (check) => check(tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAll (...args) {
|
||||||
|
args = args.flat().map((a) =>
|
||||||
|
IS_LOOKUP.get(a)
|
||||||
|
|| (isFunction(a) && a)
|
||||||
|
|| (isRegExp(a) && re(a))
|
||||||
|
|| equals(a),
|
||||||
|
);
|
||||||
|
if (args.length === 1) return (tok) => args[0](tok);
|
||||||
|
return (tok) => allBy(args, (check) => check(tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArrayOf (...args) {
|
||||||
|
const predicate = is(...args);
|
||||||
|
return (tok) => (isArray(tok) ? allBy(tok, predicate) : predicate(tok));
|
||||||
|
}
|
||||||
function isArrayOfStrings (input) { return allBy(input, isString); }
|
function isArrayOfStrings (input) { return allBy(input, isString); }
|
||||||
function isArrayOfNumbers (input) { return allBy(input, isNumber); }
|
function isArrayOfNumbers (input) { return allBy(input, isNumber); }
|
||||||
function isArrayOfBooleans (input) { return allBy(input, isBoolean); }
|
function isArrayOfBooleans (input) { return allBy(input, isBoolean); }
|
||||||
function isArrayOfObjects (input) { return allBy(input, isObject); }
|
function isArrayOfObjects (input) { return allBy(input, isObject); }
|
||||||
function isArrayOfMappables (input) { return allBy(input, isMappable); }
|
function isArrayOfMappables (input) { return allBy(input, isMappable); }
|
||||||
function isArrayOfPrimatives (input) { return allBy(input, isPrimitive); }
|
function isArrayOfPrimatives (input) { return allBy(input, isPrimitive); }
|
||||||
|
function isArrayOfFunctions (input) { return allBy(input, isFunction); }
|
||||||
|
function isArrayOfRegEx (input) { return allBy(input, isRegExp); }
|
||||||
|
function isArrayOfTruthy (input) { return allBy(input, isTruthy); }
|
||||||
|
function isArrayOfFalsey (input) { return allBy(input, isFalsey); }
|
||||||
|
|
||||||
|
function contains (...args) {
|
||||||
|
const predicate = is(...args);
|
||||||
|
return (tok) => (isArray(tok) ? anyBy(tok, predicate) : predicate(tok));
|
||||||
|
}
|
||||||
|
function containsStrings (input) { return anyBy(input, isString); }
|
||||||
|
function containsNumbers (input) { return anyBy(input, isNumber); }
|
||||||
|
function containsBooleans (input) { return anyBy(input, isBoolean); }
|
||||||
|
function containsObjects (input) { return anyBy(input, isObject); }
|
||||||
|
function containsMappables (input) { return anyBy(input, isMappable); }
|
||||||
|
function containsPrimatives (input) { return anyBy(input, isPrimitive); }
|
||||||
|
function containsFunctions (input) { return anyBy(input, isFunction); }
|
||||||
|
function containsRegEx (input) { return anyBy(input, isRegExp); }
|
||||||
|
function containsTruthy (input) { return anyBy(input, isTruthy); }
|
||||||
|
function containsFalsey (input) { return anyBy(input, isFalsey); }
|
||||||
|
|
||||||
function truthy (value) {
|
function truthy (value) {
|
||||||
if (isMappable(value)) return !!sizeOf(value);
|
if (isMappable(value)) return !!sizeOf(value);
|
||||||
@ -78,6 +170,14 @@ function hasOwn (obj, key) {
|
|||||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lc (str) {
|
||||||
|
return isString(uc) ? str.toLowerCase() : str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uc (str) {
|
||||||
|
return isString(str) ? str.toUpperCase() : str;
|
||||||
|
}
|
||||||
|
|
||||||
function ucfirst (input) {
|
function ucfirst (input) {
|
||||||
input = String(input);
|
input = String(input);
|
||||||
return input.charAt(0).toUpperCase() + input.slice(1);
|
return input.charAt(0).toUpperCase() + input.slice(1);
|
||||||
@ -215,6 +315,31 @@ function arrayify (input) {
|
|||||||
return [ input ];
|
return [ input ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function first (input, count = 1) {
|
||||||
|
if (count === 1) {
|
||||||
|
if (isArray(input) || isString(input)) return input[0];
|
||||||
|
if (isSet(input) || isObject(input)) for (const v of input) return v;
|
||||||
|
if (isMap(input)) for (const [ , v ] of input) return v;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(input) || isString(input)) return input.slice(0, count);
|
||||||
|
if (isSet(input)) return Array.from(input).slice(0, count);
|
||||||
|
if (isObject(input)) return Object.values(input).slice(0, count);
|
||||||
|
if (isMap(input)) return Array.from(input.values()).slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function last (input, count = 1) {
|
||||||
|
if (count === 1) {
|
||||||
|
if (isArray(input) || isString(input)) return input[input.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(input) || isString(input)) return input.slice(-count);
|
||||||
|
if (isSet(input)) return Array.from(input).slice(-count);
|
||||||
|
if (isObject(input)) return Object.values(input).slice(-count);
|
||||||
|
if (isMap(input)) return Array.from(input.values()).slice(-count);
|
||||||
|
}
|
||||||
|
|
||||||
function all (...args) {
|
function all (...args) {
|
||||||
let input;
|
let input;
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
@ -238,7 +363,7 @@ function allBy (collection, predicate = null) {
|
|||||||
if (!collection) return false;
|
if (!collection) return false;
|
||||||
if (predicate === null) {
|
if (predicate === null) {
|
||||||
predicate = (v) => v;
|
predicate = (v) => v;
|
||||||
} else {
|
} else if (!isFunction(predicate)) {
|
||||||
predicate = iteratee(predicate);
|
predicate = iteratee(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +425,7 @@ function anyBy (collection, predicate = null) {
|
|||||||
if (!collection) return false;
|
if (!collection) return false;
|
||||||
if (predicate === null) {
|
if (predicate === null) {
|
||||||
predicate = (v) => v;
|
predicate = (v) => v;
|
||||||
} else {
|
} else if (!isFunction(iteratee)) {
|
||||||
predicate = iteratee(predicate);
|
predicate = iteratee(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,8 +477,7 @@ function iteratee (match) {
|
|||||||
if (isObject(o)) return o[match];
|
if (isObject(o)) return o[match];
|
||||||
if (isMap(o)) return o.get(match);
|
if (isMap(o)) return o.get(match);
|
||||||
if (isSet(o)) return o.has(match);
|
if (isSet(o)) return o.has(match);
|
||||||
if (isString(o)) return o === match;
|
if (isPrimitive(o)) return o[match];
|
||||||
if (isNumber(o)) return String(o) === match;
|
|
||||||
return o === match;
|
return o === match;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -548,6 +672,22 @@ function uniq (collection, predicate = null) {
|
|||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyBy (collection, predicate) {
|
||||||
|
predicate = iteratee(predicate);
|
||||||
|
return mapReduce(collection, (value, key, index) =>
|
||||||
|
[ predicate(value, key, index), value ],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupBy (collection, predicate) {
|
||||||
|
predicate = iteratee(predicate);
|
||||||
|
return reduce(collection, (result, value, key, index) => {
|
||||||
|
const k = predicate(value, key, index);
|
||||||
|
(result[k] || (result[k] = [])).push(value);
|
||||||
|
return result;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
function filter (collection, predicate) {
|
function filter (collection, predicate) {
|
||||||
predicate = iteratee(predicate);
|
predicate = iteratee(predicate);
|
||||||
|
|
||||||
@ -722,19 +862,21 @@ function mapReduce (collection, cb) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduce (collection, cb, init) {
|
function reduce (collection, predicate, init) {
|
||||||
if (isArray(collection)) return collection.reduce(cb, init);
|
if (!isFunction(predicate)) throw new TypeError('Predicate must be a function');
|
||||||
|
|
||||||
|
if (isArray(collection)) return collection.reduce((r, v, i) => predicate(r, v, i, i), init);
|
||||||
|
|
||||||
if (isSet(collection)) {
|
if (isSet(collection)) {
|
||||||
return Array.from(collection).reduce(cb, init);
|
return Array.from(collection).reduce((r, v, i) => predicate(r, v, i, i), init);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMap(collection)) {
|
if (isMap(collection)) {
|
||||||
return Array.from(collection.entries()).reduce((prev, [ key, value ], i) => cb(prev, value, key, i), init);
|
return Array.from(collection.entries()).reduce((prev, [ key, value ], i) => predicate(prev, value, key, i), init);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isObject(collection)) {
|
if (isObject(collection)) {
|
||||||
return Object.entries(collection).reduce((prev, [ key, value ], i) => cb(prev, value, key, i), init);
|
return Object.entries(collection).reduce((prev, [ key, value ], i) => predicate(prev, value, key, i), init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1428,24 +1570,49 @@ function slugify (input, delimiter = '-', separators = false) {
|
|||||||
|
|
||||||
exports.all = all;
|
exports.all = all;
|
||||||
exports.allBy = allBy;
|
exports.allBy = allBy;
|
||||||
|
exports.allOf = allOf;
|
||||||
exports.any = any;
|
exports.any = any;
|
||||||
exports.anyBy = anyBy;
|
exports.anyBy = anyBy;
|
||||||
|
exports.anyOf = anyOf;
|
||||||
exports.arrayify = arrayify;
|
exports.arrayify = arrayify;
|
||||||
|
exports.contains = contains;
|
||||||
|
exports.containsBooleans = containsBooleans;
|
||||||
|
exports.containsFalsey = containsFalsey;
|
||||||
|
exports.containsFunctions = containsFunctions;
|
||||||
|
exports.containsMappables = containsMappables;
|
||||||
|
exports.containsNumbers = containsNumbers;
|
||||||
|
exports.containsObjects = containsObjects;
|
||||||
|
exports.containsPrimatives = containsPrimatives;
|
||||||
|
exports.containsRegEx = containsRegEx;
|
||||||
|
exports.containsStrings = containsStrings;
|
||||||
|
exports.containsTruthy = containsTruthy;
|
||||||
exports.deepPick = deepPick;
|
exports.deepPick = deepPick;
|
||||||
|
exports.equals = equals;
|
||||||
exports.filter = filter;
|
exports.filter = filter;
|
||||||
|
exports.first = first;
|
||||||
exports.flatten = flatten;
|
exports.flatten = flatten;
|
||||||
exports.fromPairs = fromPairs;
|
exports.fromPairs = fromPairs;
|
||||||
exports.get = get;
|
exports.get = get;
|
||||||
|
exports.groupBy = groupBy;
|
||||||
exports.has = has;
|
exports.has = has;
|
||||||
exports.hasOwn = hasOwn;
|
exports.hasOwn = hasOwn;
|
||||||
|
exports.is = is;
|
||||||
|
exports.isAll = isAll;
|
||||||
exports.isArray = isArray;
|
exports.isArray = isArray;
|
||||||
|
exports.isArrayOf = isArrayOf;
|
||||||
exports.isArrayOfBooleans = isArrayOfBooleans;
|
exports.isArrayOfBooleans = isArrayOfBooleans;
|
||||||
|
exports.isArrayOfFalsey = isArrayOfFalsey;
|
||||||
|
exports.isArrayOfFunctions = isArrayOfFunctions;
|
||||||
exports.isArrayOfMappables = isArrayOfMappables;
|
exports.isArrayOfMappables = isArrayOfMappables;
|
||||||
exports.isArrayOfNumbers = isArrayOfNumbers;
|
exports.isArrayOfNumbers = isArrayOfNumbers;
|
||||||
exports.isArrayOfObjects = isArrayOfObjects;
|
exports.isArrayOfObjects = isArrayOfObjects;
|
||||||
exports.isArrayOfPrimatives = isArrayOfPrimatives;
|
exports.isArrayOfPrimatives = isArrayOfPrimatives;
|
||||||
|
exports.isArrayOfRegEx = isArrayOfRegEx;
|
||||||
exports.isArrayOfStrings = isArrayOfStrings;
|
exports.isArrayOfStrings = isArrayOfStrings;
|
||||||
|
exports.isArrayOfTruthy = isArrayOfTruthy;
|
||||||
exports.isBoolean = isBoolean;
|
exports.isBoolean = isBoolean;
|
||||||
|
exports.isDate = isDate;
|
||||||
|
exports.isFalsey = isFalsey;
|
||||||
exports.isFunction = isFunction;
|
exports.isFunction = isFunction;
|
||||||
exports.isMap = isMap;
|
exports.isMap = isMap;
|
||||||
exports.isMappable = isMappable;
|
exports.isMappable = isMappable;
|
||||||
@ -1453,17 +1620,23 @@ exports.isNull = isNull;
|
|||||||
exports.isNumber = isNumber;
|
exports.isNumber = isNumber;
|
||||||
exports.isObject = isObject;
|
exports.isObject = isObject;
|
||||||
exports.isPrimitive = isPrimitive;
|
exports.isPrimitive = isPrimitive;
|
||||||
|
exports.isRegExp = isRegExp;
|
||||||
exports.isSet = isSet;
|
exports.isSet = isSet;
|
||||||
exports.isString = isString;
|
exports.isString = isString;
|
||||||
|
exports.isTruthy = isTruthy;
|
||||||
exports.isUndefined = isUndefined;
|
exports.isUndefined = isUndefined;
|
||||||
exports.iteratee = iteratee;
|
exports.iteratee = iteratee;
|
||||||
|
exports.keyBy = keyBy;
|
||||||
exports.keys = keys;
|
exports.keys = keys;
|
||||||
|
exports.last = last;
|
||||||
|
exports.lc = lc;
|
||||||
exports.map = map;
|
exports.map = map;
|
||||||
exports.mapReduce = mapReduce;
|
exports.mapReduce = mapReduce;
|
||||||
exports.merge = merge;
|
exports.merge = merge;
|
||||||
exports.omit = omit;
|
exports.omit = omit;
|
||||||
exports.pathinate = pathinate;
|
exports.pathinate = pathinate;
|
||||||
exports.pick = pick;
|
exports.pick = pick;
|
||||||
|
exports.re = re;
|
||||||
exports.reduce = reduce;
|
exports.reduce = reduce;
|
||||||
exports.set = set;
|
exports.set = set;
|
||||||
exports.sizeOf = sizeOf;
|
exports.sizeOf = sizeOf;
|
||||||
@ -1473,6 +1646,7 @@ exports.sort = sort;
|
|||||||
exports.sorter = sorter;
|
exports.sorter = sorter;
|
||||||
exports.toPairs = toPairs;
|
exports.toPairs = toPairs;
|
||||||
exports.truthy = truthy;
|
exports.truthy = truthy;
|
||||||
|
exports.uc = uc;
|
||||||
exports.ucfirst = ucfirst;
|
exports.ucfirst = ucfirst;
|
||||||
exports.ucsentence = ucsentence;
|
exports.ucsentence = ucsentence;
|
||||||
exports.ucwords = ucwords;
|
exports.ucwords = ucwords;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user