diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bdb4fb..86902f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `Release::asset_for` now searches for current `OS` and `ARCH` inside `asset.name` if `target` failed to match - Update `reqwest` to `0.12.0` - Update `hyper` to `1.2.0` +- Support variable substitutions in `bin_path_in_archive` at runtime ### Removed ## [0.39.0] diff --git a/src/backends/gitea.rs b/src/backends/gitea.rs index 9fe1613..ef16a5d 100644 --- a/src/backends/gitea.rs +++ b/src/backends/gitea.rs @@ -232,7 +232,7 @@ pub struct UpdateBuilder { target: Option, bin_name: Option, bin_install_path: Option, - bin_path_in_archive: Option, + bin_path_in_archive: Option, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -300,10 +300,10 @@ impl UpdateBuilder { /// (see `std::env::consts::EXE_SUFFIX`) to the name if it's missing. pub fn bin_name(&mut self, name: &str) -> &mut Self { let raw_bin_name = format!("{}{}", name.trim_end_matches(EXE_SUFFIX), EXE_SUFFIX); - self.bin_name = Some(raw_bin_name.clone()); if self.bin_path_in_archive.is_none() { - self.bin_path_in_archive = Some(PathBuf::from(raw_bin_name)); + self.bin_path_in_archive = Some(raw_bin_name.clone()); } + self.bin_name = Some(raw_bin_name); self } @@ -321,13 +321,20 @@ impl UpdateBuilder { /// the path to the binary (from the root of the tarball) is not equal to just /// the `bin_name`. /// + /// This also supports variable paths: + /// - `{{ bin }}` is replaced with the value of `bin_name` + /// - `{{ target }}` is replaced with the value of `target` + /// - `{{ version }}` is replaced with the value of `target_version` if set, + /// otherwise the value of the latest available release version is used. + /// /// # Example /// - /// For a tarball `myapp.tar.gz` with the contents: + /// For a `myapp` binary with `windows` target and latest release version `1.2.3`, + /// the tarball `myapp.tar.gz` has the contents: /// /// ```shell /// myapp.tar/ - /// |------- bin/ + /// |------- windows-1.2.3-bin/ /// | |--- myapp # <-- executable /// ``` /// @@ -335,15 +342,15 @@ impl UpdateBuilder { /// /// ``` /// # use self_update::backends::gitea::Update; - /// # fn run() -> Result<(), Box< dyn ::std::error::Error>> { + /// # fn run() -> Result<(), Box<::std::error::Error>> { /// Update::configure() - /// .bin_path_in_archive("bin/myapp") + /// .bin_path_in_archive("{{ target }}-{{ version }}-bin/{{ bin }}") /// # .build()?; /// # Ok(()) /// # } /// ``` pub fn bin_path_in_archive(&mut self, bin_path: &str) -> &mut Self { - self.bin_path_in_archive = Some(PathBuf::from(bin_path)); + self.bin_path_in_archive = Some(bin_path.to_owned()); self } @@ -438,8 +445,8 @@ impl UpdateBuilder { bail!(Error::Config, "`bin_name` required") }, bin_install_path, - bin_path_in_archive: if let Some(ref path) = self.bin_path_in_archive { - path.to_owned() + bin_path_in_archive: if let Some(ref bin_path) = self.bin_path_in_archive { + bin_path.to_owned() } else { bail!(Error::Config, "`bin_path_in_archive` required") }, @@ -472,7 +479,7 @@ pub struct Update { target_version: Option, bin_name: String, bin_install_path: PathBuf, - bin_path_in_archive: PathBuf, + bin_path_in_archive: String, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -554,7 +561,7 @@ impl ReleaseUpdate for Update { self.bin_install_path.clone() } - fn bin_path_in_archive(&self) -> PathBuf { + fn bin_path_in_archive(&self) -> String { self.bin_path_in_archive.clone() } diff --git a/src/backends/github.rs b/src/backends/github.rs index 02cfd2b..ca05e20 100644 --- a/src/backends/github.rs +++ b/src/backends/github.rs @@ -234,7 +234,7 @@ pub struct UpdateBuilder { identifier: Option, bin_name: Option, bin_install_path: Option, - bin_path_in_archive: Option, + bin_path_in_archive: Option, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -313,10 +313,10 @@ impl UpdateBuilder { /// (see `std::env::consts::EXE_SUFFIX`) to the name if it's missing. pub fn bin_name(&mut self, name: &str) -> &mut Self { let raw_bin_name = format!("{}{}", name.trim_end_matches(EXE_SUFFIX), EXE_SUFFIX); - self.bin_name = Some(raw_bin_name.clone()); if self.bin_path_in_archive.is_none() { - self.bin_path_in_archive = Some(PathBuf::from(raw_bin_name)); + self.bin_path_in_archive = Some(raw_bin_name.clone()); } + self.bin_name = Some(raw_bin_name); self } @@ -334,13 +334,20 @@ impl UpdateBuilder { /// the path to the binary (from the root of the tarball) is not equal to just /// the `bin_name`. /// + /// This also supports variable paths: + /// - `{{ bin }}` is replaced with the value of `bin_name` + /// - `{{ target }}` is replaced with the value of `target` + /// - `{{ version }}` is replaced with the value of `target_version` if set, + /// otherwise the value of the latest available release version is used. + /// /// # Example /// - /// For a tarball `myapp.tar.gz` with the contents: + /// For a `myapp` binary with `windows` target and latest release version `1.2.3`, + /// the tarball `myapp.tar.gz` has the contents: /// /// ```shell /// myapp.tar/ - /// |------- bin/ + /// |------- windows-1.2.3-bin/ /// | |--- myapp # <-- executable /// ``` /// @@ -350,13 +357,13 @@ impl UpdateBuilder { /// # use self_update::backends::github::Update; /// # fn run() -> Result<(), Box<::std::error::Error>> { /// Update::configure() - /// .bin_path_in_archive("bin/myapp") + /// .bin_path_in_archive("{{ target }}-{{ version }}-bin/{{ bin }}") /// # .build()?; /// # Ok(()) /// # } /// ``` pub fn bin_path_in_archive(&mut self, bin_path: &str) -> &mut Self { - self.bin_path_in_archive = Some(PathBuf::from(bin_path)); + self.bin_path_in_archive = Some(bin_path.to_owned()); self } @@ -447,8 +454,8 @@ impl UpdateBuilder { bail!(Error::Config, "`bin_name` required") }, bin_install_path, - bin_path_in_archive: if let Some(ref path) = self.bin_path_in_archive { - path.to_owned() + bin_path_in_archive: if let Some(ref bin_path) = self.bin_path_in_archive { + bin_path.to_owned() } else { bail!(Error::Config, "`bin_path_in_archive` required") }, @@ -482,7 +489,7 @@ pub struct Update { target_version: Option, bin_name: String, bin_install_path: PathBuf, - bin_path_in_archive: PathBuf, + bin_path_in_archive: String, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -578,7 +585,7 @@ impl ReleaseUpdate for Update { self.bin_install_path.clone() } - fn bin_path_in_archive(&self) -> PathBuf { + fn bin_path_in_archive(&self) -> String { self.bin_path_in_archive.clone() } diff --git a/src/backends/gitlab.rs b/src/backends/gitlab.rs index 53f835a..4dbc2f0 100644 --- a/src/backends/gitlab.rs +++ b/src/backends/gitlab.rs @@ -229,7 +229,7 @@ pub struct UpdateBuilder { target: Option, bin_name: Option, bin_install_path: Option, - bin_path_in_archive: Option, + bin_path_in_archive: Option, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -297,10 +297,10 @@ impl UpdateBuilder { /// (see `std::env::consts::EXE_SUFFIX`) to the name if it's missing. pub fn bin_name(&mut self, name: &str) -> &mut Self { let raw_bin_name = format!("{}{}", name.trim_end_matches(EXE_SUFFIX), EXE_SUFFIX); - self.bin_name = Some(raw_bin_name.clone()); if self.bin_path_in_archive.is_none() { - self.bin_path_in_archive = Some(PathBuf::from(raw_bin_name)); + self.bin_path_in_archive = Some(raw_bin_name.to_owned()); } + self.bin_name = Some(raw_bin_name); self } @@ -318,13 +318,20 @@ impl UpdateBuilder { /// the path to the binary (from the root of the tarball) is not equal to just /// the `bin_name`. /// + /// This also supports variable paths: + /// - `{{ bin }}` is replaced with the value of `bin_name` + /// - `{{ target }}` is replaced with the value of `target` + /// - `{{ version }}` is replaced with the value of `target_version` if set, + /// otherwise the value of the latest available release version is used. + /// /// # Example /// - /// For a tarball `myapp.tar.gz` with the contents: + /// For a `myapp` binary with `windows` target and latest release version `1.2.3`, + /// the tarball `myapp.tar.gz` has the contents: /// /// ```shell /// myapp.tar/ - /// |------- bin/ + /// |------- windows-1.2.3-bin/ /// | |--- myapp # <-- executable /// ``` /// @@ -332,15 +339,15 @@ impl UpdateBuilder { /// /// ``` /// # use self_update::backends::gitlab::Update; - /// # fn run() -> Result<(), Box< dyn ::std::error::Error>> { + /// # fn run() -> Result<(), Box<::std::error::Error>> { /// Update::configure() - /// .bin_path_in_archive("bin/myapp") + /// .bin_path_in_archive("{{ target }}-{{ version }}-bin/{{ bin }}") /// # .build()?; /// # Ok(()) /// # } /// ``` pub fn bin_path_in_archive(&mut self, bin_path: &str) -> &mut Self { - self.bin_path_in_archive = Some(PathBuf::from(bin_path)); + self.bin_path_in_archive = Some(bin_path.to_owned()); self } @@ -431,8 +438,8 @@ impl UpdateBuilder { bail!(Error::Config, "`bin_name` required") }, bin_install_path, - bin_path_in_archive: if let Some(ref path) = self.bin_path_in_archive { - path.to_owned() + bin_path_in_archive: if let Some(ref bin_path) = self.bin_path_in_archive { + bin_path.to_owned() } else { bail!(Error::Config, "`bin_path_in_archive` required") }, @@ -465,7 +472,7 @@ pub struct Update { target_version: Option, bin_name: String, bin_install_path: PathBuf, - bin_path_in_archive: PathBuf, + bin_path_in_archive: String, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -552,7 +559,7 @@ impl ReleaseUpdate for Update { self.bin_install_path.clone() } - fn bin_path_in_archive(&self) -> PathBuf { + fn bin_path_in_archive(&self) -> String { self.bin_path_in_archive.clone() } diff --git a/src/backends/s3.rs b/src/backends/s3.rs index 9084226..9951b99 100644 --- a/src/backends/s3.rs +++ b/src/backends/s3.rs @@ -144,7 +144,7 @@ pub struct UpdateBuilder { region: Option, bin_name: Option, bin_install_path: Option, - bin_path_in_archive: Option, + bin_path_in_archive: Option, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -241,10 +241,10 @@ impl UpdateBuilder { /// Set the exe's name. Also sets `bin_path_in_archive` if it hasn't already been set. pub fn bin_name(&mut self, name: &str) -> &mut Self { let raw_bin_name = format!("{}{}", name.trim_end_matches(EXE_SUFFIX), EXE_SUFFIX); - self.bin_name = Some(raw_bin_name.clone()); if self.bin_path_in_archive.is_none() { - self.bin_path_in_archive = Some(PathBuf::from(raw_bin_name)); + self.bin_path_in_archive = Some(raw_bin_name.to_owned()); } + self.bin_name = Some(raw_bin_name); self } @@ -262,13 +262,20 @@ impl UpdateBuilder { /// the path to the binary (from the root of the tarball) is not equal to just /// the `bin_name`. /// + /// This also supports variable paths: + /// - `{{ bin }}` is replaced with the value of `bin_name` + /// - `{{ target }}` is replaced with the value of `target` + /// - `{{ version }}` is replaced with the value of `target_version` if set, + /// otherwise the value of the latest available release version is used. + /// /// # Example /// - /// For a tarball `myapp.tar.gz` with the contents: + /// For a `myapp` binary with `windows` target and latest release version `1.2.3`, + /// the tarball `myapp.tar.gz` has the contents: /// /// ```shell /// myapp.tar/ - /// |------- bin/ + /// |------- windows-1.2.3-bin/ /// | |--- myapp # <-- executable /// ``` /// @@ -278,13 +285,13 @@ impl UpdateBuilder { /// # use self_update::backends::s3::Update; /// # fn run() -> Result<(), Box<::std::error::Error>> { /// Update::configure() - /// .bin_path_in_archive("bin/myapp") + /// .bin_path_in_archive("{{ target }}-{{ version }}-bin/{{ bin }}") /// # .build()?; /// # Ok(()) /// # } /// ``` pub fn bin_path_in_archive(&mut self, bin_path: &str) -> &mut Self { - self.bin_path_in_archive = Some(PathBuf::from(bin_path)); + self.bin_path_in_archive = Some(bin_path.to_owned()); self } @@ -366,8 +373,8 @@ impl UpdateBuilder { bail!(Error::Config, "`bin_name` required") }, bin_install_path, - bin_path_in_archive: if let Some(ref path) = self.bin_path_in_archive { - path.to_owned() + bin_path_in_archive: if let Some(ref bin_path) = self.bin_path_in_archive { + bin_path.to_owned() } else { bail!(Error::Config, "`bin_path_in_archive` required") }, @@ -401,7 +408,7 @@ pub struct Update { target_version: Option, bin_name: String, bin_install_path: PathBuf, - bin_path_in_archive: PathBuf, + bin_path_in_archive: String, show_download_progress: bool, show_output: bool, no_confirm: bool, @@ -487,7 +494,7 @@ impl ReleaseUpdate for Update { self.bin_install_path.clone() } - fn bin_path_in_archive(&self) -> PathBuf { + fn bin_path_in_archive(&self) -> String { self.bin_path_in_archive.clone() } diff --git a/src/update.rs b/src/update.rs index 103c3d5..3d0cf17 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,4 +1,6 @@ +use regex::Regex; use reqwest::{self, header}; +use std::borrow::Cow; use std::env::consts::{ARCH, OS}; use std::fs; use std::path::PathBuf; @@ -103,7 +105,7 @@ pub trait ReleaseUpdate { fn bin_install_path(&self) -> PathBuf; /// Path of the binary to be extracted from release package - fn bin_path_in_archive(&self) -> PathBuf; + fn bin_path_in_archive(&self) -> String; /// Flag indicating if progress information shall be output when downloading a release fn show_download_progress(&self) -> bool; @@ -242,10 +244,25 @@ pub trait ReleaseUpdate { verify_signature(&tmp_archive_path, self.verifying_keys())?; print_flush(show_output, "Extracting archive... ")?; - let bin_path_in_archive = self.bin_path_in_archive(); + + let bin_path_str = Cow::Owned(self.bin_path_in_archive()); + + /// Substitute the `var` variable in a string with the given `val` value. + /// + /// Variable format: `{{ var }}` + fn substitute<'a: 'b, 'b>(str: &'a str, var: &str, val: &str) -> Cow<'b, str> { + let format = format!(r"\{{\{{[[:space:]]*{}[[:space:]]*\}}\}}", var); + Regex::new(&format).unwrap().replace_all(str, val) + } + + let bin_path_str = substitute(&bin_path_str, "version", &release.version); + let bin_path_str = substitute(&bin_path_str, "target", &target); + let bin_path_str = substitute(&bin_path_str, "bin", &bin_name); + let bin_path_str = bin_path_str.as_ref(); + Extract::from_source(&tmp_archive_path) - .extract_file(tmp_archive_dir.path(), &bin_path_in_archive)?; - let new_exe = tmp_archive_dir.path().join(&bin_path_in_archive); + .extract_file(tmp_archive_dir.path(), bin_path_str)?; + let new_exe = tmp_archive_dir.path().join(bin_path_str); println(show_output, "Done");