mirror of
https://github.com/tealdeer-rs/tealdeer.git
synced 2026-08-09 09:49:10 +02:00
Add updates.download_languages setting
This commit is contained in:
parent
a74b7120bd
commit
c741146db5
5 changed files with 125 additions and 53 deletions
|
|
@ -1,5 +1,7 @@
|
|||
# Section: \[updates\]
|
||||
|
||||
This config section contains settings related to updating the tealdeer cache.
|
||||
|
||||
## Automatic updates
|
||||
|
||||
Tealdeer can refresh the cache automatically when it is outdated. This
|
||||
|
|
@ -24,7 +26,23 @@ is set to `false`.
|
|||
auto_update = true
|
||||
auto_update_interval_hours = 24
|
||||
|
||||
### archive_source
|
||||
## Download configuration
|
||||
|
||||
### `download_languages`
|
||||
|
||||
The list of languages which should be downloaded when updating.
|
||||
If unspecified, the languages listed in the `search.languages` setting are used.
|
||||
Thus, this setting is the most useful to instruct tealdeer to download pages in additional languages that are not searched by default.
|
||||
Either way, the language used can be overwritten using the `--language` command line flag.
|
||||
|
||||
[search]
|
||||
languages = ["de", "en"]
|
||||
|
||||
[updates]
|
||||
# sometimes I like to read the Italian description
|
||||
download_languages = ["de", "en", "it"]
|
||||
|
||||
### `archive_source`
|
||||
|
||||
URL for the location of the tldr pages archive. By default the pages are
|
||||
fetched from the latest `tldr-pages/tldr` GitHub release.
|
||||
|
|
|
|||
39
src/cache.rs
39
src/cache.rs
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use log::debug;
|
||||
use log::{debug, info};
|
||||
use ureq::{
|
||||
http::StatusCode,
|
||||
tls::{RootCerts, TlsConfig, TlsProvider},
|
||||
|
|
@ -27,7 +27,8 @@ pub struct CacheConfig<'a> {
|
|||
pub pages_directory: &'a Path,
|
||||
pub custom_pages_directory: Option<&'a Path>,
|
||||
pub platforms: &'a [PlatformType],
|
||||
pub languages: &'a [Language<'a>],
|
||||
pub search_languages: &'a [Language<'a>],
|
||||
pub download_languages: &'a [Language<'a>],
|
||||
}
|
||||
|
||||
/// The directory backing this cache is checked to be populated at construction.
|
||||
|
|
@ -110,7 +111,7 @@ impl<'a> Cache<'a> {
|
|||
.filter(|path| path.is_file());
|
||||
|
||||
for &platform in self.config.platforms {
|
||||
for language in self.config.languages {
|
||||
for language in self.config.search_languages {
|
||||
let mut search_path = self.config.pages_directory.to_path_buf();
|
||||
search_path.push(language.directory_name());
|
||||
search_path.push(platform.directory_name());
|
||||
|
|
@ -159,7 +160,7 @@ impl<'a> Cache<'a> {
|
|||
};
|
||||
|
||||
let mut search_path = self.config.pages_directory.to_path_buf();
|
||||
for language in self.config.languages {
|
||||
for language in self.config.search_languages {
|
||||
search_path.push(language.directory_name());
|
||||
for platform in self.config.platforms {
|
||||
search_path.push(platform.directory_name());
|
||||
|
|
@ -206,15 +207,23 @@ impl<'a> Cache<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, archive_url: &str, tls_backend: TlsBackend) -> Result<()> {
|
||||
/// Download archives for the languages in `self.config().download_languages` and replace the
|
||||
/// pages directory with the newly downloaded pages. As not all languages might have pages
|
||||
/// available (for example, `en_US` instead of `en`), an iterator yielding all languages which
|
||||
/// were successfully downloaded is returned.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
archive_url: &str,
|
||||
tls_backend: TlsBackend,
|
||||
) -> Result<impl IntoIterator<Item = Language<'_>>> {
|
||||
let client = Self::build_client(tls_backend);
|
||||
|
||||
// Download everything before deleting anything
|
||||
let archives = self
|
||||
let mut archives = self
|
||||
.config
|
||||
.languages
|
||||
.download_languages
|
||||
.iter()
|
||||
.map(|lang| {
|
||||
.map(|&lang| {
|
||||
Ok((
|
||||
lang,
|
||||
Self::download(
|
||||
|
|
@ -236,16 +245,18 @@ impl<'a> Cache<'a> {
|
|||
fs::remove_dir_all(self.config.pages_directory)?;
|
||||
fs::create_dir(self.config.pages_directory)?;
|
||||
|
||||
for (lang, archive) in archives {
|
||||
if let Some(mut archive) = archive {
|
||||
debug!("Extracting archive for {lang:?}");
|
||||
for (lang, archive) in &mut archives {
|
||||
if let Some(archive) = archive {
|
||||
info!("Extracting archive for {lang:?}");
|
||||
archive.extract(self.config.pages_directory.join(lang.directory_name()))?;
|
||||
} else {
|
||||
debug!("No archive found for {lang:?}");
|
||||
info!("No archive found for {lang:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(archives
|
||||
.into_iter()
|
||||
.filter_map(|(lang, archive)| archive.is_some().then_some(lang)))
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &CacheConfig<'a> {
|
||||
|
|
@ -347,7 +358,7 @@ impl Cache<'_> {
|
|||
|
||||
/// Download the archive from the specified URL.
|
||||
fn download(client: &Agent, archive_url: &str) -> Result<Option<Vec<u8>>> {
|
||||
debug!("Downloading archive from {archive_url}");
|
||||
info!("Downloading archive from {archive_url}");
|
||||
let response = client.get(archive_url).call();
|
||||
match response {
|
||||
Ok(response) if response.status().is_success() => {
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@ struct RawUpdatesConfig {
|
|||
pub archive_source: String,
|
||||
#[serde(default)]
|
||||
pub tls_backend: RawTlsBackend,
|
||||
#[serde(default)]
|
||||
pub download_languages: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Default for RawUpdatesConfig {
|
||||
|
|
@ -199,41 +201,11 @@ impl Default for RawUpdatesConfig {
|
|||
auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS,
|
||||
archive_source: default_archive_source(),
|
||||
tls_backend: RawTlsBackend::default(),
|
||||
download_languages: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a RawUpdatesConfig> for UpdatesConfig<'a> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(raw_updates_config: &'a RawUpdatesConfig) -> Result<Self> {
|
||||
let tls_backend = match raw_updates_config.tls_backend {
|
||||
#[cfg(feature = "native-tls")]
|
||||
RawTlsBackend::NativeTls => TlsBackend::NativeTls,
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
RawTlsBackend::RustlsWithWebpkiRoots => TlsBackend::RustlsWithWebpkiRoots,
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
RawTlsBackend::RustlsWithNativeRoots => TlsBackend::RustlsWithNativeRoots,
|
||||
// when compiling without all TLS backend features, we want to handle config error.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => return Err(anyhow!(
|
||||
"Unsupported TLS backend: {}. This tealdeer build has support for the following options: {}",
|
||||
raw_updates_config.tls_backend,
|
||||
SUPPORTED_TLS_BACKENDS.iter().map(std::string::ToString::to_string).collect::<Vec<String>>().join(", ")
|
||||
))
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
auto_update: raw_updates_config.auto_update,
|
||||
auto_update_interval: Duration::from_secs(
|
||||
raw_updates_config.auto_update_interval_hours * 3600,
|
||||
),
|
||||
archive_source: &raw_updates_config.archive_source,
|
||||
tls_backend,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
struct RawDirectoriesConfig {
|
||||
#[serde(default)]
|
||||
|
|
@ -299,6 +271,7 @@ pub struct UpdatesConfig<'a> {
|
|||
pub auto_update_interval: Duration,
|
||||
pub archive_source: &'a str,
|
||||
pub tls_backend: TlsBackend,
|
||||
pub download_languages: Vec<Language<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -330,7 +303,7 @@ pub struct SearchConfig<'a> {
|
|||
pub languages: Vec<Language<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Language<'a>(pub &'a str);
|
||||
|
||||
fn get_languages<'a>(
|
||||
|
|
@ -410,6 +383,28 @@ pub enum TlsBackend {
|
|||
RustlsWithNativeRoots,
|
||||
}
|
||||
|
||||
impl TryFrom<RawTlsBackend> for TlsBackend {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(raw: RawTlsBackend) -> Result<Self, Self::Error> {
|
||||
match raw {
|
||||
#[cfg(feature = "native-tls")]
|
||||
RawTlsBackend::NativeTls => Ok(TlsBackend::NativeTls),
|
||||
#[cfg(feature = "rustls-with-webpki-roots")]
|
||||
RawTlsBackend::RustlsWithWebpkiRoots => Ok(TlsBackend::RustlsWithWebpkiRoots),
|
||||
#[cfg(feature = "rustls-with-native-roots")]
|
||||
RawTlsBackend::RustlsWithNativeRoots => Ok(TlsBackend::RustlsWithNativeRoots),
|
||||
// when compiling without all TLS backend features, we want to handle config error.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => Err(anyhow!(
|
||||
"Unsupported TLS backend: {}. This tealdeer build has support for the following options: {}",
|
||||
raw,
|
||||
SUPPORTED_TLS_BACKENDS.iter().map(std::string::ToString::to_string).collect::<Vec<String>>().join(", ")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Config<'a> {
|
||||
pub style: StyleConfig,
|
||||
|
|
@ -428,7 +423,7 @@ impl<'a> Config<'a> {
|
|||
fn from_raw(raw_config: &'a RawConfig, config_file_path: PathWithSource) -> Result<Self> {
|
||||
let style = (&raw_config.style).into();
|
||||
let display = (&raw_config.display).into();
|
||||
let updates = (&raw_config.updates).try_into()?;
|
||||
|
||||
let search = SearchConfig {
|
||||
languages: raw_config
|
||||
.search
|
||||
|
|
@ -439,6 +434,19 @@ impl<'a> Config<'a> {
|
|||
}),
|
||||
};
|
||||
|
||||
let updates = UpdatesConfig {
|
||||
auto_update: raw_config.updates.auto_update,
|
||||
auto_update_interval: Duration::from_secs(
|
||||
raw_config.updates.auto_update_interval_hours * 3600,
|
||||
),
|
||||
archive_source: &raw_config.updates.archive_source,
|
||||
tls_backend: raw_config.updates.tls_backend.try_into()?,
|
||||
download_languages: raw_config.updates.download_languages.as_ref().map_or_else(
|
||||
|| search.languages.clone(),
|
||||
|languages| languages.iter().map(|lang| Language(lang)).collect(),
|
||||
),
|
||||
};
|
||||
|
||||
let relative_path_root = config_file_path
|
||||
.path()
|
||||
.parent()
|
||||
|
|
|
|||
21
src/main.rs
21
src/main.rs
|
|
@ -82,11 +82,21 @@ fn update_cache(
|
|||
tls_backend: TlsBackend,
|
||||
quietly: bool,
|
||||
) -> Result<()> {
|
||||
cache
|
||||
let downloaded_languages = cache
|
||||
.update(archive_source, tls_backend)
|
||||
.context("Could not update cache")?;
|
||||
if !quietly {
|
||||
eprintln!("Successfully updated cache.");
|
||||
eprint!("Pages for the following languages were downloaded: ");
|
||||
let language_strings: Vec<_> = downloaded_languages
|
||||
.into_iter()
|
||||
.map(|lang| lang.0)
|
||||
.collect();
|
||||
if language_strings.is_empty() {
|
||||
eprintln!("(none)");
|
||||
} else {
|
||||
eprintln!("{}", language_strings.join(", "));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -250,9 +260,9 @@ fn try_main(args: Cli, enable_styles: bool) -> Result<ExitCode> {
|
|||
}
|
||||
|
||||
let platforms = compute_platforms(args.platforms.as_ref());
|
||||
let languages = match args.language.as_deref() {
|
||||
Some(lang) => &[Language(lang)] as &[_],
|
||||
None => &config.search.languages,
|
||||
let (search_languages, download_languages): (&[_], &[_]) = match args.language.as_deref() {
|
||||
Some(lang) => (&[Language(lang)], &[Language(lang)]),
|
||||
None => (&config.search.languages, &config.updates.download_languages),
|
||||
};
|
||||
|
||||
let cache_config = CacheConfig {
|
||||
|
|
@ -263,7 +273,8 @@ fn try_main(args: Cli, enable_styles: bool) -> Result<ExitCode> {
|
|||
.as_ref()
|
||||
.map(PathWithSource::path),
|
||||
platforms: &platforms,
|
||||
languages,
|
||||
search_languages,
|
||||
download_languages,
|
||||
};
|
||||
|
||||
// TODO: remove in tealdeer 1.9
|
||||
|
|
|
|||
24
tests/lib.rs
24
tests/lib.rs
|
|
@ -996,6 +996,30 @@ fn test_search_language_precedence() {
|
|||
run(env_cases);
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ignore-online-tests", ignore = "online test")]
|
||||
#[test]
|
||||
fn test_update_language_arg() {
|
||||
let testenv = TestEnv::new();
|
||||
testenv
|
||||
.command()
|
||||
.env("LANG", "it")
|
||||
.arg("--update")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(contains("it"))
|
||||
.stderr(contains("en"));
|
||||
|
||||
testenv
|
||||
.command()
|
||||
.env("LANG", "en")
|
||||
.args(["--language", "it"])
|
||||
.arg("--update")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(contains("it"))
|
||||
.stderr(contains("en").not());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_flag_rendering() {
|
||||
let testenv = TestEnv::new().write_custom_pages_config();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue