Skip to content

Commit

Permalink
BREAKING: refactors options and default linkset handling
Browse files Browse the repository at this point in the history
This change is breaking because it makes the duplicates, trailingSlash, directoryIndex global options, whereas, -however unusual it may have been-, they used to be configurable per linkset (but this made little sense)
  • Loading branch information
webketje committed Oct 16, 2023
1 parent 7198c92 commit 7d5ca36
Showing 1 changed file with 55 additions and 53 deletions.
108 changes: 55 additions & 53 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const dupeHandlers = {
* Linkset definition
*
* @typedef {Object} Linkset
* @property {boolean} [isDefault] Whether this linkset should be used as the default instead
* @property {Object.<string,*>} match An object whose key:value pairs will be used to match files and transform their permalinks according to the rules in this linkset
* @property {string} pattern A permalink pattern to transform file paths into, e.g. `blog/:date/:title`
* @property {SlugifyOptions|slugFunction} [slug] [Slugify options](https://github.com/simov/slugify) or a custom slug function of the form `(pathpart) => string`
Expand All @@ -73,14 +72,17 @@ const dupeHandlers = {

// These are the invalid path chars on Windows, on *nix systems all are valid except forward slash.
// However, it is highly unlikely that anyone would want these to appear in a file path and they can still be overridden if necessary
const invalidPathChars = '[<>:"\'/\\|?*]'
const defaultSlugifyRemoveChars = '[^\\w\\s$_+~.()!\\-@]+'
const invalidPathChars = '[<>:"\'|?*]'
const defaultSlugifyRemoveChars = '[^\\w\\s$_+~.()!\\-@\\/]+'
const emptyStr = ''
const dash = '-'

/** @type {Options} */
const defaultOptions = {
date: 'YYYY/MM/DD',
const defaultLinkset = {
pattern: ':dirname/:basename',
date: {
format: 'YYYY/MM/DD',
locale: 'en-US'
},
slug: {
lower: true,
remove: new RegExp(`${defaultSlugifyRemoveChars}|${invalidPathChars}`, 'g'),
Expand All @@ -93,12 +95,15 @@ const defaultOptions = {
'<': emptyStr,
'>': emptyStr
}
},
}
}

/** @type {Options} */
const defaultOptions = {
trailingSlash: false,
linksets: [],
duplicates: 'error',
directoryIndex: 'index.html',
pattern: ':dirname/:basename'
directoryIndex: 'index.html'
}

/**
Expand Down Expand Up @@ -128,13 +133,19 @@ function slugFn(options = defaultOptions.slug) {
*/
const html = (str) => path.extname(str) === '.html'

/**
* Return a formatter for a given moment.js format `string`.
*
* @param {string} string
* @return {Function}
*/
const format = (string) => (date) => moment(date).utc().format(string)
const normalizeLinkset = (linkset, defaultLs = defaultLinkset) => {
linkset = { ...defaultLs, ...linkset }
if (typeof linkset.slug !== 'function') {
linkset.slug = slugFn(linkset.slug)
}
if (typeof linkset.date !== 'function') {
linkset.date =
typeof linkset.date === 'string'
? format(linkset.date, defaultLs.date.locale)
: format((linkset || defaultLs).date.format, linkset.date.locale)
}
return linkset
}

/**
* Normalize an options argument.
Expand All @@ -151,11 +162,17 @@ const normalizeOptions = (options) => {
if (options.duplicates && Object.keys(dupeHandlers).includes(options.duplicates)) {
options.duplicates = dupeHandlers[options.duplicates]
}

options.slug = typeof options.slug === 'function' ? options.slug : slugFn(options.slug)
options.date = format(options.date)

return options
// eslint-disable-next-line prefer-const
let { trailingSlash, linksets, duplicates, directoryIndex, ...defaultLs } = options
defaultLs = normalizeLinkset(defaultLs, defaultLinkset)
linksets = linksets.map((ls) => normalizeLinkset(ls, defaultLs)).concat([defaultLs])

return {
trailingSlash,
duplicates,
linksets,
directoryIndex
}
}

/**
Expand Down Expand Up @@ -221,24 +238,24 @@ const replace = (pattern, data, options) => {
*/
function permalinks(options) {
const normalizedOptions = normalizeOptions(options)
let primaryLinkset = normalizedOptions.linksets.find((ls) => Boolean(ls.isDefault))
if (!primaryLinkset) {
primaryLinkset = normalizedOptions
}
const defaultLinkset = normalizedOptions.linksets[normalizedOptions.linksets.length - 1]

const findLinkset = (file) => {
const set = normalizedOptions.linksets.find((ls) =>
Object.keys(ls.match).some((key) => {
if (file[key] === ls.match[key]) {
return true
}
if (Array.isArray(file[key]) && file[key].includes(ls.match[key])) {
return true
}
})
const set = normalizedOptions.linksets.find(
(ls) =>
ls.match &&
Object.keys(ls.match).some((key) => {
if (file[key] === ls.match[key]) {
return true
}
if (Array.isArray(file[key]) && file[key].includes(ls.match[key])) {
return true
}
})
)

return set ? set : primaryLinkset
if (!set) return defaultLinkset
return set
}

return function permalinks(files, metalsmith, done) {
Expand All @@ -261,20 +278,6 @@ function permalinks(options) {

debug('applying pattern: %s to file: %s', linkset.pattern, file)

const opts =
linkset === primaryLinkset
? primaryLinkset
: {
...linkset,
directoryIndex: normalizedOptions.directoryIndex,
slug:
typeof linkset.slug === 'function'
? linkset.slug
: typeof linkset.slug === 'object'
? slugFn(linkset.slug)
: normalizedOptions.slug,
date: typeof linkset.date === 'string' ? format(linkset.date) : normalizedOptions.date
}
let ppath =
replace(
linkset.pattern,
Expand All @@ -284,19 +287,18 @@ function permalinks(options) {
path.basename(file) === normalizedOptions.directoryIndex ? '' : path.basename(file, path.extname(file)),
dirname: path.dirname(file)
},
opts
{ ...normalizedOptions, ...defaultLinkset, ...linkset }
) || resolve(file, normalizedOptions.directoryIndex)

// invalid on Windows, but best practice not to use them anyway
const invalidFilepathChars = /\||:|<|>|\*|\?|"/
if (invalidFilepathChars.test(ppath)) {
if (new RegExp(invalidPathChars).test(ppath)) {
const msg = `Filepath "${file}" contains invalid filepath characters (one of :|<>"*?) after resolving as linkset pattern "${linkset.pattern}"`
debug.error(msg)
done(new Error(msg))
}

// Override the path with `permalink` option
if (Object.prototype.hasOwnProperty.call(data, 'permalink') && data.permalink !== false) {
if (Object.prototype.hasOwnProperty.call(data, 'permalink')) {
ppath = data.permalink
}

Expand Down

0 comments on commit 7d5ca36

Please sign in to comment.