gather-ts
Version:
A powerful code analysis and packaging tool designed for creating AI-friendly code representations for javascript and typescript projects.
370 lines • 15.9 kB
JavaScript
"use strict";
// src/core/compiler/ArgumentParser.ts
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArgumentParser = void 0;
const path_1 = __importDefault(require("path"));
const errors_1 = require("@/errors");
const services_1 = require("@/types/services");
class ArgumentParser extends services_1.BaseService {
constructor(deps, options = {}) {
super();
this.deps = deps;
this.debug = options.debug || false;
this.maxDepth = options.maxDepth || 10;
this.defaultBatchSize = options.defaultBatchSize || 100;
}
async initialize() {
await super.initialize();
this.logDebug("Initializing ArgumentParser");
}
cleanup() {
this.logDebug("Cleaning up ArgumentParser");
super.cleanup();
}
logDebug(message) {
if (this.debug) {
this.deps.logger.debug(message);
}
}
parseArguments(args, options = {}) {
this.checkInitialized();
this.logDebug(`Parsing arguments: ${args.join(" ")}`);
try {
if (!args.length && !options.allowEmpty) {
throw new errors_1.ValidationError("No arguments provided");
}
// First parse options to get root directory
const restArgs = args.slice(1);
let outputFile = "";
const compileOptions = {};
// Process all args as options
for (let i = 0; i < restArgs.length; i++) {
const arg = restArgs[i];
switch (arg) {
case "--output":
case "-o":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Output file path not provided");
}
outputFile = this.parseOutputFile(restArgs[i], options.requireOutput);
break;
case "--root":
case "-r":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Root directory not provided");
}
compileOptions.rootDir = this.parseRootDir(restArgs[i]);
break;
case "--depth":
case "-d":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Depth value not provided");
}
compileOptions.maxDepth = this.parseDepth(restArgs[i]);
break;
case "--batch-size":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Batch size not provided");
}
const batchSize = this.deps.validator.validateNotEmpty(restArgs[i], "Batch size");
const parsedBatchSize = parseInt(batchSize);
if (isNaN(parsedBatchSize) || parsedBatchSize <= 0) {
throw new errors_1.ValidationError("Batch size must be a positive number");
}
compileOptions.batchSize = parsedBatchSize;
break;
case "--config":
case "-c":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Config path not provided");
}
const configPath = this.deps.validator.validateNotEmpty(restArgs[i], "Config path");
if (!this.deps.fileSystem.exists(configPath)) {
throw new errors_1.ValidationError(`Config file not found: ${configPath}`);
}
compileOptions.config = { path: configPath };
break;
case "--encoding":
i++;
if (i >= restArgs.length) {
throw new errors_1.ValidationError("Encoding not provided");
}
const encoding = this.deps.validator.validateNotEmpty(restArgs[i], "Encoding");
if (!this.isValidEncoding(encoding)) {
throw new errors_1.ValidationError(`Invalid encoding: ${encoding}`);
}
compileOptions.encoding = encoding;
break;
case "--ignore":
i++;
compileOptions.ignorePatterns = this.parseIgnorePatterns(restArgs.slice(i));
i += (compileOptions.ignorePatterns?.length || 0) - 1;
break;
case "--require":
i++;
compileOptions.requiredFiles = this.parseRequiredFiles(restArgs.slice(i));
i += (compileOptions.requiredFiles?.length || 0) - 1;
break;
case "--init":
compileOptions.init = true;
break;
case "--metrics":
compileOptions.includeMetrics = true;
break;
case "--debug":
compileOptions.debug = true;
break;
default:
if (arg.startsWith("-")) {
throw new errors_1.ValidationError(`Unknown option: ${arg}`);
}
}
}
// Extract entry files first (should be first argument)
const entryFiles = args[0]
? this.parseEntryFiles(args[0], compileOptions.rootDir)
: [];
// Combine everything into final options
const result = {
entryFiles,
outputFile,
...compileOptions,
};
this.logDebug(`Parsed options: ${JSON.stringify(result)}`);
return result;
}
catch (error) {
throw new errors_1.ValidationError(`Failed to parse arguments: ${error instanceof Error ? error.message : String(error)}`);
}
}
parseEntryFiles(filesArg, rootDir) {
this.logDebug(`Parsing entry files: ${filesArg} with root: ${rootDir || "not specified"}`);
this.deps.validator.validateNotEmpty(filesArg, "Entry files argument");
const files = filesArg
.split(",")
.map((f) => f.trim())
.filter((f) => f.length > 0);
if (files.length === 0) {
throw new errors_1.ValidationError("No valid entry files provided");
}
// Validate each file exists
files.forEach((file) => {
this.deps.validator.validatePath(file, "Entry file");
const absolutePath = rootDir
? this.deps.fileSystem.resolvePath(rootDir, file)
: this.deps.fileSystem.resolvePath(file);
if (!this.deps.fileSystem.exists(absolutePath)) {
throw new errors_1.ValidationError(`Entry file does not exist: ${file}`);
}
});
return files;
}
parseOutputFile(outputArg, required = true) {
this.logDebug(`Parsing output file: ${outputArg}`);
if (required) {
this.deps.validator.validateNotEmpty(outputArg, "Output file argument");
}
const outputFile = outputArg?.trim();
if (!outputFile && required) {
throw new errors_1.ValidationError("Output file path cannot be empty");
}
// Ensure output directory exists or can be created
if (outputFile) {
const outputDir = this.deps.fileSystem.getDirName(outputFile);
if (!this.deps.fileSystem.exists(outputDir)) {
try {
this.deps.fileSystem.createDirectory(outputDir, true);
this.logDebug(`Created output directory: ${outputDir}`);
}
catch (error) {
throw new errors_1.ValidationError(`Cannot create output directory: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
return outputFile;
}
parseOptions(args) {
this.logDebug(`Parsing additional options: ${args.join(" ")}`);
const options = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case "--root":
case "-r":
i++;
options.rootDir = this.parseRootDir(args[i]);
break;
case "--depth":
case "-d":
i++;
options.maxDepth = this.parseDepth(args[i]);
break;
case "--batch-size":
i++;
const batchSize = this.deps.validator.validateNotEmpty(args[i], "Batch size");
const parsedBatchSize = parseInt(batchSize);
if (isNaN(parsedBatchSize) || parsedBatchSize <= 0) {
throw new errors_1.ValidationError("Batch size must be a positive number");
}
options.batchSize = parsedBatchSize;
break;
case "--config":
case "-c":
i++;
const configPath = this.deps.validator.validateNotEmpty(args[i], "Config path");
if (!this.deps.fileSystem.exists(configPath)) {
throw new errors_1.ValidationError(`Config file not found: ${configPath}`);
}
options.config = { path: configPath };
break;
case "--encoding":
i++;
const encoding = this.deps.validator.validateNotEmpty(args[i], "Encoding");
if (!this.isValidEncoding(encoding)) {
throw new errors_1.ValidationError(`Invalid encoding: ${encoding}`);
}
options.encoding = encoding;
break;
case "--ignore":
i++;
options.ignorePatterns = this.parseIgnorePatterns(args.slice(i));
i += (options.ignorePatterns?.length || 0) - 1;
break;
case "--require":
i++;
options.requiredFiles = this.parseRequiredFiles(args.slice(i));
i += (options.requiredFiles?.length || 0) - 1;
break;
case "--init":
options.init = true;
break;
case "--metrics":
options.includeMetrics = true;
break;
case "--debug":
options.debug = true;
break;
default:
if (arg.startsWith("-")) {
throw new errors_1.ValidationError(`Unknown option: ${arg}`);
}
}
}
return options;
}
parseRootDir(rootDir) {
this.logDebug(`Parsing root directory: ${rootDir}`);
this.deps.validator.validateNotEmpty(rootDir, "Root directory argument");
const absolutePath = path_1.default.resolve(rootDir);
if (!this.deps.fileSystem.exists(absolutePath)) {
throw new errors_1.ValidationError("Root directory does not exist", {
rootDir: absolutePath,
});
}
return absolutePath;
}
parseDepth(depthArg) {
this.logDebug(`Parsing depth: ${depthArg}`);
this.deps.validator.validateNotEmpty(depthArg, "Depth argument");
const depth = parseInt(depthArg);
if (isNaN(depth)) {
throw new errors_1.ValidationError("Depth must be a number");
}
if (depth < 0) {
throw new errors_1.ValidationError("Depth cannot be negative");
}
if (depth > this.maxDepth) {
throw new errors_1.ValidationError(`Depth cannot exceed ${this.maxDepth}`);
}
return depth;
}
isValidEncoding(encoding) {
const validEncodings = [
"utf8",
"utf-8",
"utf16le",
"latin1",
"ascii",
"base64",
"hex",
"binary",
"ucs2",
];
return validEncodings.includes(encoding);
}
parseIgnorePatterns(args) {
this.logDebug(`Parsing ignore patterns: ${args.join(" ")}`);
const patterns = [];
for (const arg of args) {
if (arg.startsWith("-"))
break;
patterns.push(arg);
}
return patterns;
}
parseRequiredFiles(args) {
this.logDebug(`Parsing required files: ${args.join(" ")}`);
const files = [];
for (const arg of args) {
if (arg.startsWith("-"))
break;
this.deps.validator.validatePath(arg, "Required file");
if (!this.deps.fileSystem.exists(arg)) {
this.deps.logger.warn(`Required file not found: ${arg}`);
continue;
}
files.push(arg);
}
return files;
}
validatePaths(entryFiles, outputFile) {
entryFiles.forEach((file) => {
this.deps.validator.validatePath(file, "Entry file");
});
if (outputFile) {
const outputDir = this.deps.fileSystem.getDirName(outputFile);
this.deps.validator.validatePath(outputDir, "Output directory");
}
}
printUsage() {
const usage = [
"Usage: gather-ts <files...> --output <output> [options]",
"",
"Arguments:",
" files Entry files to analyze (comma or space separated)",
"",
"Options:",
" -o, --output Output file path",
" -r, --root Project root directory (default: current directory)",
" -d, --depth Maximum depth for dependency analysis",
" --debug Enable debug logging",
" --batch-size Batch size for processing files",
" --metrics Include performance metrics in output",
" -c, --config Path to custom config file",
" --encoding File encoding (default: utf8)",
" --ignore Additional patterns to ignore",
" --require Required files to include",
" --init Initialize configuration in current directory",
"",
"Examples:",
" # Single file with metrics",
" $ gather-ts src/app/page.tsx --output output.txt --metrics",
"",
" # Multiple files with custom batch size",
" $ gather-ts src/app/page.tsx,src/components/Button.tsx --output output.txt --batch-size 50",
"",
"For more information, visit: https://github.com/usexr/gather-ts",
].join("\n");
this.deps.logger.info(usage);
}
}
exports.ArgumentParser = ArgumentParser;
//# sourceMappingURL=ArgumentParser.js.map