Skip to content

Conversation

@drewda
Copy link

@drewda drewda commented Aug 4, 2025

Hi, here's a PR to add some functionality that is useful for developers chaining this together with other tools on the command line. This is related to at least one issue that has requested some aspects of this in the past. This is my first PR ever for this repo, so please do feel free to revise as needed or to hold if it conflicts with other planned functionality. Thanks.

Summary:

Enables JSON reports to be optionally output directly to stdout for piping to other programs, rather than being written to file system. Log output is suppressed. Involves changing how output directory is handled, but not intended to change existing default functionality in the CLI or the GUI.

closes #1194

Expected behavior:

  • Adds an additional CLI argument
  • Should not change GUI behavior
  • CLI can now support usage such as:
java -jar cli/build/libs/gtfs-validator-0.1.0-SNAPSHOT-cli.jar -u https://data.trilliumtransit.com/gtfs/caltrain-ca-us/caltrain-ca-us.zip --stdout | jq '.notices[] .code'
"route_short_name_too_long"
"stop_without_stop_time"
"unknown_column"
"unknown_file"

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Run the unit tests with gradle test to make sure you didn't break anything
  • Add or update any needed documentation to the repo
  • Format the title like "feat: [new feature short description]". Title must follow the Conventional Commit Specification(https://www.conventionalcommits.org/en/v1.0.0/).
  • Linked all relevant issues
  • Include screenshot(s) a shell example showing how this pull request works and fixes the issue(s)

drewda added 2 commits August 4, 2025 13:16
Enables JSON reports to be optionally output directly to stdout for piping to other programs, rather than being written to file system. Log output is suppressed. Involves changing how output directory is handled, but not intended to change existing default functionality in the CLI or the GUI.

closes MobilityData#1194
@welcome
Copy link

welcome bot commented Aug 4, 2025

Thanks for opening this pull request! You're awesome. We use semantic commit messages to streamline the release process. Before your pull request can be merged, you should update your pull request title to start with a semantic prefix. Examples of titles with semantic prefixes:

  • fix: Bug with ssl network connections + Java module permissions.
  • feat: Initial support for multiple @PrimaryKey annotations.
  • docs: update RELEASE.md with new process
    To get this PR to the finish line, please do the following:
  • Read our Contribution Guidelines
  • Follow Google Java style coding standards
  • Include tests when adding/changing behavior
  • Include screenshots

@CLAassistant
Copy link

CLAassistant commented Aug 4, 2025

CLA assistant check
All committers have signed the CLA.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@emmambd
Copy link
Contributor

emmambd commented Sep 24, 2025

Hi @drewda! Thanks for this great contribution and apologies for it slipping our notice in the midst of getting on top of emails after summer vacations. We plan to take a look soon.

Copy link
Member

@davidgamez davidgamez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @drewda, great work!

I added a few minor comments that increase the readability of the code. I tested locally and it works as expected.

As a separate thought, I'm thinking of a fatal error scenario. In this case, as expected, the error is flushed to stderr, and nothing is output to stdout. I'm wondering if it makes sense to flush the "content" of the system_errors.json to the stdout so that the pipe processing can make decisions in case of errors. Otherwise, the pipe will get an empty string with no context of the error. This is an edge case where no human is looking at the screen. cc: @emmambd

}

try {
Desktop.getDesktop().browse(reportPath.toUri());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick]: As reportPath can be null now, even currently, the CLI parameters are not supported while running the GUI app; it would be nice to log here a message when reportPath is null and return without executing reportPath.toUri() that will raise a NullPointerException.

Comment on lines 37 to 39
config != null ? config.systemErrorsReportFileName() : null,
config != null ? config.validationReportFileName() : null,
config != null ? config.htmlReportFileName() : null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep consistency, we can set the exported filenames as they will not be created with the stdout parameter. Please take the suggested code snippet as a guide, I'm not sure if it has the right alignment or any other issue, GitHub editor has its limitations ;-)

