Skip to content

Commit 561bff8

Browse files
Merge pull request #243 from MordechaiHadad/add/what-the-path
Add the ability for bob to add neovim proxy to $PATH
2 parents c25b1a4 + 0ad9259 commit 561bff8

File tree

14 files changed

+1102
-354
lines changed

14 files changed

+1102
-354
lines changed

Cargo.lock

Lines changed: 812 additions & 250 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ clap_complete = "4.1"
2626
toml = "0.8.8"
2727
semver = "1.0.22"
2828
sha2 = "0.10.8"
29+
what-the-path = "^0.1.3"
2930

3031
[dependencies.chrono]
3132
version = "0.4.23"
@@ -89,7 +90,7 @@ tar = "0.4"
8990

9091
[target."cfg(windows)".dependencies]
9192
winreg = "0.10.1"
92-
zip = "0.5"
93+
zip = "2.2.0"
9394

9495

9596
[[bin]]

env/env.fish

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Credit: https://github.com/rust-lang/rustup/blob/master/src/cli/self_update/env.fish
2+
if not contains "{nvim_bin}" $PATH
3+
set -x PATH "{nvim_bin}" $PATH
4+
end

env/env.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
# Credit: https://github.com/rust-lang/rustup/blob/master/src/cli/self_update/env.sh
3+
case ":${PATH}:" in
4+
*:"{nvim_bin}":*)
5+
;;
6+
*)
7+
export PATH="{nvim_bin}:$PATH"
8+
;;
9+
esac

