use crate::command_args::EvaluatedWholeStreamCommandArgs; use crate::evaluate::scope::Scope; use crate::maybe_text_codec::StringOrBinary; use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs}; use crate::shell::Shell; use crate::whole_stream_command::Command; use encoding_rs::Encoding; use futures::stream::BoxStream; use nu_data::command::signature_dict; use nu_errors::ShellError; use nu_protocol::{ Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tagged; use nu_source::{Span, SpannedItem, Tag}; use nu_stream::OutputStream; use nu_value_ext::ValueExt; use std::collections::VecDeque; use std::ffi::OsStr; use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::Arc; pub fn command_dict(command: Command, tag: impl Into) -> Value { let tag = tag.into(); let mut cmd_dict = TaggedDictBuilder::new(&tag); cmd_dict.insert_untagged("name", UntaggedValue::string(command.name())); cmd_dict.insert_untagged("type", UntaggedValue::string("Command")); cmd_dict.insert_value("signature", signature_dict(command.signature(), tag)); cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage())); cmd_dict.into_value() } #[derive(Clone, Debug)] pub struct HelpShell { pub(crate) path: String, pub(crate) value: Value, } impl HelpShell { pub fn index(scope: &Scope) -> Result { let mut cmds = TaggedDictBuilder::new(Tag::unknown()); let mut specs = Vec::new(); for cmd in scope.get_command_names() { if let Some(cmd_value) = scope.get_command(&cmd) { let mut spec = TaggedDictBuilder::new(Tag::unknown()); let value = command_dict(cmd_value, Tag::unknown()); spec.insert_untagged("name", cmd); spec.insert_untagged( "description", value .get_data_by_key("usage".spanned_unknown()) .ok_or_else(|| { ShellError::untagged_runtime_error( "Internal error: expected to find usage", ) })? .as_string()?, ); spec.insert_value("details", value); specs.push(spec.into_value()); } else { } } cmds.insert_untagged("help", UntaggedValue::Table(specs)); Ok(HelpShell { path: "/help".to_string(), value: cmds.into_value(), }) } pub fn for_command(cmd: Value, scope: &Scope) -> Result { let mut sh = HelpShell::index(scope)?; if let Value { value: UntaggedValue::Primitive(Primitive::String(name)), .. } = cmd { sh.set_path(format!("/help/{:}/details", name)); } Ok(sh) } fn commands(&self) -> VecDeque { let mut cmds = VecDeque::new(); let full_path = PathBuf::from(&self.path); let mut viewed = self.value.clone(); let sep_string = std::path::MAIN_SEPARATOR.to_string(); let sep = OsStr::new(&sep_string); for p in full_path.iter() { match p { x if x == sep => {} step => { let step: &str = &step.to_string_lossy().to_string(); let value = viewed.get_data_by_key(step.spanned_unknown()); if let Some(v) = value { viewed = v.clone(); } } } } match viewed { Value { value: UntaggedValue::Table(l), .. } => { for item in l { cmds.push_back(item.clone()); } } x => { cmds.push_back(x); } } cmds } } impl Shell for HelpShell { fn name(&self) -> String { let anchor_name = self.value.anchor_name(); match anchor_name { Some(x) => format!("{{{}}}", x), None => format!("<{}>", self.value.type_name()), } } fn homedir(&self) -> Option { #[cfg(feature = "dirs")] { dirs_next::home_dir() } #[cfg(not(feature = "dirs"))] { None } } fn path(&self) -> String { self.path.clone() } fn pwd(&self, _: EvaluatedWholeStreamCommandArgs) -> Result { Ok(OutputStream::empty()) } fn set_path(&mut self, path: String) { let _ = std::env::set_current_dir(&path); self.path = path; } fn ls( &self, _args: LsArgs, _name: Tag, _ctrl_c: Arc, ) -> Result { let output = self .commands() .into_iter() .map(ReturnSuccess::value) .collect::>(); Ok(output.into()) } fn cd(&self, args: CdArgs, _name: Tag) -> Result { let path = match args.path { None => "/".to_string(), Some(v) => { let Tagged { item: target, .. } = v; let mut cwd = PathBuf::from(&self.path); if target == PathBuf::from("..") { cwd.pop(); } else { match target.to_str() { Some(target) => match target.chars().next() { Some(x) if x == '/' => cwd = PathBuf::from(target), _ => cwd.push(target), }, None => cwd.push(target), } } cwd.to_string_lossy().to_string() } }; let mut stream = VecDeque::new(); stream.push_back(ReturnSuccess::change_cwd(path)); Ok(stream.into()) } fn cp(&self, _args: CopyArgs, _name: Tag, _path: &str) -> Result { Ok(OutputStream::empty()) } fn mv(&self, _args: MvArgs, _name: Tag, _path: &str) -> Result { Ok(OutputStream::empty()) } fn mkdir(&self, _args: MkdirArgs, _name: Tag, _path: &str) -> Result { Ok(OutputStream::empty()) } fn rm(&self, _args: RemoveArgs, _name: Tag, _path: &str) -> Result { Ok(OutputStream::empty()) } fn open( &self, _path: &PathBuf, _name: Span, _with_encoding: Option<&'static Encoding>, ) -> Result>, ShellError> { Err(ShellError::unimplemented( "open on help shell is not supported", )) } fn save( &mut self, _path: &PathBuf, _contents: &[u8], _name: Span, ) -> Result { Err(ShellError::unimplemented( "save on help shell is not supported", )) } }