Skip to content

Commit

Permalink
feat: add line-numbers option
Browse files Browse the repository at this point in the history
  • Loading branch information
gmickel committed Jul 21, 2024
1 parent 4c136d7 commit 7fd314f
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 15 deletions.
3 changes: 3 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function cli(args: string[]) {
'File patterns to exclude (use glob patterns, e.g., "**/*.test.js")',
)
.option('-s, --suppress-comments', 'Strip comments from the code')
.option('-l, --line-numbers', 'Add line numbers to code blocks')
.option('--case-sensitive', 'Use case-sensitive pattern matching')
.option(
'--no-codeblock',
Expand Down Expand Up @@ -92,6 +93,7 @@ export function cli(args: string[]) {
noCodeblock: !options.codeblock,
basePath: options.path,
customData,
lineNumbers: options.lineNumbers,
});

if (options.prompt) {
Expand Down Expand Up @@ -132,6 +134,7 @@ export function cli(args: string[]) {
'File patterns to exclude (use glob patterns, e.g., "**/*.test.js")',
)
.option('-s, --suppress-comments', 'Strip comments from the code')
.option('-l, --line-numbers', 'Add line numbers to code blocks')
.option('--case-sensitive', 'Use case-sensitive pattern matching')
.option(
'--no-codeblock',
Expand Down
2 changes: 2 additions & 0 deletions src/cli/interactive-filtering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface InteractiveModeOptions {
cachePath?: string;
respectGitignore?: boolean;
invert?: boolean;
lineNumbers?: boolean;
}

export async function interactiveMode(options: InteractiveModeOptions) {
Expand Down Expand Up @@ -82,6 +83,7 @@ export async function interactiveMode(options: InteractiveModeOptions) {
customData: options.customData
? JSON.parse(options.customData)
: undefined,
lineNumbers: options.lineNumbers,
};

let markdown = await generateMarkdown(
Expand Down
23 changes: 18 additions & 5 deletions src/core/markdown-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@ export interface MarkdownOptions {
noCodeblock?: boolean;
customData?: Record<string, unknown>;
basePath?: string;
lineNumbers?: boolean;
}

function registerHandlebarsHelpers(noCodeblock: boolean) {
function registerHandlebarsHelpers(
noCodeblock: boolean,
options: MarkdownOptions,
) {
Handlebars.registerHelper(
'codeblock',
(content: string, language: string) => {
if (noCodeblock) {
return content;
let numberedContent = content;
if (options.lineNumbers) {
numberedContent = content
.split('\n')
.map((line, index) => `${index + 1} ${line}`)
.join('\n');
}
return new Handlebars.SafeString(`\`\`\`${language}\n${content}\n\`\`\``);
if (options.noCodeblock) {
return numberedContent;
}
return new Handlebars.SafeString(
`\`\`\`${language}\n${numberedContent}\n\`\`\``,
);
},
);

Expand Down Expand Up @@ -87,7 +100,7 @@ export async function generateMarkdown(
basePath = process.cwd(),
} = options;

registerHandlebarsHelpers(noCodeblock);
registerHandlebarsHelpers(noCodeblock, options);

const compiledTemplate = Handlebars.compile(templateContent);

Expand Down
34 changes: 25 additions & 9 deletions src/utils/file-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface CacheEntry {
data: FileInfo;
}

const MAX_CACHE_ITEM_SIZE = 1024 * 1024; // 1 MB

export class FileCache {
private cacheFile: string;
private cache: Record<string, CacheEntry> = {};
Expand Down Expand Up @@ -69,16 +71,22 @@ export class FileCache {
try {
await fs.ensureDir(path.dirname(this.cacheFile));
const tempFile = `${this.cacheFile}.tmp`;
await fs.writeFile(
tempFile,
JSON.stringify(this.cache, (key, value) => {
if (value instanceof Date) {
return value.toISOString();

// Stringify cache items individually
const cacheEntries = Object.entries(this.cache)
.map(([key, value]) => {
try {
return `"${key}":${JSON.stringify(value)}`;
} catch (error) {
console.warn(`Failed to stringify cache entry for ${key}:`, error);
return null;
}
return value;
}),
'utf-8',
);
})
.filter(Boolean);

const cacheString = `{${cacheEntries.join(',')}}`;

await fs.writeFile(tempFile, cacheString, 'utf-8');
await fs.rename(tempFile, this.cacheFile);
this.isDirty = false;
} catch (error) {
Expand All @@ -103,6 +111,14 @@ export class FileCache {
async set(filePath: string, data: FileInfo): Promise<void> {
await this.loadCache();
const hash = await this.calculateFileHash(filePath);

// Check the size of the data
const dataSize = JSON.stringify(data).length;
if (dataSize > MAX_CACHE_ITEM_SIZE) {
console.warn(`Skipping cache for large file: ${filePath}`);
return;
}

this.cache[filePath] = { hash, data };
this.isDirty = true;
}
Expand Down
65 changes: 64 additions & 1 deletion tests/e2e/cli-commands.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { execSync } from 'node:child_process';
import { exec, execSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';
import fs from 'fs-extra';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { normalizePath } from '../../src/utils/normalize-path';

const execAsync = promisify(exec);

describe('CLI Commands', () => {
const cliPath = path.resolve(__dirname, '../../cli.js');
const testProjectPath = path.resolve(__dirname, '../fixtures/test-project');
Expand Down Expand Up @@ -172,6 +176,7 @@ describe('CLI Commands', () => {
fs.removeSync(customCachePath);
}
});

it('should generate markdown with custom data and prompt', () => {
const customData = JSON.stringify({
projectName: 'My Awesome Project',
Expand Down Expand Up @@ -217,4 +222,62 @@ describe('CLI Commands', () => {
expect(output).toContain('## Your Task');
expect(output).toContain('Please review this code and provide feedback.');
});

it('should generate markdown with line numbers when --line-numbers flag is used', async () => {
const testDir1 = path.join(os.tmpdir(), 'test-project-1');
const testDir2 = path.join(os.tmpdir(), 'test-project-2');

await fs.ensureDir(testDir1);
await fs.ensureDir(testDir2);

const testFile1 = path.join(testDir1, 'test-file.js');
const testFile2 = path.join(testDir2, 'test-file.js');

await fs.writeFile(
testFile1,
'const x = 1;\nconst y = 2;\nconsole.log(x + y);',
);
await fs.writeFile(
testFile2,
'const x = 1;\nconst y = 2;\nconsole.log(x + y);',
);

const outputPath1 = path.join(testDir1, 'output-with-line-numbers.md');
const outputPath2 = path.join(testDir2, 'output-without-line-numbers.md');

const commandWithLineNumbers = `pnpm exec esno ${cliPath} generate -p "${normalizePath(testDir1)}" -o "${normalizePath(outputPath1)}" --line-numbers`;
const commandWithoutLineNumbers = `pnpm exec esno ${cliPath} generate -p "${normalizePath(testDir2)}" -o "${normalizePath(outputPath2)}"`;

try {
await execAsync(commandWithLineNumbers, {
env: { ...process.env, NODE_ENV: 'test' },
cwd: path.resolve(__dirname, '../..'),
});

// Add a small delay to ensure file system operations are complete
await new Promise((resolve) => setTimeout(resolve, 1000));

await execAsync(commandWithoutLineNumbers, {
env: { ...process.env, NODE_ENV: 'test' },
cwd: path.resolve(__dirname, '../..'),
});

const outputWithLineNumbers = await fs.readFile(outputPath1, 'utf-8');
const outputWithoutLineNumbers = await fs.readFile(outputPath2, 'utf-8');

expect(outputWithLineNumbers).toContain('1 const x = 1;');
expect(outputWithLineNumbers).toContain('2 const y = 2;');
expect(outputWithLineNumbers).toContain('3 console.log(x + y);');

expect(outputWithoutLineNumbers).not.toContain('1 const x = 1;');
expect(outputWithoutLineNumbers).toContain('const x = 1;');
} catch (error) {
console.error('Error executing commands:', error);
throw error;
} finally {
// Clean up
await fs.remove(testDir1);
await fs.remove(testDir2);
}
});
});
3 changes: 3 additions & 0 deletions tests/fixtures/test-project/src/test-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const x = 1;
const y = 2;
console.log(x + y);
32 changes: 32 additions & 0 deletions tests/unit/markdown-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,36 @@ describe('Markdown Generator', () => {
'Please review this code and provide feedback.',
);
});

it('should add line numbers to code blocks when lineNumbers option is true', async () => {
const mockFiles = [
{
path: '/project/src/index.ts',
extension: 'ts',
language: 'typescript',
size: 100,
created: new Date('2023-01-01'),
modified: new Date('2023-01-02'),
content: 'const greeting = "Hello";\nconsole.log(greeting);',
},
];

const template =
'{{#each files}}{{#codeblock this.content this.language}}{{/codeblock}}{{/each}}';

const resultWithoutLineNumbers = await generateMarkdown(
mockFiles,
template,
{ lineNumbers: false },
);
const resultWithLineNumbers = await generateMarkdown(mockFiles, template, {
lineNumbers: true,
});

expect(resultWithoutLineNumbers).not.toContain('1 const');
expect(resultWithoutLineNumbers).not.toContain('2 console');

expect(resultWithLineNumbers).toContain('1 const greeting = "Hello";');
expect(resultWithLineNumbers).toContain('2 console.log(greeting);');
});
});

0 comments on commit 7fd314f

Please sign in to comment.