Skip to content

Commit ff35673

Browse files
authored
Merge pull request #26 from thedodd/1-better-terminal-output
Better terminal output.
2 parents c988e5b + 67d79fc commit ff35673

File tree

7 files changed

+174
-54
lines changed

7 files changed

+174
-54
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ changelog
33

44
## Unreleased
55
### changed
6+
- All CLI output has been improved using console & indicatif. Builds, asset pipelines, and the like are using a progress spinner along with messages. All in all this provides a much more clean CLI output experience.
7+
- Using `console::Emoji` to ensure that emojis are only sent to stdout/stderr when supported.
8+
- All builds performed by trunk now emit warnings when a link is found in the HTML which is an invalid FS path. This does not effect hyperlinks of course.
69
- `trunk serve` now takes the `--open` option to control whether or not a browser tab will be opened. Thanks @MartinKavik for the report.
710
- The `trunk serve` listener info has been slightly updated. It continues to function as in previous releases. Thanks @MartinKavik for the report.
811

Cargo.lock

Lines changed: 98 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ keywords = ["wasm", "bundler", "web", "build-tool", "compiler"]
1414
anyhow = "1.0.32"
1515
async-process = "0.1.1"
1616
async-std = { version="1.6.3", features=["attributes", "unstable"] }
17+
console = "0.12.0"
1718
futures = "0.3.5"
19+
indicatif = "0.15.0"
1820
nipper = "0.1.8"
1921
notify = "4.0.15"
2022
open = "1.4.0"

src/build.rs

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ use std::path::PathBuf;
55
use std::sync::Arc;
66

77
use anyhow::{anyhow, bail, ensure, Result};
8+
use async_process::{Command, Stdio};
89
use async_std::fs;
910
use async_std::task::{spawn, spawn_blocking, JoinHandle};
10-
use async_process::{Command, Stdio};
11+
use console::Emoji;
1112
use futures::stream::{FuturesUnordered, StreamExt};
13+
use indicatif::ProgressBar;
1214
use nipper::Document;
1315
use serde::{Serialize, Deserialize};
1416

@@ -25,25 +27,28 @@ const HREF_ATTR: &str = "href";
2527
/// build routines can be cleanly abstracted away form any specific CLI endpoints.
2628
pub struct BuildSystem {
2729
/// The `Cargo.toml` manifest of the app being built.
28-
pub manifest: CargoManifest,
30+
manifest: CargoManifest,
2931
/// The path to the source HTML document from which the output `index.html` will be built.
30-
pub target_html_path: Arc<PathBuf>,
32+
target_html_path: Arc<PathBuf>,
3133
/// The parent directory of `target_html_path`.
32-
pub target_html_dir: Arc<PathBuf>,
34+
target_html_dir: Arc<PathBuf>,
3335
/// Build in release mode.
34-
pub release: bool,
36+
release: bool,
3537
/// The output dir for all final assets.
36-
pub dist: Arc<PathBuf>,
38+
dist: Arc<PathBuf>,
3739
/// The public URL from which assets are to be served.
3840
public_url: String,
3941

4042
/// The output dir of the wasm-bindgen execution.
41-
pub bindgen_out: Arc<PathBuf>,
43+
bindgen_out: Arc<PathBuf>,
4244
/// The path to the app's output WASM.
43-
pub app_target_wasm: Arc<PathBuf>,
45+
app_target_wasm: Arc<PathBuf>,
4446

4547
/// A stream of asset pipelines.
46-
pub pipelines: FuturesUnordered<JoinHandle<Result<AssetPipelineOutput>>>,
48+
pipelines: FuturesUnordered<JoinHandle<Result<AssetPipelineOutput>>>,
49+
50+
/// The object used for writing data to stdout, stderr & controlling the progress spinner.
51+
progress: ProgressBar,
4752
}
4853

4954
impl BuildSystem {
@@ -76,11 +81,30 @@ impl BuildSystem {
7681
bindgen_out: Arc::new(bindgen_out),
7782
app_target_wasm: Arc::new(app_target_wasm),
7883
pipelines: FuturesUnordered::new(),
84+
progress: ProgressBar::new_spinner(),
7985
})
8086
}
8187

