chore: initial sanitized public snapshot
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "aria2-rust-pro-compat"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description.workspace = true
|
||||
readme.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "aria2_rust_pro_compat"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,137 @@
|
||||
//! Compatibility inventory and baseline ledger helpers.
|
||||
|
||||
use crate::options::{OPTION_SPECS, OptionFamily, OptionSource, OptionStatus};
|
||||
|
||||
/// Protocols that the compat layer treats as part of the required surface.
|
||||
pub const REQUIRED_PROTOCOLS: &[&str] = &[
|
||||
"cli",
|
||||
"config",
|
||||
"json-rpc",
|
||||
"xml-rpc",
|
||||
"http",
|
||||
"https",
|
||||
"ftp",
|
||||
"sftp",
|
||||
"metalink",
|
||||
"bittorrent",
|
||||
"magnet",
|
||||
"docker",
|
||||
];
|
||||
|
||||
/// Compatibility depth for a surfaced feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum CompatLevel {
|
||||
/// The feature exists at the API or metadata layer.
|
||||
Surface,
|
||||
/// The feature is expected to match legacy behavior.
|
||||
Behavioral,
|
||||
/// The feature is expected to be effectively identical to the baseline.
|
||||
StrictEquivalent,
|
||||
}
|
||||
/// Top-level feature areas tracked by the compat ledger.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum FeatureSurface {
|
||||
/// Command-line and config option handling.
|
||||
Option,
|
||||
/// Config-file loading and normalization.
|
||||
Config,
|
||||
/// RPC method and field compatibility.
|
||||
Rpc,
|
||||
/// BitTorrent-related surface area.
|
||||
Bt,
|
||||
/// Metalink-related surface area.
|
||||
Metalink,
|
||||
/// Session import or export semantics.
|
||||
Session,
|
||||
/// Input-file parsing and expansion behavior.
|
||||
InputFile,
|
||||
/// Error reporting and mapping behavior.
|
||||
ErrorMap,
|
||||
/// Help text and user-facing documentation outputs.
|
||||
HelpText,
|
||||
}
|
||||
|
||||
/// One row in the compatibility inventory.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct CompatLedgerEntry {
|
||||
/// Canonical item name.
|
||||
pub name: &'static str,
|
||||
/// Feature area where the item belongs.
|
||||
pub surface: FeatureSurface,
|
||||
/// Expected depth of compatibility for the item.
|
||||
pub level: CompatLevel,
|
||||
/// Current implementation status for the item.
|
||||
pub status: OptionStatus,
|
||||
/// Short human-readable note describing the item.
|
||||
pub note: &'static str,
|
||||
}
|
||||
|
||||
/// Free-form compatibility note attached to the ledger.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct CompatNote {
|
||||
/// Stable identifier for the note.
|
||||
pub id: &'static str,
|
||||
/// User-facing note text.
|
||||
pub text: &'static str,
|
||||
}
|
||||
/// Full compatibility ledger snapshot.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct CompatLedger {
|
||||
/// Itemized compatibility entries.
|
||||
pub entries: Vec<CompatLedgerEntry>,
|
||||
/// Additional notes that summarize current caveats.
|
||||
pub notes: Vec<CompatNote>,
|
||||
}
|
||||
|
||||
/// Returns the static compatibility notes for the current rewrite snapshot.
|
||||
#[must_use]
|
||||
pub fn compatibility_notes() -> Vec<CompatNote> {
|
||||
vec![
|
||||
CompatNote {
|
||||
id: "rapid-rewrite",
|
||||
text: "Public compatibility surfaces are scaffolded for follow-up behavioral parity.",
|
||||
},
|
||||
CompatNote {
|
||||
id: "bt-options",
|
||||
text: "BT option family exists with metadata and parser stubs.",
|
||||
},
|
||||
CompatNote {
|
||||
id: "metalink-options",
|
||||
text: "Metalink option family exists with metadata and parser stubs.",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/// Builds the current compatibility ledger from the option registry.
|
||||
#[must_use]
|
||||
pub fn compat_ledger() -> CompatLedger {
|
||||
let mut entries = Vec::new();
|
||||
for spec in OPTION_SPECS.iter() {
|
||||
let surface = match spec.metadata.family {
|
||||
OptionFamily::Bt => FeatureSurface::Bt,
|
||||
OptionFamily::Metalink => FeatureSurface::Metalink,
|
||||
OptionFamily::Rpc => FeatureSurface::Rpc,
|
||||
OptionFamily::Session => FeatureSurface::Session,
|
||||
OptionFamily::Input => FeatureSurface::InputFile,
|
||||
_ => FeatureSurface::Option,
|
||||
};
|
||||
let level = match spec.source {
|
||||
OptionSource::Original => CompatLevel::Behavioral,
|
||||
OptionSource::Pro
|
||||
| OptionSource::CompatibilityAlias
|
||||
| OptionSource::RpcAlias
|
||||
| OptionSource::Experimental => CompatLevel::Surface,
|
||||
};
|
||||
entries.push(CompatLedgerEntry {
|
||||
name: spec.name,
|
||||
surface,
|
||||
level,
|
||||
status: spec.status,
|
||||
note: spec.metadata.compatibility_note,
|
||||
});
|
||||
}
|
||||
CompatLedger {
|
||||
entries,
|
||||
notes: compatibility_notes(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
//! Config parsing and normalization helpers for aria2-style option files.
|
||||
|
||||
use crate::options::{OptionScope, option_spec, reserved_option_names};
|
||||
|
||||
/// Where a compat config document originated.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ConfigLocationKind {
|
||||
/// The document was loaded from a file on disk.
|
||||
File,
|
||||
/// The document was provided inline as raw text.
|
||||
Inline,
|
||||
/// The document originated from an RPC payload.
|
||||
Rpc,
|
||||
/// The document originated from environment variables.
|
||||
Env,
|
||||
/// The document originated from CLI arguments.
|
||||
Cli,
|
||||
}
|
||||
/// Scope inferred for a config document or directive set.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ConfigScope {
|
||||
/// Only global options are expected.
|
||||
Global,
|
||||
/// Only per-download options are expected.
|
||||
PerDownload,
|
||||
/// The document can contain both global and per-download options.
|
||||
Mixed,
|
||||
}
|
||||
/// Source category for parsed config data.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ConfigSource {
|
||||
/// User-authored config file such as `aria2.conf`.
|
||||
UserConfig,
|
||||
/// Persisted session file content.
|
||||
SessionFile,
|
||||
/// Input-file content that expands downloads.
|
||||
InputFile,
|
||||
/// Runtime overrides originating from transient inputs.
|
||||
RuntimeOverride,
|
||||
/// Named profile content.
|
||||
Profile,
|
||||
}
|
||||
|
||||
/// A single `name=value` config directive.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ConfigDirective {
|
||||
/// Directive name after canonicalization.
|
||||
pub name: String,
|
||||
/// Optional directive value.
|
||||
pub value: Option<String>,
|
||||
}
|
||||
/// Parsed config directives with source metadata.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ConfigAst {
|
||||
/// Ordered directives found in the source input.
|
||||
pub directives: Vec<ConfigDirective>,
|
||||
/// Origin category for the parsed directives.
|
||||
pub source: ConfigSource,
|
||||
}
|
||||
/// Parsed config document with location metadata.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ConfigDocument {
|
||||
/// Ordered directives found in the document.
|
||||
pub directives: Vec<ConfigDirective>,
|
||||
/// Where the document was loaded from.
|
||||
pub location: ConfigLocationKind,
|
||||
/// Scope classification for the document.
|
||||
pub scope: ConfigScope,
|
||||
}
|
||||
/// Session save/load compatibility metadata.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SessionFileModel {
|
||||
/// Path where the session file is stored.
|
||||
pub session_path: String,
|
||||
/// Optional input file associated with the session.
|
||||
pub input_path: Option<String>,
|
||||
/// Autosave interval for session persistence.
|
||||
pub autosave_interval_secs: u64,
|
||||
}
|
||||
/// Named profile loaded through compat config handling.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ConfigProfile {
|
||||
/// Profile name.
|
||||
pub name: String,
|
||||
/// Source category for the profile.
|
||||
pub source: ConfigSource,
|
||||
/// Parsed profile document.
|
||||
pub document: ConfigDocument,
|
||||
}
|
||||
|
||||
/// Errors produced while parsing compat config input.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ConfigParseError {
|
||||
/// A line could not be parsed as a valid directive.
|
||||
InvalidDirective(String),
|
||||
/// A directive that requires a value omitted one.
|
||||
MissingValue(String),
|
||||
/// A directive named an unknown option.
|
||||
UnknownOption(String),
|
||||
/// A directive used an option name reserved for internal use.
|
||||
ReservedOption(String),
|
||||
}
|
||||
impl std::fmt::Display for ConfigParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::InvalidDirective(line) => write!(f, "invalid config directive: {line}"),
|
||||
Self::MissingValue(name) => write!(f, "missing value for option: {name}"),
|
||||
Self::UnknownOption(name) => write!(f, "unknown option: {name}"),
|
||||
Self::ReservedOption(name) => write!(f, "reserved option name: {name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ConfigParseError {}
|
||||
|
||||
/// Canonicalizes a config option name through the option registry.
|
||||
fn canonicalize_option_name(name: &str) -> String {
|
||||
let name = normalize_option_token(name);
|
||||
option_spec(name).map_or_else(|| name.to_owned(), |spec| spec.name.to_owned())
|
||||
}
|
||||
|
||||
/// Removes a UTF-8 byte-order mark when one prefixes a text fragment.
|
||||
fn strip_utf8_bom(text: &str) -> &str {
|
||||
text.strip_prefix('\u{feff}').unwrap_or(text)
|
||||
}
|
||||
|
||||
/// Normalizes a CLI- or config-style option token to its raw name form.
|
||||
fn normalize_option_token(name: &str) -> &str {
|
||||
let name = strip_utf8_bom(name.trim());
|
||||
name.strip_prefix("--").unwrap_or_else(|| {
|
||||
name.strip_prefix('-')
|
||||
.filter(|value| !value.is_empty())
|
||||
.map_or(name, |name| name)
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes trailing comments that are safely separated from a value.
|
||||
fn strip_safe_trailing_comment(value: &str) -> &str {
|
||||
for (index, ch) in value.char_indices() {
|
||||
let Some(prefix) = value.get(..index) else {
|
||||
continue;
|
||||
};
|
||||
if (ch == '#' || ch == ';') && prefix.chars().next_back().is_some_and(char::is_whitespace) {
|
||||
return prefix.trim_end();
|
||||
}
|
||||
}
|
||||
value.trim()
|
||||
}
|
||||
|
||||
/// Parses a single aria2-style config line, ignoring blank lines and comments.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ConfigParseError`] when the line is malformed or omits a required value.
|
||||
pub fn parse_config_line(line: &str) -> Result<Option<ConfigDirective>, ConfigParseError> {
|
||||
let trimmed = strip_utf8_bom(line).trim();
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with(';') {
|
||||
return Ok(None);
|
||||
}
|
||||
let (name, value) = trimmed
|
||||
.split_once('=')
|
||||
.ok_or_else(|| ConfigParseError::InvalidDirective(trimmed.to_owned()))?;
|
||||
let name = normalize_option_token(name);
|
||||
if name.is_empty() {
|
||||
return Err(ConfigParseError::InvalidDirective(trimmed.to_owned()));
|
||||
}
|
||||
let value = strip_safe_trailing_comment(value);
|
||||
if value.is_empty() {
|
||||
return Err(ConfigParseError::MissingValue(name.to_owned()));
|
||||
}
|
||||
Ok(Some(ConfigDirective {
|
||||
name: canonicalize_option_name(name),
|
||||
value: Some(value.to_owned()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parses a single config line and enforces the known-option / non-reserved subset.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ConfigParseError`] when the line is malformed, reserved, or names an unknown option.
|
||||
pub fn parse_config_line_strict(line: &str) -> Result<Option<ConfigDirective>, ConfigParseError> {
|
||||
let directive = parse_config_line(line)?;
|
||||
if let Some(ref d) = directive {
|
||||
if reserved_option_names().iter().any(|r| r.name == d.name) {
|
||||
return Err(ConfigParseError::ReservedOption(d.name.clone()));
|
||||
}
|
||||
if option_spec(d.name.as_str()).is_none() {
|
||||
return Err(ConfigParseError::UnknownOption(d.name.clone()));
|
||||
}
|
||||
}
|
||||
Ok(directive)
|
||||
}
|
||||
|
||||
/// Parses a full config document using strict option validation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ConfigParseError`] when any non-comment line is malformed, reserved, or unknown.
|
||||
pub fn parse_config(text: &str) -> Result<Vec<ConfigDirective>, ConfigParseError> {
|
||||
let mut directives = Vec::new();
|
||||
for line in text.lines() {
|
||||
if let Some(d) = parse_config_line_strict(line)? {
|
||||
directives.push(d);
|
||||
}
|
||||
}
|
||||
Ok(directives)
|
||||
}
|
||||
/// Parses a full config document while tolerating unknown options.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ConfigParseError`] when any non-comment line is malformed or omits a required value.
|
||||
pub fn parse_config_lenient(text: &str) -> Result<Vec<ConfigDirective>, ConfigParseError> {
|
||||
let mut directives = Vec::new();
|
||||
for line in text.lines() {
|
||||
if let Some(d) = parse_config_line(line)? {
|
||||
directives.push(d);
|
||||
}
|
||||
}
|
||||
Ok(directives)
|
||||
}
|
||||
|
||||
/// Infers config scope for a named option when the option is known.
|
||||
#[must_use]
|
||||
pub fn infer_scope(name: &str) -> Option<ConfigScope> {
|
||||
option_spec(name).map(|s| match s.metadata.scope {
|
||||
OptionScope::Global => ConfigScope::Global,
|
||||
OptionScope::PerDownload => ConfigScope::PerDownload,
|
||||
OptionScope::Both => ConfigScope::Mixed,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a document whose directive names have been canonicalized.
|
||||
#[must_use]
|
||||
pub fn normalize_document(mut document: ConfigDocument) -> ConfigDocument {
|
||||
for directive in &mut document.directives {
|
||||
directive.name = canonicalize_option_name(&directive.name);
|
||||
}
|
||||
document
|
||||
}
|
||||
|
||||
/// Loads an inline named profile using lenient parsing semantics.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ConfigParseError`] when the profile text contains malformed directives.
|
||||
pub fn load_profile(name: &str, text: &str) -> Result<ConfigProfile, ConfigParseError> {
|
||||
let directives = parse_config_lenient(text)?;
|
||||
Ok(ConfigProfile {
|
||||
name: name.to_owned(),
|
||||
source: ConfigSource::Profile,
|
||||
document: ConfigDocument {
|
||||
directives,
|
||||
location: ConfigLocationKind::Inline,
|
||||
scope: ConfigScope::Mixed,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
ConfigDirective, ConfigDocument, ConfigLocationKind, ConfigParseError, ConfigScope,
|
||||
ConfigSource, load_profile, normalize_document, parse_config_line,
|
||||
parse_config_line_strict,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn parse_config_line_strict_canonicalizes_known_alias_names() {
|
||||
let directive = parse_config_line_strict("http-want-digest=true")
|
||||
.expect("strict alias parse should succeed")
|
||||
.expect("directive should be present");
|
||||
|
||||
assert_eq!(directive.name, "no-want-digest-header");
|
||||
assert_eq!(directive.value.as_deref(), Some("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_line_lenient_canonicalizes_known_alias_names() {
|
||||
let directive = parse_config_line("http-want-digest=true")
|
||||
.expect("lenient alias parse should succeed")
|
||||
.expect("directive should be present");
|
||||
|
||||
assert_eq!(directive.name, "no-want-digest-header");
|
||||
assert_eq!(directive.value.as_deref(), Some("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_profile_keeps_profile_metadata_while_canonicalizing_known_aliases() {
|
||||
let profile = load_profile(
|
||||
"demo",
|
||||
"# comment\nhttp-want-digest=true\nrpc-listen-all=true\n",
|
||||
)
|
||||
.expect("profile should load");
|
||||
|
||||
assert_eq!(profile.name, "demo");
|
||||
assert_eq!(profile.source, ConfigSource::Profile);
|
||||
assert_eq!(profile.document.location, ConfigLocationKind::Inline);
|
||||
assert_eq!(profile.document.scope, ConfigScope::Mixed);
|
||||
let [first, second] = profile.document.directives.as_slice() else {
|
||||
panic!("profile should contain exactly two directives");
|
||||
};
|
||||
assert_eq!(first.name, "no-want-digest-header");
|
||||
assert_eq!(second.name, "rpc-listen-all");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_line_strict_still_rejects_unknown_options() {
|
||||
let error = parse_config_line_strict("not-a-real-option=true")
|
||||
.expect_err("unknown option should still be rejected");
|
||||
|
||||
assert_eq!(
|
||||
error,
|
||||
ConfigParseError::UnknownOption("not-a-real-option".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_line_accepts_bom_and_cli_style_option_spellings() {
|
||||
let parameterized = parse_config_line_strict("\u{feff}--parameterized-uri=true")
|
||||
.expect("long CLI spelling should parse")
|
||||
.expect("directive should be present");
|
||||
assert_eq!(parameterized.name, "parameterized-uri");
|
||||
assert_eq!(parameterized.value.as_deref(), Some("true"));
|
||||
|
||||
let remote_time = parse_config_line_strict("-R=true")
|
||||
.expect("short CLI alias should parse")
|
||||
.expect("directive should be present");
|
||||
assert_eq!(remote_time.name, "remote-time");
|
||||
assert_eq!(remote_time.value.as_deref(), Some("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config_line_strips_safe_trailing_comments_but_preserves_uri_fragments() {
|
||||
let referer = parse_config_line_strict(
|
||||
"referer=http://example.invalid/download#frag # copied note",
|
||||
)
|
||||
.expect("referer with fragment should parse")
|
||||
.expect("directive should be present");
|
||||
assert_eq!(referer.name, "referer");
|
||||
assert_eq!(
|
||||
referer.value.as_deref(),
|
||||
Some("http://example.invalid/download#frag")
|
||||
);
|
||||
|
||||
let tracker = parse_config_line("bt-tracker=udp://tracker.invalid:80/announce ; mirror")
|
||||
.expect("tracker line should parse")
|
||||
.expect("directive should be present");
|
||||
assert_eq!(tracker.name, "bt-tracker");
|
||||
assert_eq!(
|
||||
tracker.value.as_deref(),
|
||||
Some("udp://tracker.invalid:80/announce")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_profile_canonicalizes_extended_cli_spellings_and_comments() {
|
||||
let profile = load_profile(
|
||||
"compat",
|
||||
"\u{feff}--select-file=1-3,5 # keep files\n-R=true\n",
|
||||
)
|
||||
.expect("profile should load");
|
||||
|
||||
let [first, second] = profile.document.directives.as_slice() else {
|
||||
panic!("profile should contain exactly two directives");
|
||||
};
|
||||
assert_eq!(first.name, "select-file");
|
||||
assert_eq!(first.value.as_deref(), Some("1-3,5"));
|
||||
assert_eq!(second.name, "remote-time");
|
||||
assert_eq!(second.value.as_deref(), Some("true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_document_canonicalizes_cli_style_and_bom_prefixed_names() {
|
||||
let normalized = normalize_document(ConfigDocument {
|
||||
directives: vec![
|
||||
ConfigDirective {
|
||||
name: "\u{feff}--parameterized-uri".to_owned(),
|
||||
value: Some("true".to_owned()),
|
||||
},
|
||||
ConfigDirective {
|
||||
name: "-R".to_owned(),
|
||||
value: Some("true".to_owned()),
|
||||
},
|
||||
],
|
||||
location: ConfigLocationKind::Inline,
|
||||
scope: ConfigScope::Mixed,
|
||||
});
|
||||
|
||||
let [first, second] = normalized.directives.as_slice() else {
|
||||
panic!("normalized document should contain exactly two directives");
|
||||
};
|
||||
assert_eq!(first.name, "parameterized-uri");
|
||||
assert_eq!(second.name, "remote-time");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
//! Help-text and version-banner rendering for the compat surface.
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use crate::{
|
||||
BASELINE_COMMIT, PRODUCT_NAME, VERSION,
|
||||
options::{OptionKind, live_option_specs},
|
||||
version_line,
|
||||
};
|
||||
|
||||
/// Usage block shown at the top of compat help output.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct UsageSection {
|
||||
/// Section title or identifier.
|
||||
pub title: &'static str,
|
||||
/// Ordered lines rendered in the section.
|
||||
pub lines: Vec<String>,
|
||||
}
|
||||
|
||||
/// Named help section containing pre-rendered blocks.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct HelpSection {
|
||||
/// Section heading.
|
||||
pub name: &'static str,
|
||||
/// Pre-rendered blocks within the section.
|
||||
pub body: Vec<String>,
|
||||
}
|
||||
|
||||
/// Full help document returned by compat helpers.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct HelpDocument {
|
||||
/// Usage sections rendered before option details.
|
||||
pub usage: Vec<UsageSection>,
|
||||
/// Detail sections rendered after usage.
|
||||
pub sections: Vec<HelpSection>,
|
||||
}
|
||||
|
||||
/// Query selectors accepted by the compat help surface.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HelpQuery {
|
||||
/// Request all help entries.
|
||||
All,
|
||||
/// Request entries matching a help tag.
|
||||
Tag(String),
|
||||
/// Request entries matching a keyword search.
|
||||
Keyword(String),
|
||||
}
|
||||
|
||||
/// Internal rendered representation of a help option entry.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct HelpOptionEntry {
|
||||
/// Combined CLI switch spellings.
|
||||
switches: String,
|
||||
/// User-facing description line.
|
||||
description: String,
|
||||
/// Optional human-readable value hints.
|
||||
possible_values: Option<String>,
|
||||
/// Optional default value summary.
|
||||
default_value: Option<String>,
|
||||
/// Help tags associated with the entry.
|
||||
tags: Vec<&'static str>,
|
||||
}
|
||||
|
||||
/// Help tags recognized by the compat help renderer.
|
||||
const ALL_HELP_TAGS: &[&str] = &[
|
||||
"#basic",
|
||||
"#advanced",
|
||||
"#http",
|
||||
"#https",
|
||||
"#ftp",
|
||||
"#metalink",
|
||||
"#bittorrent",
|
||||
"#cookie",
|
||||
"#hook",
|
||||
"#file",
|
||||
"#rpc",
|
||||
"#checksum",
|
||||
"#experimental",
|
||||
"#deprecated",
|
||||
"#help",
|
||||
"#all",
|
||||
];
|
||||
|
||||
/// Returns the top-level usage section rendered by compat help.
|
||||
#[must_use]
|
||||
pub fn usage_sections() -> Vec<UsageSection> {
|
||||
vec![UsageSection {
|
||||
title: "main",
|
||||
lines: vec!["aria2c [OPTIONS] [URI | MAGNET | TORRENT_FILE | METALINK_FILE]...".to_owned()],
|
||||
}]
|
||||
}
|
||||
|
||||
/// Returns the default help sections for the compat help output.
|
||||
#[must_use]
|
||||
pub fn help_sections() -> Vec<HelpSection> {
|
||||
vec![HelpSection {
|
||||
name: "Options",
|
||||
body: render_option_entries(&option_entries()),
|
||||
}]
|
||||
}
|
||||
|
||||
/// Returns manpage metadata fields exposed by the compat help layer.
|
||||
#[must_use]
|
||||
pub fn manpage_metadata() -> Vec<(String, String)> {
|
||||
vec![
|
||||
("NAME".to_owned(), PRODUCT_NAME.to_owned()),
|
||||
("VERSION".to_owned(), version_line()),
|
||||
("BASELINE_COMMIT".to_owned(), BASELINE_COMMIT.to_owned()),
|
||||
(
|
||||
"IMPLEMENTED_OPTION_COUNT".to_owned(),
|
||||
live_option_specs().len().to_string(),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns the aria2-style version banner for the compat CLI.
|
||||
#[must_use]
|
||||
pub fn cli_version_text() -> String {
|
||||
let lines = vec![
|
||||
"aria2 version 1.37.0".to_owned(),
|
||||
format!("Rust rewrite package: {PRODUCT_NAME} {VERSION}"),
|
||||
format!("Compatibility baseline: {BASELINE_COMMIT}"),
|
||||
String::new(),
|
||||
"** Configuration **".to_owned(),
|
||||
"Enabled Features: Async DNS, BitTorrent, GZip, HTTPS, Message Digest, Metalink, XML-RPC, SFTP".to_owned(),
|
||||
"Hash Algorithms: sha-1, sha-224, sha-256, sha-384, sha-512, md5, adler32".to_owned(),
|
||||
"Libraries: tokio, reqwest, rustls, quick-xml".to_owned(),
|
||||
format!(
|
||||
"System: {} ({})",
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
),
|
||||
String::new(),
|
||||
"Report bugs to https://github.com/aria2/aria2/issues".to_owned(),
|
||||
"Visit https://aria2.github.io/".to_owned(),
|
||||
];
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Returns the default compatibility help text.
|
||||
#[must_use]
|
||||
pub fn compatibility_help_text() -> String {
|
||||
help_text()
|
||||
}
|
||||
|
||||
/// Returns the default rendered help text.
|
||||
#[must_use]
|
||||
pub fn help_text() -> String {
|
||||
help_text_for_query(None)
|
||||
}
|
||||
|
||||
/// Returns rendered help text filtered by an optional query.
|
||||
#[must_use]
|
||||
pub fn help_text_for_query(query: Option<&str>) -> String {
|
||||
render_help(
|
||||
&HelpDocument {
|
||||
usage: usage_sections(),
|
||||
sections: help_sections_for_query(query),
|
||||
},
|
||||
query.and_then(parse_help_query),
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds the internal help-entry list from built-ins and option specs.
|
||||
fn option_entries() -> Vec<HelpOptionEntry> {
|
||||
let mut entries = vec![
|
||||
HelpOptionEntry {
|
||||
switches: "-v, --version".to_owned(),
|
||||
description: "Print the version number and exit.".to_owned(),
|
||||
possible_values: None,
|
||||
default_value: None,
|
||||
tags: vec!["#basic"],
|
||||
},
|
||||
HelpOptionEntry {
|
||||
switches: "-h, --help[=TAG|KEYWORD]".to_owned(),
|
||||
description: "Print usage and exit.".to_owned(),
|
||||
possible_values: Some(ALL_HELP_TAGS.join(", ")),
|
||||
default_value: Some("#basic".to_owned()),
|
||||
tags: vec!["#basic", "#help"],
|
||||
},
|
||||
];
|
||||
|
||||
entries.extend(live_option_specs().into_iter().map(|spec| HelpOptionEntry {
|
||||
switches: spec.help_synopsis(),
|
||||
description: spec.metadata.compatibility_note.to_owned(),
|
||||
possible_values: possible_values(spec.kind, spec.value_hint(), spec.metadata.validator),
|
||||
default_value: (!spec.default_value.is_empty()).then(|| spec.default_value.to_owned()),
|
||||
tags: help_tags_for_option(spec.name, spec.metadata.family, spec.metadata.tags),
|
||||
}));
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
/// Builds help sections filtered by a parsed query.
|
||||
fn help_sections_for_query(query: Option<&str>) -> Vec<HelpSection> {
|
||||
let entries = option_entries();
|
||||
let filtered = match query.and_then(parse_help_query) {
|
||||
None | Some(HelpQuery::All) => entries,
|
||||
Some(HelpQuery::Tag(tag)) => entries
|
||||
.into_iter()
|
||||
.filter(|entry| entry.tags.iter().any(|value| *value == tag))
|
||||
.collect(),
|
||||
Some(HelpQuery::Keyword(keyword)) => {
|
||||
let needle = keyword.to_ascii_lowercase();
|
||||
entries
|
||||
.into_iter()
|
||||
.filter(|entry| {
|
||||
entry.switches.to_ascii_lowercase().contains(&needle)
|
||||
|| entry.description.to_ascii_lowercase().contains(&needle)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
vec![HelpSection {
|
||||
name: "Options",
|
||||
body: render_option_entries(&filtered),
|
||||
}]
|
||||
}
|
||||
|
||||
/// Parses a user-facing help query string.
|
||||
fn parse_help_query(query: &str) -> Option<HelpQuery> {
|
||||
let trimmed = query.trim();
|
||||
if trimmed.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if trimmed.eq_ignore_ascii_case("#all") {
|
||||
return Some(HelpQuery::All);
|
||||
}
|
||||
if trimmed.starts_with('#') {
|
||||
return Some(HelpQuery::Tag(trimmed.to_ascii_lowercase()));
|
||||
}
|
||||
Some(HelpQuery::Keyword(trimmed.to_ascii_lowercase()))
|
||||
}
|
||||
|
||||
/// Computes human-readable possible values for a help entry.
|
||||
fn possible_values(kind: OptionKind, value_hint: &str, validator: &str) -> Option<String> {
|
||||
match kind {
|
||||
OptionKind::Bool => Some("true, false".to_owned()),
|
||||
OptionKind::Path | OptionKind::Text if validator == "any" => None,
|
||||
OptionKind::Path if validator == "path" || validator == "non-empty path" => {
|
||||
Some("/path/to/file".to_owned())
|
||||
}
|
||||
OptionKind::Text if matches!(value_hint, "FILE" | "COMMAND" | "URI" | "HEADER") => {
|
||||
Some(value_hint.to_owned())
|
||||
}
|
||||
OptionKind::List if value_hint == "URI,..." => Some("URI,...".to_owned()),
|
||||
OptionKind::List if value_hint == "HOST,..." => Some("HOST,...".to_owned()),
|
||||
_ if validator.is_empty() || validator == "any" => None,
|
||||
_ if value_hint == "VALUE" => Some(validator.to_owned()),
|
||||
_ => Some(value_hint.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps option metadata to help tags used by filtered help output.
|
||||
fn help_tags_for_option(
|
||||
name: &str,
|
||||
family: crate::options::OptionFamily,
|
||||
extra_tags: &[crate::options::OptionTag],
|
||||
) -> Vec<&'static str> {
|
||||
let mut tags = vec!["#basic"];
|
||||
|
||||
match family {
|
||||
crate::options::OptionFamily::Core
|
||||
| crate::options::OptionFamily::Input
|
||||
| crate::options::OptionFamily::Session => {}
|
||||
crate::options::OptionFamily::Http => tags.push("#http"),
|
||||
crate::options::OptionFamily::Ftp | crate::options::OptionFamily::Sftp => tags.push("#ftp"),
|
||||
crate::options::OptionFamily::Rpc => tags.push("#rpc"),
|
||||
crate::options::OptionFamily::Bt => tags.push("#bittorrent"),
|
||||
crate::options::OptionFamily::Metalink => tags.push("#metalink"),
|
||||
crate::options::OptionFamily::Checksum => tags.push("#checksum"),
|
||||
crate::options::OptionFamily::Proxy => {
|
||||
tags.push("#http");
|
||||
tags.push("#ftp");
|
||||
}
|
||||
crate::options::OptionFamily::Security => tags.push("#https"),
|
||||
crate::options::OptionFamily::Performance => {
|
||||
if matches!(
|
||||
name,
|
||||
"max-overall-download-limit"
|
||||
| "max-download-limit"
|
||||
| "max-overall-upload-limit"
|
||||
| "max-upload-limit"
|
||||
) {
|
||||
tags.extend(["#http", "#ftp", "#bittorrent"]);
|
||||
} else {
|
||||
tags.extend(["#http", "#ftp"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for tag in extra_tags {
|
||||
match tag {
|
||||
crate::options::OptionTag::Deprecated => tags.push("#deprecated"),
|
||||
crate::options::OptionTag::Bt => tags.push("#bittorrent"),
|
||||
crate::options::OptionTag::Metalink => tags.push("#metalink"),
|
||||
crate::options::OptionTag::Rpc => tags.push("#rpc"),
|
||||
crate::options::OptionTag::GlobalOnly | crate::options::OptionTag::PerDownloadOnly => {
|
||||
tags.push("#advanced");
|
||||
}
|
||||
crate::options::OptionTag::InputFile => tags.push("#basic"),
|
||||
crate::options::OptionTag::SessionFile => tags.push("#advanced"),
|
||||
crate::options::OptionTag::Alias => tags.push("#experimental"),
|
||||
crate::options::OptionTag::HighRisk | crate::options::OptionTag::RequiredCompat => {}
|
||||
}
|
||||
}
|
||||
|
||||
tags.sort_unstable();
|
||||
tags.dedup();
|
||||
tags
|
||||
}
|
||||
|
||||
/// Renders each help entry into a block of text.
|
||||
fn render_option_entries(entries: &[HelpOptionEntry]) -> Vec<String> {
|
||||
entries.iter().map(format_option_entry).collect()
|
||||
}
|
||||
|
||||
/// Formats one help entry block in an aria2-style layout.
|
||||
fn format_option_entry(entry: &HelpOptionEntry) -> String {
|
||||
let mut block = Vec::new();
|
||||
if entry.switches.len() >= 34 {
|
||||
block.push(format!(" {}", entry.switches));
|
||||
block.push(format!(
|
||||
" {}",
|
||||
entry.description
|
||||
));
|
||||
} else {
|
||||
block.push(format!(" {:<34}{}", entry.switches, entry.description));
|
||||
}
|
||||
if let Some(values) = &entry.possible_values {
|
||||
block.push(String::new());
|
||||
block.push(format!(
|
||||
" Possible Values: {values}"
|
||||
));
|
||||
}
|
||||
if let Some(default_value) = &entry.default_value {
|
||||
block.push(format!(
|
||||
" Default: {default_value}"
|
||||
));
|
||||
}
|
||||
if !entry.tags.is_empty() {
|
||||
block.push(format!(
|
||||
" Tags: {}",
|
||||
entry.tags.join(", ")
|
||||
));
|
||||
}
|
||||
block.push(String::new());
|
||||
block.join("\n")
|
||||
}
|
||||
|
||||
/// Renders a full help document to user-facing text.
|
||||
fn render_help(document: &HelpDocument, query: Option<HelpQuery>) -> String {
|
||||
let mut text = String::new();
|
||||
if let Some(usage_line) = document
|
||||
.usage
|
||||
.first()
|
||||
.and_then(|section| section.lines.first())
|
||||
{
|
||||
text.push_str("Usage: ");
|
||||
text.push_str(usage_line);
|
||||
text.push('\n');
|
||||
}
|
||||
match query {
|
||||
None | Some(HelpQuery::All) => text.push_str("Printing all options.\n"),
|
||||
Some(HelpQuery::Tag(tag)) => {
|
||||
let _ = writeln!(text, "Printing options tagged with \"{tag}\".");
|
||||
}
|
||||
Some(HelpQuery::Keyword(keyword)) => {
|
||||
let _ = writeln!(text, "Printing options whose name includes \"{keyword}\".");
|
||||
}
|
||||
}
|
||||
for section in &document.sections {
|
||||
text.push_str(section.name);
|
||||
text.push_str(":\n");
|
||||
for block in §ion.body {
|
||||
text.push_str(block);
|
||||
if !block.ends_with('\n') {
|
||||
text.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
text.push_str("Refer to man page for more information.\n");
|
||||
text
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{cli_version_text, help_text, help_text_for_query};
|
||||
|
||||
#[test]
|
||||
fn version_text_uses_aria2_style_banner() {
|
||||
let version = cli_version_text();
|
||||
assert!(version.contains("aria2 version 1.37.0"));
|
||||
assert!(version.contains("Rust rewrite package: aria2-rust-pro"));
|
||||
assert!(version.contains("Enabled Features:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_text_uses_aria2_style_usage_and_options() {
|
||||
let help = help_text();
|
||||
assert!(help.starts_with("Usage: aria2c [OPTIONS]"));
|
||||
assert!(help.contains("Printing all options."));
|
||||
assert!(help.contains("Options:"));
|
||||
assert!(help.contains("-h, --help[=TAG|KEYWORD]"));
|
||||
assert!(help.contains("--retry-on-400[=true|false]"));
|
||||
assert!(help.contains("Refer to man page for more information."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_text_for_tag_filters_to_matching_entries() {
|
||||
let help = help_text_for_query(Some("#http"));
|
||||
assert!(help.contains("Printing options tagged with \"#http\"."));
|
||||
assert!(help.contains("--user-agent=VALUE, -U"));
|
||||
assert!(help.contains("--retry-on-400[=true|false]"));
|
||||
assert!(!help.contains("--bt-save-metadata[=true|false]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_text_for_keyword_filters_to_matching_entries() {
|
||||
let help = help_text_for_query(Some("rpc"));
|
||||
assert!(help.contains("Printing options whose name includes \"rpc\"."));
|
||||
assert!(help.contains("--rpc-listen-port=PORT"));
|
||||
assert!(help.contains("--rpc-secret=VALUE"));
|
||||
assert!(!help.contains("--bt-tracker=URI,..."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//! Compatibility-facing metadata and helpers for `aria2-rust-pro`.
|
||||
//!
|
||||
//! This crate centralizes the option registry, config parsing surface, help
|
||||
//! text scaffolding, and compatibility bookkeeping that mirror the historical
|
||||
//! `aria2` user-facing contract.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
/// Compatibility ledger types and baseline coverage metadata.
|
||||
pub mod compat;
|
||||
/// Config parsing and normalization helpers for aria2-style inputs.
|
||||
pub mod config;
|
||||
/// Help-text rendering and CLI version banner helpers.
|
||||
pub mod help;
|
||||
/// Canonical option metadata used by the compat surface.
|
||||
pub mod options;
|
||||
|
||||
pub use compat::{
|
||||
CompatLedger, CompatLedgerEntry, CompatLevel, CompatNote, FeatureSurface, REQUIRED_PROTOCOLS,
|
||||
compatibility_notes,
|
||||
};
|
||||
pub use config::{
|
||||
ConfigAst, ConfigDirective, ConfigDocument, ConfigLocationKind, ConfigParseError,
|
||||
ConfigProfile, ConfigScope, ConfigSource, SessionFileModel, parse_config, parse_config_lenient,
|
||||
parse_config_line, parse_config_line_strict,
|
||||
};
|
||||
pub use help::{
|
||||
HelpDocument, HelpQuery, HelpSection, UsageSection, cli_version_text, compatibility_help_text,
|
||||
help_text, help_text_for_query, manpage_metadata,
|
||||
};
|
||||
pub use options::{
|
||||
OPTION_SPECS, OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope,
|
||||
OptionSource, OptionSpec, OptionStatus, OptionTag, ReservedOptionName, env_var_for_option,
|
||||
global_option_specs, is_required_pro_option, option_spec, per_download_option_specs,
|
||||
reserved_option_names, rpc_option_name,
|
||||
};
|
||||
|
||||
/// Commit hash for the upstream baseline used by the compat layer.
|
||||
pub const BASELINE_COMMIT: &str = "1f1323128cae942f5440c035cb5f42788b3de33f";
|
||||
/// Product name shown by compat-oriented outputs.
|
||||
pub const PRODUCT_NAME: &str = "aria2-rust-pro";
|
||||
/// Crate version exposed by compatibility banners.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Errors produced while translating older compatibility inputs.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum CompatError {
|
||||
/// The requested option name is not part of the compat registry.
|
||||
UnknownOption(String),
|
||||
/// The requested protocol name is not part of the required surface.
|
||||
UnknownProtocol(String),
|
||||
/// A config line could not be interpreted as a valid directive.
|
||||
InvalidConfigDirective(String),
|
||||
/// A config option that requires a value was provided without one.
|
||||
MissingConfigValue(String),
|
||||
/// A named option received an invalid value.
|
||||
InvalidValue {
|
||||
/// Canonical option name that failed validation.
|
||||
option: String,
|
||||
/// Raw value that failed validation.
|
||||
value: String,
|
||||
/// Human-readable validation failure detail.
|
||||
reason: String,
|
||||
},
|
||||
/// A compat-only alias or deprecated switch was encountered.
|
||||
DeprecatedOption {
|
||||
/// Deprecated option spelling.
|
||||
option: String,
|
||||
/// Replacement spelling when one exists.
|
||||
replacement: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CompatError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::UnknownOption(name) => write!(f, "unknown option: {name}"),
|
||||
Self::UnknownProtocol(name) => write!(f, "unknown protocol: {name}"),
|
||||
Self::InvalidConfigDirective(line) => write!(f, "invalid config directive: {line}"),
|
||||
Self::MissingConfigValue(name) => write!(f, "missing value for option: {name}"),
|
||||
Self::InvalidValue {
|
||||
option,
|
||||
value,
|
||||
reason,
|
||||
} => write!(f, "invalid value for {option}: {value} ({reason})"),
|
||||
Self::DeprecatedOption {
|
||||
option,
|
||||
replacement,
|
||||
} => {
|
||||
if let Some(replacement) = replacement {
|
||||
write!(f, "deprecated option: {option} (use {replacement})")
|
||||
} else {
|
||||
write!(f, "deprecated option: {option}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CompatError {}
|
||||
|
||||
/// Returns the standard version banner for the compat surface.
|
||||
#[must_use]
|
||||
pub fn version_line() -> String {
|
||||
format!("{PRODUCT_NAME} {VERSION} (compat baseline {BASELINE_COMMIT})")
|
||||
}
|
||||
|
||||
/// Returns whether a protocol belongs to the required compatibility baseline.
|
||||
#[must_use]
|
||||
pub fn is_required_protocol(protocol: &str) -> bool {
|
||||
REQUIRED_PROTOCOLS.contains(&protocol)
|
||||
}
|
||||
|
||||
/// Maps a config or CLI option spelling to its canonical option name.
|
||||
#[must_use]
|
||||
pub fn normalize_option_name(name: &str) -> Option<&'static str> {
|
||||
option_spec(name).map(|spec| spec.name)
|
||||
}
|
||||
|
||||
/// Maps any known option spelling to its canonical RPC field name.
|
||||
#[must_use]
|
||||
pub fn normalize_rpc_option_name(name: &str) -> Option<&'static str> {
|
||||
rpc_option_name(name)
|
||||
}
|
||||
|
||||
/// Rewrites directive names in-place to their canonical registry spellings.
|
||||
pub fn normalize_config_document(document: &mut ConfigDocument) {
|
||||
for directive in &mut document.directives {
|
||||
if let Some(canonical) = normalize_option_name(&directive.name) {
|
||||
directive.name = canonical.to_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Canonical option registry for the compat surface.
|
||||
|
||||
/// Shared option data model used by the compat registry and query helpers.
|
||||
mod model;
|
||||
/// Lookup and filtering helpers for canonical, scoped, and RPC option spellings.
|
||||
mod query;
|
||||
/// Static aria2-compatible option specifications exposed by the compat layer.
|
||||
mod registry;
|
||||
/// Internal option names reserved for compat-layer metadata.
|
||||
mod reserved;
|
||||
|
||||
pub use self::model::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag,
|
||||
};
|
||||
pub use self::query::{
|
||||
env_var_for_option, global_option_specs, is_live_option_status, is_required_pro_option,
|
||||
live_option_specs, option_spec, per_download_option_specs, reserved_option_names,
|
||||
rpc_option_name,
|
||||
};
|
||||
pub use self::registry::OPTION_SPECS;
|
||||
pub use self::reserved::{RESERVED_OPTION_NAMES, ReservedOptionName};
|
||||
|
||||
#[cfg(test)]
|
||||
/// Regression coverage for compat option registry behavior.
|
||||
mod tests;
|
||||
@@ -0,0 +1,316 @@
|
||||
//! Canonical option registry for the compat surface.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// High-level value kind for an option.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionKind {
|
||||
/// Boolean on/off value.
|
||||
Bool,
|
||||
/// Signed or unsigned integral value.
|
||||
Integer,
|
||||
/// Byte-size value such as `1M`.
|
||||
Size,
|
||||
/// Duration value such as seconds.
|
||||
Duration,
|
||||
/// Free-form text value.
|
||||
Text,
|
||||
/// Floating-point numeric value.
|
||||
Float,
|
||||
/// Filesystem path value.
|
||||
Path,
|
||||
/// Closed set of named values.
|
||||
Enum,
|
||||
/// Comma-separated list-like value.
|
||||
List,
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Bool => f.write_str("bool"),
|
||||
Self::Integer => f.write_str("integer"),
|
||||
Self::Size => f.write_str("size"),
|
||||
Self::Duration => f.write_str("duration"),
|
||||
Self::Text => f.write_str("text"),
|
||||
Self::Float => f.write_str("float"),
|
||||
Self::Path => f.write_str("path"),
|
||||
Self::Enum => f.write_str("enum"),
|
||||
Self::List => f.write_str("list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provenance for an option specification.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionSource {
|
||||
/// Native upstream aria2 option.
|
||||
Original,
|
||||
/// Option introduced by `aria2-rust-pro`.
|
||||
Pro,
|
||||
/// Long-form alias kept for compatibility.
|
||||
CompatibilityAlias,
|
||||
/// RPC-facing alias kept for compatibility.
|
||||
RpcAlias,
|
||||
/// Experimental surface that may still change.
|
||||
Experimental,
|
||||
}
|
||||
/// Implementation status for an option surface.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionStatus {
|
||||
/// Option is planned but not yet implemented.
|
||||
Planned,
|
||||
/// Option is implemented but not fully verified.
|
||||
Implemented,
|
||||
/// Option is implemented and verified.
|
||||
Verified,
|
||||
/// Option remains available but is deprecated.
|
||||
Deprecated,
|
||||
/// Option was intentionally removed from the live surface.
|
||||
Removed,
|
||||
}
|
||||
/// Placement scope for an option.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionScope {
|
||||
/// Option is only valid globally.
|
||||
Global,
|
||||
/// Option is only valid per download.
|
||||
PerDownload,
|
||||
/// Option is valid in both scopes.
|
||||
Both,
|
||||
}
|
||||
/// Functional family used for grouping and help tagging.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionFamily {
|
||||
/// Core download behavior.
|
||||
Core,
|
||||
/// HTTP-specific behavior.
|
||||
Http,
|
||||
/// FTP-specific behavior.
|
||||
Ftp,
|
||||
/// SFTP-specific behavior.
|
||||
Sftp,
|
||||
/// RPC server behavior.
|
||||
Rpc,
|
||||
/// `BitTorrent` behavior.
|
||||
Bt,
|
||||
/// Metalink behavior.
|
||||
Metalink,
|
||||
/// Session import or save behavior.
|
||||
Session,
|
||||
/// Input-file behavior.
|
||||
Input,
|
||||
/// Checksum-related behavior.
|
||||
Checksum,
|
||||
/// Proxy-related behavior.
|
||||
Proxy,
|
||||
/// TLS and certificate behavior.
|
||||
Security,
|
||||
/// Performance and throughput behavior.
|
||||
Performance,
|
||||
}
|
||||
/// Fine-grained compatibility tags attached to option metadata.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionTag {
|
||||
/// Required to cover the targeted compatibility baseline.
|
||||
RequiredCompat,
|
||||
/// High-risk option that benefits from extra care.
|
||||
HighRisk,
|
||||
/// Option spelling is deprecated.
|
||||
Deprecated,
|
||||
/// Option acts as a compatibility alias.
|
||||
Alias,
|
||||
/// Option participates in the RPC surface.
|
||||
Rpc,
|
||||
/// Option participates in the `BitTorrent` surface.
|
||||
Bt,
|
||||
/// Option participates in the Metalink surface.
|
||||
Metalink,
|
||||
/// Option belongs in session files.
|
||||
SessionFile,
|
||||
/// Option belongs in input files.
|
||||
InputFile,
|
||||
/// Option is only valid globally.
|
||||
GlobalOnly,
|
||||
/// Option is only valid per download.
|
||||
PerDownloadOnly,
|
||||
}
|
||||
/// Parser shape used for user-facing validation hints.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum OptionParser {
|
||||
/// Boolean parser.
|
||||
Boolean,
|
||||
/// Integer parser.
|
||||
Integer,
|
||||
/// Size parser.
|
||||
Size,
|
||||
/// Duration parser.
|
||||
Duration,
|
||||
/// Free-form text parser.
|
||||
Text,
|
||||
/// Comma-separated list parser.
|
||||
Csv,
|
||||
/// Enum parser.
|
||||
Enum,
|
||||
/// URI-list parser.
|
||||
UriList,
|
||||
/// Filesystem path parser.
|
||||
Path,
|
||||
/// Repeated header parser.
|
||||
Headers,
|
||||
/// `key=value` parser.
|
||||
KeyValue,
|
||||
}
|
||||
|
||||
/// Supplemental metadata attached to an option specification.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct OptionMetadata {
|
||||
/// Scope where the option is valid.
|
||||
pub scope: OptionScope,
|
||||
/// Functional family for grouping and help tagging.
|
||||
pub family: OptionFamily,
|
||||
/// Parser shape used for validation and help text.
|
||||
pub parser: OptionParser,
|
||||
/// Extra compatibility tags attached to the option.
|
||||
pub tags: &'static [OptionTag],
|
||||
/// Human-readable validator summary.
|
||||
pub validator: &'static str,
|
||||
/// Short source description for the option origin.
|
||||
pub source_text: &'static str,
|
||||
/// Brief compat-oriented description for help and ledger output.
|
||||
pub compatibility_note: &'static str,
|
||||
}
|
||||
|
||||
/// Canonical registry entry for a compat option.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct OptionSpec {
|
||||
/// Canonical option name.
|
||||
pub name: &'static str,
|
||||
/// High-level option value kind.
|
||||
pub kind: OptionKind,
|
||||
/// Default value shown by help and metadata.
|
||||
pub default_value: &'static str,
|
||||
/// Provenance for the option surface.
|
||||
pub source: OptionSource,
|
||||
/// Current implementation status.
|
||||
pub status: OptionStatus,
|
||||
/// CLI aliases accepted for the option.
|
||||
pub aliases: &'static [&'static str],
|
||||
/// RPC names accepted for the option.
|
||||
pub rpc_names: &'static [&'static str],
|
||||
/// Supplemental metadata for the option.
|
||||
pub metadata: OptionMetadata,
|
||||
}
|
||||
|
||||
impl OptionSpec {
|
||||
/// Returns whether the option carries a given compatibility tag.
|
||||
#[must_use]
|
||||
pub fn has_tag(&self, tag: OptionTag) -> bool {
|
||||
self.metadata.tags.contains(&tag)
|
||||
}
|
||||
|
||||
/// Returns all accepted CLI spellings for the option.
|
||||
#[must_use]
|
||||
pub fn cli_spellings(&self) -> Vec<String> {
|
||||
let mut spellings = vec![format!("--{}", self.name)];
|
||||
for alias in self.aliases {
|
||||
if alias.len() == 1 {
|
||||
spellings.push(format!("-{alias}"));
|
||||
} else {
|
||||
spellings.push(format!("--{alias}"));
|
||||
}
|
||||
}
|
||||
spellings
|
||||
}
|
||||
|
||||
/// Returns config-file spellings for the option, excluding short aliases.
|
||||
#[must_use]
|
||||
pub fn config_spellings(&self) -> Vec<&'static str> {
|
||||
let mut spellings = vec![self.name];
|
||||
for alias in self.aliases {
|
||||
if alias.len() > 1 && !spellings.contains(alias) {
|
||||
spellings.push(alias);
|
||||
}
|
||||
}
|
||||
spellings
|
||||
}
|
||||
|
||||
/// Returns all accepted lookup spellings across CLI aliases and RPC names.
|
||||
#[must_use]
|
||||
pub fn lookup_spellings(&self) -> Vec<&'static str> {
|
||||
let mut spellings = vec![self.name];
|
||||
for alias in self.aliases {
|
||||
if !spellings.contains(alias) {
|
||||
spellings.push(alias);
|
||||
}
|
||||
}
|
||||
for rpc_name in self.rpc_names {
|
||||
if !spellings.contains(rpc_name) {
|
||||
spellings.push(rpc_name);
|
||||
}
|
||||
}
|
||||
spellings
|
||||
}
|
||||
|
||||
/// Returns the human-readable value hint used by help rendering.
|
||||
#[must_use]
|
||||
pub fn value_hint(&self) -> &'static str {
|
||||
if self.metadata.validator.contains('|') {
|
||||
return self.metadata.validator;
|
||||
}
|
||||
|
||||
match self.metadata.parser {
|
||||
OptionParser::Boolean => "true|false",
|
||||
OptionParser::Integer => {
|
||||
if self.name.ends_with("-port") {
|
||||
"PORT"
|
||||
} else {
|
||||
"NUM"
|
||||
}
|
||||
}
|
||||
OptionParser::Size => "SIZE",
|
||||
OptionParser::Duration => "SEC",
|
||||
OptionParser::Text => match self.metadata.validator {
|
||||
"command string" => "COMMAND",
|
||||
"proxy url" => "URI",
|
||||
"filename-safe" => "FILE",
|
||||
_ => "VALUE",
|
||||
},
|
||||
OptionParser::Csv => match self.name {
|
||||
"bt-tracker" => "URI,...",
|
||||
"no-proxy" => "HOST,...",
|
||||
"select-file" => "INDEX,...",
|
||||
_ => "VALUE,...",
|
||||
},
|
||||
OptionParser::Enum => self.metadata.validator,
|
||||
OptionParser::UriList => "URI,...",
|
||||
OptionParser::Path => "PATH",
|
||||
OptionParser::Headers => "HEADER",
|
||||
OptionParser::KeyValue => "KEY=VALUE",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the help synopsis rendered for the option.
|
||||
#[must_use]
|
||||
pub fn help_synopsis(&self) -> String {
|
||||
let mut synopsis = format!("--{}", self.name);
|
||||
let value_hint = self.value_hint();
|
||||
if self.kind == OptionKind::Bool {
|
||||
synopsis.push_str("[=");
|
||||
synopsis.push_str(value_hint);
|
||||
synopsis.push(']');
|
||||
} else {
|
||||
synopsis.push('=');
|
||||
synopsis.push_str(value_hint);
|
||||
}
|
||||
|
||||
let aliases = self.cli_spellings();
|
||||
if let Some((_, extra_aliases)) = aliases.split_first()
|
||||
&& !extra_aliases.is_empty()
|
||||
{
|
||||
synopsis.push_str(", ");
|
||||
synopsis.push_str(&extra_aliases.join(", "));
|
||||
}
|
||||
synopsis
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use super::registry::OPTION_SPECS;
|
||||
use super::reserved::{RESERVED_OPTION_NAMES, ReservedOptionName};
|
||||
use super::{OptionScope, OptionSource, OptionSpec, OptionStatus};
|
||||
|
||||
/// Returns the canonical option specification for a known spelling.
|
||||
#[must_use]
|
||||
pub fn option_spec(name: &str) -> Option<&'static OptionSpec> {
|
||||
OPTION_SPECS
|
||||
.iter()
|
||||
.find(|s| s.name == name)
|
||||
.or_else(|| OPTION_SPECS.iter().find(|s| s.aliases.contains(&name)))
|
||||
}
|
||||
/// Returns whether an option is required by the `aria2-rust-pro` surface.
|
||||
#[must_use]
|
||||
pub fn is_required_pro_option(option: &str) -> bool {
|
||||
OPTION_SPECS.iter().any(|s| {
|
||||
s.name == option
|
||||
&& (s.source == OptionSource::Pro || s.source == OptionSource::CompatibilityAlias)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether a status should be exposed in live option listings.
|
||||
#[must_use]
|
||||
pub const fn is_live_option_status(status: OptionStatus) -> bool {
|
||||
matches!(
|
||||
status,
|
||||
OptionStatus::Implemented | OptionStatus::Verified | OptionStatus::Deprecated
|
||||
)
|
||||
}
|
||||
|
||||
/// Filters the registry while preserving canonical-name uniqueness.
|
||||
fn unique_option_specs(predicate: impl Fn(&OptionSpec) -> bool) -> Vec<&'static OptionSpec> {
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
OPTION_SPECS
|
||||
.iter()
|
||||
.filter(|spec| predicate(spec) && seen.insert(spec.name))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all live option specs without duplicate canonical names.
|
||||
#[must_use]
|
||||
pub fn live_option_specs() -> Vec<&'static OptionSpec> {
|
||||
unique_option_specs(|spec| is_live_option_status(spec.status))
|
||||
}
|
||||
|
||||
/// Returns all options available in the global scope.
|
||||
#[must_use]
|
||||
pub fn global_option_specs() -> Vec<&'static OptionSpec> {
|
||||
unique_option_specs(|spec| {
|
||||
matches!(spec.metadata.scope, OptionScope::Global | OptionScope::Both)
|
||||
})
|
||||
}
|
||||
/// Returns all options available in the per-download scope.
|
||||
#[must_use]
|
||||
pub fn per_download_option_specs() -> Vec<&'static OptionSpec> {
|
||||
unique_option_specs(|spec| {
|
||||
matches!(
|
||||
spec.metadata.scope,
|
||||
OptionScope::PerDownload | OptionScope::Both
|
||||
)
|
||||
})
|
||||
}
|
||||
/// Returns the reserved option-name table.
|
||||
#[must_use]
|
||||
pub const fn reserved_option_names() -> &'static [ReservedOptionName] {
|
||||
RESERVED_OPTION_NAMES
|
||||
}
|
||||
/// Returns the canonical RPC option name for a known spelling.
|
||||
#[must_use]
|
||||
pub fn rpc_option_name(name: &str) -> Option<&'static str> {
|
||||
option_spec(name).and_then(|s| s.rpc_names.first().copied())
|
||||
}
|
||||
/// Returns the environment-variable name associated with an option.
|
||||
#[must_use]
|
||||
pub fn env_var_for_option(name: &str) -> Option<String> {
|
||||
option_spec(name).map(|s| format!("ARIA2_{}", s.name.replace('-', "_").to_ascii_uppercase()))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use super::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag,
|
||||
};
|
||||
|
||||
/// Shared tag slice for per-download-only options.
|
||||
const TAG_PER: &[OptionTag] = &[OptionTag::PerDownloadOnly];
|
||||
/// Shared tag slice for RPC options.
|
||||
const TAG_RPC: &[OptionTag] = &[OptionTag::Rpc];
|
||||
/// Shared tag slice for `BitTorrent` options.
|
||||
const TAG_BT: &[OptionTag] = &[OptionTag::Bt];
|
||||
/// Shared tag slice for Metalink options.
|
||||
const TAG_METALINK: &[OptionTag] = &[OptionTag::Metalink];
|
||||
/// Shared tag slice for compatibility aliases.
|
||||
const TAG_ALIAS: &[OptionTag] = &[OptionTag::Alias];
|
||||
/// Shared tag slice for global-only options.
|
||||
const TAG_GLOBAL: &[OptionTag] = &[OptionTag::GlobalOnly];
|
||||
|
||||
/// Option entries that extend the baseline registry with compatibility extras.
|
||||
mod compatibility_extension_entries;
|
||||
/// Foundational core, RPC, and BT registry entries used across the compat layer.
|
||||
mod foundational_entries;
|
||||
/// Hook, proxy, TLS, and RPC-adjacent registry entries.
|
||||
mod hooks_and_rpc_entries;
|
||||
/// Transfer-tuning and performance-oriented registry entries.
|
||||
mod transfer_tuning_entries;
|
||||
|
||||
use self::{
|
||||
compatibility_extension_entries::COMPATIBILITY_EXTENSION_ENTRIES,
|
||||
foundational_entries::FOUNDATIONAL_ENTRIES, hooks_and_rpc_entries::HOOKS_AND_RPC_ENTRIES,
|
||||
transfer_tuning_entries::TRANSFER_TUNING_ENTRIES,
|
||||
};
|
||||
|
||||
/// Builds the flattened compat registry once from the semantic entry groups.
|
||||
fn build_option_specs() -> Box<[OptionSpec]> {
|
||||
let total_len = FOUNDATIONAL_ENTRIES
|
||||
.len()
|
||||
.checked_add(HOOKS_AND_RPC_ENTRIES.len())
|
||||
.and_then(|value| value.checked_add(TRANSFER_TUNING_ENTRIES.len()))
|
||||
.and_then(|value| value.checked_add(COMPATIBILITY_EXTENSION_ENTRIES.len()))
|
||||
.expect("compat option registry entry count should fit in usize");
|
||||
let mut flattened = Vec::with_capacity(total_len);
|
||||
flattened.extend_from_slice(FOUNDATIONAL_ENTRIES);
|
||||
flattened.extend_from_slice(HOOKS_AND_RPC_ENTRIES);
|
||||
flattened.extend_from_slice(TRANSFER_TUNING_ENTRIES);
|
||||
flattened.extend_from_slice(COMPATIBILITY_EXTENSION_ENTRIES);
|
||||
flattened.into_boxed_slice()
|
||||
}
|
||||
|
||||
/// Canonical compat option registry.
|
||||
pub static OPTION_SPECS: LazyLock<Box<[OptionSpec]>> = LazyLock::new(build_option_specs);
|
||||
@@ -0,0 +1,368 @@
|
||||
use super::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag, TAG_ALIAS, TAG_GLOBAL, TAG_PER,
|
||||
};
|
||||
|
||||
/// Compatibility-extension entries layered onto the canonical compat registry.
|
||||
pub(super) const COMPATIBILITY_EXTENSION_ENTRIES: &[OptionSpec] = &[
|
||||
OptionSpec {
|
||||
name: "pause",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["pause"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "start in paused state after registration",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "dry-run",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["dry-run"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "validate target reachability without committing download data",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "on-download-pause",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["on-download-pause"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Text,
|
||||
tags: TAG_GLOBAL,
|
||||
validator: "command string",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "pause hook command",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "checksum",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["checksum"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Checksum,
|
||||
parser: OptionParser::KeyValue,
|
||||
tags: TAG_PER,
|
||||
validator: "TYPE=DIGEST",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "expected file digest supplied at add time",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "select-file",
|
||||
kind: OptionKind::List,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["select-file"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Csv,
|
||||
tags: &[OptionTag::Bt, OptionTag::PerDownloadOnly],
|
||||
validator: "torrent file indexes",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "choose torrent file indexes or ranges to download",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "bt-remove-unselected-file",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["bt-remove-unselected-file"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Bt, OptionTag::PerDownloadOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "delete skipped torrent files when selection is active",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "metalink-base-uri",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["metalink-base-uri"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Metalink,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::Metalink, OptionTag::PerDownloadOnly],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "base URI used to resolve relative Metalink resources",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "remote-time",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["R"],
|
||||
rpc_names: &["remote-time"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "persist remote timestamp on the output file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "legacy basic-auth rpc username",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "legacy basic-auth rpc password",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-max-request-size",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "2M",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-max-request-size"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Size,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "maximum accepted rpc request payload size",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-allow-origin-all",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-allow-origin-all"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "allow any origin for browser rpc access",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-certificate",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-certificate"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "tls certificate for secure rpc mode",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-private-key",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-private-key"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "tls private key for secure rpc mode",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-secure",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-secure"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "serve rpc over tls",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-save-upload-metadata",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-save-upload-metadata"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "persist uploaded torrent or metalink metadata",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "input-file",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["i"],
|
||||
rpc_names: &["input-file"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Input,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[OptionTag::InputFile, OptionTag::GlobalOnly],
|
||||
validator: "existing path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "load uri list from file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "save-session",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["save-session"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Session,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[OptionTag::SessionFile, OptionTag::GlobalOnly],
|
||||
validator: "writable path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "save active tasks on exit",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "save-session-interval",
|
||||
kind: OptionKind::Duration,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["save-session-interval"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Session,
|
||||
parser: OptionParser::Duration,
|
||||
tags: &[OptionTag::SessionFile, OptionTag::GlobalOnly],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "periodic session flush interval",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "no-want-digest-header",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["http-want-digest"],
|
||||
rpc_names: &["no-want-digest-header", "http-want-digest"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_ALIAS,
|
||||
validator: "bool",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "compat switch for digest header behavior",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,422 @@
|
||||
use super::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag, TAG_BT, TAG_GLOBAL, TAG_PER, TAG_RPC,
|
||||
};
|
||||
|
||||
/// Foundational option entries that anchor the compat registry surface.
|
||||
pub(super) const FOUNDATIONAL_ENTRIES: &[OptionSpec] = &[
|
||||
OptionSpec {
|
||||
name: "dir",
|
||||
kind: OptionKind::Path,
|
||||
default_value: ".",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["dir"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Path,
|
||||
tags: TAG_PER,
|
||||
validator: "non-empty path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "download target directory",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "out",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["out"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Text,
|
||||
tags: TAG_PER,
|
||||
validator: "filename-safe",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "output file name override",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "split",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "5",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["split"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_PER,
|
||||
validator: ">=1",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "piece split count",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-concurrent-downloads",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "5",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &["j"],
|
||||
rpc_names: &["max-concurrent-downloads"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_GLOBAL,
|
||||
validator: ">=1",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "maximum number of active downloads",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "continue",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &["c"],
|
||||
rpc_names: &["continue"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "resume partial download",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "pause",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["pause"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "add download in paused state",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "allow-overwrite",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["allow-overwrite"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "allow overwriting existing destination files",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "checksum",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["checksum"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Checksum,
|
||||
parser: OptionParser::KeyValue,
|
||||
tags: TAG_PER,
|
||||
validator: "algorithm=value",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "expected checksum for content verification",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "parameterized-uri",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["P"],
|
||||
rpc_names: &["parameterized-uri"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Input,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::InputFile, OptionTag::PerDownloadOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "treat input URIs as parameterized templates",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "remote-time",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["R"],
|
||||
rpc_names: &["remote-time"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "preserve remote Last-Modified timestamp",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "referer",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["referer"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Text,
|
||||
tags: TAG_PER,
|
||||
validator: "URI",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "HTTP referer header override",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "enable-rpc",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["enable-rpc"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "enable rpc server",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-listen-port",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "6800",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-listen-port"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_RPC,
|
||||
validator: "1..65535",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "rpc tcp port",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-listen-all",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-listen-all"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "bind rpc server to all interfaces",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-allow-origin-all",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-allow-origin-all"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "allow any Origin header on RPC responses",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-secure",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-secure"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "enable TLS on the RPC server",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-secret",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-secret", "token"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Text,
|
||||
tags: TAG_RPC,
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "rpc auth token",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "rpc-save-upload-metadata",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["rpc-save-upload-metadata"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Rpc,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::Rpc, OptionTag::GlobalOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "persist uploaded torrent or metalink metadata through RPC",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "listen-port",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "6881",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["listen-port"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_BT,
|
||||
validator: "1..65535",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "bt tcp/udp listen port",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "dht-listen-port",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "6881",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["dht-listen-port"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_BT,
|
||||
validator: "1..65535",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "dht udp listen port",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "select-file",
|
||||
kind: OptionKind::List,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["select-file"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Csv,
|
||||
tags: &[OptionTag::Bt, OptionTag::PerDownloadOnly],
|
||||
validator: "comma-separated file indexes",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "select BT or Metalink files to download",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "bt-tracker",
|
||||
kind: OptionKind::List,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["bt-tracker"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Csv,
|
||||
tags: TAG_BT,
|
||||
validator: "comma-separated tracker list",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "bt tracker announce list",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::GlobalOnly],
|
||||
validator: "VALUE",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "default FTP username",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,512 @@
|
||||
use super::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag, TAG_BT, TAG_METALINK,
|
||||
};
|
||||
|
||||
/// Hook, proxy, TLS, and RPC-adjacent compat registry entries.
|
||||
pub(super) const HOOKS_AND_RPC_ENTRIES: &[OptionSpec] = &[
|
||||
OptionSpec {
|
||||
name: "on-download-complete",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["on-download-complete"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::GlobalOnly],
|
||||
validator: "command string",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "completion hook command",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "on-download-stop",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["on-download-stop"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[OptionTag::GlobalOnly],
|
||||
validator: "command string",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "stop hook command",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "save-cookies",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["save-cookies"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[OptionTag::GlobalOnly],
|
||||
validator: "writable path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "persist the cookie jar to disk",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "disable-ipv6",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["disable-ipv6"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "disable ipv6 sockets and resolution",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "user-agent",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["U"],
|
||||
rpc_names: &["user-agent"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "request user agent header",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "header",
|
||||
kind: OptionKind::List,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["header"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Headers,
|
||||
tags: &[],
|
||||
validator: "header lines",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "custom request headers",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "all-proxy",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["all-proxy"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "proxy url",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "generic proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "http-proxy",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["http-proxy"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "proxy url",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "http proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "https-proxy",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["https-proxy"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "proxy url",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "https proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "no-proxy",
|
||||
kind: OptionKind::List,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["no-proxy"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Csv,
|
||||
tags: &[],
|
||||
validator: "csv host list",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "proxy bypass host list",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-proxy",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-proxy"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "proxy url",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "http-proxy-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["http-proxy-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "username override for http proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "http-proxy-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["http-proxy-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "password override for http proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "https-proxy-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["https-proxy-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "username override for https proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "https-proxy-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["https-proxy-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "password override for https proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-proxy-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-proxy-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "username override for ftp proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-proxy-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-proxy-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "password override for ftp proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "all-proxy-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["all-proxy-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "username override for generic proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "all-proxy-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["all-proxy-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Proxy,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "password override for generic proxy endpoint",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "check-certificate",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["check-certificate"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Security,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "verify peer and host certificates",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ca-certificate",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ca-certificate"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Security,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[],
|
||||
validator: "path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ca certificate file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "certificate",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["certificate"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Security,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[],
|
||||
validator: "path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "client certificate file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "private-key",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["private-key"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Security,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[],
|
||||
validator: "path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "client private key file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "retry-wait",
|
||||
kind: OptionKind::Duration,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["retry-wait"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Duration,
|
||||
tags: &[],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "retry delay in seconds",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-tries",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "5",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-tries"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Integer,
|
||||
tags: &[],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "maximum retry attempts",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "bt-save-metadata",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["bt-save-metadata"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_BT,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "save metadata file",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "follow-torrent",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["follow-torrent"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Bt,
|
||||
parser: OptionParser::Enum,
|
||||
tags: TAG_BT,
|
||||
validator: "true|false|mem",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "torrent follow behavior",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "metalink-enable-unique-protocol",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Verified,
|
||||
aliases: &[],
|
||||
rpc_names: &["metalink-enable-unique-protocol"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Metalink,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_METALINK,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "metalink dedupe by protocol",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,566 @@
|
||||
use super::{
|
||||
OptionFamily, OptionKind, OptionMetadata, OptionParser, OptionScope, OptionSource, OptionSpec,
|
||||
OptionStatus, OptionTag, TAG_GLOBAL, TAG_PER, TAG_RPC,
|
||||
};
|
||||
|
||||
/// Transfer-tuning and performance-oriented compat registry entries.
|
||||
pub(super) const TRANSFER_TUNING_ENTRIES: &[OptionSpec] = &[
|
||||
OptionSpec {
|
||||
name: "max-overall-download-limit",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-overall-download-limit"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "global aggregate download-speed cap",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-download-limit",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-download-limit"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "per-download download-speed cap",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-overall-upload-limit",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-overall-upload-limit"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "global aggregate upload-speed cap",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-upload-limit",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-upload-limit"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "per-download upload-speed cap",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "disk-cache",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "16M",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["disk-cache"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "configured disk cache budget",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-connection-per-server",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "1",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["x"],
|
||||
rpc_names: &["max-connection-per-server"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Integer,
|
||||
tags: TAG_PER,
|
||||
validator: ">=1",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "legacy speed tuning",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "min-split-size",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "20M",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["min-split-size"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=1024",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "pro lower split-size floor target",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "piece-length",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "1M",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["piece-length"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: TAG_PER,
|
||||
validator: ">=1024",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "pro lower piece-length floor target",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "retry-on-400",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["retry-on-400"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "retry HTTP 400 when explicitly enabled",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "retry-on-403",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["retry-on-403"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "retry HTTP 403 when explicitly enabled",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "retry-on-406",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["retry-on-406"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "retry HTTP 406 when explicitly enabled",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "retry-on-unknown",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Pro,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["retry-on-unknown"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_RPC,
|
||||
validator: "bool",
|
||||
source_text: "aria2-rust-pro",
|
||||
compatibility_note: "retry unknown HTTP failure when explicitly enabled",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "referer",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["referer"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "http referer header override",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "lowest-speed-limit",
|
||||
kind: OptionKind::Size,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["lowest-speed-limit"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Performance,
|
||||
parser: OptionParser::Size,
|
||||
tags: &[],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "abort slow connections below this transfer rate",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "allow-overwrite",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["allow-overwrite"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "overwrite existing target file instead of refusing",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "auto-file-renaming",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["auto-file-renaming"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Core,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "rename colliding output file automatically",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "parameterized-uri",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["P"],
|
||||
rpc_names: &["parameterized-uri"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Input,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[OptionTag::InputFile, OptionTag::PerDownloadOnly],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "expand numbered or ranged URI templates",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "realtime-chunk-checksum",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["realtime-chunk-checksum"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::PerDownload,
|
||||
family: OptionFamily::Checksum,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: TAG_PER,
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "verify chunk checksums while downloading when available",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "load-cookies",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["load-cookies"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Path,
|
||||
tags: &[],
|
||||
validator: "existing path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "load Mozilla-format cookies from disk",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "save-cookies",
|
||||
kind: OptionKind::Path,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["save-cookies"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Global,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Path,
|
||||
tags: TAG_GLOBAL,
|
||||
validator: "writable path",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "save Mozilla-format cookies on exit",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp username applied to matching transfers",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp password applied to matching transfers",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-type",
|
||||
kind: OptionKind::Enum,
|
||||
default_value: "binary",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-type"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Enum,
|
||||
tags: &[],
|
||||
validator: "binary|ascii",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp transfer type preference",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-pasv",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &["p"],
|
||||
rpc_names: &["ftp-pasv"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp passive mode toggle",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "ftp-reuse-connection",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["ftp-reuse-connection"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Ftp,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "ftp connection reuse preference",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "http-user",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["http-user"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "http auth username applied to matching transfers",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "http-passwd",
|
||||
kind: OptionKind::Text,
|
||||
default_value: "",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["http-passwd"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Text,
|
||||
tags: &[],
|
||||
validator: "any",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "http auth password applied to matching transfers",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "use-head",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["use-head"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "probe with HEAD before the first GET when supported",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "always-resume",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "true",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["always-resume"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Session,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "refuse to restart from scratch when resume is possible",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "max-resume-failure-tries",
|
||||
kind: OptionKind::Integer,
|
||||
default_value: "0",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["max-resume-failure-tries"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Session,
|
||||
parser: OptionParser::Integer,
|
||||
tags: &[],
|
||||
validator: ">=0",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "allow limited resume mismatches before restarting",
|
||||
},
|
||||
},
|
||||
OptionSpec {
|
||||
name: "conditional-get",
|
||||
kind: OptionKind::Bool,
|
||||
default_value: "false",
|
||||
source: OptionSource::Original,
|
||||
status: OptionStatus::Implemented,
|
||||
aliases: &[],
|
||||
rpc_names: &["conditional-get"],
|
||||
metadata: OptionMetadata {
|
||||
scope: OptionScope::Both,
|
||||
family: OptionFamily::Http,
|
||||
parser: OptionParser::Boolean,
|
||||
tags: &[],
|
||||
validator: "bool",
|
||||
source_text: "aria2 option",
|
||||
compatibility_note: "skip download when local file is already current",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
/// Reserved option name that cannot be used by external inputs.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ReservedOptionName {
|
||||
/// Reserved option spelling.
|
||||
pub name: &'static str,
|
||||
/// Explanation for the reservation.
|
||||
pub reason: &'static str,
|
||||
}
|
||||
|
||||
/// Reserved option spellings used internally by the compat layer.
|
||||
pub const RESERVED_OPTION_NAMES: &[ReservedOptionName] = &[
|
||||
ReservedOptionName {
|
||||
name: "_profile",
|
||||
reason: "internal profile selector",
|
||||
},
|
||||
ReservedOptionName {
|
||||
name: "_compat-mode",
|
||||
reason: "internal compatibility switch",
|
||||
},
|
||||
ReservedOptionName {
|
||||
name: "_source",
|
||||
reason: "config source marker",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,176 @@
|
||||
use super::{
|
||||
OptionFamily, OptionScope, OptionStatus, OptionTag, global_option_specs, live_option_specs,
|
||||
option_spec, per_download_option_specs,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn cli_spellings_keep_short_and_long_alias_forms() {
|
||||
let continue_spec = option_spec("continue").expect("continue should exist");
|
||||
assert_eq!(
|
||||
continue_spec.cli_spellings(),
|
||||
vec!["--continue".to_owned(), "-c".to_owned()]
|
||||
);
|
||||
|
||||
let digest_spec =
|
||||
option_spec("no-want-digest-header").expect("compat digest option should exist");
|
||||
assert_eq!(
|
||||
digest_spec.cli_spellings(),
|
||||
vec![
|
||||
"--no-want-digest-header".to_owned(),
|
||||
"--http-want-digest".to_owned()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_spellings_exclude_short_cli_aliases_but_keep_long_compat_aliases() {
|
||||
let continue_spec = option_spec("continue").expect("continue should exist");
|
||||
assert_eq!(continue_spec.config_spellings(), vec!["continue"]);
|
||||
|
||||
let digest_spec =
|
||||
option_spec("no-want-digest-header").expect("compat digest option should exist");
|
||||
assert_eq!(
|
||||
digest_spec.config_spellings(),
|
||||
vec!["no-want-digest-header", "http-want-digest"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_spellings_merge_canonical_alias_and_rpc_names_without_duplicates() {
|
||||
let rpc_secret = option_spec("rpc-secret").expect("rpc-secret should exist");
|
||||
assert_eq!(rpc_secret.lookup_spellings(), vec!["rpc-secret", "token"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_hint_and_help_synopsis_are_help_ready() {
|
||||
let continue_spec = option_spec("continue").expect("continue should exist");
|
||||
assert_eq!(continue_spec.value_hint(), "true|false");
|
||||
assert_eq!(continue_spec.help_synopsis(), "--continue[=true|false], -c");
|
||||
|
||||
let port_spec = option_spec("rpc-listen-port").expect("rpc-listen-port should exist");
|
||||
assert_eq!(port_spec.value_hint(), "PORT");
|
||||
assert_eq!(port_spec.help_synopsis(), "--rpc-listen-port=PORT");
|
||||
|
||||
let follow_torrent = option_spec("follow-torrent").expect("follow-torrent should exist");
|
||||
assert_eq!(follow_torrent.value_hint(), "true|false|mem");
|
||||
|
||||
let ftp_type = option_spec("ftp-type").expect("ftp-type should exist");
|
||||
assert_eq!(ftp_type.value_hint(), "binary|ascii");
|
||||
|
||||
let ftp_pasv = option_spec("ftp-pasv").expect("ftp-pasv should exist");
|
||||
assert_eq!(ftp_pasv.help_synopsis(), "--ftp-pasv[=true|false], -p");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_option_specs_cover_only_non_planned_surface() {
|
||||
let live_specs = live_option_specs();
|
||||
assert!(live_specs.iter().all(|spec| {
|
||||
spec.status != OptionStatus::Planned && spec.status != OptionStatus::Removed
|
||||
}));
|
||||
assert!(live_specs.iter().any(|spec| spec.name == "bt-tracker"));
|
||||
assert!(
|
||||
live_specs
|
||||
.iter()
|
||||
.any(|spec| spec.name == "on-download-complete")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_registry_surface_exposes_aliases_families_tags_and_value_hints() {
|
||||
let parameterized = option_spec("parameterized-uri")
|
||||
.expect("parameterized-uri compatibility option should exist");
|
||||
assert_eq!(option_spec("P"), Some(parameterized));
|
||||
assert_eq!(
|
||||
parameterized.cli_spellings(),
|
||||
vec!["--parameterized-uri".to_owned(), "-P".to_owned()]
|
||||
);
|
||||
assert_eq!(parameterized.metadata.family, OptionFamily::Input);
|
||||
assert_eq!(parameterized.metadata.scope, OptionScope::PerDownload);
|
||||
assert!(parameterized.has_tag(OptionTag::InputFile));
|
||||
|
||||
let remote_time = option_spec("R").expect("remote-time short alias should resolve");
|
||||
assert_eq!(remote_time.name, "remote-time");
|
||||
assert_eq!(remote_time.metadata.family, OptionFamily::Http);
|
||||
|
||||
let checksum = option_spec("checksum").expect("checksum option should exist");
|
||||
assert_eq!(checksum.metadata.family, OptionFamily::Checksum);
|
||||
assert_eq!(checksum.value_hint(), "KEY=VALUE");
|
||||
assert!(checksum.has_tag(OptionTag::PerDownloadOnly));
|
||||
|
||||
let select_file = option_spec("select-file").expect("select-file should exist");
|
||||
assert_eq!(select_file.value_hint(), "INDEX,...");
|
||||
assert_eq!(select_file.metadata.family, OptionFamily::Bt);
|
||||
assert!(select_file.has_tag(OptionTag::Bt));
|
||||
|
||||
let rpc_origin =
|
||||
option_spec("rpc-allow-origin-all").expect("rpc-allow-origin-all should exist");
|
||||
assert_eq!(rpc_origin.metadata.family, OptionFamily::Rpc);
|
||||
assert_eq!(rpc_origin.metadata.scope, OptionScope::Global);
|
||||
assert!(rpc_origin.has_tag(OptionTag::Rpc));
|
||||
|
||||
let ftp_proxy = option_spec("ftp-proxy").expect("ftp-proxy should exist");
|
||||
assert_eq!(ftp_proxy.metadata.family, OptionFamily::Proxy);
|
||||
assert_eq!(ftp_proxy.metadata.scope, OptionScope::Both);
|
||||
|
||||
let all_proxy_user = option_spec("all-proxy-user").expect("all-proxy-user should exist");
|
||||
assert_eq!(all_proxy_user.metadata.family, OptionFamily::Proxy);
|
||||
assert_eq!(all_proxy_user.value_hint(), "VALUE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extended_registry_surface_flows_into_global_and_per_download_views() {
|
||||
let global_names = global_option_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.name)
|
||||
.collect::<Vec<_>>();
|
||||
let per_names = per_download_option_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert!(global_names.contains(&"rpc-secure"));
|
||||
assert!(global_names.contains(&"save-cookies"));
|
||||
assert!(global_names.contains(&"rpc-save-upload-metadata"));
|
||||
assert!(global_names.contains(&"ftp-user"));
|
||||
assert!(global_names.contains(&"ftp-proxy-user"));
|
||||
assert!(global_names.contains(&"ftp-pasv"));
|
||||
|
||||
assert!(per_names.contains(&"select-file"));
|
||||
assert!(per_names.contains(&"pause"));
|
||||
assert!(per_names.contains(&"checksum"));
|
||||
assert!(per_names.contains(&"allow-overwrite"));
|
||||
assert!(per_names.contains(&"ftp-proxy"));
|
||||
assert!(per_names.contains(&"ftp-reuse-connection"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_registry_views_do_not_emit_duplicate_canonical_names() {
|
||||
let live_names = live_option_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.name)
|
||||
.collect::<Vec<_>>();
|
||||
let global_names = global_option_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.name)
|
||||
.collect::<Vec<_>>();
|
||||
let per_names = per_download_option_specs()
|
||||
.into_iter()
|
||||
.map(|spec| spec.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (label, names) in [
|
||||
("live", live_names),
|
||||
("global", global_names),
|
||||
("per-download", per_names),
|
||||
] {
|
||||
let unique = names
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
assert_eq!(
|
||||
unique.len(),
|
||||
names.len(),
|
||||
"{label} option projection should not contain duplicate canonical names: {names:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user