#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
}
pub mod langenv {
use directories::ProjectDirs;
use flate2::read::GzDecoder;
use semver::Version;
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tar::Archive;
use url::Url;
#[derive(serde::Deserialize, Debug, PartialEq, strum::EnumString, strum::Display)]
pub enum Platform {
#[strum(serialize = "linux")]
#[serde(rename = "linux")]
Linux,
#[strum(serialize = "windows")]
#[serde(rename = "windows")]
Windows,
#[strum(serialize = "macos")]
#[serde(rename = "macos")]
MacOs,
}
#[derive(serde::Deserialize, Debug, PartialEq, strum::EnumString, strum::Display)]
pub enum CpuArchitecture {
#[strum(serialize = "x86_64")]
#[serde(rename = "x86_64")]
Amd64,
#[strum(serialize = "aarch64")]
#[serde(rename = "aarch64")]
Arm64,
}
#[derive(serde::Deserialize, Debug, PartialEq, strum::EnumString, strum::Display)]
pub enum PackageFormat {
#[strum(serialize = "zip")]
#[serde(rename = "zip")]
Zip,
#[strum(serialize = "tar.gz")]
#[serde(rename = "tar.gz")]
TarGz,
#[strum(serialize = "bin")]
#[serde(rename = "bin")]
Binary,
}
#[derive(Debug)]
pub struct Tool {
pub name: String,
pub downloads_url: String,
pub project_url: String,
pub repo_url: String,
pub releases: Vec<Release>,
}
#[derive(Debug)]
pub struct Repo {
pub tools: Vec<Tool>,
}
impl Repo {
pub fn parse_dir(dir_path: &Path) -> Result<Repo, std::io::Error> {
let file_path: PathBuf = [PathBuf::from(dir_path), PathBuf::from("repo.json")]
.iter()
.collect();
let contents: String =
fs::read_to_string(&file_path).expect("Something went wrong reading the file");
let repo_file: RepoFile = serde_json::from_str(&contents)?;
let mut tools: Vec<Tool> = vec![];
for tool in repo_file.tools {
let tool_dir: PathBuf = [
PathBuf::from(dir_path),
PathBuf::from(&tool.name),
PathBuf::from("releases"),
]
.iter()
.collect();
let paths = fs::read_dir(&tool_dir)?;
let mut releases: Vec<Release> = vec![];
for path in paths {
let release_file_path: PathBuf = path?.path();
releases.push(Release::parse_file(&release_file_path)?);
}
tools.push(Tool {
name: tool.name,
downloads_url: tool.downloads_url,
project_url: tool.project_url,
repo_url: tool.repo_url,
releases,
})
}
Ok(Repo { tools })
}
}
#[derive(serde::Deserialize, Debug)]
pub struct Release {
pub version: Version,
pub metadata: HashMap<String, String>,
pub packages: Vec<Package>,
}
#[derive(serde::Deserialize, Debug)]
pub struct Package {
pub metadata: HashMap<String, String>,
pub download_url: Url,
pub platform: Platform,
pub cpu_architecture: CpuArchitecture,
pub package_format: PackageFormat,
pub checksums: HashMap<CheckSumType, String>,
}
#[derive(serde::Deserialize, Eq, Debug, PartialEq, strum::EnumString, strum::Display, Hash)]
pub enum CheckSumType {
#[strum(serialize = "sha256")]
#[serde(rename = "sha256")]
Sha256,
}
impl Package {
pub fn file_name(&self, tool_name: &str) -> String {
match self.package_format {
PackageFormat::Binary => tool_name.to_string(),
_ => format!("{}.{}", tool_name, self.package_format),
}
}
}
impl Release {
pub fn parse_file(file_path: &Path) -> Result<Release, std::io::Error> {
let contents: String =
fs::read_to_string(&file_path).expect("Something went wrong reading the file");
let release: Release = serde_json::from_str(&contents)?;
Ok(release)
}
}
#[derive(serde::Deserialize, Debug)]
pub struct RepoFile {
pub tools: Vec<ToolItem>,
}
#[derive(serde::Deserialize, Debug)]
pub struct ToolItem {
pub name: String,
pub downloads_url: String,
pub project_url: String,
pub repo_url: String,
}
#[derive(Debug)]
pub struct Langenv {
pub cpu_architecture: CpuArchitecture,
pub platform: Platform,
pub repo: Repo,
pub project_dirs: ProjectDirs,
}
impl Langenv {
pub fn new(repo_dir: &Path) -> Result<Langenv, std::io::Error> {
let langenv = Langenv {
cpu_architecture: CpuArchitecture::from_str(std::env::consts::ARCH).unwrap(),
platform: Platform::from_str(std::env::consts::OS).unwrap(),
repo: Repo::parse_dir(repo_dir)?,
project_dirs: ProjectDirs::from("com", "Langenv", "langenv").unwrap(),
};
for path in &[
langenv.project_dirs.cache_dir(),
langenv.project_dirs.config_dir(),
langenv.project_dirs.data_local_dir(),
] {
println!("Making sure directory {:?} exists", &path);
fs::create_dir_all(&path)?;
}
println!();
Ok(langenv)
}
pub fn install_package(
&self,
tool: &Tool,
release: &Release,
package: &Package,
) -> Result<(), std::io::Error> {
let archive_file_path: PathBuf = [
self.project_dirs.cache_dir(),
&PathBuf::from(&tool.name),
&PathBuf::from(&release.version.to_string()),
&PathBuf::from("downloads"),
&PathBuf::from(&package.file_name(&tool.name)),
]
.iter()
.collect();
{
println!(
"Downloading {:?} version {} from {} to {:?}",
&tool.name, &release.version, &package.download_url, &archive_file_path
);
let mut resp = reqwest::blocking::get(&package.download_url.to_string())
.expect("request failed");
fs::create_dir_all(archive_file_path.parent().unwrap())?;
let mut out = File::create(&archive_file_path).expect("failed to create file");
io::copy(&mut resp, &mut out).expect("failed to copy content");
}
{}
{
let extract_dir_path: PathBuf = [
self.project_dirs.cache_dir(),
&PathBuf::from(&tool.name),
&PathBuf::from(&release.version.to_string()),
&PathBuf::from("extract"),
]
.iter()
.collect();
fs::create_dir_all(&extract_dir_path)?;
match package.package_format {
PackageFormat::TarGz => {
let tar_gz = File::open(&archive_file_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(&extract_dir_path)?;
}
PackageFormat::Zip => {
}
PackageFormat::Binary => {
let extract_file_path: PathBuf = [
&extract_dir_path,
&PathBuf::from(&package.file_name(&tool.name)),
]
.iter()
.collect();
fs::copy(&archive_file_path, &extract_file_path)?;
}
}
}
{}
Ok(())
}
}
}