using OfficeIMO.Reader; using OfficeIMO.Reader.All; namespace OfficeIMO.Tool.Commands.Reader; internal static class ReaderCommand { internal const string Usage = """ OfficeIMO.Tool - Reader Usage: officeimo reader read [--name ] [--format markdown|json] [--output ] [--assets ] [--max-input-bytes ] officeimo reader folder --output [--format markdown|json] [--assets ] [--concurrency <1-64>] [--max-files ] [--max-total-bytes ] [--max-input-bytes ] [--no-recursive] officeimo reader capabilities [--format text|json] The dependency-bounded tool does not configure OCR or hosted providers. """; internal static async Task RunAsync( string[] args, Stream standardInput, TextWriter standardOutput, TextWriter standardError, CancellationToken cancellationToken = default, bool overwriteSingleOutput = true) { if (standardInput == null) throw new ArgumentNullException(nameof(standardInput)); if (standardOutput == null) throw new ArgumentNullException(nameof(standardOutput)); if (standardError == null) throw new ArgumentNullException(nameof(standardError)); ReaderToolArguments parsed; try { parsed = ReaderToolArguments.Parse(args); } catch (ReaderToolUsageException exception) { await standardError.WriteLineAsync(exception.Message).ConfigureAwait(false); await standardError.WriteLineAsync(Usage).ConfigureAwait(false); return (int)OfficeImoToolExitCode.Usage; } if (parsed.Command == ReaderToolCommand.Help) { await standardOutput.WriteLineAsync(Usage).ConfigureAwait(false); return (int)OfficeImoToolExitCode.Success; } try { OfficeDocumentReader reader = new OfficeDocumentReaderBuilder() .AddAllOfficeIMOHandlers() .WithMaxConcurrentReads(parsed.Concurrency) .Build(); return parsed.Command switch { ReaderToolCommand.Read => await RunReadAsync( parsed, reader, standardInput, standardOutput, overwriteSingleOutput, cancellationToken).ConfigureAwait(false), ReaderToolCommand.Folder => await RunFolderAsync( parsed, reader, standardError, cancellationToken).ConfigureAwait(false), ReaderToolCommand.Capabilities => await RunCapabilitiesAsync( parsed, reader, standardOutput).ConfigureAwait(false), _ => (int)OfficeImoToolExitCode.Usage }; } catch (OperationCanceledException) { await standardError.WriteLineAsync("Operation cancelled.").ConfigureAwait(false); return (int)OfficeImoToolExitCode.Cancelled; } catch (ReaderToolOutputException exception) { await standardError.WriteLineAsync(exception.Message).ConfigureAwait(false); return (int)OfficeImoToolExitCode.OutputFailed; } catch (FileNotFoundException exception) { await standardError.WriteLineAsync(exception.Message).ConfigureAwait(false); return (int)OfficeImoToolExitCode.InputNotFound; } catch (DirectoryNotFoundException exception) { await standardError.WriteLineAsync(exception.Message).ConfigureAwait(false); return (int)OfficeImoToolExitCode.InputNotFound; } catch (NotSupportedException exception) { await standardError.WriteLineAsync(exception.Message).ConfigureAwait(false); return (int)OfficeImoToolExitCode.UnsupportedInput; } catch (Exception exception) { await standardError.WriteLineAsync( "Document read failed: " + exception.GetType().Name + ": " + exception.Message).ConfigureAwait(false); return (int)OfficeImoToolExitCode.OperationFailed; } } private static async Task RunReadAsync( ReaderToolArguments options, OfficeDocumentReader reader, Stream standardInput, TextWriter standardOutput, bool overwriteOutput, CancellationToken cancellationToken) { var readerOptions = new ReaderOptions { MaxInputBytes = options.MaxInputBytes }; OfficeDocumentReadResult document; string? sourcePath = null; if (options.InputPath == "-") { document = await reader.ReadDocumentAsync( standardInput, options.SourceName, readerOptions, cancellationToken: cancellationToken).ConfigureAwait(false); } else { sourcePath = Path.GetFullPath(options.InputPath!); if (!File.Exists(sourcePath)) { throw new FileNotFoundException("Input file '" + sourcePath + "' does not exist.", sourcePath); } if (!string.IsNullOrWhiteSpace(options.OutputPath) && options.OutputPath != "-") { ReaderToolPathSafety.EnsureDistinctFile(sourcePath, options.OutputPath!); } document = await reader.ReadDocumentAsync(sourcePath, readerOptions, cancellationToken) .ConfigureAwait(false); } await ReaderToolOutput.WriteSingleDocumentAsync( document, options.Format, options.OutputPath, standardOutput, options.AssetsPath, sourcePath, overwriteOutput, cancellationToken).ConfigureAwait(false); return (int)OfficeImoToolExitCode.Success; } private static async Task RunFolderAsync( ReaderToolArguments options, OfficeDocumentReader reader, TextWriter standardError, CancellationToken cancellationToken) { string inputPath = Path.GetFullPath(options.InputPath!); if (!Directory.Exists(inputPath)) { throw new DirectoryNotFoundException("Input folder '" + inputPath + "' does not exist."); } string outputPath = Path.GetFullPath(options.OutputPath!); string? assetsPath = string.IsNullOrWhiteSpace(options.AssetsPath) ? null : Path.GetFullPath(options.AssetsPath!); ReaderToolPathSafety.EnsureOutsideInput(inputPath, outputPath, assetsPath); IReadOnlyList paths = ReaderToolFileDiscovery.FindSupportedFiles( inputPath, reader, options.Recurse, options.MaxFiles, options.MaxTotalBytes, cancellationToken); IReadOnlyList documents = await reader.ReadDocumentsAsync( paths, options: new ReaderOptions { MaxInputBytes = options.MaxInputBytes }, batchOptions: new ReaderBatchOptions { MaxDegreeOfParallelism = options.Concurrency, MaxDocuments = options.MaxFiles }, cancellationToken: cancellationToken).ConfigureAwait(false); await ReaderToolOutput.WriteFolderAsync( inputPath, outputPath, assetsPath, paths, documents, options.Format, cancellationToken).ConfigureAwait(false); await standardError.WriteLineAsync("Converted " + paths.Count + " document(s).").ConfigureAwait(false); return (int)OfficeImoToolExitCode.Success; } private static async Task RunCapabilitiesAsync( ReaderToolArguments options, OfficeDocumentReader reader, TextWriter standardOutput) { if (options.Format == ReaderToolOutputFormat.Json) { await standardOutput.WriteLineAsync(reader.GetCapabilityManifestJson(indented: true)).ConfigureAwait(false); return (int)OfficeImoToolExitCode.Success; } foreach (ReaderHandlerCapability capability in reader.GetCapabilities()) { await standardOutput.WriteLineAsync( capability.Id + "\t" + capability.Origin + "\t" + string.Join(",", capability.Extensions)).ConfigureAwait(false); } return (int)OfficeImoToolExitCode.Success; } }