use std::path::PathBuf;
use std::time::SystemTime;
use std::fs;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use bytes::Bytes;
use reqwest::Client;
use dprint_core::types::ErrBox;
use super::{Environment, ProgressBars, ProgressBarStyle};
use crate::plugins::CompilationResult;
#[derive(Clone)]
pub struct RealEnvironment {
output_lock: Arc<Mutex<u8>>,
progress_bars: Arc<ProgressBars>,
is_verbose: bool,
is_silent: bool,
}
impl RealEnvironment {
pub fn new(is_verbose: bool, is_silent: bool) -> RealEnvironment {
RealEnvironment {
output_lock: Arc::new(Mutex::new(0)),
progress_bars: Arc::new(ProgressBars::new()),
is_verbose,
is_silent,
}
}
}
const APP_INFO: app_dirs::AppInfo = app_dirs::AppInfo { name: "dprint", author: "dprint" };
#[async_trait]
impl Environment for RealEnvironment {
fn is_real(&self) -> bool {
true
}
fn read_file(&self, file_path: &PathBuf) -> Result<String, ErrBox> {
log_verbose!(self, "Reading file: {}", file_path.display());
let text = fs::read_to_string(file_path)?;
Ok(text)
}
async fn read_file_async(&self, file_path: &PathBuf) -> Result<String, ErrBox> {
log_verbose!(self, "Reading file: {}", file_path.display());
let text = tokio::fs::read_to_string(file_path).await?;
Ok(text)
}
fn read_file_bytes(&self, file_path: &PathBuf) -> Result<Bytes, ErrBox> {
log_verbose!(self, "Reading file: {}", file_path.display());
let bytes = fs::read(file_path)?;
Ok(Bytes::from(bytes))
}
fn write_file(&self, file_path: &PathBuf, file_text: &str) -> Result<(), ErrBox> {
log_verbose!(self, "Writing file: {}", file_path.display());
fs::write(file_path, file_text)?;
Ok(())
}
async fn write_file_async(&self, file_path: &PathBuf, file_text: &str) -> Result<(), ErrBox> {
log_verbose!(self, "Writing file: {}", file_path.display());
tokio::fs::write(file_path, file_text).await?;
Ok(())
}
fn write_file_bytes(&self, file_path: &PathBuf, bytes: &[u8]) -> Result<(), ErrBox> {
log_verbose!(self, "Writing file: {}", file_path.display());
fs::write(file_path, bytes)?;
Ok(())
}
fn remove_file(&self, file_path: &PathBuf) -> Result<(), ErrBox> {
log_verbose!(self, "Deleting file: {}", file_path.display());
fs::remove_file(file_path)?;
Ok(())
}
fn remove_dir_all(&self, dir_path: &PathBuf) -> Result<(), ErrBox> {
log_verbose!(self, "Deleting directory: {}", dir_path.display());
fs::remove_dir_all(dir_path)?;
Ok(())
}
async fn download_file(&self, url: &str) -> Result<Bytes, ErrBox> {
log_verbose!(self, "Downloading url: {}", url);
let client = Client::new();
let mut resp = client.get(url).send().await?;
let total_size = {
if resp.status().is_success() {
resp.content_length()
} else {
return err!("Error downloading: {}. Status: {:?}", url, resp.status());
}
}.unwrap_or(0);
if self.is_silent {
Ok(resp.bytes().await?)
} else {
let message = get_middle_truncted_text("Downloading ", url);
let pb = self.progress_bars.add_progress(&message, ProgressBarStyle::Download, total_size);
let mut final_bytes = bytes::BytesMut::with_capacity(total_size as usize);
while let Some(chunk) = resp.chunk().await? {
final_bytes.extend_from_slice(&chunk);
pb.set_position(final_bytes.len() as u64);
}
pb.finish_and_clear();
self.progress_bars.finish_one().await?;
Ok(final_bytes.freeze())
}
}
fn glob(&self, base: &PathBuf, file_patterns: &Vec<String>) -> Result<Vec<PathBuf>, ErrBox> {
let start_instant = std::time::Instant::now();
log_verbose!(self, "Globbing: {:?}", file_patterns);
let base = self.canonicalize(base)?;
let walker = globwalk::GlobWalkerBuilder::from_patterns(base, file_patterns)
.follow_links(false)
.file_type(globwalk::FileType::FILE)
.build();
let walker = match walker {
Ok(walker) => walker,
Err(err) => return err!("Error parsing file patterns: {}", err),
};
let mut file_paths = Vec::new();
for result in walker.into_iter() {
match result {
Ok(result) => file_paths.push(result.into_path()),
Err(err) => return err!("Error walking files: {}", err),
}
}
log_verbose!(self, "Finished globbing in {}ms", start_instant.elapsed().as_millis());
Ok(file_paths)
}
fn path_exists(&self, file_path: &PathBuf) -> bool {
log_verbose!(self, "Checking path exists: {}", file_path.display());
file_path.exists()
}
fn canonicalize(&self, path: &PathBuf) -> Result<PathBuf, ErrBox> {
Ok(dunce::canonicalize(path)?)
}
fn is_absolute_path(&self, path: &PathBuf) -> bool {
path.is_absolute()
}
fn mk_dir_all(&self, path: &PathBuf) -> Result<(), ErrBox> {
fs::create_dir_all(path)?;
Ok(())
}
fn cwd(&self) -> Result<PathBuf, ErrBox> {
Ok(std::env::current_dir()?)
}
fn log(&self, text: &str) {
if self.is_silent { return; }
let _g = self.output_lock.lock().unwrap();
println!("{}", text);
}
fn log_silent(&self, text: &str) {
let _g = self.output_lock.lock().unwrap();
println!("{}", text);
}
async fn log_action_with_progress<
TResult: std::marker::Send + std::marker::Sync,
TCreate: FnOnce(Box<dyn Fn(usize)>) -> TResult + std::marker::Send + std::marker::Sync,
>(&self, message: &str, action: TCreate, total_size: usize) -> Result<TResult, ErrBox> {
let pb = self.progress_bars.add_progress(message, ProgressBarStyle::Action, total_size as u64);
let pb = std::sync::Arc::new(pb);
let result = action(Box::new({
let pb = pb.clone();
move |size| pb.set_position(size as u64)
}));
pb.finish_and_clear();
self.progress_bars.finish_one().await?;
Ok(result)
}
fn log_error(&self, text: &str) {
let _g = self.output_lock.lock().unwrap();
eprintln!("{}", text);
}
fn get_cache_dir(&self) -> Result<PathBuf, ErrBox> {
log_verbose!(self, "Getting cache directory.");
match app_dirs::app_dir(app_dirs::AppDataType::UserCache, &APP_INFO, "cache") {
Ok(path) => Ok(path),
Err(err) => err!("Error getting cache directory: {:?}", err),
}
}
fn get_time_secs(&self) -> u64 {
SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs()
}
fn get_selection(&self, prompt_message: &str, items: &Vec<String>) -> Result<usize, ErrBox> {
use dialoguer::*;
let result = Select::with_theme(&CustomDialoguerTheme::new())
.with_prompt(prompt_message)
.items(items)
.default(0)
.interact()?;
Ok(result)
}
fn get_multi_selection(&self, prompt_message: &str, items: &Vec<String>) -> Result<Vec<usize>, ErrBox> {
use dialoguer::*;
let result = MultiSelect::with_theme(&CustomDialoguerTheme::new())
.with_prompt(prompt_message)
.items_checked(&items.iter().map(|item| (item, true)).collect::<Vec<_>>())
.interact()?;
Ok(result)
}
#[inline]
fn is_verbose(&self) -> bool {
self.is_verbose
}
fn compile_wasm(&self, wasm_bytes: &[u8]) -> Result<CompilationResult, ErrBox> {
crate::plugins::compile_wasm(wasm_bytes)
}
fn stdout(&self) -> Box<dyn std::io::Write + Send> {
Box::new(std::io::stdout())
}
fn stdin(&self) -> Box<dyn std::io::Read + Send> {
Box::new(std::io::stdin())
}
}
struct CustomDialoguerTheme {
}
impl CustomDialoguerTheme {
pub fn new() -> Self {
CustomDialoguerTheme {}
}
}
impl dialoguer::theme::Theme for CustomDialoguerTheme {
#[inline]
fn format_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result {
write!(f, "{}", prompt)
}
#[inline]
fn format_input_prompt_selection(
&self,
f: &mut dyn std::fmt::Write,
prompt: &str,
sel: &str,
) -> std::fmt::Result {
write!(f, "{}\n {}", prompt, sel)
}
fn format_multi_select_prompt_selection(
&self,
f: &mut dyn std::fmt::Write,
prompt: &str,
selections: &[&str],
) -> std::fmt::Result {
write!(f, "{}", prompt)?;
for sel in selections.iter() {
write!(f, "\n * {}", sel)?;
}
Ok(())
}
}
fn get_middle_truncted_text(prompt: &str, text: &str) -> String {
use terminal_size::{Width, terminal_size};
let term_width = if let Some((Width(width), _)) = terminal_size() {
width as usize
} else {
return format!("{}{}", prompt, text);
};
let prompt_width = console::measure_text_width(prompt);
let text_width = console::measure_text_width(text);
let is_text_within_term_width = prompt_width + text_width < term_width;
let should_give_up = term_width < prompt_width || (term_width - prompt_width) / 2 < 3;
if is_text_within_term_width || should_give_up {
format!("{}{}", prompt, text)
} else {
let middle_point = (term_width - prompt_width) / 2;
let text_chars = text.chars().collect::<Vec<_>>();
let first_text: String = (&text_chars[0..middle_point - 2]).iter().collect();
let second_text: String = (&text_chars[text_chars.len() - middle_point + 1..]).iter().collect();
let text = format!("{}...{}", first_text, second_text);
format!("{}{}", prompt, text)
}
}