Skip to content

Commit 4d3746a

Browse files
committedApr 17, 2024
More robust filename parsing via stdin
1 parent 23c5a27 commit 4d3746a

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed
 

‎files-to-prompt.test.ts

+52
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,58 @@ describe('files-to-prompt.ts', () => {
257257
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt']);
258258
});
259259

260+
test('should parse file paths with one file path per line', () => {
261+
const stdinData = `file1.txt\nfile2.txt\nfile3.txt`;
262+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
263+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
264+
});
265+
266+
test('should handle mixed input formats', () => {
267+
const stdinData = `file1.txt:File 1 contents.\nfile2.txt\nfile3.txt:File 3 contents.`;
268+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
269+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
270+
});
271+
272+
test('should handle empty lines in stdin data', () => {
273+
const stdinData = `file1.txt:File 1 contents.\n\nfile2.txt:File 2 contents.\n`;
274+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
275+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt']);
276+
});
277+
278+
test('should handle binary data in stdin', () => {
279+
const binaryData = Buffer.from([0x80, 0x81, 0x82, 0x83, 0x84, 0x85]);
280+
const stdinData = `file1.txt:File 1 contents.\n${binaryData.toString('utf8')}\nfile2.txt:File 2 contents.`;
281+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
282+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt']);
283+
});
284+
285+
test('should handle common text/code files in stdin', () => {
286+
const textData = `console.log('Hello, world\!');`;
287+
const stdinData = `file1.txt:File 1 contents.\n${textData}\nfile2.txt:File 2 contents.`;
288+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
289+
expect(filePathsFromStdin).toEqual(['file1.txt', textData, 'file2.txt']);
290+
});
291+
292+
test('should handle long file paths in stdin', () => {
293+
const longFilePath = 'a'.repeat(1025);
294+
const stdinData = `file1.txt:File 1 contents.\n${longFilePath}\nfile2.txt:File 2 contents.`;
295+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
296+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt']);
297+
});
298+
299+
test('should ignore file paths with the null character', () => {
300+
const invalidFilePath = 'invalid_file\0.txt';
301+
const stdinData = `file1.txt:File 1 contents.\n${invalidFilePath}\nfile2.txt:File 2 contents.`;
302+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
303+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file2.txt']);
304+
});
305+
306+
test('should ignore file paths with control characters', () => {
307+
const stdinData = `file1.txt:File 1 contents.\nfile2.txt\x07.txt:File 2 contents.\nfile3.txt:File 3 contents.`;
308+
const filePathsFromStdin = parseFilePathsFromStdin(stdinData);
309+
expect(filePathsFromStdin).toEqual(['file1.txt', 'file3.txt']);
310+
});
311+
260312
test('should output version string when --version is passed', async () => {
261313
await main(['--version']);
262314
expect(stdoutOutput).toContain(`files-to-prompt.ts version`);

‎files-to-prompt.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -249,12 +249,24 @@ let readStdin = async (): Promise<string> => {
249249
*/
250250
export function parseFilePathsFromStdin(stdinData: string): string[] {
251251
const filePathsFromStdin: string[] = [];
252-
const lines = stdinData.trim().split('\n');
253252
const seenFilePaths = new Set<string>();
253+
const lines = stdinData.trim().split('\n');
254254

255255
for (const line of lines) {
256-
const filePath = line.split(':')[0];
257-
if (!seenFilePaths.has(filePath)) {
256+
const filePath = line.trim();
257+
if (filePath === '') {
258+
// Ignore empty line
259+
continue;
260+
}
261+
if (filePath.includes(':')) {
262+
// Handle grep/ripgrep output format
263+
const parts = filePath.split(':');
264+
if (isValidFilePath(parts[0]) && !seenFilePaths.has(parts[0])) {
265+
seenFilePaths.add(parts[0]);
266+
filePathsFromStdin.push(parts[0]);
267+
}
268+
} else if (isValidFilePath(filePath) && !seenFilePaths.has(filePath)) {
269+
// Handle file path per line format
258270
seenFilePaths.add(filePath);
259271
filePathsFromStdin.push(filePath);
260272
}
@@ -263,6 +275,28 @@ export function parseFilePathsFromStdin(stdinData: string): string[] {
263275
return filePathsFromStdin;
264276
}
265277

278+
/**
279+
* Checks if a given string is a valid file path.
280+
* @function isValidFilePath
281+
* @param {string} filePath - The file path to check.
282+
* @returns {boolean} - `true` if the file path is valid, `false` otherwise.
283+
*/
284+
function isValidFilePath(filePath: string): boolean {
285+
// Check if the file path contains only valid characters
286+
for (const char of filePath) {
287+
if (char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126) {
288+
return false;
289+
}
290+
}
291+
292+
// Check if the file path is not too long
293+
if (filePath.length > 1024) {
294+
return false;
295+
}
296+
297+
// If the file path passes the above checks, consider it valid
298+
return true;
299+
}
266300

267301
/**
268302
* The main entry point of the script.

0 commit comments

Comments
 (0)
Please sign in to comment.