use console::style;
use dialoguer::{theme, Input};
use jarn::JaseciConfig;
use log::error;
use serde::{Deserialize, Serialize};
use std::{
ffi::OsStr,
fs::{self, File},
io::{Read, Seek, Write},
path::{Path, PathBuf},
};
use walkdir::{DirEntry, WalkDir};
use zip::{result::ZipError, write::FileOptions};
#[derive(Serialize, Deserialize, Debug)]
struct PackageConfig {
scope: String,
name: String,
version: String,
}
pub fn install_package(path: &str, project_dir: &str) {
let zip_path = PathBuf::from(&path);
let project_path = PathBuf::from(&project_dir);
let config_path = project_path.clone().join("jaseci.toml");
let config: JaseciConfig = toml::from_str(&fs::read_to_string(config_path).unwrap())
.expect("Unable to find jaseci.toml file in project directory!");
extract_zip(&zip_path, &project_path.join("packages"));
}
pub fn publish_package(dir: &str) {
let mut zip_path = PathBuf::new();
let config_path = PathBuf::from(&dir).join("action.toml");
zip_path.push(&dir);
let config: PackageConfig =
toml::from_str(&fs::read_to_string(config_path).unwrap()).expect("Failed to read config!");
zip_path.push(format!(
"{}-jpkg_{}.zip",
config.name,
config.version.to_string()
));
let result = create_zip(
&dir,
&zip_path.clone().to_str().unwrap(),
zip::CompressionMethod::Bzip2,
);
match result {
Ok(_) => {
println!("Package created at {}", zip_path.display());
}
Err(_err) => {
error!("Failed to create package!");
}
};
}
pub fn init_package(dir: &str) {
println!("{}", style("\nTell us about your new package: \n").blue());
let scope: String = Input::with_theme(&theme::ColorfulTheme::default())
.with_prompt("Package scope")
.interact()
.unwrap();
let name: String = Input::with_theme(&theme::ColorfulTheme::default())
.with_prompt("Package name")
.interact()
.unwrap();
let mut path = PathBuf::new();
path.push(&dir);
if !path.exists() {
fs::create_dir_all(&path).unwrap();
}
let config: PackageConfig = PackageConfig {
name: name.clone(),
scope: scope.clone(),
version: String::from("1.0.0"),
};
let default_action_code = format!(
r#"from jaseci.jsorc.live_actions import jaseci_action
@jaseci_action(act_group=["{}.{}"], allow_remote=True)
def add(first_number: int, second_number: int):
return first_number + second_number
"#,
scope, name
);
let action_path = path.join("action.toml");
let python_path = path.join("action.py");
fs::write(action_path, toml::to_string(&config).unwrap()).unwrap();
fs::write(python_path, default_action_code).unwrap();
println!(
"{}",
format!(
"{} {}",
style("\nSweet! Package created at").green(),
style(path.display()).bold().italic().green()
)
);
}
fn zip_dir<T>(
it: &mut dyn Iterator<Item = DirEntry>,
prefix: &str,
writer: T,
method: zip::CompressionMethod,
) -> zip::result::ZipResult<()>
where
T: Write + Seek,
{
let mut zip = zip::ZipWriter::new(writer);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(0o755);
let mut buffer = Vec::new();
for entry in it {
let path = entry.path();
let name = path.strip_prefix(Path::new(prefix)).unwrap();
let allowed_extensions = vec![OsStr::new("toml"), OsStr::new("txt"), OsStr::new("py")];
if path.is_file() {
match path.extension() {
Some(ext) => {
println!("{:?}", ext);
if !allowed_extensions.contains(&ext) {
println!("{}", style(format!("skipping file {path:?}...")).yellow());
continue;
}
}
None => {
println!("{}", style(format!("skipping file {path:?}...")).yellow());
continue;
}
}
println!(
"{}",
style(format!("adding file {path:?} as {name:?} ...")).green()
);
#[allow(deprecated)]
zip.start_file_from_path(name, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
buffer.clear();
} else if !name.as_os_str().is_empty() {
println!("adding dir {path:?} as {name:?} ...");
#[allow(deprecated)]
zip.add_directory_from_path(name, options)?;
}
}
zip.finish()?;
Result::Ok(())
}
fn create_zip(
src_dir: &str,
dst_file: &str,
method: zip::CompressionMethod,
) -> zip::result::ZipResult<()> {
if !Path::new(src_dir).is_dir() {
return Err(ZipError::FileNotFound);
}
let path = Path::new(dst_file);
let file = File::create(path).unwrap();
let walkdir = WalkDir::new(src_dir);
zip_dir(
&mut walkdir.into_iter().filter_map(|e| e.ok()),
src_dir,
file,
method,
)?;
Ok(())
}
fn extract_zip(fname: &PathBuf, target: &PathBuf) {
let file = fs::File::open(fname).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
let mut action_file_contents = String::new();
let mut action_file = match archive.by_name("action.toml") {
Ok(file) => file,
Err(..) => {
println!("File action.toml not found");
return ();
}
};
if let Err(err) = action_file.read_to_string(&mut action_file_contents) {
println!("Unable to read action.toml {}", err);
return ();
}
let action_config: PackageConfig =
toml::from_str(&action_file_contents).expect("Unable to read action.toml");
let target = &target.join(action_config.scope).join(action_config.name);
let file = fs::File::open(fname).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = match file.enclosed_name() {
Some(path) => path.to_owned(),
None => continue,
};
let outpath = PathBuf::from(target).join(outpath);
if (&*file.name()).ends_with('/') {
println!(
"{}",
style(format!("File {} extracted to \"{}\"", i, outpath.display())).green()
);
fs::create_dir_all(&outpath).unwrap();
} else {
println!(
"{}",
style(format!(
"File {} extracted to \"{}\" ({} bytes)",
i,
outpath.display(),
file.size()
))
.green()
);
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(&p).unwrap();
}
}
let mut outfile = fs::File::create(&outpath).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
}
}