cli updater adaptation and clippy fixes

This commit is contained in:
Stefano Fontana 2024-12-15 12:35:06 +01:00
parent b5689cfc72
commit d687d23e5e
8 changed files with 159 additions and 108 deletions

View File

@ -21,7 +21,7 @@ doc = false
typst = { workspace = true } typst = { workspace = true }
typst-eval = { workspace = true } typst-eval = { workspace = true }
typst-html = { workspace = true } typst-html = { workspace = true }
typst-kit = { workspace = true } typst-kit = { workspace = true, features = ["downloads_http"] }
typst-macros = { workspace = true } typst-macros = { workspace = true }
typst-pdf = { workspace = true } typst-pdf = { workspace = true }
typst-render = { workspace = true } typst-render = { workspace = true }

View File

@ -7,12 +7,12 @@ use semver::Version;
use serde::Deserialize; use serde::Deserialize;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use typst::diag::{bail, StrResult}; use typst::diag::{bail, StrResult};
use typst_kit::download::Downloader; use typst_kit::package_downloads::http::HttpDownloader;
use xz2::bufread::XzDecoder; use xz2::bufread::XzDecoder;
use zip::ZipArchive; use zip::ZipArchive;
use crate::args::UpdateCommand; use crate::args::UpdateCommand;
use crate::download::{self, PrintDownload}; use crate::download::PrintDownload;
const TYPST_GITHUB_ORG: &str = "typst"; const TYPST_GITHUB_ORG: &str = "typst";
const TYPST_REPO: &str = "typst"; const TYPST_REPO: &str = "typst";
@ -91,7 +91,8 @@ pub fn update(command: &UpdateCommand) -> StrResult<()> {
fs::copy(current_exe, &backup_path) fs::copy(current_exe, &backup_path)
.map_err(|err| eco_format!("failed to create backup ({err})"))?; .map_err(|err| eco_format!("failed to create backup ({err})"))?;
let downloader = download::downloader(); //no certificate is needed to download from GitHub
let downloader = HttpDownloader::new(HttpDownloader::default_user_agent());
let release = Release::from_tag(command.version.as_ref(), &downloader)?; let release = Release::from_tag(command.version.as_ref(), &downloader)?;
if !update_needed(&release)? && !command.force { if !update_needed(&release)? && !command.force {
@ -133,7 +134,7 @@ impl Release {
/// Typst repository. /// Typst repository.
pub fn from_tag( pub fn from_tag(
tag: Option<&Version>, tag: Option<&Version>,
downloader: &Downloader, downloader: &HttpDownloader,
) -> StrResult<Release> { ) -> StrResult<Release> {
let url = match tag { let url = match tag {
Some(tag) => format!( Some(tag) => format!(
@ -144,7 +145,7 @@ impl Release {
), ),
}; };
match downloader.download(&url) { match downloader.perform_download(&url) {
Ok(response) => response.into_json().map_err(|err| { Ok(response) => response.into_json().map_err(|err| {
eco_format!("failed to parse release information ({err})") eco_format!("failed to parse release information ({err})")
}), }),
@ -161,7 +162,7 @@ impl Release {
pub fn download_binary( pub fn download_binary(
&self, &self,
asset_name: &str, asset_name: &str,
downloader: &Downloader, downloader: &HttpDownloader,
) -> StrResult<Vec<u8>> { ) -> StrResult<Vec<u8>> {
let asset = self.assets.iter().find(|a| a.name.starts_with(asset_name)).ok_or( let asset = self.assets.iter().find(|a| a.name.starts_with(asset_name)).ok_or(
eco_format!( eco_format!(

View File

@ -19,9 +19,9 @@
//! [download]. It is enabled by the `packages` feature flag and implies the //! [download]. It is enabled by the `packages` feature flag and implies the
//! `downloads` feature flag. //! `downloads` feature flag.
#[cfg(feature = "downloads")]
pub mod package_downloads;
#[cfg(feature = "fonts")] #[cfg(feature = "fonts")]
pub mod fonts; pub mod fonts;
#[cfg(feature = "packages")] #[cfg(feature = "packages")]
pub mod package; pub mod package;
#[cfg(feature = "downloads")]
pub mod package_downloads;

View File

@ -1,13 +1,13 @@
//! Download and unpack packages and package indices. //! Download and unpack packages and package indices.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::package_downloads::{Downloader, PackageDownloader, Progress};
use ecow::eco_format; use ecow::eco_format;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use typst_library::diag::{PackageError, PackageResult, StrResult}; use typst_library::diag::{PackageError, PackageResult, StrResult};
use typst_syntax::package::{ use typst_syntax::package::{
PackageInfo, PackageSpec, PackageVersion, VersionlessPackageSpec, PackageInfo, PackageSpec, PackageVersion, VersionlessPackageSpec,
}; };
use crate::package_downloads::{Downloader, PackageDownloader, Progress};
/// The default packages sub directory within the package and package cache paths. /// The default packages sub directory within the package and package cache paths.
pub const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages"; pub const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages";
@ -97,12 +97,12 @@ impl PackageStorage {
&self, &self,
spec: &VersionlessPackageSpec, spec: &VersionlessPackageSpec,
) -> StrResult<PackageVersion> { ) -> StrResult<PackageVersion> {
// Same logical flow as per package download. Check package path, then check online. // Same logical flow as per package download. Check package path, then check online.
// Do not check in the data directory because the latter is not intended for storage // Do not check in the data directory because the latter is not intended for storage
// of local packages. // of local packages.
let subdir = format!("{}/{}", spec.namespace, spec.name); let subdir = format!("{}/{}", spec.namespace, spec.name);
let res = self.package_path let res = self
.package_path
.iter() .iter()
.flat_map(|dir| std::fs::read_dir(dir.join(&subdir)).ok()) .flat_map(|dir| std::fs::read_dir(dir.join(&subdir)).ok())
.flatten() .flatten()
@ -124,7 +124,10 @@ impl PackageStorage {
} }
/// Download the package index. The result of this is cached for efficiency. /// Download the package index. The result of this is cached for efficiency.
pub fn download_index(&self, spec: &VersionlessPackageSpec) -> StrResult<&[PackageInfo]> { pub fn download_index(
&self,
spec: &VersionlessPackageSpec,
) -> StrResult<&[PackageInfo]> {
self.index self.index
.get_or_try_init(|| self.downloader.download_index(spec)) .get_or_try_init(|| self.downloader.download_index(spec))
.map(AsRef::as_ref) .map(AsRef::as_ref)
@ -147,8 +150,8 @@ impl PackageStorage {
} else { } else {
Err(PackageError::NotFound(spec.clone())) Err(PackageError::NotFound(spec.clone()))
} }
}, }
val => val val => val,
} }
} }
} }

View File

@ -1,16 +1,14 @@
use crate::package_downloads::{DownloadState, PackageDownloader, Progress};
use auth_git2::GitAuthenticator;
use ecow::{eco_format, EcoString};
use git2::build::RepoBuilder;
use git2::{FetchOptions, RemoteCallbacks};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::Path; use std::path::Path;
use std::time::Instant; use std::time::Instant;
use auth_git2::GitAuthenticator;
use crate::package_downloads::{
DownloadState, PackageDownloader, Progress,
};
use ecow::{eco_format, EcoString};
use typst_library::diag::{PackageError, PackageResult}; use typst_library::diag::{PackageError, PackageResult};
use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec}; use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec};
use git2::{FetchOptions, RemoteCallbacks};
use git2::build::RepoBuilder;
#[derive(Debug)] #[derive(Debug)]
pub struct GitDownloader; pub struct GitDownloader;
@ -31,38 +29,41 @@ impl GitDownloader {
eprintln!("{} {} {}", repo, tag, dest.display()); eprintln!("{} {} {}", repo, tag, dest.display());
let state = DownloadState{ let state = DownloadState {
content_len: None, content_len: None,
total_downloaded: 0, total_downloaded: 0,
bytes_per_second: VecDeque::from(vec![0; 5]), bytes_per_second: VecDeque::from(vec![0; 5]),
start_time: Instant::now(), start_time: Instant::now(),
}; };
let auth = GitAuthenticator::default(); let auth = GitAuthenticator::default();
let git_config = git2::Config::open_default().map_err(|err| {EcoString::from(format!("{:?}", err))})?; let git_config = git2::Config::open_default()
.map_err(|err| EcoString::from(format!("{err}")))?;
let mut fetch_options = FetchOptions::new(); let mut fetch_options = FetchOptions::new();
let mut remote_callbacks = RemoteCallbacks::new(); let mut remote_callbacks = RemoteCallbacks::new();
remote_callbacks.credentials(auth.credentials(&git_config)); remote_callbacks.credentials(auth.credentials(&git_config));
fetch_options fetch_options.remote_callbacks(remote_callbacks);
.remote_callbacks(remote_callbacks);
let repo = RepoBuilder::new() let repo = RepoBuilder::new()
.fetch_options(fetch_options) .fetch_options(fetch_options)
.clone(repo, dest).map_err(|err| {EcoString::from(format!("{:?}", err))})?; .clone(repo, dest)
.map_err(|err| EcoString::from(format!("{err}")))?;
let (object, reference) = repo let (object, reference) = repo
.revparse_ext(tag).map_err(|err| {EcoString::from(format!("{:?}", err))})?; .revparse_ext(tag)
repo.checkout_tree(&object, None).map_err(|err| {EcoString::from(format!("{:?}", err))})?; .map_err(|err| EcoString::from(format!("{err}")))?;
repo.checkout_tree(&object, None)
.map_err(|err| EcoString::from(format!("{err}")))?;
match reference { match reference {
// gref is an actual reference like branches or tags // gref is an actual reference like branches or tags
Some(gref) => repo.set_head(gref.name().unwrap()), Some(gref) => repo.set_head(gref.name().unwrap()),
// this is a commit, not a reference // this is a commit, not a reference
None => repo.set_head_detached(object.id()), None => repo.set_head_detached(object.id()),
}.map_err(|err| {EcoString::from(format!("{:?}", err))})?; }
.map_err(|err| EcoString::from(format!("{err}")))?;
progress.print_finish(&state); progress.print_finish(&state);
Ok(()) Ok(())
@ -78,33 +79,31 @@ impl GitDownloader {
/// v<major>.<minor>.<patch> /// v<major>.<minor>.<patch>
/// ///
/// For example, the package /// For example, the package
/// @git:git@github.com:typst/package:0.1.0 /// @git:git@github.com:typst/package:0.0
/// will result in the cloning of the repository git@github.com:typst/package.git /// will result in the cloning of the repository git@github.com:typst/package.git
/// and the checkout and detached head state at tag v0.1.0 /// and the checkout and detached head state at tag v0.1.0
/// ///
/// NOTE: no index download is possible. /// NOTE: no index download is possible.
fn parse_namespace(ns: &str, name: &str) -> Result<String, EcoString> { fn parse_namespace(ns: &str, name: &str) -> Result<String, EcoString> {
let mut parts = ns.splitn(2, ":"); let mut parts = ns.splitn(2, ":");
let schema = parts.next().ok_or_else(|| { let schema =
eco_format!("expected schema in {}", ns) parts.next().ok_or_else(|| eco_format!("expected schema in {}", ns))?;
})?; let repo = parts
let repo = parts.next().ok_or_else(|| { .next()
eco_format!("invalid package repo {}", ns) .ok_or_else(|| eco_format!("invalid package repo {}", ns))?;
})?;
if !schema.eq("git") { if !schema.eq("git") {
Err(eco_format!("invalid schema in {}", ns))? Err(eco_format!("invalid schema in {}", ns))?
} }
Ok(format!("{}/{}.git", repo, name)) Ok(format!("{repo}/{name}.git"))
} }
} }
impl PackageDownloader for GitDownloader { impl PackageDownloader for GitDownloader {
fn download_index( fn download_index(
&self, &self,
_spec: &VersionlessPackageSpec, _spec: &VersionlessPackageSpec,
) -> Result<Vec<PackageInfo>, EcoString> { ) -> Result<Vec<PackageInfo>, EcoString> {
Err(eco_format!("Downloading index is not supported for git repositories")) Err(eco_format!("Downloading index is not supported for git repositories"))
} }
@ -115,8 +114,10 @@ impl PackageDownloader for GitDownloader {
package_dir: &Path, package_dir: &Path,
progress: &mut dyn Progress, progress: &mut dyn Progress,
) -> PackageResult<()> { ) -> PackageResult<()> {
let repo = Self::parse_namespace(spec.namespace.as_str(), spec.name.as_str()).map_err(|x| PackageError::Other(Some(x)))?; let repo = Self::parse_namespace(spec.namespace.as_str(), spec.name.as_str())
.map_err(|x| PackageError::Other(Some(x)))?;
let tag = format!("v{}", spec.version); let tag = format!("v{}", spec.version);
self.download_with_progress(repo.as_str(), tag.as_str(), package_dir, progress).map_err(|x| PackageError::Other(Some(x))) self.download_with_progress(repo.as_str(), tag.as_str(), package_dir, progress)
.map_err(|x| PackageError::Other(Some(x)))
} }
} }

View File

@ -13,18 +13,19 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::package_downloads::{
DownloadState, PackageDownloader, Progress, DEFAULT_NAMESPACE,
};
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use native_tls::{Certificate, TlsConnector}; use native_tls::{Certificate, TlsConnector};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use ureq::Response;
use typst_library::diag::{bail, PackageError, PackageResult}; use typst_library::diag::{bail, PackageError, PackageResult};
use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec}; use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec};
use crate::package_downloads::{DownloadState, PackageDownloader, Progress, DEFAULT_NAMESPACE}; use ureq::Response;
/// The default Typst registry. /// The default Typst registry.
pub const DEFAULT_REGISTRY: &str = "https://packages.typst.org"; pub const DEFAULT_REGISTRY: &str = "https://packages.typst.org";
/// An implementation of [`Progress`] with no-op reporting, i.e., reporting /// An implementation of [`Progress`] with no-op reporting, i.e., reporting
/// events are swallowed. /// events are swallowed.
pub struct ProgressSink; pub struct ProgressSink;
@ -43,6 +44,10 @@ pub struct HttpDownloader {
} }
impl HttpDownloader { impl HttpDownloader {
pub fn default_user_agent() -> String {
format!("typst-kit/{}", env!("CARGO_PKG_VERSION"))
}
/// Crates a new downloader with the given user agent and no certificate. /// Crates a new downloader with the given user agent and no certificate.
pub fn new(user_agent: impl Into<EcoString>) -> Self { pub fn new(user_agent: impl Into<EcoString>) -> Self {
Self { Self {
@ -135,25 +140,24 @@ impl HttpDownloader {
/// @https:packages.typst.org:preview/package-name>:package-version /// @https:packages.typst.org:preview/package-name>:package-version
fn parse_namespace(ns: &str) -> Result<(String, String), EcoString> { fn parse_namespace(ns: &str) -> Result<(String, String), EcoString> {
if ns.eq(DEFAULT_NAMESPACE) { if ns.eq(DEFAULT_NAMESPACE) {
return Ok((DEFAULT_REGISTRY.to_string(), DEFAULT_NAMESPACE.to_string())) return Ok((DEFAULT_REGISTRY.to_string(), DEFAULT_NAMESPACE.to_string()));
} }
let mut parts = ns.splitn(3, ":"); let mut parts = ns.splitn(3, ":");
let schema = parts.next().ok_or_else(|| { let schema =
eco_format!("expected schema in {}", ns) parts.next().ok_or_else(|| eco_format!("expected schema in {}", ns))?;
})?; let registry = parts
let registry = parts.next().ok_or_else(|| { .next()
eco_format!("invalid package registry in namespace {}", ns) .ok_or_else(|| eco_format!("invalid package registry in namespace {}", ns))?;
})?; let ns = parts
let ns = parts.next().ok_or_else(|| { .next()
eco_format!("invalid package namespace in {}", ns) .ok_or_else(|| eco_format!("invalid package namespace in {}", ns))?;
})?;
if !schema.eq("http") && !schema.eq("https") { if !schema.eq("http") && !schema.eq("https") {
Err(eco_format!("invalid schema in {}", ns))? Err(eco_format!("invalid schema in {}", ns))?
} }
Ok((format!("{}://{}", schema, registry), ns.to_string())) Ok((format!("{schema}://{registry}"), ns.to_string()))
} }
} }
@ -267,15 +271,17 @@ impl<'p> RemoteReader<'p> {
} }
} }
impl PackageDownloader for HttpDownloader { impl PackageDownloader for HttpDownloader {
fn download_index(&self, spec: &VersionlessPackageSpec) -> Result<Vec<PackageInfo>, EcoString> { fn download_index(
&self,
spec: &VersionlessPackageSpec,
) -> Result<Vec<PackageInfo>, EcoString> {
let (registry, namespace) = Self::parse_namespace(spec.namespace.as_str())?; let (registry, namespace) = Self::parse_namespace(spec.namespace.as_str())?;
let url = format!("{registry}/{namespace}/index.json"); let url = format!("{registry}/{namespace}/index.json");
match self.perform_download(&url) { match self.perform_download(&url) {
Ok(response) => response.into_json().map_err(|err| { Ok(response) => response
eco_format!("failed to parse package index: {err}") .into_json()
}), .map_err(|err| eco_format!("failed to parse package index: {err}")),
Err(ureq::Error::Status(404, _)) => { Err(ureq::Error::Status(404, _)) => {
bail!("failed to fetch package index (not found)") bail!("failed to fetch package index (not found)")
} }
@ -283,21 +289,23 @@ impl PackageDownloader for HttpDownloader {
} }
} }
fn download(&self, spec: &PackageSpec, package_dir: &Path, progress: &mut dyn Progress) -> PackageResult<()> { fn download(
let (registry, namespace) = Self::parse_namespace(spec.namespace.as_str()).map_err(|x| PackageError::Other(Some(x)))?; &self,
spec: &PackageSpec,
package_dir: &Path,
progress: &mut dyn Progress,
) -> PackageResult<()> {
let (registry, namespace) = Self::parse_namespace(spec.namespace.as_str())
.map_err(|x| PackageError::Other(Some(x)))?;
let url = format!( let url =
"{}/{}/{}-{}.tar.gz", format!("{}/{}/{}-{}.tar.gz", registry, namespace, spec.name, spec.version);
registry, namespace, spec.name, spec.version
);
let data = match self.download_with_progress(&url, progress) { let data = match self.download_with_progress(&url, progress) {
Ok(data) => data, Ok(data) => data,
Err(ureq::Error::Status(404, _)) => { Err(ureq::Error::Status(404, _)) => {
Err(PackageError::NotFound(spec.clone()))? Err(PackageError::NotFound(spec.clone()))?
} }
Err(err) => { Err(err) => Err(PackageError::NetworkFailed(Some(eco_format!("{err}"))))?,
Err(PackageError::NetworkFailed(Some(eco_format!("{err}"))))?
}
}; };
let decompressed = flate2::read::GzDecoder::new(data.as_slice()); let decompressed = flate2::read::GzDecoder::new(data.as_slice());
@ -306,4 +314,4 @@ impl PackageDownloader for HttpDownloader {
PackageError::MalformedArchive(Some(eco_format!("{err}"))) PackageError::MalformedArchive(Some(eco_format!("{err}")))
}) })
} }
} }

View File

@ -1,33 +1,39 @@
use ecow::{eco_format, EcoString};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Instant; use std::time::Instant;
use ecow::{eco_format, EcoString};
use typst_library::diag::{PackageError, PackageResult}; use typst_library::diag::{PackageError, PackageResult};
use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec}; use typst_syntax::package::{PackageInfo, PackageSpec, VersionlessPackageSpec};
use crate::package_downloads::git::GitDownloader;
/// The public namespace in the default Typst registry. /// The public namespace in the default Typst registry.
pub const DEFAULT_NAMESPACE: &str = "preview"; pub const DEFAULT_NAMESPACE: &str = "preview";
/*========BEGIN DOWNLOAD METHODS DECLARATION=========*/ /*========BEGIN DOWNLOAD METHODS DECLARATION=========*/
#[cfg(feature = "downloads_http")] #[cfg(feature = "downloads_http")]
mod http; pub mod http;
#[cfg(feature = "downloads_git")] #[cfg(feature = "downloads_git")]
mod git; mod git;
/*========END DOWNLOAD METHODS DECLARATION===========*/ /*========END DOWNLOAD METHODS DECLARATION===========*/
/// Trait abstraction for package a downloader. /// Trait abstraction for package a downloader.
pub trait PackageDownloader : Debug + Sync + Send { pub trait PackageDownloader: Debug + Sync + Send {
/// Download the repository index and returns the /// Download the repository index and returns the
/// list of PackageInfo elements contained in it. /// list of PackageInfo elements contained in it.
fn download_index(&self, spec: &VersionlessPackageSpec) -> Result<Vec<PackageInfo>, EcoString>; fn download_index(
&self,
spec: &VersionlessPackageSpec,
) -> Result<Vec<PackageInfo>, EcoString>;
/// Download a package from a remote repository/registry /// Download a package from a remote repository/registry
/// and writes it in the file system cache directory /// and writes it in the file system cache directory
fn download(&self, spec: &PackageSpec, package_dir: &Path, progress: &mut dyn Progress) -> PackageResult<()>; fn download(
&self,
spec: &PackageSpec,
package_dir: &Path,
progress: &mut dyn Progress,
) -> PackageResult<()>;
} }
/// The current state of an in progress or finished download. /// The current state of an in progress or finished download.
@ -58,13 +64,12 @@ pub trait Progress {
/// The downloader object used for downloading packages /// The downloader object used for downloading packages
#[derive(Debug)] #[derive(Debug)]
pub struct Downloader{ pub struct Downloader {
///List of all available downloaders which can be instantiated at runtime ///List of all available downloaders which can be instantiated at runtime
http_downloader: Option<Box<dyn PackageDownloader>>, http_downloader: Option<Box<dyn PackageDownloader>>,
git_downloader: Option<Box<dyn PackageDownloader>>, git_downloader: Option<Box<dyn PackageDownloader>>,
} }
impl Downloader { impl Downloader {
/// Construct the Downloader object instantiating all the available methods. /// Construct the Downloader object instantiating all the available methods.
/// The methods can be compile-time selected by features. /// The methods can be compile-time selected by features.
@ -76,31 +81,57 @@ impl Downloader {
} }
/// Creation function for the HTTP(S) download method /// Creation function for the HTTP(S) download method
fn make_http_downloader(cert: Option<PathBuf>) -> Option<Box<dyn PackageDownloader>>{ fn make_http_downloader(cert: Option<PathBuf>) -> Option<Box<dyn PackageDownloader>> {
#[cfg(not(feature = "downloads_http"))] #[cfg(not(feature = "downloads_http"))]
{ None } {
None
}
#[cfg(feature = "downloads_http")] #[cfg(feature = "downloads_http")]
{ {
let user_agent = concat!("typst/", env!("CARGO_PKG_VERSION"));
match cert { match cert {
Some(cert_path) => Some(Box::new(http::HttpDownloader::with_path(user_agent, cert_path))), Some(cert_path) => Some(Box::new(http::HttpDownloader::with_path(
None => Some(Box::new(http::HttpDownloader::new(user_agent))), http::HttpDownloader::default_user_agent(),
cert_path,
))),
None => Some(Box::new(http::HttpDownloader::new(
http::HttpDownloader::default_user_agent(),
))),
} }
} }
} }
fn get_http_downloader(&self) -> Result<&dyn PackageDownloader, PackageError> {
let reference = self.http_downloader.as_ref().ok_or_else(|| {
PackageError::Other(Some(EcoString::from(
"Http downloader has not been initialized correctly",
)))
})?;
Ok(&**reference)
}
/// Creation function for the GIT clone method /// Creation function for the GIT clone method
fn make_git_downloader(_cert: Option<PathBuf>) -> Option<Box<dyn PackageDownloader>>{ fn make_git_downloader(_cert: Option<PathBuf>) -> Option<Box<dyn PackageDownloader>> {
#[cfg(not(feature = "downloads_git"))] #[cfg(not(feature = "downloads_git"))]
{ None } {
None
}
#[cfg(feature = "downloads_git")] #[cfg(feature = "downloads_git")]
{ {
Some(Box::new(GitDownloader::new())) Some(Box::new(git::GitDownloader::new()))
} }
} }
fn get_git_downloader(&self) -> Result<&dyn PackageDownloader, PackageError> {
let reference = self.git_downloader.as_ref().ok_or_else(|| {
PackageError::Other(Some(EcoString::from(
"Http downloader has not been initialized correctly",
)))
})?;
Ok(&**reference)
}
/// Returns the correct downloader in function of the package namespace. /// Returns the correct downloader in function of the package namespace.
/// The remote location of a package is encoded in its namespace in the form /// The remote location of a package is encoded in its namespace in the form
/// @<source type>:<source path> /// @<source type>:<source path>
@ -108,37 +139,43 @@ impl Downloader {
/// It's the downloader instance's job to parse the source path in any substructure. /// It's the downloader instance's job to parse the source path in any substructure.
/// ///
/// NOTE: Treating @preview as a special case of the https downloader. /// NOTE: Treating @preview as a special case of the https downloader.
fn get_downloader(&self, ns: &str) -> Result<&Box<dyn PackageDownloader>, PackageError> { fn get_downloader(&self, ns: &str) -> Result<&dyn PackageDownloader, PackageError> {
let download_type = ns.splitn(2, ":").next(); let download_type = ns.split(":").next();
match download_type { match download_type {
#[cfg(feature = "downloads_http")] #[cfg(feature = "downloads_http")]
Some("http") => self.http_downloader.as_ref().ok_or_else(|| PackageError::Other(Some(EcoString::from("Http downloader has not been initialized correctly")))), Some("http") | Some("https") | Some("preview") => self.get_http_downloader(),
#[cfg(feature = "downloads_http")]
Some("https") => self.http_downloader.as_ref().ok_or_else(|| PackageError::Other(Some(EcoString::from("Https downloader has not been initialized correctly")))),
#[cfg(feature = "downloads_http")]
Some("preview") => self.http_downloader.as_ref().ok_or_else(|| PackageError::Other(Some(EcoString::from("Https downloader has not been initialized correctly")))),
#[cfg(feature = "downloads_git")] #[cfg(feature = "downloads_git")]
Some("git") => self.git_downloader.as_ref().ok_or_else(|| PackageError::Other(Some(EcoString::from("Git downloader has not been initialized correctly")))), Some("git") => self.get_git_downloader(),
Some(dwld) => Err(PackageError::Other(Some(eco_format!("Unknown downloader type: {}", dwld)))), Some(dwld) => Err(PackageError::Other(Some(eco_format!(
None => Err(PackageError::Other(Some(EcoString::from("No downloader type specified")))), "Unknown downloader type: {}",
dwld
)))),
None => Err(PackageError::Other(Some(EcoString::from(
"No downloader type specified",
)))),
} }
} }
} }
impl PackageDownloader for Downloader { impl PackageDownloader for Downloader {
fn download_index(&self, spec: &VersionlessPackageSpec) -> Result<Vec<PackageInfo>, EcoString> { fn download_index(
&self,
spec: &VersionlessPackageSpec,
) -> Result<Vec<PackageInfo>, EcoString> {
let downloader = self.get_downloader(spec.namespace.as_str())?; let downloader = self.get_downloader(spec.namespace.as_str())?;
downloader.download_index(spec) downloader.download_index(spec)
} }
fn download(&self, spec: &PackageSpec, package_dir: &Path, progress: &mut dyn Progress) -> PackageResult<()> { fn download(
&self,
spec: &PackageSpec,
package_dir: &Path,
progress: &mut dyn Progress,
) -> PackageResult<()> {
let downloader = self.get_downloader(spec.namespace.as_str())?; let downloader = self.get_downloader(spec.namespace.as_str())?;
downloader.download(spec, package_dir, progress) downloader.download(spec, package_dir, progress)
} }
} }

View File

@ -264,17 +264,18 @@ impl Display for VersionlessPackageSpec {
} }
fn is_namespace_valid(namespace: &str) -> bool { fn is_namespace_valid(namespace: &str) -> bool {
if is_ident(namespace){ if is_ident(namespace) {
//standard namespace //standard namespace
return true return true;
} }
//if not ident, the namespace should be formed as @<package_remote_type>:<package_path> //if not ident, the namespace should be formed as @<package_remote_type>:<package_path>
let mut tokenized = namespace.splitn(2, ":"); let mut tokenized = namespace.splitn(2, ":");
//package type //package type
if tokenized.next().is_none_or(|x| !is_ident(x)) { let package_remote_type = tokenized.next();
return false if package_remote_type.is_none() || !is_ident(package_remote_type.unwrap()) {
return false;
} }
//the package_path parsing is left to the downloader implementation //the package_path parsing is left to the downloader implementation