diff --git a/docs/source-file-settings.md b/docs/source-file-settings.md index 4570ff9..9e67f8f 100644 --- a/docs/source-file-settings.md +++ b/docs/source-file-settings.md @@ -148,6 +148,28 @@ E.g. **Default value:** `${sys.contentType.sys.id}-${sys.id}` +### `entry_filename_builder` *(optional)* + +**Use a custom function to generate the filename** + +`source/posts-that-need-fancy-filenames.html` + +```markdown +--- +title: test__posts__filenameBuilder +contentful: + content_type: 2wKn6yEnZewu2SCCkus4as + entry_filename_builder: aldente + entry_template: post.html +layout: posts.html +--- +POSTS-CONTENT-FILENAME-BUILDER +``` + +In your global configuration, define an object `filenameBuilders` with a property named to match `entity_filename_builder` with a function as its value. This function will be called with the arguments `entry` (the entry being processed) and `fileOptions`. Return the desired filename. + +Note that a function cannot be specified in the site configuration if it is stored as JSON; rather you will need to invoke Metalsmith programmatically to use this feature. + ### `entry_id` *(optional)* **Render a file based on a single entry** diff --git a/lib/util.js b/lib/util.js index 6ca8440..4439837 100644 --- a/lib/util.js +++ b/lib/util.js @@ -72,6 +72,12 @@ function getFileName (entry, fileOptions, globalOptions) { fileOptions = fileOptions || {} globalOptions = globalOptions || {} + // Hand off control if a specified custom filename builder exists. + if (fileOptions.entry_filename_builder && + typeof globalOptions.filenameBuilders[fileOptions.entry_filename_builder] === 'function') { + return globalOptions.filenameBuilders[fileOptions.entry_filename_builder](entry, fileOptions) + } + const extension = fileOptions.use_template_extension ? fileOptions.entry_template.split('.').slice(1).pop() : 'html' diff --git a/lib/util.spec.js b/lib/util.spec.js index f28319f..71b8c26 100644 --- a/lib/util.spec.js +++ b/lib/util.spec.js @@ -164,3 +164,32 @@ test('util.getFileName - should set notFound key', t => { t.is(fileName, 'foo/__not-available__.html') t.pass() }) + +test('util.getFileName - should use a custom filename builder if available', t => { + const fileName = util.getFileName( + { + sys: { + contentType: { + sys: { + id: 'foo' + } + }, + id: 'bar' + } + }, + { + entry_filename_builder: 'bazquxer' + }, + { + filenameBuilders: { + bazquxer () { + return 'baz-qux.html' + } + } + } + ) + + t.is(fileName, 'baz-qux.html') + t.pass() +}) + diff --git a/lib/validator.js b/lib/validator.js index 2398dc7..952ef08 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -102,11 +102,11 @@ function validateFileNameForEntry (fileName, entry, regex, options) { if (regex.test(fileName)) { if (options.throw_errors) { throw new Error( - `contentful-metalsmith: \'entry_file_pattern\' for entry with id '${entry.sys.id}' could not be resolved` + `contentful-metalsmith: 'entry_file_pattern' for entry with id '${entry.sys.id}' could not be resolved` ) } else { console.warn( - `contentful-metalsmith: \'entry_file_pattern\' for entry with id '${entry.sys.id}' could not be resolved -> falling back to ${fileName}` + `contentful-metalsmith: 'entry_file_pattern' for entry with id '${entry.sys.id}' could not be resolved -> falling back to ${fileName}` ) } } diff --git a/test/index.spec.js b/test/index.spec.js index f9a2aec..2bffb14 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,6 +1,7 @@ import test from 'ava' import fs from 'fs' import Metalsmith from 'metalsmith' +import slug from 'slug-component' const expectedResults = { postsCustomFileName: `Down the Rabbit HoleSeven Tips From Ernest Hemingway on How to Write Fiction @@ -60,7 +61,12 @@ test.serial.cb('e2e - it propagate errors properly', t => { test.serial.cb('e2e - it should render all templates properly', t => { const metalsmith = createMetalsmith({ space_id: 'w7sdyslol3fu', - access_token: 'baa905fc9cbfab17b1bc0b556a7e17a3e783a2068c9fd6ccf74ba09331357182' + access_token: 'baa905fc9cbfab17b1bc0b556a7e17a3e783a2068c9fd6ccf74ba09331357182', + filenameBuilders: { + aldente (entry) { + return `aldente-${slug(entry.fields.title)}.html` + } + } }) metalsmith.build(error => { @@ -121,11 +127,22 @@ test.serial.cb('e2e - it should render all templates properly', t => { fs.readFileSync(`${__dirname}/build/post-down-the-rabbit-hole.html`, { encoding: 'utf8' }), expectedResults.posts.downTheRabbitHole ) + t.is( fs.readFileSync(`${__dirname}/build/post-seven-tips-from-ernest-hemingway-on-how-to-write-fiction.html`, { encoding: 'utf8' }), expectedResults.posts.sevenTips ) + t.is( + fs.readFileSync(`${__dirname}/build/aldente-seven-tips-from-ernest-hemingway-on-how-to-write-fiction.html`, { encoding: 'utf8' }), + expectedResults.posts.sevenTips + ) + + t.is( + fs.readFileSync(`${__dirname}/build/aldente-seven-tips-from-ernest-hemingway-on-how-to-write-fiction.html`, { encoding: 'utf8' }), + expectedResults.posts.sevenTips + ) + // // render a post defined with id // diff --git a/test/src/posts-filename-builder.html b/test/src/posts-filename-builder.html new file mode 100644 index 0000000..cb54924 --- /dev/null +++ b/test/src/posts-filename-builder.html @@ -0,0 +1,9 @@ +--- +title: test__posts__filenameBuilder +contentful: + content_type: 2wKn6yEnZewu2SCCkus4as + entry_filename_builder: aldente + entry_template: post.html +layout: posts.html +--- +POSTS-CONTENT-FILENAME-BUILDER