Link command WIP

This commit is contained in:
2023-03-15 16:22:19 +01:00
parent 5fe8a29842
commit 1eaf759599
16 changed files with 500 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

89
Cargo.lock generated Normal file
View File

@@ -0,0 +1,89 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "alma"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "serde"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"

10
Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "alma"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

19
src/application/mod.rs Normal file
View File

@@ -0,0 +1,19 @@
use crate::command::Command;
use crate::commands;
pub fn run(args: Vec<String>) {
if args.len() < 2 {
println!("No command provided");
return;
}
let command: Option<Box<dyn Command>> = match args[1].as_str() {
"link" => Some(Box::new(commands::link::LinkCommand {})),
_ => None,
};
match command {
Some(command) => command.run(&args[2..]),
None => println!("Invalid command: {}", args[1]),
}
}

3
src/command/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub trait Command {
fn run(&self, args: &[std::string::String]);
}

73
src/commands/link/mod.rs Normal file
View File

@@ -0,0 +1,73 @@
use crate::command::Command;
use crate::configuration::module::resolver::*;
use crate::contants::*;
use crate::utils::path::path::*;
pub struct LinkCommand {}
impl LinkCommand {
pub fn run(args: &[std::string::String]) -> Result<(), String> {
let (repository_name, module_name) = get_repository_and_module_name(args, false);
if module_name.is_none() {
println!("No module specified!");
return Ok(());
}
let (repo_source, repo_target) =
match get_repo_source_and_target_dir_with_default(repository_name) {
Ok((source, target)) => (source, target),
Err(e) => return Err(e.to_string()),
};
let repo_source_path = std::path::Path::new(&repo_source);
if !repo_source_path.exists() {
return Err(format!(
"Repository source directory does not exist: {}",
repo_source
));
}
let module_name_as_path = module_name
.unwrap()
.replace("/", &std::path::MAIN_SEPARATOR.to_string());
let module_dir = repo_source_path
.join(module_name_as_path)
.display()
.to_string();
let module_config_dir = std::path::Path::new(&module_dir);
if !module_config_dir.exists() {
return Err(format!(
"Module source directory does not exist: {}",
module_dir
));
}
let module_config_file_stub = module_config_dir
.join(MODULE_CONFIG_FILE_STUB)
.display()
.to_string();
let module_configuration = resolve_module_configuration(module_config_file_stub);
if module_configuration.is_err() {
return Err(module_configuration.err().unwrap().to_string());
}
let module_configuration = module_configuration.unwrap();
for l in module_configuration.links.unwrap().iter() {
print!("Linking {} to {}...", l.0, l.1);
}
return Ok(());
}
}
impl Command for LinkCommand {
fn run(&self, args: &[std::string::String]) {
match Self::run(args) {
Ok(_) => {}
Err(e) => println!("Error while linking: {}", e),
}
}
}

1
src/commands/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod link;

View File

@@ -0,0 +1,62 @@
pub mod resolver;
use std::{collections::HashMap, fmt::{Display, Formatter}};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ModuleConfiguration {
pub target: Option<String>,
pub install: Option<String>,
pub configure: Option<String>,
pub links: Option<HashMap<String, String>>,
}
impl ModuleConfiguration {
pub fn empty() -> ModuleConfiguration {
return ModuleConfiguration {
target: None,
install: None,
configure: None,
links: None,
};
}
fn clone_option<T: Clone>(s: &Option<T>) -> Option<T> {
match s {
Some(s) => Some(s.clone()),
None => None,
}
}
pub fn merge(self, to_merge: &ModuleConfiguration) -> ModuleConfiguration {
let mut merged_links = self.links.unwrap_or(HashMap::new()).clone();
let links_to_merge = Self::clone_option::<HashMap<String, String>>(&to_merge.links)
.unwrap_or(HashMap::new())
.clone();
for (key, value) in links_to_merge {
merged_links.insert(key, value);
}
let to_merge_target = Self::clone_option::<String>(&to_merge.target);
let to_merge_install = Self::clone_option::<String>(&to_merge.install);
let to_merge_configure = Self::clone_option::<String>(&to_merge.configure);
return ModuleConfiguration {
target: to_merge_target.or(self.target),
install: to_merge_install.or(self.install),
configure: to_merge_configure.or(self.configure),
links: Some(merged_links),
};
}
}
impl Display for ModuleConfiguration {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"target: {:?}, install: {:?}, configure: {:?}, links: {:?}",
self.target, self.install, self.configure, self.links
)
}
}

