use crate::{
LintLevel,
context::{ExternalCmdFixData, LintContext},
rule::{DetectFix, Rule},
violation::{Detection, Fix, Replacement},
};
const NOTE: &str = "Use 'http get URL | save file' to download files. This provides structured \
data handling and better pipeline integration than wget.";
#[derive(Default)]
struct WgetOptions {
url: Option<String>,
output_file: Option<String>,
}
impl WgetOptions {
fn parse_wget<'a>(args: impl IntoIterator<Item = &'a str>) -> Self {
args.into_iter()
.fold(
(Self::default(), None::<String>),
|(mut opts, expecting), arg| match (expecting.as_deref(), arg) {
(Some("-O" | "--output-document"), file) => {
opts.output_file = Some(file.to_string());
(opts, None)
}
(None, "-O" | "--output-document") => (opts, Some(arg.to_string())),
(None, s) if !s.starts_with('-') && opts.url.is_none() => {
opts.url = Some(s.to_string());
(opts, None)
}
_ => (opts, None),
},
)
.0
}
fn to_nushell(&self) -> (String, String) {
let url = self.url.as_deref().unwrap_or("URL");
let mut replacement = format!("http get {url}");
if let Some(file) = &self.output_file {
replacement = format!("{replacement} | save {file}");
}
let description = if self.output_file.is_some() {
"Replace wget with 'http get | save'. Downloads return structured data that can be \
processed before saving."
.to_string()
} else {
"Replace wget with 'http get'. Use '| save <file>' to persist downloads. Nushell's \
http returns structured data and integrates with pipelines."
.to_string()
};
(replacement, description)
}
}
struct UseBuiltinWget;
impl DetectFix for UseBuiltinWget {
type FixInput<'a> = ExternalCmdFixData<'a>;
fn id(&self) -> &'static str {
"wget_to_http_get"
}
fn short_description(&self) -> &'static str {
"`wget` replaceable with `http get`"
}
fn source_link(&self) -> Option<&'static str> {
Some("https://www.nushell.sh/commands/docs/http_get.html")
}
fn level(&self) -> LintLevel {
LintLevel::Warning
}
fn detect<'a>(&self, context: &'a LintContext) -> Vec<(Detection, Self::FixInput<'a>)> {
context.detect_external_with_validation("wget", |_, fix_data, ctx| {
let has_complex = fix_data.arg_texts(ctx).any(|text| {
matches!(
text,
"--mirror" | "-m" | "--recursive" | "-r" | "--span-hosts" | "-H" | "--page-requisites" | "-p" | "--convert-links" | "-k" | "--backup-converted" | "-K" | "--reject" | "-R" | "--accept" | "-A" | "--level" | "-l" | "--quota" | "-Q" | "--wait" | "-w" | "--random-wait" | "--no-parent" | "-np" | "--timestamping" | "-N" | "--continue" | "-c" )
});
if has_complex { None } else { Some(NOTE) }
})
}
fn fix(&self, context: &LintContext, fix_data: &Self::FixInput<'_>) -> Option<Fix> {
let opts = WgetOptions::parse_wget(fix_data.arg_texts(context));
let (replacement, description) = opts.to_nushell();
Some(Fix {
explanation: description.into(),
replacements: vec![Replacement::new(fix_data.expr_span, replacement)],
})
}
}
pub static RULE: &dyn Rule = &UseBuiltinWget;
#[cfg(test)]
mod detect_bad;
#[cfg(test)]
mod generated_fix;
#[cfg(test)]
mod ignore_good;