Skip to content

Commit 9a42bcf

Browse files
authored
Update printing style (#252)
* Add bullet_stream for printing * Update expect messages * Update main printing * Update error printing * Update integration tests * Changelog * Include filename in errors By default Rust's `fs` module does not include the filename when an error is returned. The `fs_err` crate adds filenames back in. * Add where processes are coming from While the name of the buildpack "Procfile Buildpack" hints that it's looking at a Procfile, be specific with the value. * Explain when no process are found The output `(none)` is not a full sentence. This explains that the buildpack saw an empty file (not entirely true, it could be an error or mis-parse, but there's an open issue for that) and as a result defined no processes. * Update changelog
1 parent 3ebbcac commit 9a42bcf

7 files changed

+89
-82
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Build output style updated to `bullet_stream`. Output now also includes the commands that were pulled from the `Procfile` in the output. ([#252](https://github.com/heroku/buildpacks-procfile/pull/252))
13+
1014
## [3.1.2] - 2024-07-02
1115

1216
### Changed

Cargo.lock

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pedantic = { level = "warn", priority = -1 }
1414
unwrap_used = "warn"
1515

1616
[dependencies]
17+
bullet_stream = "0.3.0"
18+
fs-err = "3.0.0"
1719
indoc = "2"
1820
libcnb = { version = "0.26", features = ["trace"] }
1921
libherokubuildpack = { version = "0.26", default-features = false, features = ["error", "log"] }

src/error.rs

+22-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::launch::ProcfileConversionError;
22
use crate::procfile::ProcfileParsingError;
3+
use bullet_stream::Print;
34
use indoc::formatdoc;
4-
use libherokubuildpack::log::log_error;
55

66
#[derive(Debug)]
77
pub(crate) enum ProcfileBuildpackError {
@@ -11,37 +11,36 @@ pub(crate) enum ProcfileBuildpackError {
1111
}
1212

1313
pub(crate) fn error_handler(buildpack_error: ProcfileBuildpackError) {
14+
let build_output = Print::new(std::io::stdout()).without_header();
1415
match buildpack_error {
1516
ProcfileBuildpackError::CannotReadProcfileContents(io_error) => {
16-
log_error(
17-
"Cannot read Procfile contents",
18-
formatdoc! {"
19-
Please ensure the Procfile in the root of your application is a readable UTF-8
20-
encoded file and try again.
21-
22-
Underlying cause was: {io_error}
23-
"},
24-
);
17+
build_output.error(formatdoc! {"
18+
Cannot read Procfile contents
19+
20+
Please ensure the Procfile in the root of your application is a readable UTF-8
21+
encoded file and try again.
22+
23+
Underlying cause was: {io_error}
24+
"});
2525
}
2626
// There are currently no ways in which parsing can fail, however we will add some in the future:
2727
// https://github.com/heroku/buildpacks-procfile/issues/73
2828
ProcfileBuildpackError::ProcfileParsingError(parsing_error) => match parsing_error {},
2929
ProcfileBuildpackError::ProcfileConversionError(conversion_error) => match conversion_error
3030
{
3131
ProcfileConversionError::InvalidProcessType(libcnb_error) => {
32-
log_error(
33-
"Cannot convert Procfile to CNB launch configuration",
34-
formatdoc! {"
35-
This is an unexpected internal error that occurs when a Procfile entry is not
36-
compatible with the CNB launch configuration. At the time of writing, Procfile
37-
process names are a strict subset of CNB launch process names and this should
38-
never happen.
39-
40-
Please report this issue with the details below.
41-
42-
Details: {libcnb_error}
43-
"},
44-
);
32+
build_output.error(formatdoc! {"
33+
Cannot convert Procfile to CNB launch configuration
34+
35+
This is an unexpected internal error that occurs when a Procfile entry is not
36+
compatible with the CNB launch configuration. At the time of writing, Procfile
37+
process names are a strict subset of CNB launch process names and this should
38+
never happen.
39+
40+
Please report this issue with the details below.
41+
42+
Details: {libcnb_error}
43+
"});
4544
}
4645
},
4746
}

src/main.rs

+14-44
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ mod procfile;
44

55
use crate::error::{error_handler, ProcfileBuildpackError};
66
use crate::procfile::Procfile;
7+
use bullet_stream::{style, Print};
78
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
89
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
910
use libcnb::generic::{GenericMetadata, GenericPlatform};
1011
use libcnb::{buildpack_main, Buildpack};
11-
use libherokubuildpack::log::{log_header, log_info};
12-
use std::fs;
12+
use std::io::stdout;
1313
use std::path::Path;
1414

1515
#[cfg(test)]
@@ -31,20 +31,26 @@ impl Buildpack for ProcfileBuildpack {
3131
}
3232

3333
fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
34-
log_header("Discovering process types");
34+
let mut bullet = Print::new(stdout())
35+
.h2("Procfile Buildpack")
36+
.bullet(format!("Processes from {}", style::value("Procfile")));
3537

36-
let procfile = fs::read_to_string(context.app_dir.join("Procfile"))
38+
let procfile: Procfile = fs_err::read_to_string(context.app_dir.join("Procfile"))
3739
.map_err(ProcfileBuildpackError::CannotReadProcfileContents)
3840
.and_then(|procfile_contents| {
3941
procfile_contents
4042
.parse()
4143
.map_err(ProcfileBuildpackError::ProcfileParsingError)
4244
})?;
4345

44-
log_info(format!(
45-
"Procfile declares types -> {}",
46-
format_processes_for_log(&procfile)
47-
));
46+
if procfile.is_empty() {
47+
bullet = bullet.sub_bullet("Empty file, no processes defined");
48+
} else {
49+
for (name, command) in &procfile.processes {
50+
bullet = bullet.sub_bullet(format!("{name}: {}", style::command(command)));
51+
}
52+
}
53+
bullet.done().done();
4854

4955
BuildResultBuilder::new()
5056
.launch(
@@ -64,19 +70,6 @@ fn dir_has_procfile(app_dir: impl AsRef<Path>) -> bool {
6470
app_dir.as_ref().join("Procfile").exists()
6571
}
6672

67-
fn format_processes_for_log(procfile: &Procfile) -> String {
68-
if procfile.is_empty() {
69-
String::from("(none)")
70-
} else {
71-
procfile
72-
.processes
73-
.keys()
74-
.cloned()
75-
.collect::<Vec<String>>()
76-
.join(", ")
77-
}
78-
}
79-
8073
// Implements the main function and wires up the framework for the given buildpack.
8174
buildpack_main!(ProcfileBuildpack);
8275

@@ -96,27 +89,4 @@ mod tests {
9689
let app_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/missing_procfile");
9790
assert!(!dir_has_procfile(app_dir));
9891
}
99-
100-
#[test]
101-
fn test_empty_names_from_processes() {
102-
let procfile = Procfile::new();
103-
104-
let out = format_processes_for_log(&procfile);
105-
assert_eq!(out, "(none)");
106-
}
107-
108-
#[test]
109-
fn test_valid_process_names_from_processes() {
110-
let mut procfile = Procfile::new();
111-
112-
procfile.insert("web", "rails -s");
113-
114-
let out = format_processes_for_log(&procfile);
115-
assert_eq!(out, "web");
116-
117-
procfile.insert("worker", "rake sidekiq");
118-
119-
let out = format_processes_for_log(&procfile);
120-
assert_eq!(out, "web, worker");
121-
}
12292
}

src/procfile.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ impl FromStr for Procfile {
3434
type Err = ProcfileParsingError;
3535

3636
fn from_str(procfile_contents: &str) -> Result<Self, Self::Err> {
37-
// Using `.expect()` since these can only fail if we've supplied invalid an invalid regex,
38-
// which would be caught by both the `invalid_regex` Clippy lint and the buildpack's tests.
39-
let re_carriage_return_newline = Regex::new("\\r\\n?").expect("Invalid Procfile regex");
40-
let re_multiple_newline = Regex::new("\\n*\\z").expect("Invalid Procfile regex");
37+
let re_carriage_return_newline = Regex::new("\\r\\n?").expect("Clippy checked");
38+
let re_multiple_newline = Regex::new("\\n*\\z").expect("Clippy checked");
4139

4240
// https://github.com/heroku/codon/blob/2613554383cb298076b4a722f4a1aa982ad757e6/lib/slug_compiler/slug.rb#L538-L545
4341
let re_procfile_entry = Regex::new("^[[:space:]]*([a-zA-Z0-9_-]+):?\\s+(.*)[[:space:]]*")
44-
.expect("Invalid Procfile regex");
42+
.expect("Clippy checked");
4543

4644
let procfile_contents = re_carriage_return_newline.replace_all(procfile_contents, "\n");
4745
let procfile_contents = re_multiple_newline.replace(&procfile_contents, "\n");

tests/integration_test.rs

+27-10
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ fn test_web_and_worker_procfile() {
2121
assert_contains!(
2222
context.pack_stdout,
2323
indoc! {"
24-
[Discovering process types]
25-
Procfile declares types -> web, worker
24+
## Procfile Buildpack
25+
26+
- Processes from `Procfile`
27+
- web: `echo 'this is the web process!'`
28+
- worker: `echo 'this is the worker process!'`
29+
- Done (finished in < 0.1s)
2630
"}
2731
);
2832

@@ -51,8 +55,11 @@ fn test_worker_only_procfile() {
5155
assert_contains!(
5256
context.pack_stdout,
5357
indoc! {"
54-
[Discovering process types]
55-
Procfile declares types -> worker
58+
## Procfile Buildpack
59+
60+
- Processes from `Procfile`
61+
- worker: `echo 'this is the worker process!'`
62+
- Done (finished in < 0.1s)
5663
"}
5764
);
5865

@@ -79,8 +86,12 @@ fn test_multiple_non_web_procfile() {
7986
assert_contains!(
8087
context.pack_stdout,
8188
indoc! {"
82-
[Discovering process types]
83-
Procfile declares types -> worker, console
89+
## Procfile Buildpack
90+
91+
- Processes from `Procfile`
92+
- worker: `echo 'this is the worker process!'`
93+
- console: `echo 'this is the console process!'`
94+
- Done (finished in < 0.1s)
8495
"}
8596
);
8697

@@ -140,8 +151,11 @@ fn test_not_yaml_procfile() {
140151
assert_contains!(
141152
context.pack_stdout,
142153
indoc! {"
143-
[Discovering process types]
144-
Procfile declares types -> web
154+
## Procfile Buildpack
155+
156+
- Processes from `Procfile`
157+
- web: `echo foo: bar `
158+
- Done (finished in < 0.1s)
145159
"}
146160
);
147161
assert_contains!(context.pack_stdout, "Setting default process type 'web'");
@@ -162,8 +176,11 @@ fn test_empty_procfile() {
162176
assert_contains!(
163177
context.pack_stdout,
164178
indoc! {"
165-
[Discovering process types]
166-
Procfile declares types -> (none)
179+
## Procfile Buildpack
180+
181+
- Processes from `Procfile`
182+
- Empty file, no processes defined
183+
- Done (finished in < 0.1s)
167184
"}
168185
);
169186
assert_contains!(context.pack_stdout, "no default process type");

0 commit comments

Comments
 (0)