View File

@@ -0,0 +1,24 @@
use std::collections::HashMap;
use crate::configuration::module::ModuleConfiguration;
use crate::configuration::reader;
use crate::utils::os_information::*;
pub fn resolve_module_configuration(
config_file_stub: String,
) -> std::io::Result<ModuleConfiguration> {
let configurations =
reader::read_config::<HashMap<String, ModuleConfiguration>>(config_file_stub)?;
let os_identifier = get_os_identifier();
let configurations: Vec<&ModuleConfiguration> = configurations
.iter()
.filter(|(k, _)| is_on_platform(&os_identifier, k))
.map(|c| c.1)
.collect();
let configuration = configurations.iter()
.fold(ModuleConfiguration::empty(), |acc, c| acc.merge(c));
return Ok(configuration);
}

View File

@@ -0,0 +1,7 @@
pub fn read_config<T>(config_file_stub: String) -> std::io::Result<T> where T: serde::de::DeserializeOwned {
let config_file_path = format!("{}.json", config_file_stub);
let config_file = std::fs::File::open(config_file_path)?;
let config: T = serde_json::from_reader(config_file)?;
return Ok(config);
}

View File

@@ -0,0 +1,29 @@
use std::path::Path;
use serde::{Serialize, Deserialize};
use crate::utils::folder::get_config_home_path;
use super::reader;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RepositoryConfigurationEntry {
pub name: Option<String>,
pub repository_path: Option<String>,
pub link_path: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct RepositoryConfigurationRoot {
pub repositories: Vec<RepositoryConfigurationEntry>
}
pub fn read_repository_configuration() -> std::io::Result<RepositoryConfigurationRoot> {
let config_file_path = get_config_home_path();
let repo_config_file_path_stub = Path::new(&config_file_path).join("repository").display().to_string();
let config = reader::read_config::<RepositoryConfigurationRoot>(repo_config_file_path_stub)?;
return Ok(config);
}

1
src/contants.rs Normal file
View File

@@ -0,0 +1 @@
pub const MODULE_CONFIG_FILE_STUB: &str = ".alma-config";

22
src/main.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::env;
mod application;
mod command;
mod commands;
mod contants;
mod utils {
pub mod path;
pub mod folder;
pub mod os_information;
}
pub mod configuration {
pub mod module;
pub mod reader;
pub mod repository;
}
fn main() {
let args: Vec<String> = env::args().collect();
application::run(args);
}

18
src/utils/folder.rs Normal file
View File

@@ -0,0 +1,18 @@
use std::path::Path;
const APPLICATION_SUBFOLDER_NAME: &str = "alma";
pub fn get_config_home_path() -> String {
let config_home_path = std::env::var("XDG_CONFIG_HOME")
.map_or_else(
|_| {
let home_path = std::env::var("HOME").unwrap();
Path::new(&home_path).join(".config")
},
|c| Path::new(&c).to_path_buf(),
)
.join(APPLICATION_SUBFOLDER_NAME)
.display()
.to_string();
return config_home_path;
}

View File

@@ -0,0 +1,60 @@
use std::{
fs::File,
io::{BufRead, BufReader},
path::Path,
};
pub const OS_IDENTIFIER_DEFAULT: &str = "default";
pub const OS_IDENTIFIER_WINDOWS: &str = "windows";
pub const OS_IDENTIFIER_MAC: &str = "macos";
pub const OS_IDENTIFIER_LINUX: &str = "linux";
pub const OS_IDENTIFIER_FREEBSD: &str = "freebsd";
pub fn is_on_platform(current_os_identifier: &String, taget_os_identifier: &String) -> bool {
return taget_os_identifier == OS_IDENTIFIER_DEFAULT
|| current_os_identifier == taget_os_identifier;
}
pub fn get_os_identifier() -> String {
let base_os_identifier = match std::env::consts::OS {
"windows" => OS_IDENTIFIER_WINDOWS.to_string(),
"macos" => OS_IDENTIFIER_MAC.to_string(),
"linux" => get_linux_identifier(),
"freebsd" => OS_IDENTIFIER_FREEBSD.to_string(),
_ => String::from("unknown"),
};
let processor_architecture = std::env::consts::ARCH;
return base_os_identifier + "-" + processor_architecture;
}
fn get_linux_identifier() -> String {
let os_release_path = Path::new("/etc/os-release");
let distroname = if os_release_path.exists() {
get_os_release(os_release_path)
} else {
None
};
distroname.map_or(OS_IDENTIFIER_LINUX.to_string(), |s| {
OS_IDENTIFIER_LINUX.to_string() + "-" + &s
})
}
fn get_os_release(os_release_path: &Path) -> Option<String> {
let os_release_file = File::open(os_release_path).unwrap();
let reader = BufReader::new(os_release_file);
for line in reader.lines() {
if line.is_err() {
continue;
}
let line = line.unwrap();
if line.starts_with("ID=") {
let os_identifier = line.replace("ID=", "");
return Some(os_identifier);
}
}
return None;
}

81
src/utils/path.rs Normal file
View File

@@ -0,0 +1,81 @@
pub mod path {
use std::env::current_dir;
use crate::configuration::repository::read_repository_configuration;
pub fn get_repository_and_module_name(
args: &[std::string::String],
single_param_is_repo: bool,
) -> (Option<String>, Option<String>) {
//TODO: handle parameters
match args.len() {
0 => (None, None),
1 => {
return if single_param_is_repo {
(Some(args[0].clone()), None)
} else {
(None, Some(args[0].clone()))
};
}
_ => (Some(args[0].clone()), Some(args[1].clone())),
}
}
pub fn get_repo_source_and_target_dir_with_default(
repo_name: Option<String>,
) -> std::io::Result<(String, String)> {
if current_dir().is_err() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Could not get current directory!",
));
}
let source_dir_fallback = current_dir()?.display().to_string();
let mut target_dir_fallback = current_dir()?;
target_dir_fallback.push("..");
let target_dir_fallback = target_dir_fallback.display().to_string();
let (repo_source_dir, repo_target_dir) =
get_repo_source_and_target_dir(repo_name, source_dir_fallback, target_dir_fallback)?;
return Ok((repo_source_dir, repo_target_dir));
}
pub fn get_repo_source_and_target_dir(
repo_name: Option<String>,
fallback_source: String,
fallback_target: String,
) -> std::io::Result<(String, String)> {
if repo_name.is_none() {
return Ok((fallback_source, fallback_target));
}
let repo_name = repo_name.unwrap();
let repo_configurtaion = read_repository_configuration()?;
let repo_config = repo_configurtaion
.repositories
.iter()
.find(|x| x.name.clone().map_or(false, |s| s == repo_name));
if repo_config.is_none() {
println!(
"No repository configuration found for repository: {}",
repo_name
);
return Ok((fallback_source, fallback_target));
}
let repo_config = repo_config.unwrap();
let source_path = repo_config
.repository_path
.clone()
.unwrap_or(fallback_source);
let target_path = repo_config.link_path.clone().unwrap_or(fallback_target);
return Ok((source_path, target_path));
}
}