@cod3vil/kount-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import fs from 'node:fs';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { IgnoreParser } from './ignore-parser.js';
5
+
6
+ export interface ScannedFile {
7
+ filePath: string;
8
+ size: number;
9
+ }
10
+
11
+ export type FileChunkCallback = (chunk: Buffer, isLast: boolean) => void;
12
+
13
+ /**
14
+ * Recursively walks directories and streams files chunk-by-chunk.
15
+ * Respects .gitignore and built-in binary exclusions via IgnoreParser.
16
+ */
17
+ export class Scanner {
18
+ private parser: IgnoreParser;
19
+
20
+ constructor(rootDir: string, respectGitignore: boolean = true) {
21
+ this.parser = new IgnoreParser(rootDir, respectGitignore);
22
+ }
23
+
24
+ /**
25
+ * Discovers all files in the directory tree matching rules.
26
+ */
27
+ async discover(dirPath: string): Promise<ScannedFile[]> {
28
+ await this.parser.init();
29
+ return this.walk(path.resolve(dirPath));
30
+ }
31
+
32
+ private async walk(currentDir: string): Promise<ScannedFile[]> {
33
+ const filesList: ScannedFile[] = [];
34
+
35
+ // Provide ignore rules for this specific directory level
36
+ await this.parser.loadIgnoreForDir(currentDir);
37
+
38
+ try {
39
+ const entries = await fsp.readdir(currentDir, { withFileTypes: true });
40
+
41
+ for (const entry of entries) {
42
+ const fullPath = path.join(currentDir, entry.name);
43
+
44
+ // Check ignore rules immediately to avoid stat calls on ignored paths
45
+ if (this.parser.isIgnored(fullPath, entry.isDirectory())) {
46
+ continue;
47
+ }
48
+
49
+ if (entry.isDirectory()) {
50
+ const subFiles = await this.walk(fullPath);
51
+ filesList.push(...subFiles);
52
+ } else if (entry.isFile()) {
53
+ try {
54
+ // We need the size, though we could get it via stats here or let the caller do it.
55
+ // We'll calculate it so plugins can use it.
56
+ const stats = await fsp.stat(fullPath);
57
+ filesList.push({ filePath: fullPath, size: stats.size });
58
+ } catch (e) {
59
+ // Handle gracefully: e.g. broken symlink or permission denied
60
+ // Could hook a logger here later.
61
+ }
62
+ }
63
+ }
64
+ } catch (e) {
65
+ // e.g. Permission denied on directory
66
+ }
67
+
68
+ return filesList;
69
+ }
70
+
71
+ /**
72
+ * Streams a file in chunks without reading the whole file into memory.
73
+ */
74
+ streamFile(filePath: string, onChunk: FileChunkCallback): Promise<void> {
75
+ return new Promise((resolve, reject) => {
76
+ // High water mark set to a reasonable chunk size (e.g. 64KB) to keep memory low
77
+ const stream = fs.createReadStream(filePath, { highWaterMark: 64 * 1024 });
78
+
79
+ stream.on('data', (chunk: Buffer | string) => {
80
+ // Enforce Buffer
81
+ const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
82
+ // We pass false for `isLast` because we don't know yet.
83
+ // We will signal `isLast` on 'end' if needed, but typically passing the chunk is enough.
84
+ // For line counting plugins relying on chunks, we might need a signal to flush remaining lines.
85
+ onChunk(bufferChunk, false);
86
+ });
87
+
88
+ stream.on('error', (err) => {
89
+ reject(err);
90
+ });
91
+
92
+ stream.on('end', () => {
93
+ // Send a final empty chunk with isLast = true so plugins can flush state.
94
+ onChunk(Buffer.alloc(0), true);
95
+ resolve();
96
+ });
97
+ });
98
+ }
99
+ }
File without changes
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Maps file extensions to their corresponding comment syntaxes.
3
+ * This is used by the comment-lines plugin to accurately count comments.
4
+ */
5
+ export const LANGUAGE_COMMENT_MAP: Record<string, string[]> = {
6
+ // C-style (JS, TS, Java, C, C++, C#, Go, Swift, Rust, Kotlin, Dart, Scala, Objective-C)
7
+ '.js': ['//', '/* */'],
8
+ '.ts': ['//', '/* */'],
9
+ '.jsx': ['//', '/* */'],
10
+ '.tsx': ['//', '/* */'],
11
+ '.java': ['//', '/* */'],
12
+ '.c': ['//', '/* */'],
13
+ '.cpp': ['//', '/* */'],
14
+ '.cs': ['//', '/* */'],
15
+ '.go': ['//', '/* */'],
16
+ '.swift': ['//', '/* */'],
17
+ '.rs': ['//', '/* */'],
18
+ '.kt': ['//', '/* */'],
19
+ '.dart': ['//', '/* */'],
20
+ '.scala': ['//', '/* */'],
21
+ '.m': ['//', '/* */'],
22
+ '.mm': ['//', '/* */'],
23
+ '.css': ['/* */'],
24
+ '.scss': ['//', '/* */'],
25
+ '.less': ['//', '/* */'],
26
+
27
+ // Hash-style (Python, Ruby, Shell, YAML, Perl, PHP (also uses //), R, PowerShell, Makefile)
28
+ '.py': ['#'],
29
+ '.rb': ['#'],
30
+ '.sh': ['#'],
31
+ '.bash': ['#'],
32
+ '.zsh': ['#'],
33
+ '.yaml': ['#'],
34
+ '.yml': ['#'],
35
+ '.pl': ['#'],
36
+ '.pm': ['#'],
37
+ '.r': ['#'],
38
+ '.ps1': ['#'],
39
+ 'makefile': ['#'], // Note: Makefile might not have an extension, handle separately if needed
40
+
41
+ // HTML/XML-style (HTML, XML, SVG, Markdown, Vue)
42
+ '.html': ['<!-- -->'],
43
+ '.htm': ['<!-- -->'],
44
+ '.xml': ['<!-- -->'],
45
+ '.svg': ['<!-- -->'],
46
+ '.md': ['<!-- -->'],
47
+ '.vue': ['<!-- -->', '//', '/* */'], // Vue can have JS/TS inside <script> and CSS inside <style>
48
+
49
+ // Dash-style (SQL, Lua, Haskell, Ada, VHDL)
50
+ '.sql': ['--'],
51
+ '.lua': ['--'],
52
+ '.hs': ['--'],
53
+ '.ada': ['--'],
54
+ '.vhdl': ['--'],
55
+
56
+ // Lisp-style (Lisp, Clojure, Scheme)
57
+ '.lisp': [';'],
58
+ '.clj': [';'],
59
+ '.scm': [';'],
60
+
61
+ // PHP
62
+ '.php': ['//', '#', '/* */'],
63
+
64
+ // JSON (Standard JSON has no comments, but JSONC allowing comments is common in tools)
65
+ '.jsonc': ['//', '/* */'],
66
+ };
67
+
68
+ /**
69
+ * Returns the expected comment syntax array for a given file extension.
70
+ * If the extension is not mapped, returns an empty array.
71
+ *
72
+ * @param extension The file extension including the dot (e.g., '.ts')
73
+ * @returns Array of comment syntaxes (e.g., ['//', '/* *\/']) or empty array if not found.
74
+ */
75
+ export function getCommentSyntax(extension: string): string[] {
76
+ // Ensure the extension starts with a dot if it's not a known full filename like Makefile
77
+ const normalizedExt = extension.toLowerCase();
78
+ return LANGUAGE_COMMENT_MAP[normalizedExt] || [];
79
+ }