src/cli.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
config::Config,
2+
config::ConfigFile,
33
handlers::{
44
self, erase_handler, list_handler, list_remote_handler, rollback_handler, run_handler,
55
sync_handler, uninstall_handler, update_handler, InstallResult,
@@ -57,6 +57,8 @@ enum Cli {
5757
/// install command if the version is not installed already
5858
Use {
5959
/// Version to switch to |nightly|stable|<version-string>|<commit-hash>|
60+
///
61+
/// A version-string can either be `vx.x.x` or `x.x.x` examples: `v0.6.1` and `0.6.0`
6062
version: String,
6163

6264
/// Whether not to auto-invoke install command
@@ -68,6 +70,8 @@ enum Cli {
6870
/// out-of-date nightly version
6971
Install {
7072
/// Version to be installed |nightly|stable|<version-string>|<commit-hash>|
73+
///
74+
/// A version-string can either be `vx.x.x` or `x.x.x` examples: `v0.6.1` and `0.6.0`
7175
version: String,
7276
},
7377

@@ -79,6 +83,9 @@ enum Cli {
7983
#[clap(alias = "remove", visible_alias = "rm")]
8084
Uninstall {
8185
/// Optional Version to be uninstalled |nightly|stable|<version-string>|<commit-hash>|
86+
///
87+
/// A version-string can either be `vx.x.x` or `x.x.x` examples: `v0.6.1` and `0.6.0`
88+
///
8289
/// If no Version is provided a prompt is used to select the versions to be uninstalled
8390
version: Option<String>,
8491
},
@@ -163,7 +170,7 @@ pub struct Update {
163170
/// let config = Config::default();
164171
/// start(config).await.unwrap();
165172
/// ```
166-
pub async fn start(config: Config) -> Result<()> {
173+
pub async fn start(config: ConfigFile) -> Result<()> {
167174
let client = create_reqwest_client()?;
168175
let cli = Cli::parse();
169176

@@ -201,19 +208,21 @@ pub async fn start(config: Config) -> Result<()> {
201208
}
202209
Cli::Uninstall { version } => {
203210
info!("Starting uninstallation process");
204-
uninstall_handler::start(version.as_deref(), config).await?;
211+
uninstall_handler::start(version.as_deref(), config.config).await?;
205212
}
206-
Cli::Rollback => rollback_handler::start(config).await?,
207-
Cli::Erase => erase_handler::start(config).await?,
208-
Cli::List => list_handler::start(config).await?,
213+
Cli::Rollback => rollback_handler::start(config.config).await?,
214+
Cli::Erase => erase_handler::start(config.config).await?,
215+
Cli::List => list_handler::start(config.config).await?,
209216
Cli::Complete { shell } => {
210217
clap_complete::generate(shell, &mut Cli::command(), "bob", &mut std::io::stdout())
211218
}
212219
Cli::Update(data) => {
213220
update_handler::start(data, &client, config).await?;
214221
}
215-
Cli::ListRemote => list_remote_handler::start(config, client).await?,
216-
Cli::Run { version, args } => run_handler::start(&version, &args, &client, &config).await?,
222+
Cli::ListRemote => list_remote_handler::start(config.config, client).await?,
223+
Cli::Run { version, args } => {
224+
run_handler::start(&version, &args, &client, &config.config).await?
225+
}
217226
}
218227

219228
Ok(())

src/config.rs

Lines changed: 90 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,84 @@
11
use anyhow::Result;
22
use regex::Regex;
33
use serde::{Deserialize, Serialize};
4-
use std::env;
5-
use tokio::fs::{self};
4+
use std::{env, path::PathBuf};
5+
use tokio::{
6+
fs::{self, File},
7+
io::AsyncWriteExt,
8+
};
9+
10+
#[derive(Debug, Clone)]
11+
pub struct ConfigFile {
12+
pub path: PathBuf,
13+
pub format: ConfigFormat,
14+
pub config: Config,
15+
}
16+
17+
impl ConfigFile {
18+
pub async fn save_to_file(&self) -> Result<()> {
19+
if let Some(parent) = self.path.parent() {
20+
tokio::fs::create_dir_all(parent).await?;
21+
}
22+
23+
let data = match self.format {
24+
ConfigFormat::Toml => toml::to_string(&self.config)?,
25+
ConfigFormat::Json => serde_json::to_string_pretty(&self.config)?,
26+
};
27+
28+
let tmp_path = self.path.with_extension("tmp");
29+
let mut file = File::create(&tmp_path).await?;
30+
file.write_all(data.as_bytes()).await?;
31+
file.flush().await?;
32+
33+
// atomic operation i guess
34+
tokio::fs::rename(tmp_path, &self.path).await?;
35+
36+
Ok(())
37+
}
38+
}
39+
40+
impl ConfigFile {
41+
pub async fn get() -> Result<ConfigFile> {
42+
let config_file = crate::helpers::directories::get_config_file()?;
43+
let mut config_format = ConfigFormat::Json;
44+
let config = match fs::read_to_string(&config_file).await {
45+
Ok(config) => {
46+
if config_file.extension().unwrap() == "toml" {
47+
let mut config: Config = toml::from_str(&config)?;
48+
handle_envars(&mut config)?;
49+
config_format = ConfigFormat::Toml;
50+
config
51+
} else {
52+
let mut config: Config = serde_json::from_str(&config)?;
53+
handle_envars(&mut config)?;
54+
config
55+
}
56+
}
57+
Err(_) => Config {
58+
enable_nightly_info: None,
59+
enable_release_build: None,
60+
downloads_location: None,
61+
installation_location: None,
62+
version_sync_file_location: None,
63+
github_mirror: None,
64+
rollback_limit: None,
65+
add_neovim_binary_to_path: None,
66+
},
67+
};
68+
69+
Ok(ConfigFile {
70+
path: config_file,
71+
format: config_format,
72+
config,
73+
})
74+
}
75+
}
76+
77+
#[derive(Debug, Clone)]
78+
pub enum ConfigFormat {
79+
Toml,
80+
Json,
81+
}
682

783
/// Represents the application configuration.
884
///
@@ -17,6 +93,7 @@ use tokio::fs::{self};
1793
/// * `version_sync_file_location: Option<String>` - The location for the version sync file. This is optional and may be `None`.
1894
/// * `github_mirror: Option<String>` - The GitHub mirror to use. This is optional and may be `None`.
1995
/// * `rollback_limit: Option<u8>` - The rollback limit. This is optional and may be `None`.
96+
/// * `add_neovim_binary_to_path: Option<bool>` - Tells bob whenever to add neovim proxy path to $PATH.
2097
///
2198
/// # Example
2299
///
@@ -29,60 +106,28 @@ use tokio::fs::{self};
29106
/// version_sync_file_location: Some("/path/to/version_sync_file".to_string()),
30107
/// github_mirror: Some("https://github.com".to_string()),
31108
/// rollback_limit: Some(5),
109+
/// rollback_limit: Some(true),
32110
/// };
33111
/// println!("The configuration is {:?}", config);
34112
/// ```
35-
#[derive(Serialize, Deserialize, Debug)]
113+
#[derive(Serialize, Deserialize, Debug, Clone)]
36114
pub struct Config {
115+
#[serde(skip_serializing_if = "Option::is_none")]
37116
pub enable_nightly_info: Option<bool>,
117+
#[serde(skip_serializing_if = "Option::is_none")]
38118
pub enable_release_build: Option<bool>,
119+
#[serde(skip_serializing_if = "Option::is_none")]
39120
pub downloads_location: Option<String>,
121+
#[serde(skip_serializing_if = "Option::is_none")]
40122
pub installation_location: Option<String>,
123+
#[serde(skip_serializing_if = "Option::is_none")]
41124
pub version_sync_file_location: Option<String>,
125+
#[serde(skip_serializing_if = "Option::is_none")]
42126
pub github_mirror: Option<String>,
127+
#[serde(skip_serializing_if = "Option::is_none")]
43128
pub rollback_limit: Option<u8>,
44-
}
45-
46-
/// Handles the application configuration.
47-
///
48-
/// This function reads the configuration file, which can be in either TOML or JSON format, and returns a `Config` object. If the configuration file does not exist, it returns a `Config` object with all fields set to `None`.
49-
///
50-
/// # Returns
51-
///
52-
/// * `Result<Config>` - Returns a `Result` that contains a `Config` object if the function completes successfully. If an error occurs, it returns `Err`.
53-
///
54-
/// # Example
55-
///
56-
/// ```rust
57-
/// let config = handle_config().await.unwrap();
58-
/// println!("The configuration is {:?}", config);
59-
/// ```
60-
pub async fn handle_config() -> Result<Config> {
61-
let config_file = crate::helpers::directories::get_config_file()?;
62-
let config = match fs::read_to_string(&config_file).await {
63-
Ok(config) => {
64-
if config_file.extension().unwrap() == "toml" {
65-
let mut config: Config = toml::from_str(&config)?;
66-
handle_envars(&mut config)?;
67-
config
68-
} else {
69-
let mut config: Config = serde_json::from_str(&config)?;
70-
handle_envars(&mut config)?;
71-
config
72-
}
73-
}
74-
Err(_) => Config {
75-
enable_nightly_info: None,
76-
enable_release_build: None,
77-
downloads_location: None,
78-
installation_location: None,
79-
version_sync_file_location: None,
80-
github_mirror: None,
81-
rollback_limit: None,
82-
},
83-
};
84-
85-
Ok(config)
129+
#[serde(skip_serializing_if = "Option::is_none")]
130+
pub add_neovim_binary_to_path: Option<bool>,
86131
}
87132

88133
/// Handles environment variables in the configuration.

src/handlers/erase_handler.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use anyhow::{anyhow, Result};
22
use tokio::fs;
33
use tracing::info;
44

5-
use crate::{config::Config, helpers::directories};
5+
use crate::{
6+
config::Config,
7+
helpers::directories::{self},
8+
};
69

710
/// Starts the erase process based on the provided `Config`.
811
///
@@ -74,7 +77,29 @@ pub async fn start(config: Config) -> Result<()> {
7477

7578
info!("Successfully removed neovim's installation PATH from registry");
7679
}
80+
} else {
81+
use what_the_path::shell::Shell;
82+
use crate::helpers::directories::get_downloads_directory;
7783

84+
let shell = Shell::detect_by_shell_var()?;
85+
86+
match shell {
87+
Shell::Fish(fish) => {
88+
let files = fish.get_rcfiles()?;
89+
let fish_file = files[0].join("bob.fish");
90+
if !fish_file.exists() { return Ok(()) }
91+
fs::remove_file(fish_file).await?;
92+
},
93+
shell => {
94+
let files = shell.get_rcfiles()?;
95+
let downloads_dir = get_downloads_directory(&config).await?;
96+
let env_path = downloads_dir.join("env/env.sh");
97+
let source_string = format!(". \"{}\"", env_path.display());
98+
for file in files {
99+
what_the_path::shell::remove_from_rcfile(file, &source_string)?;
100+
}
101+
}
102+
}
78103
}
79104
}
80105

src/handlers/install_handler.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::config::Config;
1+
use crate::config::{Config, ConfigFile};
22
use crate::github_requests::{get_commits_for_nightly, get_upstream_nightly, UpstreamVersion};
33
use crate::helpers::checksum::sha256cmp;
44
use crate::helpers::processes::handle_subprocess;
@@ -64,7 +64,7 @@ use super::{InstallResult, PostDownloadVersionType};
6464
pub async fn start(
6565
version: &mut ParsedVersion,
6666
client: &Client,
67-
config: &Config,
67+
config: &ConfigFile,
6868
) -> Result<InstallResult> {
6969
if version.version_type == VersionType::NightlyRollback {
7070
return Ok(InstallResult::GivenNightlyRollback);
@@ -76,13 +76,13 @@ pub async fn start(
7676
}
7777
}
7878

79-
let root = directories::get_downloads_directory(config).await?;
79+
let root = directories::get_downloads_directory(&config.config).await?;
8080

8181
env::set_current_dir(&root)?;
8282
let root = root.as_path();
8383

8484
let is_version_installed =
85-
helpers::version::is_version_installed(&version.tag_name, config).await?;
85+
helpers::version::is_version_installed(&version.tag_name, &config.config).await?;
8686

8787
if is_version_installed && version.version_type != VersionType::Nightly {
8888
return Ok(InstallResult::VersionAlreadyInstalled);
@@ -98,15 +98,15 @@ pub async fn start(
9898
info!("Looking for nightly updates");
9999

100100
let upstream_nightly = nightly_version.as_ref().unwrap();
101-
let local_nightly = helpers::version::nightly::get_local_nightly(config).await?;
101+
let local_nightly = helpers::version::nightly::get_local_nightly(&config.config).await?;
102102

103103
if upstream_nightly.published_at == local_nightly.published_at {
104104
return Ok(InstallResult::NightlyIsUpdated);
105105
}
106106

107-
handle_rollback(config).await?;
107+
handle_rollback(&config.config).await?;
108108

109-
match config.enable_nightly_info {
109+
match config.config.enable_nightly_info {
110110
Some(boolean) if boolean => {
111111
print_commits(client, &local_nightly, upstream_nightly).await?
112112
}
@@ -117,24 +117,25 @@ pub async fn start(
117117

118118
let downloaded_archive = match version.version_type {
119119
VersionType::Normal | VersionType::Latest => {
120-
download_version(client, version, root, config, false).await
120+
download_version(client, version, root, &config.config, false).await
121121
}
122122
VersionType::Nightly => {
123-
if config.enable_release_build == Some(true) {
124-
handle_building_from_source(version, config).await
123+
if config.config.enable_release_build == Some(true) {
124+
handle_building_from_source(version, &config.config).await
125125
} else {
126-
download_version(client, version, root, config, false).await
126+
download_version(client, version, root, &config.config, false).await
127127
}
128128
}
129-
VersionType::Hash => handle_building_from_source(version, config).await,
129+
VersionType::Hash => handle_building_from_source(version, &config.config).await,
130130
VersionType::NightlyRollback => Ok(PostDownloadVersionType::None),
131131
}?;
132132

133133
if let PostDownloadVersionType::Standard(downloaded_archive) = downloaded_archive {
134134
if version.semver.is_some() && version.semver.as_ref().unwrap() <= &Version::new(0, 4, 4) {
135135
unarchive::start(downloaded_archive).await?
136136
} else {
137-
let downloaded_checksum = download_version(client, version, root, config, true).await?;
137+
let downloaded_checksum =
138+
download_version(client, version, root, &config.config, true).await?;
138139

139140
if let PostDownloadVersionType::Standard(downloaded_checksum) = downloaded_checksum {
140141
let archive_path = root.join(format!(

0 commit comments

Comments
 (0)