@metalsmith/collections 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,79 @@
1
+ ### Changelog
2
+
3
+ All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4
+
5
+ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
+
7
+ #### [v1.1.0](https://github.com/metalsmith/collections/compare/v1.0.0...v1.1.0)
8
+
9
+ - Added standardised code formatting and QA [`#86`](https://github.com/metalsmith/collections/pull/86)
10
+ - Updated History with v1 PRs [`#85`](https://github.com/metalsmith/collections/pull/85)
11
+ - Added better JSDoc types, return named plugin function [`3aa3443`](https://github.com/metalsmith/collections/commit/3aa3443802c2f814c90cf39c7b43de8fc3d3ff13)
12
+ - Updated multimatch to 4.0.0, debug to 4.3.3 [`71d6f65`](https://github.com/metalsmith/collections/commit/71d6f65b9ec5572196e17dfebf5cff2361853f9d)
13
+
14
+ <!-- auto-changelog-above -->
15
+
16
+ # [1.0.0][] / 2018-10-17
17
+
18
+ - Fixed API and merged many PRs
19
+ - Allow metadata-based filtering with `filterBy` option
20
+ - removed unused module
21
+ - Add documentation: `sortBy` can be a function
22
+ - display only matching files when debugging
23
+ - assign data.path where undefined
24
+ - Clear collections
25
+ - Added multiple collections syntax to Readme.md
26
+
27
+ #### [0.7.0][] / 2015-02-07
28
+
29
+ - Allow front matter and pattern collections.
30
+ - Added the ability to limit the size of a collection
31
+ - Allow collections through metadata alone
32
+ - add ability to disable next/previous references
33
+
34
+ #### [0.6.0][] / 2014-08-12
35
+
36
+ - Added the ability to set multiple collections
37
+
38
+ #### [0.5.1][] / 2014-08-05
39
+
40
+ - Fixed bug with require statement
41
+
42
+ #### [0.5.0][] / 2014-08-04
43
+
44
+ - Added the ability to add metadata to collections
45
+
46
+ #### [0.4.1][] - May 4, 2014
47
+
48
+ - fix empty collections
49
+
50
+ #### [0.4.0][] - March 25, 2014
51
+
52
+ - add option for `sortBy` to be a sorting function
53
+
54
+ #### [0.3.0][] - March 24, 2014
55
+
56
+ - add `collection` property to each file for pattern matches
57
+
58
+ #### [0.2.0][] - March 20, 2014
59
+
60
+ - add collections dictionary to global metadata
61
+
62
+ #### [0.1.0][] - March 6, 2014
63
+
64
+ - add matching by pattern
65
+ - add shorthand for pattern matching
66
+ - add previous and next references
67
+
68
+ #### [0.0.3][] - February 6, 2014
69
+
70
+ - add debug statements
71
+ - swap to `extend` from `defaults` to avoid cloning
72
+
73
+ #### [0.0.2][] - February 6, 2014
74
+
75
+ - swap to `merge` to not act on clones
76
+
77
+ #### [0.0.1][] - February 5, 2014
78
+
79
+ :sparkles:
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 webketje
4
+ Copyright (c) 2014-2021 Segment
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # @metalsmith/collections
2
+
3
+ A [Metalsmith](https://github.com/metalsmith/metalsmith) plugin that lets you group files together into an ordered collection, like blog posts. That way you can loop over them to generate an index, or add 'next' and 'previous' links between them.
4
+
5
+ [![metalsmith: core plugin][metalsmith-badge]][metalsmith-url]
6
+ [![npm version][npm-badge]][npm-url]
7
+ [![ci: build][ci-badge]][ci-url]
8
+ [![code coverage][codecov-badge]][codecov-url]
9
+ [![license: MIT][license-badge]][license-url]
10
+
11
+ ## Features
12
+
13
+ - can match files by `collection` metadata
14
+ - can match files by pattern
15
+ - can limit the number of files in a collection
16
+ - can filter files in a collection based on metadata
17
+ - adds collections to global metadata
18
+ - adds `next` and `previous` references to each file in the collection
19
+
20
+ ## Installation
21
+
22
+ NPM:
23
+
24
+ ```
25
+ npm install @metalsmith/collections
26
+ ```
27
+
28
+ Yarn:
29
+
30
+ ```
31
+ yarn add @metalsmith/collections
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ There are two ways to create collections (they can be used together):
37
+
38
+ - **by pattern** - this is just passing a globbing pattern that will group any files that match into the same collection. The passed pattern can be a single pattern (as a string) or an array of globing patterns. For more information read the [multimatch patterns documentation](https://www.npmjs.com/package/multimatch#how-multiple-patterns-work).
39
+ - **by metadata** - this is adding a specific `collection` metadata field to each item that you want to add to a collection.
40
+
41
+ The simplest way to create a collection is to use a pattern to match the files you want to group together:
42
+
43
+ ```js
44
+ const collections = require('@metalsmith/collections')
45
+
46
+ metalsmith.use(
47
+ collections({
48
+ articles: '*.md'
49
+ })
50
+ )
51
+ ```
52
+
53
+ Which is just a shorthand. You could also add additional options:
54
+
55
+ ```js
56
+ metalsmith.use(
57
+ collections({
58
+ articles: {
59
+ pattern: '*.md',
60
+ sortBy: 'date',
61
+ reverse: true
62
+ }
63
+ })
64
+ )
65
+ ```
66
+
67
+ But you can also match based on a `collection` property in each file's metadata by omitting a pattern, and adding the property to your files:
68
+
69
+ ```js
70
+ metalsmith.use(
71
+ collections({
72
+ articles: {
73
+ sortBy: 'date',
74
+ reverse: true
75
+ }
76
+ })
77
+ )
78
+ ```
79
+
80
+ ```markdown
81
+ ---
82
+ title: My Article
83
+ collection: articles
84
+ date: 2021-12-01
85
+ ---
86
+
87
+ My article contents...
88
+ ```
89
+
90
+ Multiple collections can also be assigned per file:
91
+
92
+ ```markdown
93
+ ---
94
+ title: My Article
95
+ collection:
96
+ - articles
97
+ - news
98
+ date: 2021-12-01
99
+ ---
100
+
101
+ My article contents...
102
+ ```
103
+
104
+ All of the files with a matching `collection` will be added to an array that is exposed as a key of the same name on the global Metalsmith `metadata`.
105
+ You can omit passing any options to the plugin when matching based on a `collection` property.
106
+
107
+ Adds a `path` property to the collection item's data which contains the file path of the generated file. For example, this can be used in mustache templates to create links:
108
+
109
+ ```html
110
+ <h1><a href="/{{ path }}">{{ title }}</a></h1>
111
+ ```
112
+
113
+ The sorting method can be overridden with a custom function in order to sort the files in any order you prefer. For instance, this function sorts the "subpages" collection by a numerical "index" property but places unindexed items last.
114
+
115
+ ```js
116
+ metalsmith.use(
117
+ collections({
118
+ subpages: {
119
+ sortBy: function(a, b) {
120
+ let aNum, bNum
121
+
122
+ aNum = +a.index
123
+ bNum = +b.index
124
+
125
+ // Test for NaN
126
+ if (aNum != aNum && bNum != bNum) return 0
127
+ if (aNum != aNum) return 1
128
+ if (bNum != bNum) return -1
129
+
130
+ // Normal comparison, want lower numbers first
131
+ if (aNum > bNum) return 1
132
+ if (bNum > aNum) return -1
133
+ return 0
134
+ }
135
+ }
136
+ })
137
+ )
138
+ ```
139
+
140
+ The `filterBy` function is passed a single argument which corresponds to each file's metadata. You can use the metadata to perform comparisons or carry out other decision-making logic. If the function you supply evaluates to `true`, the file will be added to the collection. If it evaluates to `false`, the file will not be added.
141
+
142
+ ### Collection Metadata
143
+
144
+ Additional metadata can be added to the collection object.
145
+
146
+ ```js
147
+ metalsmith.use(
148
+ collections({
149
+ articles: {
150
+ sortBy: 'date',
151
+ reverse: true,
152
+ metadata: {
153
+ name: 'Articles',
154
+ description: 'The Articles listed here...'
155
+ }
156
+ }
157
+ })
158
+ )
159
+ ```
160
+
161
+ Collection metadata can also be assigned from a `json` or `yaml` file.
162
+
163
+ ```js
164
+ metalsmith.use(
165
+ collections({
166
+ articles: {
167
+ sortBy: 'date',
168
+ reverse: true,
169
+ metadata: 'path/to/file.json'
170
+ }
171
+ })
172
+ )
173
+ ```
174
+
175
+ On each collection definition, it's possible to add a `limit` option so that the
176
+ collection length is not higher than the given limit:
177
+
178
+ ```js
179
+ metalsmith.use(
180
+ collections({
181
+ lastArticles: {
182
+ sortBy: 'date',
183
+ limit: 10
184
+ }
185
+ })
186
+ )
187
+ ```
188
+
189
+ By adding `refer: false` to your options, it will skip adding the "next" and
190
+ "previous" links to your articles.
191
+
192
+ ```js
193
+ metalsmith.use(
194
+ collections({
195
+ articles: {
196
+ refer: false
197
+ }
198
+ })
199
+ )
200
+ ```
201
+
202
+ ### Debug
203
+
204
+ To log debug output, set the `DEBUG` environment variable to `@metalsmith/collections`:
205
+
206
+ Linux/Mac:
207
+
208
+ ```sh
209
+ DEBUG=@metalsmith/collections
210
+ ```
211
+
212
+ Windows:
213
+
214
+ ```cmd
215
+ set "DEBUG=@metalsmith/collections"
216
+ ```
217
+
218
+ ## CLI Usage
219
+
220
+ Add the `@metalsmith/collections` key to your `metalsmith.json` `plugins` key:
221
+
222
+ ```json
223
+ {
224
+ "plugins": [
225
+ {
226
+ "@metalsmith/collections": {
227
+ "articles": {
228
+ "sortBy": "date",
229
+ "reverse": true
230
+ }
231
+ }
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ ## License
238
+
239
+ [MIT](LICENSE)
240
+
241
+ [npm-badge]: https://img.shields.io/npm/v/@metalsmith/collections.svg
242
+ [npm-url]: https://www.npmjs.com/package/@metalsmith/collections
243
+ [ci-badge]: https://app.travis-ci.com/github/metalsmith/collections.svg?branch=master
244
+ [ci-url]: https://app.travis-ci.com/github/metalsmith/collections
245
+ [metalsmith-badge]: https://img.shields.io/badge/metalsmith-plugin-green.svg?longCache=true
246
+ [metalsmith-url]: http://metalsmith.io
247
+ [codecov-badge]: https://img.shields.io/coveralls/github/metalsmith/collections
248
+ [codecov-url]: https://coveralls.io/github/metalsmith/collections
249
+ [license-badge]: https://img.shields.io/github/license/metalsmith/collections
250
+ [license-url]: LICENSE
package/lib/index.js ADDED
@@ -0,0 +1,236 @@
1
+ var debug = require('debug')('@metalsmith/collections')
2
+ var multimatch = require('multimatch')
3
+ var unique = require('uniq')
4
+ var loadMetadata = require('read-metadata').sync
5
+
6
+ /**
7
+ * Expose `plugin`.
8
+ */
9
+
10
+ module.exports = plugin
11
+
12
+ /**
13
+ * @typedef {Object} CollectionConfig
14
+ * @property {String|String[]} pattern - One or more glob patterns to match files to a collection
15
+ * @property {'date'|Function} sortBy
16
+ * @property {Boolean} reverse - Whether to invert the sorting function results (asc/descending)
17
+ * @property {Function} filterBy - A function that gets a `Metalsmith.File` as first argument and returns `true` for every file to include in the collection
18
+ * @property {Object} metadata - An object with metadata to attach to the collection
19
+ */
20
+
21
+ /**
22
+ * Metalsmith plugin that adds `collections` of files to the global
23
+ * metadata as a sorted array.
24
+ *
25
+ * @param {Object.<String,CollectionConfig|String>} opts
26
+ * @return {import('metalsmith').Plugin}
27
+ */
28
+
29
+ function plugin(opts) {
30
+ opts = normalize(opts)
31
+ var keys = Object.keys(opts)
32
+ var match = matcher(opts)
33
+
34
+ return function collections(files, metalsmith, done) {
35
+ var metadata = metalsmith.metadata()
36
+
37
+ /**
38
+ * Clear collections (to prevent multiple additions of the same file)
39
+ */
40
+
41
+ keys.forEach(function(key) {
42
+ metadata[key] = []
43
+ })
44
+
45
+ /**
46
+ * Clear collections (to prevent multiple additions of the same file when running via metalsmith-browser-sync)
47
+ */
48
+
49
+ keys.forEach(function(key) {
50
+ metadata[key] = []
51
+ })
52
+
53
+ /**
54
+ * Find the files in each collection.
55
+ */
56
+
57
+ Object.keys(files).forEach(function(file) {
58
+ var data = files[file]
59
+
60
+ data.path = data.path || file
61
+
62
+ const matches = match(file, data)
63
+ if (matches.length) {
64
+ debug('processing file: %s', file)
65
+
66
+ matches.forEach(function(key) {
67
+ if (key && keys.indexOf(key) < 0) {
68
+ opts[key] = {}
69
+ keys.push(key)
70
+ }
71
+
72
+ metadata[key] = metadata[key] || []
73
+ // Check if the user supplied a filter function. If so, pass the file metadata to it and
74
+ // only add files that pass the filter test.
75
+ if (typeof opts[key].filterBy == 'function') {
76
+ var filterFunc = opts[key].filterBy
77
+ if (filterFunc(data)) {
78
+ metadata[key].push(data)
79
+ }
80
+ } else {
81
+ // If no filter function is provided, add every file to the collection.
82
+ metadata[key].push(data)
83
+ }
84
+ })
85
+ }
86
+ })
87
+
88
+ /**
89
+ * Ensure that a default empty collection exists.
90
+ */
91
+
92
+ keys.forEach(function(key) {
93
+ metadata[key] = metadata[key] || []
94
+ })
95
+
96
+ /**
97
+ * Sort the collections.
98
+ */
99
+
100
+ keys.forEach(function(key) {
101
+ debug('sorting collection: %s', key)
102
+ var settings = opts[key]
103
+ var sort = settings.sortBy || 'date'
104
+ var col = metadata[key]
105
+
106
+ if ('function' == typeof sort) {
107
+ col.sort(sort)
108
+ } else {
109
+ col.sort(function(a, b) {
110
+ a = a[sort]
111
+ b = b[sort]
112
+ if (!a && !b) return 0
113
+ if (!a) return -1
114
+ if (!b) return 1
115
+ if (b > a) return -1
116
+ if (a > b) return 1
117
+ return 0
118
+ })
119
+ }
120
+
121
+ if (settings.reverse) col.reverse()
122
+ })
123
+
124
+ /**
125
+ * Add `next` and `previous` references and apply the `limit` option
126
+ */
127
+
128
+ keys.forEach(function(key) {
129
+ debug('referencing collection: %s', key)
130
+ var settings = opts[key]
131
+ var col = metadata[key]
132
+ var last = col.length - 1
133
+ if (opts[key].limit && opts[key].limit < col.length) {
134
+ col = metadata[key] = col.slice(0, opts[key].limit)
135
+ last = opts[key].limit - 1
136
+ }
137
+ if (settings.refer === false) return
138
+ col.forEach(function(file, i) {
139
+ if (0 != i) file.previous = col[i - 1]
140
+ if (last != i) file.next = col[i + 1]
141
+ })
142
+ })
143
+
144
+ /**
145
+ * Add collection metadata
146
+ */
147
+
148
+ keys.forEach(function(key) {
149
+ debug('adding metadata: %s', key)
150
+ var settings = opts[key]
151
+ var col = metadata[key]
152
+ col.metadata = typeof settings.metadata === 'string' ? loadMetadata(settings.metadata) : settings.metadata
153
+ })
154
+
155
+ /**
156
+ * Add them grouped together to the global metadata.
157
+ */
158
+
159
+ metadata.collections = {}
160
+ keys.forEach(function(key) {
161
+ return (metadata.collections[key] = metadata[key])
162
+ })
163
+
164
+ done()
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Normalize an `options` dictionary.
170
+ *
171
+ * @param {Object.<string,CollectionConfig>} options
172
+ */
173
+
174
+ function normalize(options) {
175
+ options = options || {}
176
+
177
+ for (var key in options) {
178
+ var val = options[key]
179
+ if ('string' == typeof val) options[key] = { pattern: val }
180
+ if (val instanceof Array) options[key] = { pattern: val }
181
+ }
182
+
183
+ return options
184
+ }
185
+
186
+ /**
187
+ * Generate a matching function for a given set of `collections`.
188
+ *
189
+ * @param {Object.<String, CollectionConfig>} collections
190
+ * @return {Function}
191
+ */
192
+
193
+ function matcher(cols) {
194
+ var keys = Object.keys(cols)
195
+ var matchers = {}
196
+
197
+ keys.forEach(function(key) {
198
+ var opts = cols[key]
199
+ if (!opts.pattern) {
200
+ return
201
+ }
202
+ matchers[key] = {
203
+ match: function(file) {
204
+ return multimatch(file, opts.pattern)
205
+ }
206
+ }
207
+ })
208
+
209
+ return function(file, data) {
210
+ var matches = []
211
+
212
+ if (data.collection) {
213
+ var collection = data.collection
214
+ if (!Array.isArray(collection)) {
215
+ collection = [collection]
216
+ }
217
+ collection.forEach(function(key) {
218
+ matches.push(key)
219
+
220
+ if (key && keys.indexOf(key) < 0) {
221
+ debug('adding new collection through metadata: %s', key)
222
+ }
223
+ })
224
+ }
225
+
226
+ for (var key in matchers) {
227
+ var m = matchers[key]
228
+ if (m.match(file).length) {
229
+ matches.push(key)
230
+ }
231
+ }
232
+
233
+ data.collection = unique(matches)
234
+ return data.collection
235
+ }
236
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@metalsmith/collections",
3
+ "description": "A Metalsmith plugin that adds collections of files to the global metadata.",
4
+ "keywords": [
5
+ "collections",
6
+ "metalsmith",
7
+ "metalsmith-plugin",
8
+ "static-site"
9
+ ],
10
+ "homepage": "https://github.com/metalsmith/collections#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/metalsmith/collections/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/metalsmith/collections.git"
17
+ },
18
+ "version": "1.1.0",
19
+ "license": "MIT",
20
+ "main": "lib/index.js",
21
+ "dependencies": {
22
+ "debug": "^4.3.3",
23
+ "extend": "^3.0.0",
24
+ "multimatch": "^4.0.0",
25
+ "read-metadata": "^1.0.0",
26
+ "uniq": "^1.0.1"
27
+ },
28
+ "devDependencies": {
29
+ "auto-changelog": "^2.3.0",
30
+ "coveralls": "^3.1.1",
31
+ "eslint": "^8.4.1",
32
+ "eslint-config-prettier": "^8.3.0",
33
+ "metalsmith": "^2.3.0",
34
+ "mocha": "^7.2.0",
35
+ "nodemon": "^1.18.4",
36
+ "nyc": "^15.1.0",
37
+ "prettier": "^1.19.1",
38
+ "release-it": "^14.11.8"
39
+ },
40
+ "directories": {
41
+ "lib": "lib",
42
+ "test": "test"
43
+ },
44
+ "files": [
45
+ "lib/*"
46
+ ],
47
+ "scripts": {
48
+ "changelog": "auto-changelog -u --starting-date 2021-12-01 --sort-commits date-desc --commit-limit false --ignore-commit-pattern '(dev|Release|Merge)'",
49
+ "test": "nyc mocha",
50
+ "coverage": "nyc report --reporter=text-lcov > ./coverage.info",
51
+ "coveralls": "npm run coverage && cat ./coverage.info | coveralls",
52
+ "format": "prettier --write \"**/*.{yml,md,js,json}\"",
53
+ "lint": "eslint --cache --fix-dry-run .",
54
+ "dev": "nodemon --exec 'npm test'",
55
+ "release": "release-it ."
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "peerDependencies": {
61
+ "metalsmith": "^2.3.0"
62
+ },
63
+ "engines": {
64
+ "node": ">=8"
65
+ }
66
+ }