path-search-sort
v1.2.0
Published
Search, filter, and sort file paths, ranked the way a human expects
Maintainers
Readme
path-search-sort
Search, filter, and sort file paths, ranked the way a human expects.
Features
- Exact names first, then starts-with, then contains, in natural order
- Directory and RegExp queries:
block/stone,/shot_\d+/ - Scope by root, folders, extensions, or your own filter
- Forgiving: case, pasted paths, and either slash direction all just work
Install
For Node.js, or the browser through a bundler:
npm install path-search-sortOr in the browser, import it straight from a CDN:
import pathSearch from "https://cdn.jsdelivr.net/npm/path-search-sort/+esm"Examples
import pathSearch from "path-search-sort"
const paths = [
"img/photo.png",
"img/old/photo.png",
"img/photo_edit.jpg",
"screenshots/2024/shot_2.png",
"screenshots/2024/shot_10.png",
"screenshots/2025/my_shot.png",
"backup/2024/screenshot.jpg",
"backup/Shot.png"
]
pathSearch(paths, "photo") // every file with photo in its name, best matches first
pathSearch(paths, "img/photo") // files starting with photo under an img/ folder only
pathSearch(paths, /shot_\d+/) // a RegExp works too, ranked by where it hits
// where to look
pathSearch(paths, "shot", { root: "screenshots" }) // only from the top of the tree
pathSearch(paths, "shot", { path: "2024" }) // only through a 2024/ folder, wherever it sits
// what to keep
pathSearch(paths, "shot", { extension: ["png", "jpg"] }) // only those extensions
pathSearch(paths, "shot", { filter: p => !p.endsWith(".jpg") }) // your own custom filter
pathSearch(paths, "shot", { limit: 25 }) // top 25 after sorting
// how to match
pathSearch(paths, "screenshot", { folders: false }) // the file, not the screenshots/ folder
pathSearch(paths, "Shot", { caseSensitive: true }) // case matters
pathSearch(paths, "my shot", { spaces: "_" }) // typed spaces mean underscores
// items can be strings or objects with a .path, with either slash direction;
[
"img\\photo.png",
{ path: "img/old/photo.png" }
]
// whatever went in comes back untouchedOptions
pathSearch(items, query, options), and all options are optional:
| Option | Default | Description |
|---|---|---|
| root | (everywhere) | Only paths starting with this prefix |
| path | (everywhere) | Only paths passing through these folders, wherever they sit |
| extension | (all) | Only these file types: one extension or several, leading dot optional |
| filter | (none) | Your own predicate over the item, only called for what already matched |
| limit | (all) | Cap on results, applied after sorting |
| folders | true | Also match folder names: files inside a matching folder rank after name matches |
| caseSensitive | false | Case matters, except for the extension filter; RegExp queries ignore it |
| spaces | (kept) | What a typed space becomes, e.g. "_" for underscore-named files; RegExp queries ignore it |
How it matches
Matching is on the full filename, extension included, and case insensitive. A directory in the query constrains it: block/stone means a stone under a block/ folder wherever that sits. Folder names are matched too, so a file inside a matching folder is found even when its own name does not match, and block/ on its own lists everything under block folders. folders: false narrows all of that to file names alone, where a bare block/ then matches nothing.
RegExp queries are also supported and run globally over the path: every hit is found and the best-placed one does the ranking. The casing is case sensitive in RegEx mode: the caseSensitive and spaces options are ignored.
How it sorts
const results = pathSearch(paths, "stone")
// results
[
"stone.png", // exact name first, the extension not counting
"ore/stone.png", // equal names: the shallower path wins
"stone_axe.png", // then names starting with the query
"stone_axe_2.png", // in natural order within every tier
"stone_axe_10.png", // so 10 after 2
"stonework.png", // but whole words go first, so this one waits
"smooth_stone.png", // then names containing the query
"redstone.png", // buried in a word, so last of those too
"stone/gravel.png", // folder matches last: an exact stone folder
"mine/stone/ores/coal.png", // the earlier the matching segment the better
"mine/stone/deep/dark/caves/gem.png", // no matter how deep the path goes on
"mine/ores/stone/iron.png", // a later stone comes after
"stonework/gravel.png", // then a folder starting with stone
"old_stone/gravel.png" // then a folder containing it
]Also exported
import { naturalCompare } from "path-search-sort"
names.sort(naturalCompare)naturalCompare is the comparator on its own, for sorting anything.
