Skip to content

Commit 8f93d6c

Browse files
authored
cargo like cli (#19)
finishes #18
1 parent 43eddab commit 8f93d6c

File tree

3 files changed

+120
-70
lines changed

3 files changed

+120
-70
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "godot-package-manager"
3-
version = "0.9.0"
3+
version = "1.0.0"
44
edition = "2021"
55
authors = ["bendn <[email protected]>"]
66
description = "A package manager for godot"

src/main.rs

Lines changed: 117 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use crate::package::Package;
55
use clap::{ColorChoice, Parser, Subcommand, ValueEnum};
66
use config_file::ConfigFile;
77
use console::{self, Term};
8-
use indicatif::{ProgressBar, ProgressStyle};
8+
use indicatif::{HumanCount, HumanDuration, ProgressBar, ProgressIterator};
99
use std::env::current_dir;
1010
use std::fs::{create_dir, read_dir, read_to_string, remove_dir, write};
1111
use std::io::{stdin, Read, Result};
1212
use std::panic;
1313
use std::path::{Path, PathBuf};
14-
use std::time::Duration;
14+
use std::time::Instant;
1515

1616
#[derive(Parser)]
1717
#[command(name = "gpm")]
@@ -41,17 +41,17 @@ struct Args {
4141
#[arg(long = "colors", default_value = "auto", global = true)]
4242
/// Control color output.
4343
colors: ColorChoice,
44+
45+
#[arg(long = "nv", global = true)]
46+
/// Disable progress bars
47+
not_verbose: bool,
4448
}
4549

4650
#[derive(Subcommand)]
4751
enum Actions {
4852
#[clap(short_flag = 'u')]
4953
/// Downloads the latest versions of your wanted packages.
50-
Update {
51-
#[arg(short = 's')]
52-
/// To print the progress bar
53-
silent: bool,
54-
},
54+
Update,
5555
#[clap(short_flag = 'p')]
5656
/// Deletes all installed packages.
5757
Purge,
@@ -99,14 +99,18 @@ enum PrefixType {
9999

100100
fn main() {
101101
panic::set_hook(Box::new(|panic_info| {
102-
match panic_info.location() {
103-
Some(s) => eprint!("{}@{}:{}: ", print_consts::err(), s.file(), s.line()),
104-
None => eprint!("{}: ", print_consts::err()),
102+
eprint!("{:>12} ", print_consts::err());
103+
if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
104+
eprint!("{s}");
105+
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
106+
eprint!("{s}");
107+
} else {
108+
eprint!("unknown");
109+
};
110+
if let Some(s) = panic_info.location() {
111+
eprint!(" (@{}:{})", s.file(), s.line())
105112
}
106-
#[rustfmt::skip]
107-
if let Some(s) = panic_info.payload().downcast_ref::<&str>() { eprintln!("{s}"); }
108-
else if let Some(s) = panic_info.payload().downcast_ref::<String>() { eprintln!("{s}"); }
109-
else { eprintln!("unknown"); };
113+
eprintln!("");
110114
}));
111115
let args = Args::parse();
112116
fn set_colors(val: bool) {
@@ -131,8 +135,8 @@ fn main() {
131135
};
132136
let mut cfg_file = ConfigFile::new(&contents);
133137
match args.action {
134-
Actions::Update { silent } => update(&mut cfg_file, true, silent),
135-
Actions::Purge => purge(&mut cfg_file),
138+
Actions::Update => update(&mut cfg_file, true, args.not_verbose),
139+
Actions::Purge => purge(&mut cfg_file, args.not_verbose),
136140
Actions::Tree {
137141
charset,
138142
prefix,
@@ -147,48 +151,48 @@ fn main() {
147151
}
148152
}
149153

150-
fn update(cfg: &mut ConfigFile, modify: bool, silent: bool) {
154+
fn update(cfg: &mut ConfigFile, modify: bool, not_verbose: bool) {
151155
if !Path::new("./addons/").exists() {
152156
create_dir("./addons/").expect("Should be able to create addons folder");
153157
}
154158
let packages = cfg.collect();
155159
if packages.is_empty() {
156160
panic!("No packages to update (modify the \"godot.package\" file to add packages)");
157161
}
158-
println!(
159-
"Updating {} package{}",
160-
packages.len(),
161-
if packages.len() > 1 { "s" } else { "" }
162-
);
163-
let bar = if silent {
164-
ProgressBar::hidden()
162+
let bar;
163+
let p_count = packages.len() as u64;
164+
if not_verbose {
165+
bar = ProgressBar::hidden();
165166
} else {
166-
let bar = ProgressBar::new(packages.len() as u64 * 3);
167-
bar.set_style(
168-
ProgressStyle::with_template(
169-
"[{elapsed}] {bar:20.green/red} {human_pos:>3}/{human_len:3} {msg}",
170-
)
171-
.unwrap()
172-
.progress_chars("-|-"),
173-
);
174-
bar.enable_steady_tick(Duration::new(0, 500));
175-
bar
167+
bar = print_consts::bar(p_count);
168+
bar.set_prefix("Updating");
176169
};
177-
packages.into_iter().for_each(|mut p| {
178-
bar.set_message(format!("downloading {p}"));
179-
p.download();
180-
bar.inc(1);
181-
bar.set_message(format!("modifying {p}"));
182-
if modify {
183-
if let Err(e) = p.modify() {
184-
eprintln!(
185-
"{}: modification of {p} failed with err {e}",
186-
print_consts::warn()
187-
)
170+
let now = Instant::now();
171+
packages
172+
.into_iter()
173+
.progress_with(bar.clone())
174+
.for_each(|mut p| {
175+
bar.set_message(format!("{p}"));
176+
p.download();
177+
if modify {
178+
if let Err(e) = p.modify() {
179+
bar.suspend(|| {
180+
eprintln!(
181+
"{:>12} modification of {p} failed with err {e}",
182+
print_consts::warn()
183+
)
184+
});
185+
}
188186
}
189-
}
190-
bar.inc(1);
191-
});
187+
bar.suspend(|| println!("{:>12} {p}", print_consts::green("Downloaded")));
188+
});
189+
println!(
190+
"{:>12} updated {} package{} in {}",
191+
print_consts::green("Finished"),
192+
HumanCount(p_count),
193+
if p_count > 0 { "s" } else { "" },
194+
HumanDuration(now.elapsed())
195+
)
192196
}
193197

194198
/// Recursively deletes empty directories.
@@ -214,32 +218,54 @@ fn recursive_delete_empty(dir: String) -> Result<()> {
214218
Ok(())
215219
}
216220

217-
fn purge(cfg: &mut ConfigFile) {
221+
fn purge(cfg: &mut ConfigFile, not_verbose: bool) {
218222
let packages = cfg
219223
.collect()
220224
.into_iter()
221225
.filter(|p| p.is_installed())
222226
.collect::<Vec<Package>>();
223227
if packages.is_empty() {
224228
if cfg.packages.is_empty() {
225-
panic!("No packages to update (modify the \"godot.package\" file to add packages)")
229+
panic!("No packages configured (modify the \"godot.package\" file to add packages)")
226230
} else {
227-
panic!("No packages installed(use \"gpm --update\" to install packages)")
231+
panic!("No packages installed (use \"gpm --update\" to install packages)")
228232
};
229233
};
230-
println!(
231-
"Purging {} package{}",
232-
packages.len(),
233-
if packages.len() > 1 { "s" } else { "" }
234-
);
235-
packages.into_iter().for_each(|p| p.purge());
234+
let p_count = packages.len() as u64;
235+
let bar;
236+
if not_verbose {
237+
bar = ProgressBar::hidden();
238+
} else {
239+
bar = print_consts::bar(p_count);
240+
bar.set_prefix("Purging");
241+
}
242+
let now = Instant::now();
243+
packages
244+
.into_iter()
245+
.progress_with(bar.clone()) // the last steps
246+
.for_each(|p| {
247+
bar.set_message(format!("{p}"));
248+
bar.println(format!(
249+
"{:>12} {p} ({})",
250+
print_consts::green("Deleting"),
251+
p.download_dir(),
252+
));
253+
p.purge()
254+
});
236255

237256
// run multiple times because the algorithm goes from top to bottom, stupidly.
238257
for _ in 0..3 {
239258
if let Err(e) = recursive_delete_empty("./addons".to_string()) {
240259
eprintln!("Unable to remove empty directorys: {e}")
241260
}
242261
}
262+
println!(
263+
"{:>12} purge {} package{} in {}",
264+
print_consts::green("Finished"),
265+
HumanCount(p_count),
266+
if p_count > 0 { "s" } else { "" },
267+
HumanDuration(now.elapsed())
268+
)
243269
}
244270

245271
fn tree(
@@ -253,15 +279,14 @@ fn tree(
253279
} else {
254280
".\n".to_string()
255281
};
282+
let mut count: u64 = 0;
256283
iter(
257284
&mut cfg.packages,
258285
"",
259286
&mut tree,
260287
match charset {
261-
CharSet::UTF8 => "├──", // believe it or not, these are unlike
262-
CharSet::ASCII => "|--", // its hard to tell, with ligatures enabled
263-
// and rustfmt wants to indent like
264-
// it must not be very stabled
288+
CharSet::UTF8 => "├──", // believe it or not, these are quite unlike
289+
CharSet::ASCII => "|--", // its hard to tell, with ligatures enable
265290
},
266291
match charset {
267292
CharSet::UTF8 => "└──",
@@ -270,7 +295,9 @@ fn tree(
270295
prefix,
271296
print_tarballs,
272297
0,
298+
&mut count,
273299
);
300+
tree.push_str(format!("{} dependencies", HumanCount(count)).as_str());
274301

275302
fn iter(
276303
packages: &mut Vec<Package>,
@@ -281,11 +308,13 @@ fn tree(
281308
prefix_type: PrefixType,
282309
print_tarballs: bool,
283310
depth: u32,
311+
count: &mut u64,
284312
) {
285313
// the index is used to decide if the package is the last package,
286314
// so we can use a L instead of a T.
287315
let mut tmp: String;
288316
let mut index = packages.len();
317+
*count += index as u64;
289318
for p in packages {
290319
let name = p.to_string();
291320
index -= 1;
@@ -323,6 +352,7 @@ fn tree(
323352
prefix_type,
324353
print_tarballs,
325354
depth + 1,
355+
count,
326356
);
327357
}
328358
}
@@ -368,7 +398,7 @@ fn gpm() {
368398
let cfg_file = &mut config_file::ConfigFile::new(&r#"packages: {"@bendn/test":2.0.10}"#.into());
369399
update(cfg_file, false, false);
370400
assert_eq!(test_utils::hashd("addons").join("|"), "1c2fd93634817a9e5f3f22427bb6b487520d48cf3cbf33e93614b055bcbd1329|8e77e3adf577d32c8bc98981f05d40b2eb303271da08bfa7e205d3f27e188bd7|a625595a71b159e33b3d1ee6c13bea9fc4372be426dd067186fe2e614ce76e3c|c5566e4fbea9cc6dbebd9366b09e523b20870b1d69dc812249fccd766ebce48e|c5566e4fbea9cc6dbebd9366b09e523b20870b1d69dc812249fccd766ebce48e|c850a9300388d6da1566c12a389927c3353bf931c4d6ea59b02beb302aac03ea|d060936e5f1e8b1f705066ade6d8c6de90435a91c51f122905a322251a181a5c|d711b57105906669572a0e53b8b726619e3a21463638aeda54e586a320ed0fc5|d794f3cee783779f50f37a53e1d46d9ebbc5ee7b37c36d7b6ee717773b6955cd|e4f9df20b366a114759282209ff14560401e316b0059c1746c979f478e363e87");
371-
purge(cfg_file);
401+
purge(cfg_file, false);
372402
assert_eq!(test_utils::hashd("addons"), vec![] as Vec<String>);
373403
assert_eq!(
374404
tree(
@@ -381,20 +411,40 @@ fn gpm() {
381411
.skip(1)
382412
.collect::<Vec<&str>>()
383413
.join("\n"),
384-
"└── @bendn/[email protected]\n └── @bendn/[email protected]"
414+
"└── @bendn/[email protected]\n └── @bendn/[email protected]\n2 dependencies"
385415
);
386416
}
387417

418+
/// Remember to use {:>12}
388419
pub mod print_consts {
389420
use console::{style, StyledObject};
421+
use indicatif::{ProgressBar, ProgressStyle};
390422

391423
#[inline]
392-
pub fn err() -> StyledObject<&'static str> {
393-
style("err").red().bold()
424+
pub fn err() -> StyledObject<String> {
425+
style(format!("Error")).red().bold()
394426
}
395427

396428
#[inline]
397-
pub fn warn() -> StyledObject<&'static str> {
398-
style("err").yellow().bold()
429+
pub fn warn() -> StyledObject<String> {
430+
style(format!("Warn")).yellow().bold()
431+
}
432+
433+
#[inline]
434+
pub fn green(t: &str) -> StyledObject<&str> {
435+
style(t).green().bold()
436+
}
437+
438+
#[inline]
439+
pub fn bar(len: u64) -> ProgressBar {
440+
let bar = ProgressBar::new(len);
441+
bar.set_style(
442+
ProgressStyle::with_template(
443+
"{prefix:>12.cyan.bold} [{bar:20.green}] {human_pos}/{human_len}: {wide_msg}",
444+
)
445+
.unwrap()
446+
.progress_chars("-> "),
447+
);
448+
bar
399449
}
400450
}

src/package.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ impl Package {
217217
}
218218

219219
/// Returns the download directory for this package depending on wether it is indirect or not.
220-
fn download_dir(&self) -> String {
220+
pub fn download_dir(&self) -> String {
221221
if self.indirect {
222222
self.indirect_download_dir()
223223
} else {
@@ -338,7 +338,7 @@ impl Package {
338338
}
339339
};
340340
eprintln!(
341-
"{}: Could not find path for {path:#?}",
341+
"{:>12} Could not find path for {path:#?}",
342342
crate::print_consts::warn()
343343
);
344344
return path.to_path_buf();

0 commit comments

Comments
 (0)