ProbeOutputWriter.java
package net.sasasin.sreader.cli;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import net.sasasin.sreader.service.extraction.ExtractionDecision;
import net.sasasin.sreader.service.extraction.ExtractionFallbackReason;
import net.sasasin.sreader.service.extraction.PaginationMetadata;
import net.sasasin.sreader.service.extraction.PaginationPageTrace;
import net.sasasin.sreader.service.probe.ProbeDocument;
import net.sasasin.sreader.service.probe.ProbeOutcome;
import picocli.CommandLine.Model.CommandSpec;
public class ProbeOutputWriter {
private final CommandSpec spec;
public ProbeOutputWriter(CommandSpec spec) {
this.spec = spec;
}
public int writeSucceeded(
ProbeOutcome.Succeeded succeeded, boolean verbose, String outputPath, Integer maxChars) {
String text = succeeded.text();
String outputText = text;
if (maxChars != null && maxChars > 0 && outputText.length() > maxChars) {
outputText = outputText.substring(0, maxChars);
}
if (verbose) {
writeVerboseDiagnostics(
succeeded.document(), succeeded.decision(), succeeded.pagination(), Optional.empty());
}
return writeBody(outputText, outputPath);
}
public void writeNoContentDiagnostics(
ProbeDocument document, Optional<PaginationMetadata> pagination) {
writeVerboseDiagnostics(document, null, pagination, Optional.empty());
}
public void writeFailureDiagnostics(
ProbeDocument documentOrNull,
Optional<PaginationMetadata> pagination,
Optional<String> errorMessage) {
if (documentOrNull != null) {
writeVerboseDiagnostics(documentOrNull, null, pagination, errorMessage);
} else {
writePaginationOnly(pagination, errorMessage);
}
}
private int writeBody(String outputText, String outputPath) {
if (outputPath != null) {
try {
Files.writeString(Path.of(outputPath), outputText, StandardCharsets.UTF_8);
spec.commandLine().getErr().println("Wrote probe output to " + outputPath);
} catch (IOException e) {
throw new RuntimeException("Failed to write --output file: " + outputPath, e);
}
return CliExitCodes.SUCCESS;
}
spec.commandLine().getOut().print(outputText);
return CliExitCodes.SUCCESS;
}
private void writeVerboseDiagnostics(
ProbeDocument document,
ExtractionDecision decision,
Optional<PaginationMetadata> pagination,
Optional<String> errorMessage) {
PrintWriter err = spec.commandLine().getErr();
err.printf("method:%n %s%n", document.method().value());
err.printf("input URL:%n %s%n", document.inputUrl());
err.printf("first final URL:%n %s%n", document.finalUrl());
if (decision != null) {
err.printf("extractor source:%n %s%n", decision.source().wireValue());
err.printf(
"fallback:%n %s%n",
decision.fallbackReason().map(ExtractionFallbackReason::name).orElse("none"));
}
document.title().ifPresent(title -> err.printf("title:%n %s%n", title));
errorMessage.ifPresent(msg -> err.printf("error:%n %s%n", msg));
pagination.ifPresent(this::writePaginationBlock);
}
private void writePaginationOnly(
Optional<PaginationMetadata> pagination, Optional<String> errorMessage) {
PrintWriter err = spec.commandLine().getErr();
errorMessage.ifPresent(msg -> err.printf("error:%n %s%n", msg));
pagination.ifPresent(this::writePaginationBlock);
}
private void writePaginationBlock(PaginationMetadata meta) {
PrintWriter err = spec.commandLine().getErr();
err.println();
err.println("AutoPagerize dataset:");
err.printf(" id: %d%n", meta.datasetId());
err.printf(" sha256: %s%n", meta.datasetSha256());
err.printf(" importer version: %d%n", meta.importerVersion());
err.printf(
" active or explicitly selected: %s%n",
meta.explicitDatasetSelection() ? "explicit" : "active");
err.println();
err.println("Matched rule:");
if (meta.ruleOrdinal().isEmpty()) {
err.println(" none");
} else {
err.printf(" ordinal: %d%n", meta.ruleOrdinal().get());
err.printf(" name: %s%n", meta.ruleName().orElse(""));
err.printf(" url pattern: %s%n", meta.urlPattern().orElse(""));
err.printf(" nextLink: %s%n", meta.nextLinkXpath().orElse(""));
err.printf(" pageElement: %s%n", meta.pageElementXpath().orElse(""));
}
err.println();
err.println("Pages:");
if (meta.pages().isEmpty()) {
err.println(" (none loaded)");
} else {
for (PaginationPageTrace page : meta.pages()) {
err.printf(
" %d requested=%s final=%s bytes=%d%n",
page.pageNumber(), page.requestedUri(), page.finalUri(), page.byteSize());
}
}
meta.failedRequestedUrl().ifPresent(uri -> err.printf(" failed requested URL: %s%n", uri));
meta.lastPageFinalUrl().ifPresent(uri -> err.printf(" last successful final URL: %s%n", uri));
err.println();
err.println("Pagination:");
err.printf(" page count: %d%n", meta.pageCount());
err.printf(" stop reason: %s%n", meta.stopReason().name());
err.printf(" complete: %s%n", meta.complete());
err.printf(" total bytes: %d%n", meta.totalBytes());
if (!meta.contributions().isEmpty()) {
err.println();
err.println("Per-page extraction source:");
meta.contributions()
.forEach(
c ->
err.printf(
" %d source=%s fallback=%s%n",
c.pageNumber(),
c.source().wireValue(),
c.fallbackReason().map(Enum::name).orElse("none")));
}
}
}