diff --git a/src/index.ts b/src/index.ts index a6f840b..00357b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,5 +6,4 @@ export { StatsFileSource } from './data/StatsFileSource'; export { AtlasError, AtlasValidationError } from './utils/errors'; export { createAtlasMiddleware } from './utils/middleware'; -export { fuzzyFilterModules } from './utils/search'; export { createStatsFile, validateStatsFile, getStatsMetdata, getStatsPath } from './utils/stats'; diff --git a/src/utils/search.ts b/src/utils/search.ts deleted file mode 100644 index bf8b7e9..0000000 --- a/src/utils/search.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Fuse from 'fuse.js'; - -import { type StatsModule } from '../data/types'; - -type ModuleFilters = { - include?: string; - exclude?: string; -}; - -export function fuzzyFilterModules(items: StatsModule[], options: ModuleFilters) { - if (!options.include && !options.exclude) { - return items; - } - - let results = items; - const fuse = new Fuse(items, { - keys: ['path'], - useExtendedSearch: true, - shouldSort: false, - findAllMatches: true, - threshold: 0.7, - }); - - if (options.include) { - results = fuse.search(sanitizePattern(options.include)).map((result) => result.item); - } - - if (options.exclude) { - const excluded = new Set( - fuse.search(sanitizePattern(options.exclude)).map((result) => result.item.path) - ); - - results = results.filter((item) => !excluded.has(item.path)); - } - - return results; -} - -function sanitizePattern(pattern: string) { - return pattern.replaceAll(/\s*,\s*/g, '|'); -} diff --git a/webui/src/app/api/stats/[entry]/modules/index+api.ts b/webui/src/app/api/stats/[entry]/modules/index+api.ts index bfbab70..4d47cf0 100644 --- a/webui/src/app/api/stats/[entry]/modules/index+api.ts +++ b/webui/src/app/api/stats/[entry]/modules/index+api.ts @@ -1,7 +1,7 @@ import { statsModuleFiltersFromUrlParams } from '~/components/forms/StatsModuleFilter'; import { getSource } from '~/utils/atlas'; +import { globFilterModules } from '~/utils/search'; import { type StatsEntry, type StatsModule } from '~core/data/types'; -import { fuzzyFilterModules } from '~core/utils/search'; /** The partial module data, when listing all available modules from a stats entry */ export type ModuleMetadata = Omit & { @@ -62,7 +62,7 @@ function filterModules(request: Request, stats: StatsEntry): ModuleMetadata[] { modules = modules.filter((module) => !module.package); } - return fuzzyFilterModules(modules, filters).map((module) => ({ + return globFilterModules(modules, stats.projectRoot, filters).map((module) => ({ ...module, source: undefined, output: undefined, diff --git a/webui/src/utils/search.ts b/webui/src/utils/search.ts new file mode 100644 index 0000000..7196594 --- /dev/null +++ b/webui/src/utils/search.ts @@ -0,0 +1,42 @@ +import path from 'path'; +import picomatch from 'picomatch'; + +import { type StatsModule } from '~core/data/types'; + +type ModuleFilters = { + include?: string; + exclude?: string; +}; + +/** + * Filter the modules based on the include and exclude glob patterns. + * Note, you can provide multiple patterns using the comma separator. + * This also only searches the relative module path from project root, avoiding false positives. + */ +export function globFilterModules( + items: StatsModule[], + projectRoot: string, + options: ModuleFilters +) { + if (!options.include && !options.exclude) { + return items; + } + + const matcher = picomatch(options.include ? splitPattern(options.include) : '**', { + cwd: '', + dot: true, + nocase: true, + contains: true, + ignore: !options.exclude ? undefined : splitPattern(options.exclude), + }); + + return items.filter((item) => matcher(path.relative(projectRoot, item.path))); +} + +/** + * Split the comma separated string into an array of separate patterns. + * This splits on any combination of `,` and whitespaces. + */ +function splitPattern(pattern: string) { + return pattern.split(/\s*,\s*/); +}