Skip to content

Commit

Permalink
Improved collection discovery, assign collection files based on path
Browse files Browse the repository at this point in the history
  • Loading branch information
rphillips-nz committed May 19, 2022
1 parent 513efb3 commit 9b4211a
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 68 deletions.
2 changes: 1 addition & 1 deletion integration-tests/0.12.1-legacy/site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/0.12.1/site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/1.0.0-file-source/site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/1.0.0-source/site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/1.0.0/site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 60 additions & 51 deletions src/generators/collections.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
const { readdirSync } = require('fs');
const { dirname, join } = require('path');
const { bold, yellow } = require('chalk');
const { bold } = require('chalk');
const { log } = require('../util/logger.js');
const { stringifyJson } = require('../util/json.js');
const { getSourcePath, isTopPath } = require('../util/paths.js');
const { isStaticPage, isPage, hasPages, processItem } = require('../util/items.js');

function cheapPlural(amount, str) {
const amountStr = amount === 0 ? 'no' : amount;
return `${amountStr} ${str}${amount === 1 ? '' : 's'}`;
}

// Tests stringify individually to avoid one item breaking it
function processItems(items, tag, config) {
return items?.reduce?.((memo, item) => {
const processed = processItem(item, tag, config.source);
const stringified = stringifyJson(processed);

if (stringified === undefined) {
log(`⚠️ ${bold(item?.inputPath || 'unknown file')} skipped due to JSON stringify failing`);
} else {
memo.push(JSON.parse(stringified));
}
function processCollectionItem(item, collectionKey, config) {
const processed = processItem(item, collectionKey, config.source);
const stringified = stringifyJson(processed);

return memo;
}, []) || [];
if (stringified !== undefined) {
return JSON.parse(stringified);
}

log(`⚠️ ${bold(item?.inputPath || 'unknown file')} skipped due to JSON stringify failing`);
}

function hasDataFiles(dataPath) {
Expand All @@ -36,69 +27,87 @@ function hasDataFiles(dataPath) {
}
}

function getCollections(collectionsConfig, context, config) {
const { all, ...otherCollections } = context.collections;
function getCollectionKey(item, collectionsConfig, config) {
const sourcePath = getSourcePath(item?.inputPath, config.source) || '';
const parts = sourcePath.split('/');

if (!otherCollections.pages) {
otherCollections.pages = all.filter((item) => isPage(item) || isStaticPage(item));
// Find collection from config based on explicit path
for (let i = parts.length - 1; i >= 0; i--) {
const collectionPath = parts.slice(0, i).join('/');

const configKey = Object.keys(collectionsConfig || {}).find((key) => {
return collectionsConfig[key]?.path === collectionPath;
});

if (configKey) {
return configKey;
}
}

return Object.keys(collectionsConfig).reduce((memo, collectionKey) => {
const items = otherCollections[collectionKey];

if (items?.length) {
memo[collectionKey] = processItems(items, collectionKey, config);
const filesCount = cheapPlural(memo[collectionKey].length, 'file');
log(`📁 Processed ${bold(collectionKey)} collection with ${filesCount}`);
} else if (collectionsConfig[collectionKey].auto_discovered) {
log(`📂 ${yellow('Ignored')} ${bold(collectionKey)} collection`);
delete collectionsConfig[collectionKey];
} else {
log(`📁 Processed ${bold(collectionKey)} collection`);
if (isPage(item) || isStaticPage(item)) {
return 'pages';
}
}

function getCollections(collectionsConfig, context, config) {
return context.collections.all.reduce((memo, item) => {
const collectionKey = getCollectionKey(item, collectionsConfig, config);

if (!collectionKey) {
log(`⚠️ No collection for ${bold(item?.inputPath || 'unknown file')}`);
return memo;
}

const processed = processCollectionItem(item, collectionKey, config);

memo[collectionKey] = memo[collectionKey] || [];
memo[collectionKey].push(processed);

return memo;
}, {});
}

function discoverCollectionsConfig(context, config) {
const { all, ...otherCollections } = context.collections;

const guessed = all.reduce((memo, item) => {
const discovered = context.collections.all.reduce((memo, item) => {
const tag = item.data?.tags?.[0];

if (tag && item.inputPath) {
memo[tag] = memo[tag] ?? { basePaths: new Set(), outputOffset: 0 };
// Map tags to basePaths, items with same tags can exist in separate folders
const inputPath = getSourcePath(item.inputPath, config.source);
memo[tag].basePaths.add(dirname(inputPath));
// Tracks how collection items are output
const sourcePath = getSourcePath(item.inputPath, config.source);
memo[tag].basePaths.add(dirname(sourcePath));
// Tracks how many collection items are output
memo[tag].outputOffset += item.url === false ? -1 : 1;
}

return memo;
}, {});

return Object.keys(guessed).reduce((memo, key) => {
if (!otherCollections[key]) {
return memo;
}

return Object.keys(discovered).reduce((memo, tag) => {
// Finds the top-most common basePaths to prevent sub-folders becoming separate entries
const topBasePaths = Array.from(guessed[key].basePaths).filter(isTopPath);
const topBasePaths = Array.from(discovered[tag].basePaths).filter(isTopPath);

// Consider a collection output unless more items are not output
const isOutput = guessed[key].outputOffset >= 0;
const isOutput = discovered[tag].outputOffset >= 0;

topBasePaths.forEach((basePath) => {
// Multiple collections can share this basePath, but this should cover common use-cases
const collectionKey = topBasePaths.length === 1 ? key : basePath;
const customPath = memo[collectionKey]?.path;
// Finds the existing key for this base path, or creates one
// If multiple discovered collections use the same base path, the first one processed is used
const collectionKey = Object.keys(memo).find((k) => memo[k].path === basePath)
// Use the tag as the collection key if the files are all in one base path
|| (topBasePaths.length === 1 ? tag : basePath);

const existingPath = memo[collectionKey]?.path;
const autoDiscovered = !existingPath && existingPath !== '';

if (autoDiscovered) {
log(`🔍 Discovered ${bold(collectionKey)} collection`);
}

memo[collectionKey] = {
path: basePath,
output: isOutput,
auto_discovered: !customPath && customPath !== '',
auto_discovered: autoDiscovered,
...memo[collectionKey]
};
});
Expand Down
19 changes: 19 additions & 0 deletions src/generators/info.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
const { getData } = require('./data.js');
const { getGenerator } = require('./generator.js');
const { getCollections, getCollectionsConfig } = require('./collections.js');
const { bold, yellow } = require('chalk');
const { log } = require('../util/logger.js');
require('pkginfo')(module, 'name', 'version');

function cheapPlural(amount, str) {
const amountStr = amount === 0 ? 'no' : amount;
return `${amountStr} ${str}${amount === 1 ? '' : 's'}`;
}

const cloudcannon = {
name: module.exports.name,
version: module.exports.version
Expand All @@ -12,6 +19,18 @@ function getInfo(context, config) {
const collectionsConfig = getCollectionsConfig(context, config);
const collections = getCollections(collectionsConfig, context, config);

Object.keys(collectionsConfig).forEach((collectionKey) => {
if (collections[collectionKey]?.length) {
const filesCount = cheapPlural(collections[collectionKey].length, 'file');
log(`📁 Processed ${bold(collectionKey)} collection with ${filesCount}`);
} else if (collectionsConfig[collectionKey].auto_discovered) {
log(`📂 ${yellow('Ignored')} ${bold(collectionKey)} collection`);
delete collectionsConfig[collectionKey];
} else {
log(`📁 Processed ${bold(collectionKey)} collection`);
}
});

return {
...config,
cloudcannon: cloudcannon,
Expand Down
6 changes: 3 additions & 3 deletions src/util/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function isIgnoredItemKey(item, key) {
|| isEqual(item.template?.templateData?.globalData?.[key], item.data?.[key]);
}

function processItem(item, tag, source) {
function processItem(item, collectionKey, source) {
if (!item.inputPath) {
return;
}
Expand Down Expand Up @@ -69,8 +69,8 @@ function processItem(item, tag, source) {
processed.filePathStem = item.filePathStem;
}

if (tag) {
processed.collection = tag;
if (collectionKey) {
processed.collection = collectionKey;
}

if (item.template?._layoutKey) {
Expand Down
Loading

0 comments on commit 9b4211a

Please sign in to comment.