···1111serde = { version = "1.0.219", features = ["serde_derive", "derive"] }
1212serde_yml = "0.0.12"
1313toml = "0.9.5"
1414+url = "2.5.6"
+8
README.md
···88888989You can customize it and run `oh-my-droid apply` to apply the changes.
90909191+## Remote Configuration
9292+9393+You can use a remote configuration file by specifying a git URL:
9494+9595+```bash
9696+oh-my-droid apply github:tsirysndr/pkgs
9797+```
9898+9199## License
9210093101This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
+96-6
src/cmd/setup.rs
···11-use std::path::Path;
11+use std::{fs, path::Path};
2233-use anyhow::Error;
33+use anyhow::{Context, Error};
44use owo_colors::OwoColorize;
55+use url::Url;
5666-use crate::{config::Configuration, consts::CONFIG_FILE, diff::compare_configurations};
77+use crate::{
88+ command::run_command, config::Configuration, consts::CONFIG_FILE, diff::compare_configurations,
99+};
71088-pub fn setup(dry_run: bool, no_confirm: bool) -> Result<(), Error> {
1111+pub fn setup(dry_run: bool, no_confirm: bool, config_path: &str) -> Result<(), Error> {
912 let mut cfg = Configuration::default();
10131111- if std::path::Path::new(CONFIG_FILE).exists() {
1212- let toml_str = std::fs::read_to_string(CONFIG_FILE)?;
1414+ let repo_url = parse_config_path(config_path)?;
1515+1616+ let toml_config = match clone_repo(&repo_url) {
1717+ Ok(toml_config) => toml_config,
1818+ Err(err) => {
1919+ if !repo_url.starts_with("https://") {
2020+ repo_url
2121+ } else {
2222+ return Err(err);
2323+ }
2424+ }
2525+ };
2626+2727+ if std::path::Path::new(&toml_config).exists() {
2828+ let toml_str = std::fs::read_to_string(&toml_config)?;
1329 cfg = toml::from_str(&toml_str)?;
3030+ }
3131+3232+ if toml_config != CONFIG_FILE && !std::path::Path::new(&toml_config).exists() {
3333+ return Err(anyhow::anyhow!(
3434+ "{} does not exist.",
3535+ toml_config.as_str().green()
3636+ ));
1437 }
15381639 let home_dir =
···63866487 Ok(())
6588}
8989+9090+fn parse_config_path(config_path: &str) -> Result<String, Error> {
9191+ if config_path.starts_with("github:") {
9292+ let repo = &config_path["github:".len()..];
9393+ return Ok(format!("https://github.com/{}.git", repo));
9494+ }
9595+9696+ if config_path.starts_with("tangled:") {
9797+ let repo = &config_path["tangled:".len()..];
9898+ return Ok(format!("https://tangled.sh/{}.git", repo));
9999+ }
100100+101101+ Ok(config_path.to_string())
102102+}
103103+104104+fn clone_repo(repo_url: &str) -> Result<String, Error> {
105105+ if !repo_url.starts_with("https://") {
106106+ return Err(anyhow::anyhow!(
107107+ "Unsupported repository URL. Only HTTPS URLs are supported."
108108+ ));
109109+ }
110110+111111+ // extract repo name: username-repo: e.g: https://github.com/tsirysndr/pkgs.git -> tsirysndr-pkgs
112112+ let repo_name = extract_repo_name(repo_url).context("Failed to extract repository name")?;
113113+ let home_dir = dirs::home_dir().context("Failed to get home directory")?;
114114+ let cache = home_dir.join(".oh-my-droid").join("cache");
115115+ fs::create_dir_all(&cache)?;
116116+ let dest = cache.join(repo_name);
117117+118118+ run_command(
119119+ "bash",
120120+ &["-c", "type git || (apt update && apt install -y git)"],
121121+ )?;
122122+123123+ match dest.exists() {
124124+ true => {
125125+ run_command("git", &["-C", dest.to_str().unwrap(), "pull"])?;
126126+ }
127127+ false => {
128128+ run_command(
129129+ "git",
130130+ &["clone", "--depth", "1", repo_url, dest.to_str().unwrap()],
131131+ )?;
132132+ }
133133+ }
134134+135135+ if !dest.join("oh-my-droid.toml").exists() {
136136+ return Err(anyhow::anyhow!(
137137+ "The repository does not contain an oh-my-droid.toml configuration file."
138138+ ));
139139+ }
140140+141141+ Ok(dest.join("oh-my-droid.toml").to_str().unwrap().to_string())
142142+}
143143+144144+fn extract_repo_name(url: &str) -> Option<String> {
145145+ let parsed = Url::parse(url).ok()?;
146146+ let mut segments = parsed.path_segments()?;
147147+ let username = segments.next()?;
148148+ let mut repo = segments.next()?;
149149+150150+ if let Some(stripped) = repo.strip_suffix(".git") {
151151+ repo = stripped;
152152+ }
153153+154154+ Some(format!("{}-{}", username, repo))
155155+}
+13-2
src/main.rs
···4343 .about("Set up the environment with the default configuration.")
4444 .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes."))
4545 .arg(arg!(-y --"yes" "Skip confirmation prompts during setup."))
4646+ .arg(
4747+ arg!([config] "Path to a custom configuration file or a remote git repository e.g., github:tsirysndr/pkgs")
4848+ .default_value(CONFIG_FILE),
4949+ )
4650 .alias("apply"),
4751 )
4852 .arg(arg!(-d --"dry-run" "Simulate the setup process without making any changes."))
4953 .arg(arg!(-y --"yes" "Skip confirmation prompts during setup."))
5454+ .arg(
5555+ arg!([config] "Path to a custom configuration file or a remote git repository e.g., github:tsirysndr/pkgs")
5656+ .default_value(CONFIG_FILE),
5757+ )
5858+ .after_help("If no subcommand is provided, the 'setup' command will be executed by default.")
5059}
51605261fn main() -> Result<(), Error> {
···5766 Some(("setup", args)) => {
5867 let yes = args.get_flag("yes");
5968 let dry_run = args.get_flag("dry-run");
6060- setup(dry_run, yes)?
6969+ let config = args.get_one::<String>("config").unwrap();
7070+ setup(dry_run, yes, config)?
6171 }
6272 _ => {
6373 let yes = matches.get_flag("yes");
6474 let dry_run = matches.get_flag("dry-run");
6565- setup(dry_run, yes)?
7575+ let config = matches.get_one::<String>("config").unwrap();
7676+ setup(dry_run, yes, config)?
6677 }
6778 }
6879