1use crate::{env::Env, paths::ruby_env};
4use std::{
5 borrow::Cow,
6 fs::ReadDir,
7 io::Result,
8 path::PathBuf,
9 process::{Command, ExitStatus},
10};
11
12pub struct RubyEnv {
13 venv: Option<PathBuf>,
14}
15
16impl Default for RubyEnv {
17 fn default() -> Self {
18 Self::at(ruby_env())
19 }
20}
21
22impl Env for RubyEnv {
23 fn path(&self) -> Option<&std::path::PathBuf> {
24 self.venv.as_ref()
25 }
26
27 fn is_initialized(&self) -> bool {
28 match self.venv {
29 None => ruby().is_some(),
30 Some(ref path) => path.is_dir(),
31 }
32 }
33
34 fn initialize(&self) -> Result<()> {
35 if self.is_initialized() {
36 return Ok(());
37 }
38
39 let Some(ref path) = self.venv else {
40 return Ok(());
41 };
42
43 std::fs::create_dir_all(path)?;
45
46 Ok(())
47 }
48
49 fn available_modules(&self) -> std::io::Result<Vec<String>> {
50 Ok(vec![]) }
52
53 fn installed_modules(&self) -> std::io::Result<Vec<String>> {
54 let mut result = vec![];
55 for entry in self.gems_dir()? {
56 let Ok(entry) = entry else {
57 continue; };
59 if !entry.path().is_dir() {
60 continue; }
62 let name = entry.file_name();
63 let Some(name) = name.to_str() else {
64 continue; };
66 let Some(name) = name.strip_prefix("asimov-") else {
67 continue;
68 };
69 let Some(pos) = name.find("-module-") else {
70 continue;
71 };
72 result.push(name[..pos].to_string());
73 }
74 Ok(result)
75 }
76
77 fn install_module(
78 &self,
79 module_name: impl ToString,
80 verbosity: Option<u8>,
81 ) -> Result<ExitStatus> {
82 if !self.is_initialized() {
83 self.initialize()?;
84 }
85
86 let package_name = format!("asimov-{}-module", module_name.to_string());
87
88 self.gem_command("install", verbosity.unwrap_or(0))
89 .args(["--prerelease", "--no-document", &package_name])
90 .status()
91 }
92
93 fn uninstall_module(
94 &self,
95 module_name: impl ToString,
96 verbosity: Option<u8>,
97 ) -> Result<ExitStatus> {
98 if !self.is_initialized() {
99 return Ok(ExitStatus::default());
100 }
101
102 let package_name = format!("asimov-{}-module", module_name.to_string());
103
104 self.gem_command("uninstall", verbosity.unwrap_or(0))
105 .args(["--all", "--executables", &package_name])
106 .status()
107 }
108}
109
110impl RubyEnv {
111 pub fn system() -> Self {
112 Self { venv: None }
113 }
114
115 pub fn at(path: PathBuf) -> Self {
116 Self { venv: Some(path) }
117 }
118
119 pub fn ruby(&self) -> Command {
120 match self.venv {
121 None => Command::new(ruby().unwrap().as_ref()),
122 Some(ref path) => {
123 let venv_ruby = path.join("bin/ruby");
124 Command::new(if venv_ruby.is_file() {
125 venv_ruby
126 } else {
127 PathBuf::from(ruby().unwrap().as_ref()) })
129 },
130 }
131 }
132
133 pub fn gem(&self) -> Command {
134 let mut command = self.ruby();
135 command.args(["-S", "gem"]);
136 command
137 }
138
139 pub fn gem_command(&self, subcommand: &str, verbosity: u8) -> Command {
140 let mut command = self.gem();
141 command.arg(subcommand).args(Self::gem_verbosity(verbosity));
142 if let Some(ref path) = self.venv {
143 command.args(["--install-dir", path.to_str().unwrap()]);
144 }
145 command
146 }
147
148 pub fn gem_verbosity(verbosity: u8) -> Vec<&'static str> {
150 match verbosity {
151 0 => vec!["--silent"],
152 1 => vec!["--quiet"], 2 => vec![],
154 _ => vec!["--verbose"], }
156 }
157
158 pub fn gems_dir(&self) -> std::io::Result<ReadDir> {
159 let Some(ref path) = self.gems_path() else {
160 return Err(std::io::Error::new(
161 std::io::ErrorKind::NotFound,
162 "`gems` directory not found",
163 ));
164 };
165 std::fs::read_dir(path)
166 }
167
168 pub fn gems_path(&self) -> Option<PathBuf> {
169 let Some(ref path) = self.venv else {
170 return None;
171 };
172 let path = path.join("gems");
173 if !path.is_dir() {
174 return None; }
176 Some(path)
177 }
178}
179
180pub fn ruby() -> Option<Cow<'static, str>> {
181 getenv::ruby()
182 .map(Cow::from)
183 .or_else(|| Some(Cow::from("ruby")))
184}
185
186pub fn gem() -> Option<Cow<'static, str>> {
187 getenv::gem()
188 .map(Cow::from)
189 .or_else(|| Some(Cow::from("gem")))
190}