1
0
Fork 0
pull/271/head
eric sciple 2019-12-21 02:57:23 -05:00
parent e29784d870
commit e8758cfefd
6 changed files with 63 additions and 57 deletions

View File

@ -370,6 +370,7 @@ describe('glob (search)', () => {
await fs.mkdir(root, {recursive: true})
await fs.writeFile(path.join(root, 'file'), 'test file content')
await createSymlinkDir(root, path.join(root, 'symDir'))
await fs.stat(path.join(root, 'symDir'))
const itemPaths = await glob.glob(path.join(root, 'symDir'))
expect(itemPaths).toHaveLength(2)

View File

@ -3,7 +3,7 @@ import * as fs from 'fs'
import * as path from 'path'
import * as patternHelper from './internal-pattern-helper'
import {IGlobOptions} from './internal-glob-options'
import {MatchResult} from './internal-match-result'
import {MatchKind} from './internal-match-kind'
import {Pattern} from './internal-pattern'
import {SearchState} from './internal-search-state'
@ -24,12 +24,9 @@ export async function glob(
// Parse patterns
const patterns: Pattern[] = patternHelper.parse([pattern], options)
// Get search paths
const searchPaths: string[] = patternHelper.getSearchPaths(patterns)
// Search
const result: string[] = []
for (const searchPath of searchPaths) {
// Push the search paths
const stack: SearchState[] = []
for (const searchPath of patternHelper.getSearchPaths(patterns)) {
// Exists? Note, intentionally using lstat. Detection for broken symlink
// will be performed later (if following symlinks).
try {
@ -41,49 +38,56 @@ export async function glob(
throw err
}
// Push the first item
const stack: SearchState[] = [new SearchState(searchPath, 1)]
const traversalChain: string[] = [] // used to detect cycles
stack.unshift(new SearchState(searchPath, 1))
}
while (stack.length) {
// Pop
const item = stack.pop() as SearchState
const stats: fs.Stats | undefined = await stat(
item,
options,
traversalChain
)
const result: string[] = []
// Broken symlink or symlink cycle detected
if (!stats) {
// Search
const traversalChain: string[] = [] // used to detect cycles
while (stack.length) {
// Pop
const item = stack.pop() as SearchState
// Match
const match = patternHelper.match(patterns, item.path)
if (!match) {
continue
}
// Stat
const stats: fs.Stats | undefined = await stat(
item,
options,
traversalChain
)
// Broken symlink, or symlink cycle detected, or no longer exists
if (!stats) {
continue
}
// Directory
if (stats.isDirectory()) {
// Matched
if (match & MatchKind.Directory) {
result.push(item.path)
}
// Descend?
else if (!patternHelper.partialMatch(patterns, item.path)) {
continue
}
// Match
const matchResult = patternHelper.match(patterns, item.path)
// Directory
if (stats.isDirectory()) {
// Matched
if (matchResult & MatchResult.Directory) {
result.push(item.path)
}
// Descend?
else if (!patternHelper.partialMatch(patterns, item.path)) {
continue
}
// Push the child items in reverse
const childLevel = item.level + 1
const childItems = (await fs.promises.readdir(item.path)).map(
x => new SearchState(path.join(item.path, x), childLevel)
)
stack.push(...childItems.reverse())
}
// File
else if (matchResult & MatchResult.File) {
result.push(item.path)
}
// Push the child items in reverse
const childLevel = item.level + 1
const childItems = (await fs.promises.readdir(item.path)).map(
x => new SearchState(path.join(item.path, x), childLevel)
)
stack.push(...childItems.reverse())
}
// File
else if (match & MatchKind.File) {
result.push(item.path)
}
}

View File

@ -1,7 +1,7 @@
/**
* Indicates whether a pattern matches a path
*/
export enum MatchResult {
export enum MatchKind {
/** Not matched */
None = 0,

View File

@ -76,6 +76,7 @@ export class Path {
!segment.includes(path.sep),
`Parameter 'itemPath' contains unexpected path separators`
)
this.segments.push(segment)
}
}
}

View File

@ -1,6 +1,6 @@
import * as pathHelper from './internal-path-helper'
import {IGlobOptions} from './internal-glob-options'
import {MatchResult} from './internal-match-result'
import {MatchKind} from './internal-match-kind'
import {Pattern} from './internal-pattern'
const IS_WINDOWS = process.platform === 'win32'
@ -60,8 +60,8 @@ export function getSearchPaths(patterns: Pattern[]): string[] {
/**
* Matches the patterns against the path
*/
export function match(patterns: Pattern[], itemPath: string): MatchResult {
let result: MatchResult = MatchResult.None
export function match(patterns: Pattern[], itemPath: string): MatchKind {
let result: MatchKind = MatchKind.None
for (const pattern of patterns) {
if (pattern.comment) {

View File

@ -2,7 +2,7 @@ import * as assert from 'assert'
import * as path from 'path'
import * as pathHelper from './internal-path-helper'
import {Minimatch, IMinimatch, IOptions as IMinimatchOptions} from 'minimatch'
import {MatchResult} from './internal-match-result'
import {MatchKind} from './internal-match-kind'
import {Path} from './internal-path'
const IS_WINDOWS = process.platform === 'win32'
@ -64,16 +64,16 @@ export class Pattern {
/**
* Matches the pattern against the specified path
*/
match(itemPath: string): MatchResult {
match(itemPath: string): MatchKind {
if (this.comment) {
return MatchResult.None
return MatchKind.None
}
if (this.minimatch.match(itemPath)) {
return this.trailingSlash ? MatchResult.Directory : MatchResult.All
return this.trailingSlash ? MatchKind.Directory : MatchKind.All
}
return MatchResult.None
return MatchKind.None
}
/**
@ -146,11 +146,11 @@ export class Pattern {
private initializePaths(pattern: string): void {
// Build the search path
const searchSegments: string[] = []
for (const literalSegment of this.getLiterals(pattern)) {
if (!literalSegment) {
for (const literal of this.getLiterals(pattern)) {
if (!literal) {
break
}
searchSegments.push(literalSegment)
searchSegments.push(literal)
}
this.searchPath = new Path(searchSegments).toString()