use crate::batbelt::metadata::functions_source_code_metadata::FunctionSourceCodeMetadata;
use crate::batbelt::metadata::structs_source_code_metadata::{
StructMetadataType, StructSourceCodeMetadata,
};
use crate::batbelt::metadata::traits_source_code_metadata::TraitSourceCodeMetadata;
use crate::batbelt::metadata::{BatMetadata, BatMetadataParser, SourceCodeMetadata};
use crate::batbelt::path::BatFolder;
use crate::batbelt::sonar::{BatSonarError, SonarResultType};
use crate::config::BatConfig;
use colored::Colorize;
use dialoguer::console::{style, Emoji};
use error_stack::{Result, ResultExt};
use indicatif::{HumanDuration, MultiProgress, ProgressBar, ProgressStyle};
use crate::batbelt::metadata::context_accounts_metadata::ContextAccountsMetadata;
use crate::batbelt::parser::context_accounts_parser::CAAccountParser;
use crate::batbelt::parser::entrypoint_parser::EntrypointParser;
use crate::batbelt::parser::function_parser::FunctionParser;
use crate::batbelt::parser::syn_context_accounts_parser;
use crate::batbelt::parser::trait_parser::TraitParser;
use crate::batbelt::metadata::enums_source_code_metadata::EnumSourceCodeMetadata;
use std::thread;
use std::time::{Duration, Instant};
static BAT: Emoji<'_, '_> = Emoji("🦇", "BatSonar");
static FOLDER: Emoji<'_, '_> = Emoji("📂", "Program Folder");
static WAVE: Emoji<'_, '_> = Emoji("〰", "-");
static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)");
#[derive(Debug, PartialEq, Clone, Copy, strum_macros::Display, strum_macros::EnumIter)]
pub enum BatSonarInteractive {
SonarStart { sonar_result_type: SonarResultType },
GetSourceCodeMetadata,
GetEntryPointsMetadata,
GetTraitsMetadata,
GetFunctionDependenciesMetadata,
GetContextAccountsMetadata,
}
impl BatSonarInteractive {
pub fn print_interactive(&self) -> Result<(), BatSonarError> {
match self {
BatSonarInteractive::SonarStart { sonar_result_type } => {
self.sonar_start(*sonar_result_type)?
}
BatSonarInteractive::GetSourceCodeMetadata => self.get_source_code_metadata()?,
BatSonarInteractive::GetEntryPointsMetadata => {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(100));
Self::run_entry_points_with_pb(&pb)?;
}
BatSonarInteractive::GetTraitsMetadata => {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(100));
Self::run_traits_with_pb(&pb)?;
}
BatSonarInteractive::GetFunctionDependenciesMetadata => {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(100));
Self::run_function_deps_with_pb(&pb)?;
}
BatSonarInteractive::GetContextAccountsMetadata => {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(100));
Self::run_context_accounts_with_pb(&pb)?;
}
}
Ok(())
}
fn sonar_start(&self, _sonar_result_type: SonarResultType) -> Result<(), BatSonarError> {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(100));
pb.set_style(
ProgressStyle::with_template("{spinner:.blue} {msg}")
.unwrap()
.tick_strings(&[
&format!("{} {}{}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{}{} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {} {}", FOLDER, WAVE, BAT),
&format!("{} {}{}", FOLDER, WAVE, BAT),
&format!("{} {}", FOLDER, BAT),
]),
);
pb.set_message(format!("Initializing {}...", "BatSonar".red(),));
thread::sleep(Duration::from_millis(1500));
pb.finish_with_message(format!("{} initialized", "BatSonar".red()));
Ok(())
}
fn get_source_code_metadata(&self) -> Result<(), BatSonarError> {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
let started = Instant::now();
let spinner_style = ProgressStyle::with_template("{prefix:.bold.dim} {spinner} {wide_msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ");
let all_program_paths = BatFolder::get_all_program_paths().change_context(BatSonarError)?;
let mut program_dir_entries = vec![];
for program_path in &all_program_paths {
let entries: Vec<walkdir::DirEntry> = walkdir::WalkDir::new(program_path)
.into_iter()
.filter_map(|f| {
let dir_entry = f.ok()?;
if !dir_entry.metadata().ok()?.is_file() || dir_entry.file_name() == ".gitkeep"
{
return None;
}
if !dir_entry.file_name().to_str()?.ends_with(".rs") {
return None;
}
Some(dir_entry)
})
.collect();
program_dir_entries.extend(entries);
}
let total_files = program_dir_entries.len();
println!(
"Analyzing {} files",
style(format!("{}", total_files)).bold().dim(),
);
let pb = ProgressBar::new(total_files as u64);
pb.set_style(spinner_style);
let num_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.min(total_files.max(1));
let chunk_size = total_files.div_ceil(num_threads);
let counter = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = program_dir_entries
.chunks(chunk_size)
.map(|chunk| {
let entries: Vec<_> = chunk.to_vec();
let pb = pb.clone();
let counter = Arc::clone(&counter);
thread::spawn(move || {
let mut structs = vec![];
let mut functions = vec![];
let mut traits = vec![];
let mut enums = vec![];
for entry in &entries {
let entry_path = entry.path().to_str().unwrap().to_string();
let current = counter.fetch_add(1, Ordering::Relaxed) + 1;
pb.set_prefix(format!("[{}/{}]", current, total_files));
pb.set_message(format!("Scanning: {}", &entry_path));
let file_content = std::fs::read_to_string(entry.path()).unwrap();
structs.append(
&mut StructSourceCodeMetadata::create_metadata_from_content(
&entry_path,
&file_content,
)
.unwrap(),
);
functions.append(
&mut FunctionSourceCodeMetadata::create_metadata_from_content(
&entry_path,
&file_content,
)
.unwrap(),
);
traits.append(
&mut TraitSourceCodeMetadata::create_metadata_from_content(
&entry_path,
&file_content,
)
.unwrap(),
);
enums.append(
&mut EnumSourceCodeMetadata::create_metadata_from_content(
&entry_path,
&file_content,
)
.unwrap(),
);
pb.inc(1);
}
(structs, functions, traits, enums)
})
})
.collect();
let mut all_structs = vec![];
let mut all_functions = vec![];
let mut all_traits = vec![];
let mut all_enums = vec![];
for h in handles {
let (s, f, t, e) = h.join().unwrap();
all_structs.extend(s);
all_functions.extend(f);
all_traits.extend(t);
all_enums.extend(e);
}
pb.finish_with_message(format!(
"Found {} structs, {} functions, {} traits, {} enums",
all_structs.len(),
all_functions.len(),
all_traits.len(),
all_enums.len()
));
let bat_metadata = BatMetadata::read_metadata().unwrap();
bat_metadata
.source_code
.update_structs(all_structs)
.unwrap();
bat_metadata
.source_code
.update_functions(all_functions)
.unwrap();
bat_metadata.source_code.update_traits(all_traits).unwrap();
bat_metadata.source_code.update_enums(all_enums).unwrap();
println!("{} Done in {}", SPARKLE, HumanDuration(started.elapsed()));
Ok(())
}
pub fn run_post_scan_parallel() -> Result<(), BatSonarError> {
let started = Instant::now();
let is_anchor = BatConfig::get_config()
.map(|c| c.project_type == crate::config::ProjectType::Anchor)
.unwrap_or(false);
let m = MultiProgress::new();
let spinner_style = ProgressStyle::with_template("{spinner:.blue} {wide_msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ");
let pb_tr = m.add(ProgressBar::new_spinner());
pb_tr.set_style(spinner_style.clone());
pb_tr.enable_steady_tick(Duration::from_millis(100));
pb_tr.set_message("Traits: starting...");
let pb_fd = m.add(ProgressBar::new_spinner());
pb_fd.set_style(spinner_style.clone());
pb_fd.enable_steady_tick(Duration::from_millis(100));
pb_fd.set_message("Function dependencies: starting...");
Self::run_traits_with_pb(&pb_tr)?;
if is_anchor {
let pb_ep = m.add(ProgressBar::new_spinner());
pb_ep.set_style(spinner_style.clone());
pb_ep.enable_steady_tick(Duration::from_millis(100));
pb_ep.set_message("Entry points: starting...");
let pb_ca = m.add(ProgressBar::new_spinner());
pb_ca.set_style(spinner_style.clone());
pb_ca.enable_steady_tick(Duration::from_millis(100));
pb_ca.set_message("Context accounts: starting...");
let ca_handle = {
let pb = pb_ca.clone();
thread::spawn(move || Self::run_context_accounts_with_pb(&pb))
};
Self::run_function_deps_with_pb(&pb_fd)?;
Self::run_entry_points_with_pb(&pb_ep)?;
ca_handle.join().expect("Thread panicked")?;
} else {
Self::run_function_deps_with_pb(&pb_fd)?;
}
println!("{} Done in {}", SPARKLE, HumanDuration(started.elapsed()));
Ok(())
}
fn run_entry_points_with_pb(pb: &ProgressBar) -> Result<(), BatSonarError> {
let config = BatConfig::get_config().change_context(BatSonarError)?;
let lib_paths = if config.program_lib_paths.is_empty() {
vec![config.program_lib_path.clone()]
} else {
config.program_lib_paths.clone()
};
let program_names = config.get_program_names();
let mut all_entries: Vec<(String, String)> = Vec::new();
for (lib_path, program_name) in lib_paths.iter().zip(program_names.iter()) {
let ep_names = EntrypointParser::get_entrypoint_names_filtered(false, Some(lib_path))
.change_context(BatSonarError)?;
for name in ep_names {
all_entries.push((name, program_name.clone()));
}
}
let total = all_entries.len();
pb.set_message(format!("Entry points [0/{}]", total));
for (idx, (entry, program_name)) in all_entries.iter().enumerate() {
pb.set_message(format!("Entry points [{}/{}]: {}", idx + 1, total, entry));
EntrypointParser::new_from_name_and_program(entry, Some(program_name)).unwrap();
}
pb.finish_with_message(format!("{} Entry points: {} processed", SPARKLE, total));
Ok(())
}
fn run_traits_with_pb(pb: &ProgressBar) -> Result<(), BatSonarError> {
let bat_metadata = BatMetadata::read_metadata().change_context(BatSonarError)?;
let traits_sc_metadata = bat_metadata.source_code.traits_source_code;
let total = traits_sc_metadata.len();
pb.set_message(format!("Traits [0/{}]", total));
for (idx, trait_sc) in traits_sc_metadata.iter().enumerate() {
pb.set_message(format!("Traits [{}/{}]: {}", idx + 1, total, trait_sc.name,));
TraitParser::new_from_metadata(trait_sc.clone()).unwrap();
}
pb.finish_with_message(format!("{} Traits: {} processed", SPARKLE, total));
Ok(())
}
fn run_function_deps_with_pb(pb: &ProgressBar) -> Result<(), BatSonarError> {
let bat_metadata = BatMetadata::read_metadata().change_context(BatSonarError)?;
let functions_sc_metadata = bat_metadata.source_code.functions_source_code;
let total = functions_sc_metadata.len();
pb.set_message(format!("Function dependencies [0/{}]", total));
for (idx, function_sc) in functions_sc_metadata.iter().enumerate() {
pb.set_message(format!(
"Function dependencies [{}/{}]: {}",
idx + 1,
total,
function_sc.name,
));
match FunctionParser::new_from_metadata(function_sc.clone()) {
Ok(_) => {}
Err(e) => {
log::warn!(
"Failed to parse dependencies for {}: {:?}",
function_sc.name,
e
);
}
}
}
pb.finish_with_message(format!(
"{} Function dependencies: {} processed",
SPARKLE, total
));
Ok(())
}
fn run_context_accounts_with_pb(pb: &ProgressBar) -> Result<(), BatSonarError> {
let ca_sc_metadata = SourceCodeMetadata::get_filtered_structs(
None,
Some(StructMetadataType::ContextAccounts),
)
.change_context(BatSonarError)?;
let total = ca_sc_metadata.len();
pb.set_message(format!("Context accounts [0/{}]", total));
let solana_account_names: Vec<String> =
SourceCodeMetadata::get_filtered_structs(None, Some(StructMetadataType::SolanaAccount))
.change_context(BatSonarError)?
.iter()
.map(|s| s.name.clone())
.collect();
let mut structs_by_file: std::collections::HashMap<String, Vec<_>> =
std::collections::HashMap::new();
for ca_sc in &ca_sc_metadata {
structs_by_file
.entry(ca_sc.path.clone())
.or_default()
.push(ca_sc.clone());
}
let mut count = 0usize;
for (file_path, ca_structs) in &structs_by_file {
let parsed_structs =
match syn_context_accounts_parser::parse_context_accounts_from_file(file_path) {
Ok(s) => s,
Err(e) => {
log::warn!("Failed to parse file {} with syn: {:?}", file_path, e);
for ca_sc in ca_structs {
count += 1;
pb.set_message(format!(
"Context accounts [{}/{}]: {} (fallback)",
count, total, ca_sc.name,
));
let ca_content =
ca_sc.to_source_code_parser(None).get_source_code_content();
let context_account_regex =
CAAccountParser::get_context_account_lazy_regex();
let ca_info = context_account_regex
.find_iter(&ca_content)
.map(|result: regex::Match<'_>| {
CAAccountParser::new_from_context_account_content(
result.as_str(),
)
.unwrap()
})
.collect::<Vec<_>>();
let context_accounts_metadata = ContextAccountsMetadata::new(
ca_sc.name.clone(),
BatMetadata::create_metadata_id(),
ca_sc.metadata_id.clone(),
ca_info,
ca_sc.program_name.clone(),
);
context_accounts_metadata.update_metadata_file().unwrap();
}
continue;
}
};
for ca_sc in ca_structs {
count += 1;
pb.set_message(format!(
"Context accounts [{}/{}]: {}",
count, total, ca_sc.name,
));
let ca_info = if let Some(parsed) =
parsed_structs.iter().find(|p| p.name == ca_sc.name)
{
parsed
.accounts
.iter()
.map(|acc| {
let solana_type =
acc.determine_solana_account_type(&solana_account_names);
let content =
ca_sc.to_source_code_parser(None).get_source_code_content();
acc.to_ca_account_parser(solana_type, &content)
})
.collect::<Vec<_>>()
} else {
log::warn!(
"Struct {} not found in syn parse of {}, using fallback",
ca_sc.name,
file_path
);
let ca_content = ca_sc.to_source_code_parser(None).get_source_code_content();
let context_account_regex = CAAccountParser::get_context_account_lazy_regex();
context_account_regex
.find_iter(&ca_content)
.map(|result: regex::Match<'_>| {
CAAccountParser::new_from_context_account_content(result.as_str())
.unwrap()
})
.collect::<Vec<_>>()
};
let context_accounts_metadata = ContextAccountsMetadata::new(
ca_sc.name.clone(),
BatMetadata::create_metadata_id(),
ca_sc.metadata_id.clone(),
ca_info,
ca_sc.program_name.clone(),
);
context_accounts_metadata.update_metadata_file().unwrap();
}
}
pb.finish_with_message(format!("{} Context accounts: {} processed", SPARKLE, total));
Ok(())
}
}
#[cfg(test)]
mod sonar_interactive_test {
use super::*;
use dialoguer::console::{style, Emoji, Style, Term};
use indicatif::{
HumanDuration, MultiProgress, ProgressBar, ProgressIterator, ProgressState, ProgressStyle,
};
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use std::cmp::min;
use std::fmt::Write;
use std::io::{BufRead, BufReader};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use std::{process, thread};
static PACKAGES: &[&str] = &[
"fs-events",
"my-awesome-module",
"emoji-speaker",
"wrap-ansi",
"stream-browserify",
"acorn-dynamic-import",
];
static COMMANDS: &[&str] = &[
"cmake .",
"make",
"make clean",
"gcc foo.c -o foo",
"gcc bar.c -o bar",
"./helper.sh rebuild-cache",
"make all-clean",
"make test",
];
static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍 ", "");
static TRUCK: Emoji<'_, '_> = Emoji("🚚 ", "");
static CLIP: Emoji<'_, '_> = Emoji("🔗 ", "");
static PAPER: Emoji<'_, '_> = Emoji("📃 ", "");
static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)");
#[test]
fn test_yarnish() {
let mut rng = rand::thread_rng();
let started = Instant::now();
let spinner_style = ProgressStyle::with_template("{prefix:.bold.dim} {spinner} {wide_msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ");
println!(
"{} {}Resolving packages...",
style("[1/4]").bold().dim(),
LOOKING_GLASS
);
println!(
"{} {}Fetching packages...",
style("[2/4]").bold().dim(),
TRUCK
);
println!(
"{} {}Linking dependencies...",
style("[3/4]").bold().dim(),
CLIP
);
let deps = 1232;
let pb = ProgressBar::new(deps);
for _ in 0..deps {
pb.inc(1);
thread::sleep(Duration::from_millis(3));
}
pb.finish_and_clear();
println!(
"{} {}Building fresh packages...",
style("[4/4]").bold().dim(),
PAPER
);
let m = MultiProgress::new();
let handles: Vec<_> = (0..4u32)
.map(|i| {
let count = rng.gen_range(30..80);
let pb = m.add(ProgressBar::new(count));
pb.set_style(spinner_style.clone());
pb.set_prefix(format!("[{}/?]", i + 1));
thread::spawn(move || {
let mut rng = rand::thread_rng();
let pkg = PACKAGES.choose(&mut rng).unwrap();
for _ in 0..count {
let cmd = COMMANDS.choose(&mut rng).unwrap();
pb.set_message(format!("{pkg}: {cmd}"));
pb.inc(1);
thread::sleep(Duration::from_millis(rng.gen_range(25..200)));
}
pb.finish_with_message("waiting...");
})
})
.collect();
for h in handles {
let _ = h.join();
}
println!("{} Done in {}", SPARKLE, HumanDuration(started.elapsed()));
}
#[test]
fn test_download() {
let mut downloaded = 0;
let total_size = 231231231;
let pb = ProgressBar::new(total_size);
pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
.unwrap()
.with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap())
.progress_chars("#>-"));
while downloaded < total_size {
let new = min(downloaded + 223211, total_size);
downloaded = new;
pb.set_position(new);
thread::sleep(Duration::from_millis(12));
}
pb.finish_with_message("downloaded");
}
#[test]
fn test_cargowrap() {
let started = Instant::now();
println!("Compiling package in release mode...");
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(200));
pb.set_style(
ProgressStyle::with_template("{spinner:.dim.bold} cargo: {wide_msg}")
.unwrap()
.tick_chars("/|\\- "),
);
let mut p = process::Command::new("cargo")
.arg("build")
.arg("--release")
.stderr(process::Stdio::piped())
.spawn()
.unwrap();
for line in BufReader::new(p.stderr.take().unwrap()).lines() {
let line = line.unwrap();
let stripped_line = line.trim();
if !stripped_line.is_empty() {
pb.set_message(stripped_line.to_owned());
}
pb.tick();
}
p.wait().unwrap();
pb.finish_and_clear();
println!("Done in {}", HumanDuration(started.elapsed()));
}
#[test]
fn test_finebars() {
let styles = [
("Rough bar:", "█ ", "red"),
("Fine bar: ", "█▉▊▋▌▍▎▏ ", "yellow"),
("Vertical: ", "█▇▆▅▄▃▂▁ ", "green"),
("Fade in: ", "█▓▒░ ", "blue"),
("Blocky: ", "█▛▌▖ ", "magenta"),
];
let m = MultiProgress::new();
let handles: Vec<_> = styles
.iter()
.map(|s| {
let pb = m.add(ProgressBar::new(512));
pb.set_style(
ProgressStyle::with_template(&format!(
"{{prefix:.bold}}▕{{bar:.{}}}▏{{msg}}",
s.2
))
.unwrap()
.progress_chars(s.1),
);
pb.set_prefix(s.0);
let wait = Duration::from_millis(thread_rng().gen_range(10..30));
thread::spawn(move || {
for i in 0..512 {
pb.inc(1);
pb.set_message(format!("{:3}%", 100 * i / 512));
thread::sleep(wait);
}
pb.finish_with_message("100%");
})
})
.collect();
for h in handles {
let _ = h.join();
}
}
#[test]
fn test_log() {
let pb = ProgressBar::new(100);
for i in 0..100 {
thread::sleep(Duration::from_millis(25));
pb.println(format!("[+] finished #{i}"));
pb.inc(1);
}
pb.finish_with_message("done");
}
static CRATES: &[(&str, &str)] = &[
("console", "v0.14.1"),
("lazy_static", "v1.4.0"),
("libc", "v0.2.93"),
("regex", "v1.4.6"),
("regex-syntax", "v0.6.23"),
("terminal_size", "v0.1.16"),
("libc", "v0.2.93"),
("unicode-width", "v0.1.8"),
("lazy_static", "v1.4.0"),
("number_prefix", "v0.4.0"),
("regex", "v1.4.6"),
("rand", "v0.8.3"),
("getrandom", "v0.2.2"),
("cfg-if", "v1.0.0"),
("libc", "v0.2.93"),
("rand_chacha", "v0.3.0"),
("ppv-lite86", "v0.2.10"),
("rand_core", "v0.6.2"),
("getrandom", "v0.2.2"),
("rand_core", "v0.6.2"),
("tokio", "v1.5.0"),
("bytes", "v1.0.1"),
("pin-project-lite", "v0.2.6"),
("slab", "v0.4.3"),
("indicatif", "v0.15.0"),
];
#[test]
fn test_cargo() {
const NUM_CPUS: usize = 4;
let start = Instant::now();
let pb = ProgressBar::new(CRATES.len() as u64);
pb.set_style(
ProgressStyle::with_template(
if Term::stdout().size().1 > 80 {
"{prefix:>12.cyan.bold} [{bar:57}] {pos}/{len} {wide_msg}"
} else {
"{prefix:>12.cyan.bold} [{bar:57}] {pos}/{len}"
},
)
.unwrap()
.progress_chars("=> "),
);
pb.set_prefix("Building");
let crates = Arc::new(Mutex::new(CRATES.iter()));
let (tx, rx) = mpsc::channel();
for n in 0..NUM_CPUS {
let tx = tx.clone();
let crates = crates.clone();
thread::spawn(move || {
let mut rng = rand::thread_rng();
loop {
let krate = crates.lock().unwrap().next();
tx.send((n, krate)).unwrap();
if let Some(krate) = krate {
thread::sleep(Duration::from_millis(
if CRATES.last() == Some(krate) {
rng.gen_range(1_000..2_000)
} else {
rng.gen_range(250..1_000)
},
));
} else {
break;
}
}
});
}
drop(tx);
let green_bold = Style::new().green().bold();
let mut processing = vec![None; NUM_CPUS];
while let Ok((n, krate)) = rx.recv() {
processing[n] = krate;
let crates: Vec<&str> = processing
.iter()
.filter_map(|t| t.copied().map(|(name, _)| name))
.collect();
pb.set_message(crates.join(", "));
if let Some((name, version)) = krate {
let line = format!(
"{:>12} {} {}",
green_bold.apply_to("Compiling"),
name,
version
);
pb.println(line);
pb.inc(1);
}
}
pb.finish_and_clear();
println!(
"{:>12} dev [unoptimized + debuginfo] target(s) in {}",
green_bold.apply_to("Finished"),
HumanDuration(start.elapsed())
);
}
#[test]
fn test_download_continued() {
let mut downloaded = 69369369;
let total_size = 231231231;
let pb = ProgressBar::new(total_size);
pb.set_style(
ProgressStyle::with_template(
"{spinner:.green} [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})",
)
.unwrap()
.progress_chars("#>-"),
);
pb.set_position(downloaded);
pb.reset_eta();
while downloaded < total_size {
downloaded = min(downloaded + 123211, total_size);
pb.set_position(downloaded);
thread::sleep(Duration::from_millis(12));
}
pb.finish_with_message("downloaded");
}
#[test]
fn test_download_speed() {
let mut downloaded = 0;
let total_size = 231231231;
let pb = ProgressBar::new(total_size);
pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")
.unwrap()
.progress_chars("#>-"));
while downloaded < total_size {
let new = min(downloaded + 223211, total_size);
downloaded = new;
pb.set_position(new);
thread::sleep(Duration::from_millis(12));
}
pb.finish_with_message("downloaded");
}
#[test]
fn test_fastbar() {
let n: u64 = 1 << 20;
let label = "Default progress bar ";
let pb = ProgressBar::new(n);
let mut sum = 0;
for i in 0..n {
sum += 2 * i + 3;
pb.inc(1);
}
pb.finish();
println!("[{}] Sum ({}) calculated in {:?}", label, sum, pb.elapsed());
}
#[test]
fn test_iterator() {
for _ in (0..1000).progress() {
thread::sleep(Duration::from_millis(5));
}
for _ in (0..1000).progress_count(1000) {
thread::sleep(Duration::from_millis(5));
}
let pb = ProgressBar::new(1000);
pb.set_style(
ProgressStyle::with_template(
"{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] ({pos}/{len}, ETA {eta})",
)
.unwrap(),
);
for _ in (0..1000).progress_with(pb) {
thread::sleep(Duration::from_millis(5));
}
}
#[test]
fn test_long_spinner() {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(120));
pb.set_style(
ProgressStyle::with_template("{spinner:.blue} {msg}")
.unwrap()
.tick_strings(&[
"▹▹▹▹▹",
"▸▹▹▹▹",
"▹▸▹▹▹",
"▹▹▸▹▹",
"▹▹▹▸▹",
"▹▹▹▹▸",
"▪▪▪▪▪",
]),
);
pb.set_message("Calculating...");
thread::sleep(Duration::from_secs(5));
pb.finish_with_message("Done");
}
#[test]
fn test_morebars() {
let m = Arc::new(MultiProgress::new());
let sty = ProgressStyle::with_template("{bar:40.green/yellow} {pos:>7}/{len:7}").unwrap();
let pb = m.add(ProgressBar::new(5));
pb.set_style(sty.clone());
pb.tick();
for _ in 0..5 {
let pb2 = m.add(ProgressBar::new(128));
pb2.set_style(sty.clone());
for _ in 0..128 {
pb2.inc(1);
thread::sleep(Duration::from_millis(5));
}
pb2.finish();
pb.inc(1);
}
pb.finish_with_message("done");
}
#[test]
fn test_multi() {
let m = MultiProgress::new();
let sty = ProgressStyle::with_template(
"[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
)
.unwrap()
.progress_chars("##-");
let pb = m.add(ProgressBar::new(128));
pb.set_style(sty.clone());
let pb2 = m.insert_after(&pb, ProgressBar::new(128));
pb2.set_style(sty.clone());
let pb3 = m.insert_after(&pb2, ProgressBar::new(1024));
pb3.set_style(sty);
m.println("starting!").unwrap();
let m_clone = m.clone();
let h1 = thread::spawn(move || {
for i in 0..128 {
pb.set_message(format!("item #{}", i + 1));
pb.inc(1);
thread::sleep(Duration::from_millis(15));
}
m_clone.println("pb1 is done!").unwrap();
pb.finish_with_message("done");
});
let m_clone = m.clone();
let h2 = thread::spawn(move || {
for _ in 0..3 {
pb2.set_position(0);
for i in 0..128 {
pb2.set_message(format!("item #{}", i + 1));
pb2.inc(1);
thread::sleep(Duration::from_millis(8));
}
}
m_clone.println("pb2 is done!").unwrap();
pb2.finish_with_message("done");
});
let m_clone = m.clone();
let h3 = thread::spawn(move || {
for i in 0..1024 {
pb3.set_message(format!("item #{}", i + 1));
pb3.inc(1);
thread::sleep(Duration::from_millis(2));
}
m_clone.println("pb3 is done!").unwrap();
pb3.finish_with_message("done");
});
let _ = h1.join();
let _ = h2.join();
let _ = h3.join();
m.clear().unwrap();
}
mod multi_tree {
use super::*;
use clap::__macro_refs::once_cell::sync::Lazy;
use rand::rngs::ThreadRng;
use rand::RngCore;
#[derive(Debug, Clone)]
enum Action {
AddProgressBar(usize),
IncProgressBar(usize),
}
#[derive(Clone, Debug)]
struct Elem {
key: String,
index: usize,
indent: usize,
progress_bar: ProgressBar,
}
static ELEMENTS: Lazy<[Elem; 9]> = Lazy::new(|| {
[
Elem {
indent: 1,
index: 0,
progress_bar: ProgressBar::new(32),
key: "jumps".to_string(),
},
Elem {
indent: 2,
index: 1,
progress_bar: ProgressBar::new(32),
key: "lazy".to_string(),
},
Elem {
indent: 0,
index: 0,
progress_bar: ProgressBar::new(32),
key: "the".to_string(),
},
Elem {
indent: 3,
index: 3,
progress_bar: ProgressBar::new(32),
key: "dog".to_string(),
},
Elem {
indent: 2,
index: 2,
progress_bar: ProgressBar::new(32),
key: "over".to_string(),
},
Elem {
indent: 2,
index: 1,
progress_bar: ProgressBar::new(32),
key: "brown".to_string(),
},
Elem {
indent: 1,
index: 1,
progress_bar: ProgressBar::new(32),
key: "quick".to_string(),
},
Elem {
indent: 3,
index: 5,
progress_bar: ProgressBar::new(32),
key: "a".to_string(),
},
Elem {
indent: 3,
index: 3,
progress_bar: ProgressBar::new(32),
key: "fox".to_string(),
},
]
});
fn get_action(rng: &mut dyn RngCore, tree: &Mutex<Vec<&Elem>>) -> Option<Action> {
let elem_len = ELEMENTS.len() as u64;
let list_len = tree.lock().unwrap().len() as u64;
let sum_free = tree
.lock()
.unwrap()
.iter()
.map(|e| {
let pos = e.progress_bar.position();
let len = e.progress_bar.length().unwrap();
len - pos
})
.sum::<u64>();
if sum_free == 0 && list_len == elem_len {
None
} else if sum_free == 0 && list_len < elem_len {
Some(Action::AddProgressBar(tree.lock().unwrap().len()))
} else {
loop {
let list = tree.lock().unwrap();
let k = rng.gen_range(0..17);
if k == 0 && list_len < elem_len {
return Some(Action::AddProgressBar(list.len()));
} else {
let l = (k % list_len) as usize;
let pos = list[l].progress_bar.position();
let len = list[l].progress_bar.length();
if pos < len.unwrap() {
return Some(Action::IncProgressBar(l));
}
}
}
}
}
#[test]
fn test_multi_tree() {
let mp = Arc::new(MultiProgress::new());
let sty_main =
ProgressStyle::with_template("{bar:40.green/yellow} {pos:>4}/{len:4}").unwrap();
let sty_aux =
ProgressStyle::with_template("{spinner:.green} {msg} {pos:>4}/{len:4}").unwrap();
let pb_main = mp.add(ProgressBar::new(
ELEMENTS
.iter()
.map(|e| e.progress_bar.length().unwrap())
.sum(),
));
pb_main.set_style(sty_main);
for elem in ELEMENTS.iter() {
elem.progress_bar.set_style(sty_aux.clone());
}
let tree: Arc<Mutex<Vec<&Elem>>> =
Arc::new(Mutex::new(Vec::with_capacity(ELEMENTS.len())));
let tree2 = Arc::clone(&tree);
let mp2 = Arc::clone(&mp);
let _ = thread::spawn(move || {
let mut rng = ThreadRng::default();
pb_main.tick();
loop {
match get_action(&mut rng, &tree) {
None => {
pb_main.finish();
return;
}
Some(Action::AddProgressBar(el_idx)) => {
let elem = &ELEMENTS[el_idx];
let pb = mp2.insert(elem.index + 1, elem.progress_bar.clone());
pb.set_message(format!("{} {}", " ".repeat(elem.indent), elem.key));
tree.lock().unwrap().insert(elem.index, elem);
}
Some(Action::IncProgressBar(el_idx)) => {
let elem = &tree.lock().unwrap()[el_idx];
elem.progress_bar.inc(1);
let pos = elem.progress_bar.position();
if pos >= elem.progress_bar.length().unwrap() {
elem.progress_bar.finish_with_message(format!(
"{}{} {}",
" ".repeat(elem.indent),
"✔",
elem.key
));
}
pb_main.inc(1);
}
}
thread::sleep(Duration::from_millis(15));
}
})
.join();
println!("===============================");
println!("the tree should be the same as:");
for elem in tree2.lock().unwrap().iter() {
println!("{} {}", " ".repeat(elem.indent), elem.key);
}
}
}
mod multi_tree_ext {
use super::*;
use clap::__macro_refs::once_cell::sync::Lazy;
use indicatif::MultiProgressAlignment;
use rand::rngs::ThreadRng;
use rand::RngCore;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
enum Action {
ModifyTree(usize),
IncProgressBar(usize),
Stop,
}
#[derive(Clone, Debug)]
enum Elem {
AddItem(Item),
RemoveItem(Index),
}
#[derive(Clone, Debug)]
struct Item {
key: String,
index: usize,
indent: usize,
progress_bar: ProgressBar,
}
#[derive(Clone, Debug)]
struct Index(usize);
const PB_LEN: u64 = 32;
static ELEM_IDX: AtomicUsize = AtomicUsize::new(0);
static ELEMENTS: Lazy<[Elem; 27]> = Lazy::new(|| {
[
Elem::AddItem(Item {
indent: 9,
index: 0,
progress_bar: ProgressBar::new(PB_LEN),
key: "dog".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 0,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_1".to_string(),
}),
Elem::AddItem(Item {
indent: 8,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "lazy".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_2".to_string(),
}),
Elem::AddItem(Item {
indent: 1,
index: 0,
progress_bar: ProgressBar::new(PB_LEN),
key: "the".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 0,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_3".to_string(),
}),
Elem::AddItem(Item {
indent: 7,
index: 3,
progress_bar: ProgressBar::new(PB_LEN),
key: "a".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 3,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_4".to_string(),
}),
Elem::AddItem(Item {
indent: 6,
index: 2,
progress_bar: ProgressBar::new(PB_LEN),
key: "over".to_string(),
}),
Elem::RemoveItem(Index(6)),
Elem::RemoveItem(Index(4)),
Elem::RemoveItem(Index(3)),
Elem::RemoveItem(Index(0)),
Elem::AddItem(Item {
indent: 0,
index: 2,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_5".to_string(),
}),
Elem::AddItem(Item {
indent: 4,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "fox".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_6".to_string(),
}),
Elem::AddItem(Item {
indent: 2,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "quick".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 1,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_7".to_string(),
}),
Elem::AddItem(Item {
indent: 5,
index: 5,
progress_bar: ProgressBar::new(PB_LEN),
key: "jumps".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 5,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_8".to_string(),
}),
Elem::AddItem(Item {
indent: 3,
index: 4,
progress_bar: ProgressBar::new(PB_LEN),
key: "brown".to_string(),
}),
Elem::AddItem(Item {
indent: 0,
index: 3,
progress_bar: ProgressBar::new(PB_LEN),
key: "temp_9".to_string(),
}),
Elem::RemoveItem(Index(10)),
Elem::RemoveItem(Index(7)),
Elem::RemoveItem(Index(4)),
Elem::RemoveItem(Index(3)),
Elem::RemoveItem(Index(1)),
]
});
#[test]
fn test_multi_tree_ext() {
let mp = Arc::new(MultiProgress::new());
let alignment = MultiProgressAlignment::Top;
mp.set_alignment(alignment);
let sty_main =
ProgressStyle::with_template("{bar:40.green/yellow} {pos:>4}/{len:4}").unwrap();
let sty_aux =
ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{spinner:.green} {msg}")
.unwrap();
let sty_fin = ProgressStyle::with_template("[{pos:>2}/{len:2}] {prefix}{msg}").unwrap();
let pb_main = mp.add(ProgressBar::new(
ELEMENTS
.iter()
.map(|e| match e {
Elem::AddItem(item) => item.progress_bar.length().unwrap(),
Elem::RemoveItem(_) => 1,
})
.sum(),
));
pb_main.set_style(sty_main);
for e in ELEMENTS.iter() {
match e {
Elem::AddItem(item) => item.progress_bar.set_style(sty_aux.clone()),
Elem::RemoveItem(_) => {}
}
}
let mut items: Vec<&Item> = Vec::with_capacity(ELEMENTS.len());
let mp2 = Arc::clone(&mp);
let mut rng = ThreadRng::default();
pb_main.tick();
loop {
match get_action(&mut rng, &items) {
Action::Stop => {
pb_main.finish();
return;
}
Action::ModifyTree(elem_idx) => match &ELEMENTS[elem_idx] {
Elem::AddItem(item) => {
let pb = mp2.insert(item.index, item.progress_bar.clone());
pb.set_prefix(" ".repeat(item.indent));
pb.set_message(&item.key);
items.insert(item.index, item);
}
Elem::RemoveItem(Index(index)) => {
let item = items.remove(*index);
let pb = &item.progress_bar;
mp2.remove(pb);
pb_main.inc(pb.length().unwrap() - pb.position());
}
},
Action::IncProgressBar(item_idx) => {
let item = &items[item_idx];
item.progress_bar.inc(1);
let pos = item.progress_bar.position();
if pos >= item.progress_bar.length().unwrap() {
item.progress_bar.set_style(sty_fin.clone());
item.progress_bar.finish_with_message(format!(
"{} {}",
style("✔").green(),
item.key
));
}
pb_main.inc(1);
}
}
thread::sleep(Duration::from_millis(20));
}
}
fn get_action(rng: &mut dyn RngCore, items: &[&Item]) -> Action {
let elem_idx = ELEM_IDX.load(Ordering::SeqCst);
let uncompleted = items
.iter()
.enumerate()
.filter(|(_, item)| {
let pos = item.progress_bar.position();
pos < item.progress_bar.length().unwrap()
})
.map(|(idx, _)| idx)
.collect::<Vec<usize>>();
let k = rng.gen_range(0..16);
if (k > 0 || k == 0 && elem_idx == ELEMENTS.len()) && !uncompleted.is_empty() {
let idx = rng.gen_range(0..uncompleted.len() as u64) as usize;
Action::IncProgressBar(uncompleted[idx])
} else if elem_idx < ELEMENTS.len() {
ELEM_IDX.fetch_add(1, Ordering::SeqCst);
Action::ModifyTree(elem_idx)
} else {
Action::Stop
}
}
}
}