runShaderConverterCli function

Future<ShaderConverterCliResult> runShaderConverterCli(
  1. List<String> args, {
  2. StringSink? out,
  3. StringSink? err,
})

Runs the shader conversion CLI with args.

Returns a structured summary and does not call exit() directly.

Implementation

Future<ShaderConverterCliResult> runShaderConverterCli(
  List<String> args, {
  StringSink? out,
  StringSink? err,
}) async {
  final stdoutSink = out ?? stdout;
  final stderrSink = err ?? stderr;

  _ParsedArgs parsed;
  try {
    parsed = _parseArgs(args);
  } on FormatException catch (error) {
    stderrSink.writeln(error.message);
    _printUsage(stdoutSink);
    return const ShaderConverterCliResult(
      exitCode: 64,
      convertedCount: 0,
      skippedCount: 0,
      failedCount: 0,
    );
  }

  if (parsed.showHelp) {
    _printUsage(stdoutSink);
    return const ShaderConverterCliResult(
      exitCode: 0,
      convertedCount: 0,
      skippedCount: 0,
      failedCount: 0,
    );
  }

  final inputEntity = FileSystemEntity.typeSync(parsed.inputPath);
  if (inputEntity == FileSystemEntityType.notFound) {
    stderrSink.writeln('Input path not found: ${parsed.inputPath}');
    return const ShaderConverterCliResult(
      exitCode: 2,
      convertedCount: 0,
      skippedCount: 0,
      failedCount: 0,
    );
  }

  final outputDir = Directory(parsed.outputDirPath);
  if (!parsed.dryRun) {
    outputDir.createSync(recursive: true);
  }

  Directory? propertiesDir;
  if (parsed.propertiesDirPath != null) {
    propertiesDir = Directory(parsed.propertiesDirPath!);
    if (!parsed.dryRun) {
      propertiesDir.createSync(recursive: true);
    }
  }

  final sourceFiles = _collectSourceFiles(parsed.inputPath, inputEntity);
  if (sourceFiles.isEmpty) {
    stderrSink.writeln('No .glsl files found from input: ${parsed.inputPath}');
    return const ShaderConverterCliResult(
      exitCode: 2,
      convertedCount: 0,
      skippedCount: 0,
      failedCount: 0,
    );
  }

  final converter = ShaderConverter();
  var convertedCount = 0;
  var failedCount = 0;
  var skippedCount = 0;

  for (final sourceFile in sourceFiles) {
    final sourceBaseName = _basenameWithoutExtension(sourceFile.path);
    final outputBaseName = parsed.fileNameMap[sourceBaseName] ?? sourceBaseName;
    final targetShaderPath = '${outputDir.path}/$outputBaseName.frag';
    final targetShaderFile = File(targetShaderPath);

    ShaderConversionOutput result;
    try {
      final source = sourceFile.readAsStringSync();
      result = converter.convert(source);
    } on IOException catch (error) {
      failedCount++;
      stderrSink.writeln('[FAIL] ${sourceFile.path} -> $targetShaderPath');
      stderrSink.writeln('  - [error] $error');
      continue;
    }

    if (result.hasErrors) {
      failedCount++;
      stderrSink.writeln('[FAIL] ${sourceFile.path} -> $targetShaderPath');
      for (final issue in result.issues) {
        final level = issue.isError ? 'error' : 'warn';
        stderrSink.writeln('  - [$level] ${issue.message}');
      }
      continue;
    }

    if (!parsed.overwrite && targetShaderFile.existsSync()) {
      skippedCount++;
      stdoutSink.writeln(
          '[SKIP] $targetShaderPath already exists (use --overwrite).');
      continue;
    }

    if (!parsed.dryRun) {
      final shaderOutput = parsed.mobileCompatible
          ? _makeMobileCompatibleShader(result.shaderSource)
          : result.shaderSource;
      targetShaderFile.writeAsStringSync(shaderOutput);
      if (propertiesDir != null) {
        final propertiesPath = '${propertiesDir.path}/$outputBaseName.json';
        final propertiesFile = File(propertiesPath);
        propertiesFile.writeAsStringSync(result.propertiesJson());
      }
    }

    convertedCount++;
    stdoutSink.writeln('[OK] ${sourceFile.path} -> $targetShaderPath');
    for (final issue in result.issues) {
      stdoutSink.writeln('  - [warn] ${issue.message}');
    }
  }

  stdoutSink.writeln('');
  stdoutSink.writeln('Conversion summary:');
  stdoutSink.writeln('  Converted: $convertedCount');
  stdoutSink.writeln('  Skipped:   $skippedCount');
  stdoutSink.writeln('  Failed:    $failedCount');

  return ShaderConverterCliResult(
    exitCode: failedCount > 0 ? 1 : 0,
    convertedCount: convertedCount,
    skippedCount: skippedCount,
    failedCount: failedCount,
  );
}