diff --git a/changelog.md b/changelog.md
index 8b6a08e..f5cdee3 100644
--- a/changelog.md
+++ b/changelog.md
@@ -8,6 +8,9 @@
### Minor
+- Added the ability to customize what are extractable files, fixing [#10](https://github.com/jaydenseric/extract-files/issues/10) via [#11](https://github.com/jaydenseric/extract-files/pull/11):
+ - Added a new third parameter to the `extractFiles` function, for specifying a custom extractable file matcher.
+ - Export a new `isExtractableFile` function that is used as the default extractable file matcher for the `extractFiles` function. This can be used in a custom extractable file matcher implementation to match the default extractable files, along with additional custom files.
- Setup [GitHub Sponsors funding](https://github.com/sponsors/jaydenseric):
- Added `.github/funding.yml` to display a sponsor button in GitHub.
- Added a `package.json` `funding` field to enable npm CLI funding features.
diff --git a/readme.md b/readme.md
index 6dbb545..dad15bf 100644
--- a/readme.md
+++ b/readme.md
@@ -28,7 +28,9 @@ See the [`extractFiles`](#function-extractfiles) documentation to get started.
- [class ReactNativeFile](#class-reactnativefile)
- [function extractFiles](#function-extractfiles)
+- [function isExtractableFile](#function-isextractablefile)
- [type ExtractableFile](#type-extractablefile)
+- [type ExtractableFileMatcher](#type-extractablefilematcher)
- [type ExtractFilesResult](#type-extractfilesresult)
- [type ObjectPath](#type-objectpath)
- [type ReactNativeFileSubstitute](#type-reactnativefilesubstitute)
@@ -65,6 +67,7 @@ Clones a value, recursively extracting [`File`](https://developer.mozilla.org/do
| :-- | :-- | :-- |
| `value` | \* | Value (typically an object tree) to extract files from. |
| `path` | [ObjectPath](#type-objectpath)? = `''` | Prefix for object paths for extracted files. |
+| `isExtractableFile` | [ExtractableFileMatcher](#type-extractablefilematcher)? = [isExtractableFile](#function-isextractablefile) | The function used to identify extractable files. |
**Returns:** [ExtractFilesResult](#type-extractfilesresult) — Result.
@@ -107,6 +110,28 @@ _Extract files from an object._
---
+### function isExtractableFile
+
+Checks if a value is an [extractable file](#type-extractablefile).
+
+**Type:** [ExtractableFileMatcher](#type-extractablefilematcher)
+
+| Parameter | Type | Description |
+| :-------- | :--- | :-------------- |
+| `value` | \* | Value to check. |
+
+**Returns:** boolean — Is the value an [extractable file](#type-extractablefile).
+
+#### Examples
+
+_How to import._
+
+> ```js
+> import { isExtractableFile } from 'extract-files'
+> ```
+
+---
+
### type ExtractableFile
An extractable file.
@@ -115,6 +140,36 @@ An extractable file.
---
+### type ExtractableFileMatcher
+
+A function that checks if a value is an extractable file.
+
+**Type:** Function
+
+| Parameter | Type | Description |
+| :-------- | :--- | :-------------- |
+| `value` | \* | Value to check. |
+
+**Returns:** boolean — Is the value an extractable file.
+
+#### See
+
+- [`isExtractableFile`](#function-isextractablefile) is the default extractable file matcher.
+
+#### Examples
+
+_How to check for the default exactable files, as well as a custom type of file._
+
+> ```js
+> import { isExtractableFile } from 'extract-files'
+>
+> const isExtractableFileEnhanced = value =>
+> isExtractableFile(value) ||
+> (typeof CustomFile !== 'undefined' && value instanceof CustomFile)
+> ```
+
+---
+
### type ExtractFilesResult
What [`extractFiles`](#function-extractfiles) returns.
diff --git a/src/extractFiles.mjs b/src/extractFiles.mjs
index 7ac66e7..d3da221 100644
--- a/src/extractFiles.mjs
+++ b/src/extractFiles.mjs
@@ -1,4 +1,4 @@
-import { ReactNativeFile } from './ReactNativeFile.mjs'
+import { isExtractableFile as defaultIsExtractableFile } from './isExtractableFile.mjs'
/**
* Clones a value, recursively extracting
@@ -13,6 +13,7 @@ import { ReactNativeFile } from './ReactNativeFile.mjs'
* @name extractFiles
* @param {*} value Value (typically an object tree) to extract files from.
* @param {ObjectPath} [path=''] Prefix for object paths for extracted files.
+ * @param {ExtractableFileMatcher} [isExtractableFile=isExtractableFile] The function used to identify extractable files.
* @returns {ExtractFilesResult} Result.
* @example
Extract files from an object.
* For the following:
@@ -48,7 +49,11 @@ import { ReactNativeFile } from './ReactNativeFile.mjs'
* | `file1` | `['prefix.a', 'prefix.b.0']` |
* | `file2` | `['prefix.b.1']` |
*/
-export function extractFiles(value, path = '') {
+export function extractFiles(
+ value,
+ path = '',
+ isExtractableFile = defaultIsExtractableFile
+) {
let clone
const files = new Map()
@@ -66,11 +71,7 @@ export function extractFiles(value, path = '') {
else files.set(file, paths)
}
- if (
- (typeof File !== 'undefined' && value instanceof File) ||
- (typeof Blob !== 'undefined' && value instanceof Blob) ||
- value instanceof ReactNativeFile
- ) {
+ if (isExtractableFile(value)) {
clone = null
addFile([path], value)
} else {
@@ -83,14 +84,18 @@ export function extractFiles(value, path = '') {
})
else if (Array.isArray(value))
clone = value.map((child, i) => {
- const result = extractFiles(child, `${prefix}${i}`)
+ const result = extractFiles(child, `${prefix}${i}`, isExtractableFile)
result.files.forEach(addFile)
return result.clone
})
else if (value && value.constructor === Object) {
clone = {}
for (const i in value) {
- const result = extractFiles(value[i], `${prefix}${i}`)
+ const result = extractFiles(
+ value[i],
+ `${prefix}${i}`,
+ isExtractableFile
+ )
result.files.forEach(addFile)
clone[i] = result.clone
}
diff --git a/src/index.mjs b/src/index.mjs
index ef37740..4a6096f 100644
--- a/src/index.mjs
+++ b/src/index.mjs
@@ -1,5 +1,6 @@
export { extractFiles } from './extractFiles.mjs'
export { ReactNativeFile } from './ReactNativeFile.mjs'
+export { isExtractableFile } from './isExtractableFile.mjs'
/**
* An extractable file.
@@ -8,6 +9,24 @@ export { ReactNativeFile } from './ReactNativeFile.mjs'
* @type {File|Blob|ReactNativeFile}
*/
+/**
+ * A function that checks if a value is an extractable file.
+ * @kind typedef
+ * @name ExtractableFileMatcher
+ * @type {Function}
+ * @param {*} value Value to check.
+ * @returns {boolean} Is the value an extractable file.
+ * @see [`isExtractableFile`]{@link isExtractableFile} is the default extractable file matcher.
+ * @example How to check for the default exactable files, as well as a custom type of file.
+ * ```js
+ * import { isExtractableFile } from 'extract-files'
+ *
+ * const isExtractableFileEnhanced = value =>
+ * isExtractableFile(value) ||
+ * (typeof CustomFile !== 'undefined' && value instanceof CustomFile)
+ * ```
+ */
+
/**
* What [`extractFiles`]{@link extractFiles} returns.
* @kind typedef
diff --git a/src/isExtractableFile.mjs b/src/isExtractableFile.mjs
new file mode 100644
index 0000000..6bd805a
--- /dev/null
+++ b/src/isExtractableFile.mjs
@@ -0,0 +1,18 @@
+import { ReactNativeFile } from './ReactNativeFile.mjs'
+
+/**
+ * Checks if a value is an [extractable file]{@link ExtractableFile}.
+ * @kind function
+ * @name isExtractableFile
+ * @type {ExtractableFileMatcher}
+ * @param {*} value Value to check.
+ * @returns {boolean} Is the value an [extractable file]{@link ExtractableFile}.
+ * @example How to import.
+ * ```js
+ * import { isExtractableFile } from 'extract-files'
+ * ```
+ */
+export const isExtractableFile = value =>
+ (typeof File !== 'undefined' && value instanceof File) ||
+ (typeof Blob !== 'undefined' && value instanceof Blob) ||
+ value instanceof ReactNativeFile
diff --git a/src/test.mjs b/src/test.mjs
index 55358bd..91442a7 100644
--- a/src/test.mjs
+++ b/src/test.mjs
@@ -1,8 +1,21 @@
import t from 'tap'
import { extractFiles } from './extractFiles.mjs'
+import { isExtractableFile } from './isExtractableFile.mjs'
import { ReactNativeFile } from './ReactNativeFile.mjs'
-t.test('Extracts a file value.', t => {
+t.test('`isExtractableFile` matches a file', t => {
+ const file = new ReactNativeFile({ name: '', type: '', uri: '' })
+ t.equal(isExtractableFile(file), true)
+ t.end()
+})
+
+t.test('`isExtractableFile` doesn’t match a non-file', t => {
+ const notAFile = {}
+ t.equal(isExtractableFile(notAFile), false)
+ t.end()
+})
+
+t.test('`extractFiles` extracts a file value.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -18,7 +31,7 @@ t.test('Extracts a file value.', t => {
})
t.test(
- 'Extracts File instances from a FileList instance in an object value.',
+ '`extractFiles` extracts `File` instances from a `FileList` instance in an object value.',
t => {
const originalFile = global.File
const originalFileList = global.FileList
@@ -56,7 +69,7 @@ t.test(
}
)
-t.test('Extracts a File instance in an object value.', t => {
+t.test('`extractFiles` extracts a `File` instance in an object value.', t => {
const original = global.File
global.File = class File {}
const file = new File()
@@ -75,7 +88,7 @@ t.test('Extracts a File instance in an object value.', t => {
t.end()
})
-t.test('Extracts a Blob instance in an object value.', t => {
+t.test('`extractFiles` extracts a `Blob` instance in an object value.', t => {
const original = global.Blob
global.Blob = class Blob {}
const file = new Blob()
@@ -94,22 +107,25 @@ t.test('Extracts a Blob instance in an object value.', t => {
t.end()
})
-t.test('Extracts a ReactNativeFile instance in an object value.', t => {
- const file = new ReactNativeFile({ name: '', type: '', uri: '' })
+t.test(
+ '`extractFiles` extracts a `ReactNativeFile` instance in an object value.',
+ t => {
+ const file = new ReactNativeFile({ name: '', type: '', uri: '' })
- t.strictDeepEqual(
- extractFiles({ a: file }),
- {
- clone: { a: null },
- files: new Map([[file, ['a']]])
- },
- 'Result.'
- )
+ t.strictDeepEqual(
+ extractFiles({ a: file }),
+ {
+ clone: { a: null },
+ files: new Map([[file, ['a']]])
+ },
+ 'Result.'
+ )
- t.end()
-})
+ t.end()
+ }
+)
-t.test('Extracts files from an array value.', t => {
+t.test('`extractFiles` extracts files from an array value.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -124,7 +140,7 @@ t.test('Extracts files from an array value.', t => {
t.end()
})
-t.test('Extracts files from a nested array value.', t => {
+t.test('`extractFiles` extracts files from a nested array value.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -139,7 +155,7 @@ t.test('Extracts files from a nested array value.', t => {
t.end()
})
-t.test('Extracts files in an object value.', t => {
+t.test('`extractFiles` extracts files in an object value.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -154,7 +170,7 @@ t.test('Extracts files in an object value.', t => {
t.end()
})
-t.test('Extracts files from a nested object value.', t => {
+t.test('`extractFiles` extracts files from a nested object value.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -169,7 +185,7 @@ t.test('Extracts files from a nested object value.', t => {
t.end()
})
-t.test('Extracts files with a path.', t => {
+t.test('`extractFiles` extracts files with a path.', t => {
const file = new ReactNativeFile({ name: '', type: '', uri: '' })
t.strictDeepEqual(
@@ -184,7 +200,7 @@ t.test('Extracts files with a path.', t => {
t.end()
})
-t.test('Handles an undefined value.', t => {
+t.test('`extractFiles` handles an undefined value.', t => {
t.strictDeepEqual(
extractFiles(undefined),
{ clone: undefined, files: new Map() },
@@ -193,7 +209,7 @@ t.test('Handles an undefined value.', t => {
t.end()
})
-t.test('Handles a null value.', t => {
+t.test('`extractFiles` handles a null value.', t => {
t.strictDeepEqual(
extractFiles(null),
{ clone: null, files: new Map() },
@@ -202,7 +218,7 @@ t.test('Handles a null value.', t => {
t.end()
})
-t.test('Handles an instance value.', t => {
+t.test('`extractFiles` handles an instance value.', t => {
const dateInstance = new Date(2019, 0, 20)
t.strictDeepEqual(
@@ -213,7 +229,7 @@ t.test('Handles an instance value.', t => {
t.end()
})
-t.test('Handles an empty object value.', t => {
+t.test('`extractFiles` handles an empty object value.', t => {
t.strictDeepEqual(
extractFiles({}),
{ clone: {}, files: new Map() },
@@ -222,35 +238,21 @@ t.test('Handles an empty object value.', t => {
t.end()
})
-t.test('Handles an object value with various property types.', t => {
- const func = () => {}
- const dateInstance = new Date(2019, 0, 20)
- const numberInstance = new Number(1)
- class Class {
- a = true
- }
- const classInstance = new Class()
- const objectInstance = new Object()
- objectInstance.a = true
+t.test(
+ '`extractFiles` handles an object value with various property types.',
+ t => {
+ const func = () => {}
+ const dateInstance = new Date(2019, 0, 20)
+ const numberInstance = new Number(1)
+ class Class {
+ a = true
+ }
+ const classInstance = new Class()
+ const objectInstance = new Object()
+ objectInstance.a = true
- t.strictDeepEqual(
- extractFiles({
- a: '',
- b: 'a',
- c: 0,
- d: 1,
- e: true,
- f: false,
- g: null,
- h: undefined,
- i: func,
- j: objectInstance,
- k: classInstance,
- l: numberInstance,
- m: dateInstance
- }),
- {
- clone: {
+ t.strictDeepEqual(
+ extractFiles({
a: '',
b: 'a',
c: 0,
@@ -264,8 +266,60 @@ t.test('Handles an object value with various property types.', t => {
k: classInstance,
l: numberInstance,
m: dateInstance
+ }),
+ {
+ clone: {
+ a: '',
+ b: 'a',
+ c: 0,
+ d: 1,
+ e: true,
+ f: false,
+ g: null,
+ h: undefined,
+ i: func,
+ j: objectInstance,
+ k: classInstance,
+ l: numberInstance,
+ m: dateInstance
+ },
+ files: new Map()
+ },
+ 'Result.'
+ )
+
+ t.end()
+ }
+)
+
+t.test('`extractFiles` allows overriding `isExtractableFile`.', t => {
+ class CustomFile {}
+
+ const isExtractableFile = value => value instanceof CustomFile
+
+ const file1 = new CustomFile()
+ const file2 = new CustomFile()
+ const file3 = new CustomFile()
+
+ t.strictDeepEqual(
+ extractFiles(
+ {
+ a: file1,
+ b: [file2, { c: file3 }]
+ },
+ '',
+ isExtractableFile
+ ),
+ {
+ clone: {
+ a: null,
+ b: [null, { c: null }]
},
- files: new Map()
+ files: new Map([
+ [file1, ['a']],
+ [file2, ['b.0']],
+ [file3, ['b.1.c']]
+ ])
},
'Result.'
)