Suggested change
config != null ? config.systemErrorsReportFileName() : null,
config != null ? config.validationReportFileName() : null,
config != null ? config.htmlReportFileName() : null,
config != null && !config.stdoutOutput() ? config.systemErrorsReportFileName() : null,
config != null && !config.stdoutOutput() ? config.validationReportFileName() : null,
config != null && !config.stdoutOutput() ? config.htmlReportFileName() : null,

Comment on lines +340 to 379
if (config.outputDirectory().isPresent()) {
Path outputDir = config.outputDirectory().get();
if (!Files.exists(outputDir)) {
try {
Files.createDirectories(outputDir);
} catch (IOException ex) {
logger.atSevere().withCause(ex).log("Error creating output directory: %s", outputDir);
}
}
}
boolean is_different_date = !now.toLocalDate().equals(config.dateForValidation());

HtmlReportGenerator htmlGenerator = new HtmlReportGenerator();
if (config.outputDirectory().isPresent()) {
Path outputDir = config.outputDirectory().get();
try {
Files.write(
outputDir.resolve(config.validationReportFileName()),
gson.toJson(jsonReport).getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
logger.atSevere().withCause(ex).log("Error creating JSON report");
}

try {
htmlGenerator.generateReport(
feedMetadata,
noticeContainer,
config,
versionInfo,
outputDir.resolve(config.htmlReportFileName()),
date,
is_different_date);
Files.write(
outputDir.resolve(config.systemErrorsReportFileName()),
gson.toJson(noticeContainer.exportSystemErrors()).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
logger.atSevere().withCause(e).log("Cannot store report files");
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion]: if the execution reached L340, then the previous conditional config.stdoutOutput() is false(if true, the function would return in line L336). In this case, we can remove the two IF conditions as the output directory is expected to be populated. Please take the suggested code snippet as a guide, I'm not sure if it has the right alignment or any other issue, GitHub editor has its limitations ;-)

Suggested change
if (config.outputDirectory().isPresent()) {
Path outputDir = config.outputDirectory().get();
if (!Files.exists(outputDir)) {
try {
Files.createDirectories(outputDir);
} catch (IOException ex) {
logger.atSevere().withCause(ex).log("Error creating output directory: %s", outputDir);
}
}
}
boolean is_different_date = !now.toLocalDate().equals(config.dateForValidation());
HtmlReportGenerator htmlGenerator = new HtmlReportGenerator();
if (config.outputDirectory().isPresent()) {
Path outputDir = config.outputDirectory().get();
try {
Files.write(
outputDir.resolve(config.validationReportFileName()),
gson.toJson(jsonReport).getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
logger.atSevere().withCause(ex).log("Error creating JSON report");
}
try {
htmlGenerator.generateReport(
feedMetadata,
noticeContainer,
config,
versionInfo,
outputDir.resolve(config.htmlReportFileName()),
date,
is_different_date);
Files.write(
outputDir.resolve(config.systemErrorsReportFileName()),
gson.toJson(noticeContainer.exportSystemErrors()).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
logger.atSevere().withCause(e).log("Cannot store report files");
}
}
}
Path outputDir = config.outputDirectory().get();
if (!Files.exists(outputDir)) {
try {
Files.createDirectories(outputDir);
} catch (IOException ex) {
logger.atSevere().withCause(ex).log("Error creating output directory: %s", outputDir);
}
}
boolean is_different_date = !now.toLocalDate().equals(config.dateForValidation());
HtmlReportGenerator htmlGenerator = new HtmlReportGenerator();
try {
Files.write(
outputDir.resolve(config.validationReportFileName()),
gson.toJson(jsonReport).getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
logger.atSevere().withCause(ex).log("Error creating JSON report");
}
try {
htmlGenerator.generateReport(
feedMetadata,
noticeContainer,
config,
versionInfo,
outputDir.resolve(config.htmlReportFileName()),
date,
is_different_date);
Files.write(
outputDir.resolve(config.systemErrorsReportFileName()),
gson.toJson(noticeContainer.exportSystemErrors()).getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
logger.atSevere().withCause(e).log("Cannot store report files");
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add command-line parameter to silence non-error log messages

4 participants