use std::fmt::Display;
use async_trait::async_trait;
use instant::Instant;
use rand::seq::IndexedRandom;
use rand::{Rng, rng};
use yansi::Paint;
use crate::args::AppConfig;
use crate::data::JULIA_PACKAGES_LIST;
use crate::generators::gen_hex_string;
use crate::io::{csleep, cursor_up, dprint, erase_line, newline, print};
use crate::modules::Module;
#[derive(Debug)]
pub struct Package<'a> {
pub name: &'a str,
pub id: &'a str,
pub versions: Vec<&'a str>,
}
pub struct Julia;
#[async_trait(?Send)]
impl Module for Julia {
fn name(&self) -> &'static str {
"julia"
}
fn signature(&self) -> String {
"julia --threads auto --project .".to_string()
}
async fn run(&self, appconfig: &AppConfig) {
let mut rng = rng();
let num_packages = rng.random_range(10..150);
let num_artifacts = rng.random_range(1..10);
let all_packages: Vec<&Package> = JULIA_PACKAGES_LIST
.choose_multiple(&mut rng, num_packages + num_artifacts)
.collect();
let (packages, artifacts) = all_packages.split_at(num_packages);
let project = if rng.random_bool(0.3) {
"@v1.7"
} else {
JULIA_PACKAGES_LIST.choose(&mut rng).unwrap().name
};
print_banner().await;
csleep(rng.random_range(50..150)).await;
newline().await;
print_julia_prompt().await;
csleep(rng.random_range(500..2500)).await;
erase_line().await;
print_pkg_prompt(project).await;
csleep(rng.random_range(500..1500)).await;
dprint("up", rng.random_range(100..500)).await;
dprint("date", rng.random_range(100..500)).await;
csleep(rng.random_range(500..1500)).await;
newline().await;
csleep(rng.random_range(200..1000)).await;
log_action("Updating", "registry at `~/.julia/registries/General.toml`").await;
csleep(rng.random_range(1500..5000)).await;
log_action("Resolving", "package versions...").await;
csleep(rng.random_range(500..2500)).await;
install_packages(packages).await;
csleep(rng.random_range(250..1000)).await;
download_artifacts(artifacts).await;
csleep(rng.random_range(250..1000)).await;
update_project_and_manifest(project).await;
csleep(rng.random_range(10..100)).await;
report_packages(packages).await;
csleep(rng.random_range(150..500)).await;
build_artifacts(artifacts).await;
csleep(rng.random_range(150..500)).await;
precompile(packages).await;
if rng.random_bool(0.25) {
gc().await;
}
csleep(rng.random_range(50..250)).await;
newline().await;
print_pkg_prompt(project).await;
csleep(rng.random_range(500..5000)).await;
erase_line().await;
print_julia_prompt().await;
csleep(rng.random_range(1000..7000)).await;
newline().await;
if appconfig.should_exit() {
return;
}
}
}
async fn print_banner() {
print(format!(
r#" {gu}{cr}
{bu} _ {ru}{g}{mu} | Documentation: https://docs.julialang.org{cr}
{b} | {r} {m} |{cr}
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.{cr}
| | | | | | |/ _` | |{cr}
| | |_| | | | (_| | | Version 1.7.3 (2022-05-06){cr}
_/ |\__'_|_|_|\__'_| | Fedora 35 build{cr}
|__/ |{cr}
"#,
r = Paint::red("(_)").bold(),
ru = Paint::red("_").bold(),
g = Paint::green("(_)").bold(),
gu = Paint::green("_").bold(),
b = Paint::blue("(_)").bold(),
bu = Paint::blue("_").bold(),
m = Paint::magenta("(_)").bold(),
mu = Paint::magenta("_").bold(),
cr = '\r',
))
.await;
}
async fn print_julia_prompt() {
print(format!("{} ", Paint::green("julia>").bold())).await;
}
async fn print_pkg_prompt(project: &str) {
print(format!("{} ", format!("({project}) pkg>").blue().bold())).await;
}
async fn log_action(action: impl Display, message: impl Display) {
print(format!("{:>12} {}", action.green().bold(), message)).await;
newline().await;
}
async fn log_progress(bar: &progress_string::Bar) {
print(format!("{:>10} {}", Paint::cyan("Progress").bold(), bar)).await;
newline().await;
}
async fn install_packages(packages: &[&Package<'_>]) {
let mut rng = rng();
let max_name_length = packages.iter().map(|p| p.name.len()).max().unwrap();
for package in packages {
let package_and_version = format!(
"{name} {empty:─>width$} v{version}",
name = package.name,
width = max_name_length - package.name.len() + 1,
empty = "",
version = package.versions.last().unwrap()
);
if rng.random_bool(0.1) {
log_action("Installing", &package_and_version).await;
csleep(rng.random_range(250..1000)).await;
cursor_up(1).await;
erase_line().await;
} else {
csleep(rng.random_range(10..200)).await;
}
log_action("Installed", &package_and_version).await;
}
}
async fn download_artifacts(artifacts: &[&Package<'_>]) {
let mut rng = rng();
for artifact in artifacts {
csleep(rng.random_range(50..100)).await;
log_action("Downloading", format!("artifact: {}", artifact.name)).await;
csleep(rng.random_range(100..150)).await;
let mut bar = progress_string::BarBuilder::new()
.total(10000)
.width(41)
.full_char('=')
.include_percent()
.build();
print(format!("{:>15} {}", Paint::cyan("Downloading").bold(), bar)).await;
newline().await;
while bar.current_partial < bar.total {
let add = rng.random_range(0..=500.min(bar.total - bar.current_partial));
bar.update(add);
cursor_up(1).await;
erase_line().await;
print(format!("{:>15} {}", Paint::cyan("Downloading").bold(), bar)).await;
newline().await;
csleep(rng.random_range(50..75)).await;
}
cursor_up(1).await;
erase_line().await;
csleep(rng.random_range(100..200)).await;
cursor_up(1).await;
erase_line().await;
log_action("Downloaded", format!("artifact: {}", artifact.name)).await;
}
}
async fn update_project_and_manifest(project: &str) {
let mut rng = rng();
let project_path;
let manifest_path;
if let Some(project) = project.strip_prefix('@') {
project_path = format!("~/.julia/environments/{project}/Project.toml");
manifest_path = format!("~/.julia/environments/{project}/Manifest.toml");
} else {
project_path = format!("~/Documents/code/julia/projects/{project}.jl/Project.toml");
manifest_path = format!("~/Documents/code/julia/projects/{project}.jl/Manifest.toml");
}
let old_format = rng.random_bool(0.25);
if old_format {
print_old_manifest_format_before(&manifest_path).await;
}
if rng.random_bool(0.9) {
log_action("Updating", format!("`{project_path}`")).await;
} else {
log_action("No Changes", format!("to `{project_path}`")).await;
}
csleep(rng.random_range(10..100)).await;
log_action("Updating", format!("`{manifest_path}`")).await;
if old_format {
print_old_manifest_format_after(&manifest_path).await;
}
}
async fn print_old_manifest_format_before(manifest_path: &str) {
print(format!(
"{} The active manifest file at `{}` has an old format that is being maintained.",
Paint::yellow("┌ Warning:").bold(),
manifest_path
))
.await;
newline().await;
print(format!(
"{} To update to the new format run `Pkg.upgrade_manifest()` which will upgrade the format without re-resolving.",
Paint::yellow("│").bold()
))
.await;
newline().await;
print(format!(
"{} {}",
"└".yellow().bold(),
"@ Pkg.Types /builddir/build/BUILD/julia-1.7.3/build/usr/share/julia/stdlib/v1.7/Pkg/src/manifest.jl:287".fixed(8)
))
.await;
newline().await;
}
async fn print_old_manifest_format_after(manifest_path: &str) {
print(format!(
"{} The active manifest file is an older format with no julia version entry. Dependencies may have been resolved with a different julia version.",
Paint::yellow("┌ Warning:").bold(),
))
.await;
newline().await;
print(format!(
"{} {}",
"└".yellow().bold(),
format!("@ {manifest_path}:0").fixed(8)
))
.await;
newline().await;
}
async fn report_packages(packages: &[&Package<'_>]) {
let mut rng = rng();
for package in packages {
if package.versions.len() > 1 && rng.random_bool(0.75) {
let package_id = format!("[{}]", &package.id[0..8]);
let version_change = format!(
"↑ {} v{} ⇒ v{}",
package.name,
package.versions[rng.random_range(0..package.versions.len() - 1)],
package.versions.last().unwrap()
);
print(format!(
" {} {}",
package_id.fixed(8),
version_change.fixed(11)
))
.await;
newline().await;
} else if rng.random_bool(0.9) {
let package_id = format!("[{}]", &package.id[0..8]);
let version_change =
format!("+ {} v{}", package.name, package.versions.last().unwrap());
print(format!(
" {} {}",
package_id.fixed(8),
version_change.fixed(10)
))
.await;
newline().await;
} else {
let package_id = format!("[{}]", &package.id[0..8]);
let version_change =
format!("- {} v{}", package.name, package.versions.last().unwrap());
print(format!(
" {} {}",
package_id.fixed(8),
version_change.fixed(9)
))
.await;
newline().await;
}
csleep(rng.random_range(10..100)).await;
}
}
async fn build_artifacts(artifacts: &[&Package<'_>]) {
let mut rng = rng();
let mut bar = progress_string::BarBuilder::new()
.total(artifacts.len())
.width(41)
.full_char('=')
.include_numbers()
.build();
log_progress(&bar).await;
let max_name_length = artifacts.iter().map(|p| p.name.len()).max().unwrap();
for artifact in artifacts {
bar.update(1);
cursor_up(1).await;
erase_line().await;
log_action(
"Building",
format!(
"{name} {empty:─>width$}→ `~/.julia/scratchspaces/{}-{}-{}-{}-{}/{}/build.log`",
gen_hex_string(&mut rng, 8),
gen_hex_string(&mut rng, 4),
gen_hex_string(&mut rng, 4),
gen_hex_string(&mut rng, 4),
gen_hex_string(&mut rng, 12),
gen_hex_string(&mut rng, 40),
name = artifact.name,
width = max_name_length - artifact.name.len() + 1,
empty = "",
),
)
.await;
log_progress(&bar).await;
csleep(rng.random_range(500..5000)).await;
}
cursor_up(1).await;
erase_line().await;
}
async fn precompile(packages: &[&Package<'_>]) {
let mut rng = rng();
let num_packages = packages.len();
let now = Instant::now();
log_action("Precompiling", "project...").await;
let mut bar = progress_string::BarBuilder::new()
.total(num_packages)
.width(41)
.full_char('=')
.include_numbers()
.build();
log_progress(&bar).await;
for i in 1..=num_packages {
bar.replace(i);
cursor_up(1).await;
erase_line().await;
log_progress(&bar).await;
csleep(rng.random_range(50..1000)).await;
}
let elapsed = now.elapsed();
let seconds = elapsed.as_secs() as f32;
cursor_up(1).await;
erase_line().await;
print(format!(
" {num_packages} dependencies successfully precompiled in {seconds:.0} seconds ({} already precompiled)",
rng.random_range(10..500)
))
.await;
newline().await;
}
async fn gc() {
let mut rng = rng();
csleep(rng.random_range(50..250)).await;
print(format!(
"{} We haven't cleaned this depot up for a bit, running Pkg.gc()...",
Paint::cyan("[ Info:").bold(),
))
.await;
newline().await;
csleep(rng.random_range(100..250)).await;
log_action(
"Active",
format!("manifest files: {} found", rng.random_range(1..10)),
)
.await;
csleep(rng.random_range(100..250)).await;
log_action(
"Active",
format!("artifact files: {} found", rng.random_range(10..200)),
)
.await;
csleep(rng.random_range(100..250)).await;
log_action(
"Active",
format!("scratchspaces: {} found", rng.random_range(10..20)),
)
.await;
csleep(rng.random_range(100..250)).await;
log_action(
"Deleted",
format!(
"{} package installations ({:.3} MiB)",
rng.random_range(2..100),
rng.random_range(10f32..250f32)
),
)
.await;
csleep(rng.random_range(100..250)).await;
log_action(
"Deleted",
format!(
"{} artifact installations ({:.3} MiB)",
rng.random_range(2..10),
rng.random_range(10f32..250f32)
),
)
.await;
csleep(rng.random_range(100..250)).await;
log_action(
"Deleted",
format!(
"{} scratchspaces ({:.3} byte)",
rng.random_range(2..10),
rng.random_range(10f32..1000f32)
),
)
.await;
}