1use serde::{Deserialize, Serialize};
11use std::{
12 collections::{HashMap, VecDeque},
13 env::{self, VarError, home_dir},
14 fs::read_to_string,
15 io::{Error, ErrorKind, Result as IoResult},
16 path::PathBuf,
17 process::Command,
18};
19
20#[derive(PartialEq, Default, Eq, Hash, Deserialize, Serialize)]
26pub struct RuntimeMetadata {
27 pub display_name: String,
28 pub search_paths: Vec<String>,
29}
30
31#[derive(PartialEq, Eq, Hash)]
37pub struct Runtime {
38 pub name: String,
39 pub metadata: RuntimeMetadata,
40}
41
42impl Runtime {
43 pub fn unsafe_new(name: String) -> Runtime {
45 Runtime {
46 name,
47 metadata: RuntimeMetadata::default(),
48 }
49 }
50
51 pub fn new(name: String) -> IoResult<Runtime> {
59 let mut buf: PathBuf = Runtime::get_runtime(&name)?;
60 buf.push("meta.ron");
61 let data: String = read_to_string(buf)?;
62 match ron::from_str::<RuntimeMetadata>(data.as_str()) {
63 Ok(metadata) => Ok(Runtime { name, metadata }),
64 Err(_) => Err(Error::new(
65 ErrorKind::InvalidData,
66 format!(
67 "Metadata file for runtime \"{}\" is not valid runtime metadata",
68 name
69 ),
70 )),
71 }
72 }
73
74 pub fn get_root() -> IoResult<PathBuf> {
76 if let Some(mut home) = home_dir() {
77 home.push(".ver");
78 Ok(home)
79 } else {
80 Err(Error::new(
81 ErrorKind::NotFound,
82 "Could not access home directory",
83 ))
84 }
85 }
86
87 pub fn get_runtime(name: &str) -> IoResult<PathBuf> {
89 let mut buf: PathBuf = Runtime::get_root()?;
90 buf.push(name);
91 Ok(buf)
92 }
93
94 pub fn get_version(&self, version: String) -> IoResult<PathBuf> {
96 let mut buf: PathBuf = Runtime::get_runtime(&self.name)?;
97 buf.push(version);
98 Ok(buf)
99 }
100
101 pub fn get_safe_version(&self, version: String) -> IoResult<PathBuf> {
103 let path: PathBuf = self.get_version(version.to_string())?;
104 if path.try_exists()? {
105 Ok(path)
106 } else {
107 Err(Error::new(
108 ErrorKind::NotFound,
109 format!(
110 "Version {} for runtime \"{}\" was not found",
111 version, self.name
112 ),
113 ))
114 }
115 }
116
117 pub fn get_version_search_paths(&self, version: String) -> IoResult<Vec<PathBuf>> {
120 let mut paths: Vec<PathBuf> = Vec::new();
121 let version_dir: PathBuf = self.get_safe_version(version)?;
122 for search_path in &self.metadata.search_paths {
123 let search_buf: PathBuf = search_path.into();
124 let proper_search_buf: PathBuf = version_dir.join(search_buf);
125 if proper_search_buf.try_exists()? {
126 paths.push(proper_search_buf);
127 } else {
128 return Err(Error::new(
129 ErrorKind::NotFound,
130 format!(
131 "Search path \"{}\" does not exist",
132 proper_search_buf.display()
133 ),
134 ));
135 }
136 }
137 Ok(paths)
138 }
139}
140
141pub mod conf {
142 use crate::*;
143 use std::io::{Error, ErrorKind, Result as IoResult};
144
145 pub fn parse<T: AsRef<str>>(path: T) -> IoResult<HashMap<String, String>> {
147 let data: String = read_to_string(path.as_ref())?;
148 match ron::from_str::<HashMap<String, String>>(data.as_str()) {
149 Ok(map) => Ok(map),
150 Err(_) => Err(Error::new(
151 ErrorKind::InvalidData,
152 "Configuration file is invalid",
153 )),
154 }
155 }
156
157 pub fn unsafe_collect(data: HashMap<String, String>) -> HashMap<Runtime, String> {
160 let mut parsed: HashMap<Runtime, String> = HashMap::new();
161 for (name, value) in data.iter() {
162 let runtime: Runtime = Runtime::unsafe_new(name.to_string());
163 parsed.insert(runtime, value.to_string());
164 }
165 parsed
166 }
167
168 pub fn collect(data: HashMap<String, String>) -> IoResult<HashMap<Runtime, String>> {
170 let mut parsed: HashMap<Runtime, String> = HashMap::new();
171 for (name, value) in data.iter() {
172 let runtime: Runtime = Runtime::new(name.to_string())?;
173 parsed.insert(runtime, value.to_string());
174 }
175 Ok(parsed)
176 }
177}
178
179pub fn exec(mut args: VecDeque<String>, config: HashMap<Runtime, String>) -> IoResult<Command> {
186 let mut cmd: Command = Command::new(if let Some(data) = args.pop_front() {
187 data
188 } else if let Ok(shell) = env::var("SHELL") {
189 shell
190 } else if cfg!(windows) {
191 "cmd".into()
192 } else {
193 "sh".into()
194 });
195 let mut paths: Vec<PathBuf> = Vec::with_capacity(config.len());
196 for (runtime, version) in config.iter() {
197 paths.push(runtime.get_safe_version(version.to_string())?);
198 paths.extend(runtime.get_version_search_paths(version.to_string())?);
199 }
200 let path: Result<String, VarError> = env::var("PATH");
201 cmd.args(args)
202 .env("PATH", {
203 let mut iter = paths.iter();
204 let mut prefix: String = String::new();
205 if let Some(path) = iter.next()
206 && let Some(data) = path.to_str()
207 {
208 prefix.push_str(data);
209 }
210
211 let delim: char = if cfg!(windows) { ';' } else { ':' };
212 for path in iter {
213 if let Some(data) = path.to_str() {
214 prefix.push(delim);
215 prefix.push_str(data);
216 }
217 }
218 if let Ok(good_path) = path {
219 prefix.push(delim);
220 prefix.push_str(good_path.as_str());
221 }
222 prefix
223 })
224 .env("VER_OVERRIDE", "1");
225 Ok(cmd)
226}