import 'dart:convert'; import 'dart:io'; import 'package:oto/cli/commands/command_base.dart'; import 'package:oto/oto/commands/command_catalog.dart'; class CommandCatalogCli extends CommandBase { final Future Function(String) _printString; CommandCatalogCli({Future Function(String)? printString}) : _printString = printString ?? ((value) async { stdout.writeln(value); }); @override String get name => 'catalog'; @override String get usage => '[arguments]'; @override String getDescription() => 'Expose registered command specs and catalog details.'; @override Map> get arguments => { '--json': ['Output catalog as structured JSON format.'], '--category ': ['Filter commands by category.'], '--command ': ['Filter commands by command name.'], }; @override bool shouldPrintExecuteLog(List parameters) { return !parameters.contains('--json'); } @override bool shouldExecuteWithoutParameters() => true; @override Future execute(List parameters) async { String? category; String? command; bool isJson = false; for (int i = 0; i < parameters.length; i++) { final param = parameters[i]; if (param == '--json') { isJson = true; } else if (param.startsWith('--category=')) { category = param.substring('--category='.length); } else if (param == '--category') { if (i + 1 < parameters.length) { category = parameters[++i]; } else { throw Exception('Missing value for --category'); } } else if (param.startsWith('--command=')) { command = param.substring('--command='.length); } else if (param == '--command') { if (i + 1 < parameters.length) { command = parameters[++i]; } else { throw Exception('Missing value for --command'); } } else { throw Exception('Unknown option: $param'); } } final catalog = const CommandCatalog(); final entries = catalog.entries(category: category, command: command); if (isJson) { final jsonMap = { 'schemaVersion': 1, 'type': 'commandCatalog', 'filters': { 'category': category, 'command': command, }, 'commands': entries.map((e) => e.toJson()).toList(), }; await _printString(const JsonEncoder.withIndent(' ').convert(jsonMap)); } else { int maxCommand = 7; int maxCategory = 8; int maxDataModel = 9; int maxSamplePath = 10; int maxSampleStatus = 12; final rows = entries.map((e) { final status = e.sampleExists ? 'Exists' : (e.hasSample ? 'Missing File' : 'No Sample'); final cmdStr = e.command; final catStr = e.category; final modelStr = e.dataModel; final pathStr = e.samplePath ?? ''; if (cmdStr.length > maxCommand) maxCommand = cmdStr.length; if (catStr.length > maxCategory) maxCategory = catStr.length; if (modelStr.length > maxDataModel) maxDataModel = modelStr.length; if (pathStr.length > maxSamplePath) maxSamplePath = pathStr.length; if (status.length > maxSampleStatus) maxSampleStatus = status.length; return _Row(cmdStr, catStr, modelStr, pathStr, status); }).toList(); final header = '${'Command'.padRight(maxCommand)} | ' '${'Category'.padRight(maxCategory)} | ' '${'DataModel'.padRight(maxDataModel)} | ' '${'SamplePath'.padRight(maxSamplePath)} | ' '${'SampleStatus'.padRight(maxSampleStatus)}'; final divider = '${'-' * maxCommand}-+-${'-' * maxCategory}-+-${'-' * maxDataModel}-+-${'-' * maxSamplePath}-+-${'-' * maxSampleStatus}'; await _printString(header); await _printString(divider); for (final row in rows) { final line = '${row.command.padRight(maxCommand)} | ' '${row.category.padRight(maxCategory)} | ' '${row.dataModel.padRight(maxDataModel)} | ' '${row.samplePath.padRight(maxSamplePath)} | ' '${row.status.padRight(maxSampleStatus)}'; await _printString(line); } } } } class _Row { final String command; final String category; final String dataModel; final String samplePath; final String status; _Row(this.command, this.category, this.dataModel, this.samplePath, this.status); }