-
Notifications
You must be signed in to change notification settings - Fork 9
Add BuildpackOutput
#721
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
Merged
Merged
Add BuildpackOutput
#721
Changes from all commits
Commits
Show all changes
100 commits
Select commit
Hold shift + click to select a range
86fba2f
Port build output from Ruby build pack
a5843fc
[Stacked PR] Remove warn_later from output (#760)
schneems d4b83f9
Removes the boxed trait state machine pattern and replaces it with st…
schneems fafddee
Remove ReadYourWrite utility struct
schneems 8127b19
Fix typos
Malax 314742f
Draft: Slimmer build output PR experiment (#761)
Malax 70ac8d0
Fix tests, address clippy lints
schneems 80c28c3
Address `strip_trailing_whitespace` comments
schneems b55e2c7
Remove ascii_table dependency
schneems 15c828e
Rename `build_log` module to `BuildpackOutput`
schneems 4e2887f
Rename `InSection` to `Section`
schneems 4e6f3d3
Fix spelling in test
schneems d677ee5
s/log/output for buildpack_output
schneems b9f6e2f
Replace announce struct with additional state
schneems 153cd53
Hide docs for public but internal structures
schneems f4c210c
Remove BuildData
Malax 9070ac2
Rustdoc touchups
Malax 83a195a
Rename NOCOLOR to NO_COLOR
Malax a497bdb
Fix "miliseconds" typo
Malax 537df68
Remove output module, rename output feature
Malax c8793a7
Add failing test and fix test spelling
schneems 8ba3248
Add ParagraphInspectWrite
8000
Malax ff79b30
Replace state::Announce with ParagraphInspectWrite
schneems d63cb52
Fix typo
Malax 53db8f8
Remove infectious Debug trait bounds
Malax fc36f23
Replace `Stream` with `BuildpackOutput` state
Malax 174fc02
Remove inline_output module
Malax 81f63d0
Remove trim_end_lines
Malax f23b888
Refactor prefix_indent
Malax 870ca33
Refactor style
Malax af7494c
Fix warning and double warning tests
schneems b3d498d
Fix header followed by warning tests
schneems 367504c
Reduce clippy warnings
schneems 08478b8
Refactor warn/important/error internals
schneems dd83998
Adjust visibility due to clippy
schneems 644c5ae
Change &str to impl AsRef<str> for flexability
schneems f36c4b3
Move style logic closer to the end use
schneems b5b29e0
Rename TimedStream to Stream
schneems 2f7a13e
Remove pretty_assertions
Malax 910c619
Remove fun_run
Malax 6f5c6d6
Move Duration formatting into dedicated file
Malax e5daaf4
Clean up duration_format.rs
Malax 83dacfc
Remove unused pub constants
Malax 3624627
Move constants to constants.rs
Malax bba601d
Move strip_control_codes closer to single usage
Malax 549e73a
Remove single-use renamed constants
Malax 7efe225
Refactor and rename strip_control_codes
Malax a534371
Move ANSI related code to separate module
Malax 6b1cea9
Move prefix functions to util module
Malax 3e3cc80
Remove wildcard import
Malax 089964f
Remove const_format
Malax 2d4fc79
Remove unnecessary must_use attributes
Malax b3ecc86
Simplify BuildpackOutput<state::Section<W>>::step
Malax 17922c3
Remove BuildpackOutput<state::Section<W>>::step_mut
Malax 3e029e7
Rename colorize_multiline to inject_default_ansi_escape, add docs
Malax 075c468
Fix module docs for buildpack_output::style
Malax 2f8b845
Rename all state finishing functions to "finish"
Malax 390f330
Update README
Malax b4e877c
Apply suggestions from code review
schneems 1c27a74
Update CHANGELOG.md to correct module name
schneems 26a9b09
Follow rust style guides for .expect()
schneems e772758
Add doctests for `build_output::state`
schneems f31f660
Apply suggestions from code review
schneems a3571b6
Add BuildpackOutput docs
schneems 0d8629b
Apply suggestions from code review
schneems 7fd6408
Update date formatting
schneems 5fdedb5
Add tests for nested ansi cases
schneems 71b3af2
Remove unnecessary iterator
schneems b98c6a4
Make LockedWriter #[cfg(test)] move to bottom
schneems ad08878
Re-order functions
schneems 220fa16
Update ANSI color docs
schneems 63c08c8
Refactor inject_default_ansi_escape to take enum
schneems bd7f2cd
Rename function
schneems 6993046
Move module docs from file to the code
schneems f620ce3
Apply suggestions from code review
schneems 5993033
Add warning language
schneems 5b88969
Specify various streaming states must be used
schneems 912e53f
Test important and error buildpack output
schneems 3240465
Test color codes for warning/important/etc.
schneems 6abd930
Remove confusing fmt::Write usage
schneems 167cdc7
Add docs and tests for ParagraphInspectWrite
schneems ddb6bbf
Assert prefix_first_rest_lines preserves inner newlines
schneems 8be7f90
Add test for empty line case
schneems acf3f77
Apply suggestions from code review
schneems ab5d9a5
Document, test, and refactor prefix functions
schneems f3f6e39
Remove `log` and `error` features
schneems fd2a647
Merge branch 'main' into schneems/output
schneems a76c490
Update changelog
schneems 55868b3
Revert removing error, only remove log
schneems 1da805e
Revert removing `log` and changes to `on_error`
edmorley 16c90e6
Remove stray README newline
edmorley 7d8fa16
Revert the implementation change in ab5d9a5579b3d9b1a7b54da80f27c9134…
edmorley 7185440
Remove stray word from CHANGELOG
edmorley 5e20c41
Fix `write_paragraph`'s handling of prefixes for empty lines
edmorley 3ad2008
Sanitize stray newlines at point of origin
schneems aa19940
Update testcase to cover trailing newlines in another case
edmorley 67e67aa
Add a test for streaming with blank lines and a trailing newline
edmorley d423789
Fix the `line_mapped` closure used in `start_stream`
edmorley 29f1a4e
Make line wrapping of rustdocs more consistent
edmorley e177d78
Fix double newline after streaming
schneems File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/// Wraps each line in an ANSI escape sequence while preserving prior ANSI escape sequences. | ||
/// | ||
/// ## Why does this exist? | ||
/// | ||
/// When buildpack output is streamed to the user, each line is prefixed with `remote: ` by Git. | ||
/// Any colorization of text will apply to those prefixes which is not the desired behavior. This | ||
/// function colors lines of text while ensuring that styles are disabled at the end of each line. | ||
/// | ||
/// ## Supports recursive colorization | ||
/// | ||
/// Strings that are previously colorized will not be overridden by this function. For example, | ||
/// if a word is already colored yellow, that word will continue to be yellow. | ||
pub(crate) fn wrap_ansi_escape_each_line(ansi: &ANSI, body: impl AsRef<str>) -> String { | ||
let ansi_escape = ansi.to_str(); | ||
body.as_ref() | ||
.split('\n') | ||
// If sub contents are colorized it will contain SUBCOLOR ... RESET. After the reset, | ||
// ensure we change back to the current color | ||
.map(|line| line.replace(RESET, &format!("{RESET}{ansi_escape}"))) // Handles nested color | ||
// Set the main color for each line and reset after so we don't colorize `remote:` by accident | ||
.map(|line| format!("{ansi_escape}{line}{RESET}")) | ||
// The above logic causes redundant colors and resets, clean them up | ||
.map(|line| line.replace(&format!("{ansi_escape}{ansi_escape}"), ansi_escape)) // Reduce useless color | ||
.map(|line| line.replace(&format!("{ansi_escape}{RESET}"), "")) // Empty lines or where the nested color is at the end of the line | ||
.collect::<Vec<String>>() | ||
.join("\n") | ||
} | ||
|
||
const RESET: &str = "\x1B[0m"; | ||
const RED: &str = "\x1B[0;31m"; | ||
const YELLOW: &str = "\x1B[0;33m"; | ||
const BOLD_CYAN: &str = "\x1B[1;36m"; | ||
const BOLD_PURPLE: &str = "\x1B[1;35m"; | ||
|
||
#[derive(Debug)] | ||
#[allow(clippy::upper_case_acronyms)] | ||
pub(crate) enum ANSI { | ||
Red, | ||
Yellow, | ||
BoldCyan, | ||
BoldPurple, | ||
} | ||
|
||
impl ANSI { | ||
fn to_str(&self) -> &'static str { | ||
match self { | ||
ANSI::Red => RED, | ||
ANSI::Yellow => YELLOW, | ||
ANSI::BoldCyan => BOLD_CYAN, | ||
ANSI::BoldPurple => BOLD_PURPLE, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
schneems marked this conversation as resolved.
Show resolved
Hide resolved
|
||
use super::*; | ||
|
||
#[test] | ||
fn empty_line() { | ||
let actual = wrap_ansi_escape_each_line(&ANSI::Red, "\n"); | ||
let expected = String::from("\n"); | ||
assert_eq!(expected, actual); | ||
} | ||
|
||
#[test] | ||
fn handles_nested_color_at_start() { | ||
let start = wrap_ansi_escape_each_line(&ANSI::BoldCyan, "hello"); | ||
let out = wrap_ansi_escape_each_line(&ANSI::Red, format!("{start} world")); | ||
let expected = format!("{RED}{BOLD_CYAN}hello{RESET}{RED} world{RESET}"); | ||
|
||
assert_eq!(expected, out); | ||
} | ||
|
||
#[test] | ||
fn handles_nested_color_in_middle() { | ||
let middle = wrap_ansi_escape_each_line(&ANSI::BoldCyan, "middle"); | ||
let out = wrap_ansi_escape_each_line(&ANSI::Red, format!("hello {middle} color")); | ||
let expected = format!("{RED}hello {BOLD_CYAN}middle{RESET}{RED} color{RESET}"); | ||
assert_eq!(expected, out); | ||
} | ||
|
||
#[test] | ||
fn handles_nested_color_at_end() { | ||
let end = wrap_ansi_escape_each_line(&ANSI::BoldCyan, "world"); | ||
let out = wrap_ansi_escape_each_line(&ANSI::Red, format!("hello {end}")); | ||
let expected = format!("{RED}hello {BOLD_CYAN}world{RESET}"); | ||
|
||
assert_eq!(expected, out); | ||
} | ||
|
||
#[test] | ||
fn handles_double_nested_color() { | ||
let inner = wrap_ansi_escape_each_line(&ANSI::BoldCyan, "inner"); | ||
let outer = wrap_ansi_escape_each_line(&ANSI::Red, format!("outer {inner}")); | ||
let out = wrap_ansi_escape_each_line(&ANSI::Yellow, format!("hello {outer}")); | ||
let expected = format!("{YELLOW}hello {RED}outer {BOLD_CYAN}inner{RESET}"); | ||
|
||
assert_eq!(expected, out); | ||
} | ||
|
||
#[test] | ||
fn splits_newlines() { | ||
let actual = wrap_ansi_escape_each_line(&ANSI::Red, "hello\nworld"); | ||
let expected = format!("{RED}hello{RESET}\n{RED}world{RESET}"); | ||
|
||
assert_eq!(expected, actual); | ||
} | ||
|
||
#[test] | ||
fn simple_case() { | ||
let actual = wrap_ansi_escape_each_line(&ANSI::Red, "hello world"); | ||
assert_eq!(format!("{RED}hello world{RESET}"), actual); | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
libherokubuildpack/src/buildpack_output/duration_format.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use std::time::Duration; | ||
|
||
pub(crate) fn human(duration: &Duration) -> String { | ||
schneems marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let hours = (duration.as_secs() / 3600) % 60; | ||
let minutes = (duration.as_secs() / 60) % 60; | ||
let seconds = duration.as_secs() % 60; | ||
let milliseconds = duration.subsec_millis(); | ||
let tenths = milliseconds / 100; | ||
|
||
if hours > 0 { | ||
format!("{hours}h {minutes}m {seconds}s") | ||
} else if minutes > 0 { | ||
format!("{minutes}m {seconds}s") | ||
} else if seconds > 0 || milliseconds >= 100 { | ||
format!("{seconds}.{tenths}s") | ||
} else { | ||
String::from("< 0.1s") | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_display_duration() { | ||
schneems marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let duration = Duration::ZERO; | ||
assert_eq!(human(&duration), "< 0.1s"); | ||
|
||
let duration = Duration::from_millis(99); | ||
assert_eq!(human(&duration), "< 0.1s"); | ||
|
||
let duration = Duration::from_millis(100); | ||
assert_eq!(human(&duration), "0.1s"); | ||
|
||
let duration = Duration::from_millis(210); | ||
assert_eq!(human(&duration), "0.2s"); | ||
|
||
let duration = Duration::from_millis(1100); | ||
assert_eq!(human(&duration), "1.1s"); | ||
|
||
let duration = Duration::from_millis(9100); | ||
assert_eq!(human(&duration), "9.1s"); | ||
|
||
let duration = Duration::from_millis(10100); | ||
assert_eq!(human(&duration), "10.1s"); | ||
|
||
let duration = Duration::from_millis(52100); | ||
assert_eq!(human(&duration), "52.1s"); | ||
|
||
let duration = Duration::from_millis(60 * 1000); | ||
assert_eq!(human(&duration), "1m 0s"); | ||
|
||
let duration = Duration::from_millis(60 * 1000 + 2000); | ||
assert_eq!(human(&duration), "1m 2s"); | ||
|
||
let duration = Duration::from_millis(60 * 60 * 1000 - 1); | ||
assert_eq!(human(&duration), "59m 59s"); | ||
|
||
let duration = Duration::from_millis(60 * 60 * 1000); | ||
assert_eq!(human(&duration), "1h 0m 0s"); | ||
|
||
let duration = Duration::from_millis(75 * 60 * 1000 - 1); | ||
assert_eq!(human(&duration), "1h 14m 59s"); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.