fix: supports nested folders for --include (fixes #332)

pull/336/head
Romain 1 year ago
parent 8cb7a3596b
commit 34b957fb7e

@ -16,7 +16,7 @@ const RAW_PHOTO_EXT = [
exports.find = function (rootFolder, options, callback) {
const entries = {}
const pattern = new GlobPattern({
include: (options.include && options.include.length > 0) ? options.include : '**/**',
include: (options.include && options.include.length > 0) ? options.include : ['**/**'],
exclude: options.exclude || [],
extensions: exports.supportedExtensions(options)
})

@ -1,9 +1,12 @@
const _ = require('lodash')
const path = require('path')
const micromatch = require('micromatch')
class GlobPattern {
constructor ({ include, exclude, extensions }) {
this.includeList = include
this.excludeList = exclude
this.includeFolders = _.uniq(_.flatMap(this.includeList, this.subFolders))
this.directoryExcludeList = exclude.concat(['**/@eaDir/**', '#recycle/**'])
this.extensions = extPattern(extensions)
}
@ -20,9 +23,23 @@ class GlobPattern {
canTraverse (folderPath) {
const opts = { dot: false, nocase: true }
const withSlash = `${folderPath}/`
return micromatch.any(withSlash, this.includeList, opts) &&
return micromatch.any(withSlash, this.includeFolders, opts) &&
micromatch.any(withSlash, this.directoryExcludeList, opts) === false
}
// returns the list of all folder names in a path
// so they can be included in traversal
subFolders (filepath) {
// keep the required path if it allows traversal (thing/ or thing/**)
const list = filepath.match(/(\/$)|(\*\*$)/) ? [filepath] : []
// then find all parent folders
let dir = path.dirname(filepath)
while (dir !== '.' && dir !== '/') {
list.push(dir + '/')
dir = path.dirname(dir)
}
return list
}
}
function extPattern (extensions) {

@ -141,6 +141,13 @@ describe('options', function () {
should(opts.logFile).eql(path.join(process.cwd(), 'custom.log'))
})
})
describe('includes', () => {
it('always creates an array', () => {
const args = BASE_ARGS.concat(['--include', 'holidays/**'])
const opts = options.get(args)
should(opts.include).eql(['holidays/**'])
})
})
})
describe('deprecated', () => {
it('--original-photos false', () => {

@ -176,6 +176,51 @@ describe('Index: glob', function () {
], done)
})
it('can include deep subfolders', (done) => {
mock({
'media/work/IMG_0001.jpg': '...',
'media/holidays/venice/IMG_0002.jpg': '...'
})
const options = {
include: [
'holidays/**'
]
}
assertGlobReturns('media', options, [
'holidays/venice/IMG_0002.jpg'
], done)
})
it('can include nested subfolders', (done) => {
mock({
'media/work/IMG_0001.jpg': '...',
'media/holidays/venice/IMG_0002.jpg': '...'
})
const options = {
include: [
'holidays/venice/**'
]
}
assertGlobReturns('media', options, [
'holidays/venice/IMG_0002.jpg'
], done)
})
it('can include a specific file by path', (done) => {
mock({
'media/work/IMG_0001.jpg': '...',
'media/holidays/venice/IMG_0002.jpg': '...'
})
const options = {
include: [
'holidays/venice/IMG_0002.jpg'
]
}
assertGlobReturns('media', options, [
'holidays/venice/IMG_0002.jpg'
], done)
})
it('can specify an exclude pattern', (done) => {
mock({
'media/work/IMG_0001.jpg': '...',

@ -37,6 +37,7 @@ describe('Index: pattern', function () {
extensions: ['jpg']
})
should(pattern.match('holidays/IMG_0001.jpg')).eql(true)
should(pattern.match('holidays/venice/IMG_0001.jpg')).eql(true)
})
it('matches files that meet one of the include patterns', () => {
@ -64,6 +65,7 @@ describe('Index: pattern', function () {
extensions: ['jpg']
})
should(pattern.match('holidays/IMG_0001.jpg')).eql(true)
should(pattern.match('holidays/venice/IMG_0001.jpg')).eql(true)
})
it('rejects files that dont meet any of the include patterns', () => {
@ -103,6 +105,67 @@ describe('Index: pattern', function () {
})
})
describe('calculating sub-folders for traversal', () => {
it('includes all sub-folders', () => {
const pattern = new GlobPattern({
include: ['holidays/venice/IMG001.jpg'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql(['holidays/venice/', 'holidays/'])
})
it('keeps the required include if it ends with a wildcard', () => {
// to ensure sub-sub folders can be traversed as expected
const pattern = new GlobPattern({
include: ['holidays/venice/**'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql(['holidays/venice/**', 'holidays/venice/', 'holidays/'])
})
it('keeps the required include if it ends with a /', () => {
const pattern = new GlobPattern({
include: ['holidays/venice/'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql(['holidays/venice/', 'holidays/'])
})
it('combines all include paths (no repetitions)', () => {
const pattern = new GlobPattern({
include: ['holidays/venice/IMG_001.jpg', 'holidays/milan/IMG_002.jpg'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql([
'holidays/venice/',
'holidays/',
'holidays/milan/'
])
})
it('works with a root wildcard', () => {
const pattern = new GlobPattern({
include: ['**'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql(['**'])
})
it('works with a root double wildcard', () => {
const pattern = new GlobPattern({
include: ['**/**'],
exclude: [],
extensions: []
})
should(pattern.includeFolders).eql(['**/**', '**/'])
})
})
describe('traversing folders', () => {
it('traverses folders that meet an include pattern', () => {
const pattern = new GlobPattern({
@ -113,22 +176,58 @@ describe('Index: pattern', function () {
should(pattern.canTraverse('holidays')).eql(true)
})
it('traverses nested folders that meet an include pattern', () => {
it('traverses nested folders that meet a deep wildcard (**)', () => {
const pattern = new GlobPattern({
include: ['holidays/**', 'home/**'],
exclude: [],
extensions: []
})
should(pattern.canTraverse('holidays/2016')).eql(true)
should(pattern.canTraverse('holidays/2016/venice')).eql(true)
})
it('traverses folders that meet a nested deep wildcard', () => {
const pattern = new GlobPattern({
include: ['holidays/2016/**', 'home/**'],
exclude: [],
extensions: []
})
should(pattern.canTraverse('holidays')).eql(true)
should(pattern.canTraverse('holidays/2016')).eql(true)
should(pattern.canTraverse('holidays/2016/venice')).eql(true)
})
it('traverses folders that meet an include directory', () => {
it('traverses a single folder (no children)', () => {
const pattern = new GlobPattern({
include: ['holidays/'],
exclude: [],
extensions: []
})
should(pattern.canTraverse('holidays')).eql(true)
// only traverses a single level since '/**' wasn't specified
should(pattern.canTraverse('holidays/2016')).eql(false)
})
it('traverses a nested folder (no children)', () => {
const pattern = new GlobPattern({
include: ['holidays/2016/'],
exclude: [],
extensions: []
})
should(pattern.canTraverse('holidays')).eql(true)
should(pattern.canTraverse('holidays/2016')).eql(true)
// not beyond since '/**' wasn't specified
should(pattern.canTraverse('holidays/2016/venice')).eql(false)
})
it('traverses folders that meet an full-path include pattern', () => {
const pattern = new GlobPattern({
include: ['holidays/venice/IMG_001.jpg'],
exclude: [],
extensions: []
})
should(pattern.canTraverse('holidays')).eql(true)
should(pattern.canTraverse('holidays/venice')).eql(true)
})
it('ignores folders that meet an exclude pattern', () => {

Loading…
Cancel
Save