public sealed override string Process(string[] args)
            {
                Setup(args);

                var items  = SelectFiles(Brotli.ListPath(args[ExtraArgumentCount + 0]));
                var output = args.Length >= ExtraArgumentCount + 2 ? args[ExtraArgumentCount + 1] : GetTemporaryOutputFile();

                using var table = new Table.CSV(output, Columns);

                var result = new FileWorker <T> {
                    Work  = GenerateRows,
                    Error = OnError
                }.Start(table, items);

                Finalize(table);

                return($"{WorkDesc} {result.TotalProcessed} file(s) with {result.TotalErrors} error(s).");
            }
 protected virtual void Finalize(Table.CSV table)
 {
 }
        public string Process(string[] args)
        {
            int totalFiles  = 0;
            int failedFiles = 0;

            var items = Brotli.ListPath(args[0]).SelectCompressedFiles().ToArray();

            using (var progress = new Progress(items.Length))
                using (var table = new Table.CSV(args[1], new [] {
                    "File", "Quality", "Original Bytes", "Reserialize Bytes", "Rebuild Bytes", "Avg Reserialize Time (ms)", "Avg Rebuild Time (ms)"
                })){
                    foreach (var(group, file) in items)
                    {
                        progress.Start($"Processing {file}");

                        int? originalBytes        = file.SizeBytes;
                        int? reserializeBytes     = null;
                        int? rebuildBytes         = null;
                        long?reserializeTotalTime = 0L;
                        long?rebuildTotalTime     = 0L;

                        for (int run = 1; run <= SkippedRuns + CountedRuns; run++)
                        {
                            Stopwatch swReserialize = Stopwatch.StartNew();

                            try{
                                reserializeBytes = group.CountBytesAndValidate(file.Reader);
                            }catch (Exception e) {
                                Debug.WriteLine(e.ToString());
                                ++failedFiles;
                                reserializeTotalTime = null;
                                rebuildTotalTime     = null;
                                break;
                            }finally{
                                swReserialize.Stop();
                            }

                            Stopwatch swRebuild = Stopwatch.StartNew();

                            try{
                                rebuildBytes = group.CountBytesAndValidate(file.Transforming(new TransformRebuild()));
                            }catch (Exception e) {
                                Debug.WriteLine(e.ToString());
                                ++failedFiles;
                                reserializeTotalTime = null;
                                rebuildTotalTime     = null;
                                break;
                            }finally{
                                swRebuild.Stop();
                            }

                            if (run > SkippedRuns)
                            {
                                reserializeTotalTime += swReserialize.ElapsedMilliseconds;
                                rebuildTotalTime     += swRebuild.ElapsedMilliseconds;
                            }
                        }

                        ++totalFiles;
                        table.AddRow(file.Name, file.Identifier, originalBytes, reserializeBytes, rebuildBytes, reserializeTotalTime / CountedRuns, rebuildTotalTime / CountedRuns); // subtraction propagates null

                        progress.Finish($"Completed  {file}");
                    }
                }

            return($"Processed {totalFiles} file(s) with {failedFiles} error(s).");
        }