88+
/// Get a handle to the progress / terminal system.
89+
pub fn get_progress_handle(&self) -> ProgressBar {
90+
self.progress.clone()
91+
}
92+
8293
/// Build the application described in the given build data.
8394
pub async fn build_app(&mut self) -> Result<()> {
95+
self.progress.reset();
96+
self.progress.enable_steady_tick(100);
97+
let res = self.build_app_internal().await;
98+
self.progress.disable_steady_tick();
99+
if let Err(err) = res {
100+
self.progress.finish_with_message(&format!("{}build finished with errors", Emoji("❌ ", "")));
101+
return Err(err);
102+
}
103+
self.progress.finish_with_message(&format!("{}build completed successfully", Emoji("✅ ", "")));
104+
Ok(())
105+
}
106+
107+
async fn build_app_internal(&mut self) -> Result<()> {
84108
// Update the contents of the source HTML.
85109
let target_html_raw = fs::read_to_string(self.target_html_path.as_ref()).await?;
86110
let mut target_html = Document::from(&target_html_raw);
@@ -117,7 +141,7 @@ impl BuildSystem {
117141
let asset = match asset_res {
118142
Ok(asset) => asset,
119143
Err(err) => {
120-
eprintln!("{}", err);
144+
self.progress.println(format!("{}", err));
121145
continue;
122146
}
123147
};
@@ -154,7 +178,7 @@ impl BuildSystem {
154178
if self.release {
155179
args.push("--release");
156180
}
157-
println!("starting cargo build on {}", &self.manifest.package.name); // TODO: pin down logging.
181+
self.progress.set_message(&format!("{}starting cargo build on {}", Emoji("📦 ", ""), &self.manifest.package.name));
158182
let app_target_wasm = self.app_target_wasm.clone();
159183
spawn(async move {
160184
// Spawn the cargo build process.
@@ -187,7 +211,7 @@ impl BuildSystem {
187211
fn spawn_wasm_bindgen_build(&self, file_name: String) -> JoinHandle<Result<WasmBindgenOutput>> {
188212
let (dist, bindgen_out, app_target_wasm) = (self.dist.clone(), self.bindgen_out.clone(), self.app_target_wasm.clone());
189213

190-
println!("starting wasm-bindgen build"); // TODO: pin down logging.
214+
self.progress.set_message(&format!("{}starting wasm-bindgen build", Emoji("📦 ", "")));
191215
spawn(async move {
192216
let arg_out_path = format!("--out-dir={}", bindgen_out.display());
193217
let arg_out_name = format!("--out-name={}", &file_name);
@@ -232,7 +256,7 @@ impl BuildSystem {
232256
/// for the asset is finished, it will be able to update the DOM correctly based on its own
233257
/// ID. All of these trunk specific IDs will be removed from the DOM before it is written.
234258
async fn spawn_asset_pipelines(&mut self, target_html: &mut Document) -> Result<()> {
235-
println!("spawning asset pipelines");
259+
self.progress.set_message(&format!("{}spawning asset pipelines", Emoji("📦 ", "")));
236260

237261
// Accumulate assets declared in HTML head section links for processing.
238262
let asset_links = target_html.select(r#"html head link"#)
@@ -254,7 +278,7 @@ impl BuildSystem {
254278
let path = self.target_html_dir.join(href.as_ref());
255279
let rel = node.attr_or("rel", "").to_string().to_lowercase();
256280
let id = format!("link-{}", idx);
257-
let asset = match AssetFile::new(path, AssetType::Link{rel}, id).await {
281+
let asset = match AssetFile::new(path, AssetType::Link{rel}, id, &self.progress).await {
258282
Ok(asset) => asset,
259283
Err(_) => continue,
260284
};
@@ -291,8 +315,7 @@ impl BuildSystem {
291315

292316
/// Spawn a concurrent build pipeline for a SASS/SCSS asset.
293317
fn spawn_sass_pipeline(&mut self, asset: AssetFile) -> JoinHandle<Result<AssetPipelineOutput>> {
294-
let dist = self.dist.clone();
295-
let release = self.release;
318+
let (dist, release, progress) = (self.dist.clone(), self.release, self.progress.clone());
296319
spawn(async move {
297320
// Compile the target SASS/SCSS file.
298321
let path_str = asset.path.to_string_lossy().to_string();
@@ -304,7 +327,7 @@ impl BuildSystem {
304327
match sass_rs::compile_file(&path_str, opts) {
305328
Ok(css) => Ok(css),
306329
Err(err) => {
307-
eprintln!("{}", err);
330+
progress.println(format!("{}", err));
308331
Err(anyhow!("error compiling sass for {}", &path_str))
309332
}
310333
}
@@ -377,13 +400,13 @@ impl AssetFile {
377400
///
378401
/// Any errors returned from this constructor indicate that one of these invariants was not
379402
/// upheld.
380-
pub async fn new(path: PathBuf, atype: AssetType, id: String) -> Result<Self> {
403+
pub async fn new(path: PathBuf, atype: AssetType, id: String, progress: &ProgressBar) -> Result<Self> {
381404
// Take the path to referenced resource, if it is actually an FS path, then we continue.
382405
let path = match fs::canonicalize(&path).await {
383406
Ok(path) => path,
384407
Err(_) => {
385408
if !path.to_string_lossy().contains("://") {
386-
eprintln!("skipping invalid path: {}", path.to_string_lossy());
409+
progress.println(format!("{}skipping invalid path: {}", Emoji("︎🚫 ", ""), path.to_string_lossy()));
387410
}
388411
return Err(anyhow!("skipping asset which is not a valid path"));
389412
}

src/cmd/serve.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::sync::Arc;
44
use anyhow::Result;
55
use async_std::fs;
66
use async_std::task::{spawn, spawn_local, JoinHandle};
7+
use console::Emoji;
8+
use indicatif::ProgressBar;
79
use structopt::StructOpt;
810
use tide::{Request, Response, Middleware, Next, StatusCode};
911
use tide::http::mime;
@@ -46,16 +48,17 @@ impl Serve {
4648
);
4749
let mut watcher = WatchSystem::new(target, release, dist, public_url, ignore).await?;
4850
watcher.build().await;
51+
let progress = watcher.get_progress_handle();
4952

5053
// Spawn the watcher & the server.
5154
let http_addr = format!("http://127.0.0.1:{}{}", self.port, &self.public_url);
5255
let watch_handle = spawn_local(watcher.run());
53-
let server_handle = self.spawn_server(http_addr.clone())?;
56+
let server_handle = self.spawn_server(http_addr.clone(), progress.clone())?;
5457

5558
// Open the browser.
5659
if self.open {
5760
if let Err(err) = open::that(http_addr) {
58-
eprintln!("error opening browser: {}", err);
61+
progress.println(format!("error opening browser: {}", err));
5962
}
6063
}
6164

@@ -64,7 +67,7 @@ impl Serve {
6467
Ok(())
6568
}
6669

67-
fn spawn_server(&self, http_addr: String) -> Result<JoinHandle<()>> {
70+
fn spawn_server(&self, http_addr: String, progress: ProgressBar) -> Result<JoinHandle<()>> {
6871
// Prep state.
6972
let listen_addr = format!("0.0.0.0:{}", self.port);
7073
let index = Arc::new(self.dist.join("index.html"));
@@ -75,10 +78,10 @@ impl Serve {
7578
app.at(&self.public_url).serve_dir(self.dist.to_string_lossy().as_ref())?;
7679

7780
// Listen and serve.
78-
println!("Server running at {}", &http_addr);
81+
progress.println(format!("{}server running at {}\n", Emoji("📡 ", " "), &http_addr));
7982
Ok(spawn(async move {
8083
if let Err(err) = app.listen(listen_addr).await {
81-
eprintln!("{}", err);
84+
progress.println(format!("{}", err));
8285
}
8386
}))
8487
}

src/cmd/watch.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ pub struct Watch {
3030

3131
impl Watch {
3232
pub async fn run(self) -> Result<()> {
33-
let mut system = WatchSystem::new(self.target, self.release, self.dist, self.public_url, self.ignore.unwrap_or_default())
34-
.await?;
33+
let mut system = WatchSystem::new(self.target, self.release, self.dist, self.public_url, self.ignore.unwrap_or_default()).await?;
3534
system.build().await;
3635
system.run().await;
3736
Ok(())

src/watch.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use std::time::Duration;
55
use anyhow::{anyhow, Result};
66
use async_std::sync::channel;
77
use async_std::task::spawn_blocking;
8+
use console::Emoji;
89
use futures::stream::{FusedStream, StreamExt};
10+
use indicatif::ProgressBar;
911
use notify::{Watcher, RecursiveMode, watcher};
1012

1113
use crate::common::get_cwd;
@@ -15,6 +17,7 @@ use crate::build::{BuildSystem, CargoManifest};
1517
pub struct WatchSystem {
1618
build: BuildSystem,
1719
watcher: TrunkWatcher,
20+
progress: ProgressBar,
1821
}
1922

2023
impl WatchSystem {
@@ -29,41 +32,47 @@ impl WatchSystem {
2932
})?;
3033
ignore.append(&mut vec![cwd.join("target"), cwd.join(&dist)]);
3134

32-
// Build the watcher.
33-
let watcher = TrunkWatcher::new(ignore)?;
34-
3535
// Perform an initial build.
3636
let manifest = CargoManifest::read_cwd_manifest().await?;
3737
let build = BuildSystem::new(manifest, target, release, dist, public_url).await?;
38-
Ok(Self{build, watcher})
38+
let progress = build.get_progress_handle();
39+
40+
let watcher = TrunkWatcher::new(ignore, progress.clone())?;
41+
Ok(Self{build, watcher, progress})
3942
}
4043

4144
/// Run a build.
4245
pub async fn build(&mut self) {
4346
if let Err(err) = self.build.build_app().await {
44-
eprintln!("{}", err);
47+
self.progress.println(format!("{}", err));
4548
}
4649
}
4750

4851
/// Run the watch system, responding to events and triggering builds.
4952
pub async fn run(mut self) {
5053
while let Some(_) = self.watcher.rx.next().await {
5154
if let Err(err) = self.build.build_app().await {
52-
eprintln!("{}", err);
55+
self.progress.println(format!("{}", err));
5356
}
5457
}
5558
}
59+
60+
/// Get a handle to the progress / terminal system.
61+
pub fn get_progress_handle(&self) -> ProgressBar {
62+
self.build.get_progress_handle()
63+
}
5664
}
5765

5866
/// A watcher system for triggering Trunk builds.
5967
struct TrunkWatcher {
60-
pub watcher: notify::RecommendedWatcher,
61-
pub rx: Box<dyn FusedStream<Item=()> + Send + Unpin>,
68+
#[allow(dead_code)]
69+
watcher: notify::RecommendedWatcher,
70+
rx: Box<dyn FusedStream<Item=()> + Send + Unpin>,
6271
}
6372

6473
impl TrunkWatcher {
6574
/// Spawn a watcher to trigger builds as changes are detected on the filesystem.
66-
pub fn new(ignore: Vec<PathBuf>) -> Result<TrunkWatcher> {
75+
pub fn new(ignore: Vec<PathBuf>, progress: ProgressBar) -> Result<TrunkWatcher> {
6776
// Setup core watcher functionality.
6877
let (tx, rx) = std_channel();
6978
let mut watcher = watcher(tx, Duration::from_secs(1))
@@ -88,8 +97,8 @@ impl TrunkWatcher {
8897
let _ = async_tx.try_send(());
8998
}
9099
Event::Error(err, path_opt) => match path_opt {
91-
Some(path) => eprintln!("watch error at {}\n{}", path.to_string_lossy(), err),
92-
None => eprintln!("{}", err),
100+
Some(path) => progress.println(&format!("{}watch error at {}\n{}", Emoji("🚫 ", ""), path.to_string_lossy(), err)),
101+
None => progress.println(format!("{}", err)),
93102
}
94103
_ => continue,
95104
}

0 commit comments

Comments
 (0)