-
-
Notifications
You must be signed in to change notification settings - Fork 18
feat(terminal): Add an unconditional terminal colour policy #2808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| RUN --color always STDIN /dev/null IN . INTO result.txt EXPECTING 0 | ||
| WRITE expected.txt UNTIL END | ||
| 1> [31mstreamed[0m | ||
| 1> [1;32mpainted[0m | ||
| END | ||
| COMPARE result.txt AGAINST expected.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| RUN --color auto STDIN /dev/null IN . INTO result.txt EXPECTING 0 | ||
| WRITE expected.txt UNTIL END | ||
| 1> streamed | ||
| 1> painted | ||
| END | ||
| COMPARE result.txt AGAINST expected.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| RUN --color never STDIN /dev/null IN . INTO result.txt EXPECTING 0 | ||
| WRITE expected.txt UNTIL END | ||
| 1> streamed | ||
| 1> painted | ||
| END | ||
| COMPARE result.txt AGAINST expected.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| #include <sourcemeta/core/options.h> | ||
| #include <sourcemeta/core/terminal.h> | ||
|
|
||
| #include <cassert> | ||
| #include <cstdlib> | ||
| #include <iostream> | ||
| #include <string_view> | ||
|
|
||
| #if defined(_WIN32) | ||
| #include <fcntl.h> // _O_BINARY | ||
| #include <io.h> // _setmode, _fileno | ||
| #endif | ||
|
|
||
| namespace { | ||
|
|
||
| auto run_mode(sourcemeta::core::TerminalColorPolicy policy) -> int { | ||
| sourcemeta::core::terminal_set_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdout, policy); | ||
| assert(!sourcemeta::core::terminal_is_interactive( | ||
| sourcemeta::core::TerminalStream::Stdout)); | ||
| sourcemeta::core::terminal_paint( | ||
| std::cout, sourcemeta::core::TerminalStream::Stdout, "streamed", | ||
| sourcemeta::core::TerminalStyle::Red); | ||
| std::cout << "\n" | ||
| << sourcemeta::core::terminal_paint( | ||
| sourcemeta::core::TerminalStream::Stdout, "painted", | ||
| sourcemeta::core::TerminalStyle::Bold | | ||
| sourcemeta::core::TerminalStyle::Green) | ||
| << "\n" | ||
| << std::flush; | ||
| return EXIT_SUCCESS; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| auto main(int argc, char **argv) -> int { | ||
| #if defined(_WIN32) | ||
| // Ensure standard output is binary on Windows so newlines are not converted | ||
| // to CRLF (\r\n), which would alter captured byte stream comparisons. | ||
| _setmode(_fileno(stdout), _O_BINARY); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed btw? Might be worth a comment explaining it? |
||
| #endif | ||
|
|
||
| sourcemeta::core::Options application; | ||
| application.option("color", {}); | ||
| application.parse(argc, argv); | ||
|
|
||
| auto policy{sourcemeta::core::TerminalColorPolicy::WhenInteractive}; | ||
| if (application.contains("color")) { | ||
| const auto &values{application.at("color")}; | ||
| if (values.empty()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: The Prompt for AI agents |
||
| std::cerr << "missing value for --color\n"; | ||
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| const auto value{values.front()}; | ||
| if (value == "always") { | ||
| policy = sourcemeta::core::TerminalColorPolicy::Always; | ||
| } else if (value == "auto" || value == "when-interactive") { | ||
| policy = sourcemeta::core::TerminalColorPolicy::WhenInteractive; | ||
| } else if (value == "never" || value == "disabled") { | ||
| policy = sourcemeta::core::TerminalColorPolicy::Disabled; | ||
| } else { | ||
| std::cerr << "unknown --color option: " << value << "\n"; | ||
| return EXIT_FAILURE; | ||
| } | ||
| } | ||
|
|
||
| return run_mode(policy); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -211,6 +211,8 @@ TEST(policy_default_argument) { | |
| } | ||
|
|
||
| TEST(policy_lifecycle_and_isolation) { | ||
| sourcemeta::core::terminal_reset_color_policy(); | ||
|
|
||
| // Global configuration affects all streams | ||
| sourcemeta::core::terminal_set_color_policy( | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
|
|
@@ -251,11 +253,46 @@ TEST(policy_lifecycle_and_isolation) { | |
| EXPECT_EQ(stderr_stream.str(), "error_stream"); | ||
|
|
||
| // Restore default policy across all streams | ||
| sourcemeta::core::terminal_reset_color_policy(); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stderr), | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| } | ||
|
|
||
| TEST(policy_reset_global) { | ||
| sourcemeta::core::terminal_set_color_policy( | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| sourcemeta::core::TerminalColorPolicy::Always); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdout), | ||
| sourcemeta::core::TerminalColorPolicy::Always); | ||
| sourcemeta::core::terminal_reset_color_policy(); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdout), | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stderr), | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdin), | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| } | ||
|
|
||
| TEST(policy_reset_per_stream) { | ||
| sourcemeta::core::terminal_set_color_policy( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The ScopedColorPolicy guard was removed in favor of manual terminal_reset_color_policy() calls placed only at the end of each policy test. Because the test runner executes every TEST in this file sequentially in a single process, any test that aborts before its trailing reset (a debug assert, crash, or a future early return) leaks the global per-stream policy into all subsequent tests, producing cascade failures. The removed RAII guard restored state in its destructor unconditionally, so this is a real loss of test isolation. Reset the policy at the start of each policy test (several tests, including policy_reset_global, policy_always_enables_styling, policy_disabled_suppresses_styling and paint_destination_aware_*, only reset at the end), or keep a fixture/teardown that always restores. Prompt for AI agents |
||
| sourcemeta::core::TerminalStream::Stderr, | ||
| sourcemeta::core::TerminalColorPolicy::Disabled); | ||
| sourcemeta::core::terminal_set_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdout, | ||
| sourcemeta::core::TerminalColorPolicy::Always); | ||
| sourcemeta::core::terminal_reset_color_policy( | ||
| sourcemeta::core::TerminalStream::Stderr); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stderr), | ||
| sourcemeta::core::TerminalColorPolicy::WhenInteractive); | ||
| EXPECT_EQ(sourcemeta::core::terminal_color_policy( | ||
| sourcemeta::core::TerminalStream::Stdout), | ||
| sourcemeta::core::TerminalColorPolicy::Always); | ||
| sourcemeta::core::terminal_reset_color_policy(); | ||
| } | ||
|
|
||
| TEST(stream_detection_runs_safely) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: These reset functions hardcode
TerminalColorPolicy::WhenInteractiveand do not restore the policy that was active before the change, even though the review feedback that motivated them asked for a reset that lets the module "fall back to the original policy". Any caller that setsDisabledorAlwaysand later invokesterminal_reset_color_policy()(expecting the usual restore-previous semantics of a "reset" API) silently getsWhenInteractiveinstead — for example re-enabling ANSI styling on an interactive terminal that the application explicitly disabled. Either track and restore the prior per-stream policy, or rename/document these as set-to-default so the contract is not misleading.Prompt for AI agents