Skip to content

Commit 2bbd819

Browse files
lambdalisueclaude
andcommitted
feat: add file info refiner for filtering by file properties
Implements a refiner that filters items based on file information such as extension, size, modification time, and file type. Supports excluding hidden files and pattern-based filtering for flexible file selection. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 65a2b5a commit 2bbd819

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

builtin/refiner/file_info.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { defineRefiner, type Refiner } from "../../refiner.ts";
2+
import { extname } from "@std/path/extname";
3+
4+
type Detail = {
5+
/**
6+
* File path
7+
*/
8+
path: string;
9+
};
10+
11+
export type FileInfoRefinerOptions = {
12+
/**
13+
* Filter by file extensions.
14+
* If provided, only files with these extensions will pass.
15+
*/
16+
extensions?: string[];
17+
18+
/**
19+
* Filter by file size.
20+
* Files must be within this range (in bytes).
21+
*/
22+
sizeRange?: {
23+
min?: number;
24+
max?: number;
25+
};
26+
27+
/**
28+
* Filter by modification time.
29+
* Files must be modified within this time range.
30+
*/
31+
modifiedWithin?: {
32+
days?: number;
33+
hours?: number;
34+
minutes?: number;
35+
};
36+
37+
/**
38+
* Whether to include directories.
39+
* @default true
40+
*/
41+
includeDirectories?: boolean;
42+
43+
/**
44+
* Whether to include files.
45+
* @default true
46+
*/
47+
includeFiles?: boolean;
48+
49+
/**
50+
* Whether to include symlinks.
51+
* @default true
52+
*/
53+
includeSymlinks?: boolean;
54+
55+
/**
56+
* Whether to exclude hidden files (starting with dot).
57+
* @default false
58+
*/
59+
excludeHidden?: boolean;
60+
61+
/**
62+
* Patterns to exclude (glob patterns).
63+
*/
64+
excludePatterns?: string[];
65+
};
66+
67+
/**
68+
* Creates a Refiner that filters items based on file information.
69+
*
70+
* This Refiner can filter files based on various criteria such as
71+
* file extension, size, modification time, and file type.
72+
*
73+
* @param options - Options to customize file filtering.
74+
* @returns A Refiner that filters items based on file information.
75+
*/
76+
export function fileInfo(
77+
options: Readonly<FileInfoRefinerOptions> = {},
78+
): Refiner<Detail> {
79+
const extensions = options.extensions;
80+
const sizeRange = options.sizeRange;
81+
const modifiedWithin = options.modifiedWithin;
82+
const includeDirectories = options.includeDirectories ?? true;
83+
const includeFiles = options.includeFiles ?? true;
84+
const includeSymlinks = options.includeSymlinks ?? true;
85+
const excludeHidden = options.excludeHidden ?? false;
86+
const excludePatterns = options.excludePatterns ?? [];
87+
88+
return defineRefiner(async (_denops, { items }) => {
89+
// Process items in parallel and filter
90+
const results = await Promise.all(
91+
items.map(async (item) => {
92+
const { path } = item.detail;
93+
94+
// Check if hidden file should be excluded
95+
if (
96+
excludeHidden && path.split("/").some((part) => part.startsWith("."))
97+
) {
98+
return null;
99+
}
100+
101+
// Check exclude patterns
102+
for (const pattern of excludePatterns) {
103+
// Simple glob pattern matching (could be enhanced)
104+
const regex = new RegExp(
105+
pattern.replace(/\*/g, ".*").replace(/\?/g, "."),
106+
);
107+
if (regex.test(path)) {
108+
return null;
109+
}
110+
}
111+
112+
// Check extension filter
113+
if (extensions && extensions.length > 0) {
114+
const ext = extname(path).toLowerCase();
115+
if (!extensions.includes(ext)) {
116+
return null;
117+
}
118+
}
119+
120+
try {
121+
// Get file stats
122+
const stat = await Deno.stat(path);
123+
124+
// Check file type filters
125+
if (!includeFiles && stat.isFile) {
126+
return null;
127+
}
128+
if (!includeDirectories && stat.isDirectory) {
129+
return null;
130+
}
131+
if (!includeSymlinks && stat.isSymlink) {
132+
return null;
133+
}
134+
135+
// Check size filter
136+
if (sizeRange && stat.isFile) {
137+
if (sizeRange.min !== undefined && stat.size < sizeRange.min) {
138+
return null;
139+
}
140+
if (sizeRange.max !== undefined && stat.size > sizeRange.max) {
141+
return null;
142+
}
143+
}
144+
145+
// Check modification time filter
146+
if (modifiedWithin && stat.mtime) {
147+
const now = new Date();
148+
const mtime = stat.mtime;
149+
const diffMs = now.getTime() - mtime.getTime();
150+
151+
let maxMs = 0;
152+
if (modifiedWithin.days !== undefined) {
153+
maxMs += modifiedWithin.days * 24 * 60 * 60 * 1000;
154+
}
155+
if (modifiedWithin.hours !== undefined) {
156+
maxMs += modifiedWithin.hours * 60 * 60 * 1000;
157+
}
158+
if (modifiedWithin.minutes !== undefined) {
159+
maxMs += modifiedWithin.minutes * 60 * 1000;
160+
}
161+
162+
if (diffMs > maxMs) {
163+
return null;
164+
}
165+
}
166+
167+
return item;
168+
} catch {
169+
// If stat fails, exclude the item
170+
return null;
171+
}
172+
}),
173+
);
174+
175+
// Return only non-null items
176+
return results.filter((item) => item !== null);
177+
});
178+
}

builtin/refiner/mod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// This file is generated by gen-mod.ts
22
export * from "./absolute_path.ts";
3+
export * from "./buffer_info.ts";
34
export * from "./cwd.ts";
45
export * from "./exists.ts";
6+
export * from "./file_info.ts";
57
export * from "./noop.ts";
68
export * from "./regexp.ts";
79
export * from "./relative_path.ts";

0 commit comments

Comments
 (0)