duat_core/cmd/
parameters.rs

1//! Defines the processing of parameters in commands
2//!
3//! This processing first separates the [`Args`] of the call and then
4//! transforms the list of arguments into a list of [`Parameter`]s, as
5//! defined by the command. Each [`Parameter`] may take multiple
6//! words, which makes this structure very flexible for
7//! multiple branching paths on how to read the arguments, all from
8//! the same command.
9use std::{
10    any::TypeId,
11    iter::Peekable,
12    ops::Range,
13    path::PathBuf,
14    sync::atomic::{AtomicBool, Ordering},
15};
16
17use crossterm::style::Color;
18
19use crate::{
20    context::Handle,
21    data::Pass,
22    form::{self, FormId},
23    text::{Text, txt},
24};
25
26macro_rules! implDeref {
27    ($type:ty, $target:ty $(, $($args:tt)+)?) => {
28        impl$(<$($args)+>)? std::ops::Deref for $type$(<$($args)+>)? {
29            type Target = $target;
30
31            fn deref(&self) -> &Self::Target {
32                &self.0
33            }
34        }
35
36        impl$(<$($args)+>)? std::ops::DerefMut for $type$(<$($args)+>)? {
37            fn deref_mut(&mut self) -> &mut Self::Target {
38                &mut self.0
39            }
40        }
41    }
42}
43
44/// A parameter for commands that can be called
45///
46/// This parameter must be parseable from [`Args`], which come from a
47/// `&str`. It can take multiple words, and can be composed of other
48/// [`Parameter`]s. An example of this is the [`Form`], which is
49/// composed of multiple [`Color`] parameters, which are then composed
50/// of some format (rgb, hsl), which is then composed of more
51/// parameters, like rgb values, for example.
52///
53/// Other types of [`Parameter`] are just a "list" of other
54/// [`Parameter`]s. For example, [`Vec<P>`] can be used as a
55/// [`Parameter`] to capture any number of `P` arguments.
56/// Additionally, there is the [`Between<MIN, MAX, P>`], which is
57/// _like_ [`Vec<P>`], but takes at least `MIN` `P`s and at most `MAX`
58/// `P`s.
59///
60/// [`Form`]: crate::form::Form
61pub trait Parameter: Sized + 'static {
62    /// Tries to consume arguments until forming a parameter
63    ///
64    /// Since parameters shouldn't mutate data, pa is just a regular
65    /// shared reference.
66    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text>;
67
68    /// A short descriptive name
69    ///
70    /// This is for the purpose of writing documentation, and will be
71    /// used like `<path>` in `buffer <path>`, displaying what type it
72    /// is.
73    fn arg_name() -> Text {
74        txt!("[param]arg")
75    }
76}
77
78/// Command [`Parameter`]: A flag passed to the command
79///
80/// `Flag`s in duat differ from those of UNIX like operating system
81/// commands, since a flag can show up anywhere, not just before some
82/// standalone `--` which separates flags and "not flags". Instead,
83/// what determines if an argument starting with `--` or `-` is a flag
84/// or not is if said argument is _quoted_:
85///
86/// ```text
87/// mycmd --this-is-a-flag "--this-is not a flag" -blobflag -- --flag
88/// ```
89pub enum Flag<S: AsRef<str> = String> {
90    /// A word flag is prefixed by `--` and represents only one thing
91    ///
92    /// Examples of this are the `--cfg` and `--cfg-manifest`, which
93    /// are used by the `edit` and `open` commands to open Duat
94    /// configuration files.
95    Word(S),
96    /// A blob flag is prefixed by `-` and represents one thing per
97    /// `char`
98    ///
99    /// An example, coming from UNIX like operating systems is `rm
100    /// -rf`, witch will forcefully (`f`) remove files recursively
101    /// (`r`).
102    Blob(S),
103}
104
105impl Parameter for Flag {
106    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
107        let arg = args.next()?;
108        if !arg.is_quoted {
109            if let Some(word) = arg.strip_prefix("--") {
110                Ok((
111                    Flag::Word(word.to_string()),
112                    Some(form::id_of!("param.flag")),
113                ))
114            } else if let Some(blob_chars) = arg.strip_prefix("-") {
115                let mut blob = String::new();
116                for char in blob_chars.chars() {
117                    if !blob.chars().any(|c| c == char) {
118                        blob.push(char);
119                    }
120                }
121
122                Ok((Flag::Blob(blob), Some(form::id_of!("param.flag"))))
123            } else {
124                Err(txt!("[param.info]Flag[]s must start with `-` or `--`"))
125            }
126        } else {
127            Err(txt!("Quoted arguments can't be [param.info]Flag[]s"))
128        }
129    }
130
131    fn arg_name() -> Text {
132        txt!("[param.flag]flag")
133    }
134}
135
136impl<S: AsRef<str>> Flag<S> {
137    /// Returns `Ok` only if the `Flag` is of type [`Flag::Word`]
138    pub fn as_word(self) -> Result<S, Text> {
139        match self {
140            Flag::Word(word) => Ok(word),
141            Flag::Blob(_) => Err(txt!(
142                "[param.info]Flag[] is of type [param.info]blob[], not [param.info]word"
143            )),
144        }
145    }
146
147    /// Returns true if this `Flag` is a `Flag::Word(word)`
148    pub fn is_word(&self, word: &str) -> bool {
149        self.as_str().as_word().ok().is_some_and(|w| w == word)
150    }
151
152    /// Returns `Ok` only if the `Flag` is of type [`Flag::Blob`]
153    pub fn as_blob(self) -> Result<S, Text> {
154        match self {
155            Flag::Blob(blob) => Ok(blob),
156            Flag::Word(_) => Err(txt!(
157                "[param.info]Flag[] is of type [param.info]word[], not [param.info]blob"
158            )),
159        }
160    }
161
162    /// Returns true if this `Flag` is a [`Flag::Blob`] with all
163    /// characters in `blob`
164    pub fn has_blob(&self, blob: &str) -> bool {
165        self.as_str()
166            .as_blob()
167            .ok()
168            .is_some_and(|b| blob.chars().all(|char| b.chars().any(|c| c == char)))
169    }
170
171    /// Returns an [`Err`] if the `Flag` is a blob or doesn't belong
172    /// on the list
173    ///
174    /// this is useful for quickly matching against a fixed list of
175    /// possible words, while having a "catchall `_ => {}`, which will
176    /// never match.
177    pub fn word_from_list<const N: usize>(self, list: [&str; N]) -> Result<&str, Text> {
178        let word = self.as_word()?;
179        if let Some(word) = list.into_iter().find(|w| w == &word.as_ref()) {
180            Ok(word)
181        } else {
182            Err(txt!("Word not in list of valid options"))
183        }
184    }
185
186    /// Returns a new `Flag<&str>`
187    ///
188    /// This is particularly useful in pattern matching.
189    pub fn as_str(&self) -> Flag<&str> {
190        match self {
191            Flag::Word(word) => Flag::Word(word.as_ref()),
192            Flag::Blob(blob) => Flag::Blob(blob.as_ref()),
193        }
194    }
195}
196
197/// Command [`Parameter`]: A list of [`Flag`]s passed to the command
198///
199/// This `Parameter` will capture all following arguments, until it
200/// finds one that can't be parsed as a `Flag` (i.e. not starting with
201/// `--` or `-`, or quoted arguments).
202///
203/// Unlike [`Vec`], this `Parameter` _can_ be followed up by other
204/// ones, and if there are no `Flag`s, then this will have an empty
205/// list. As such, this function never actually fails.
206pub struct Flags(pub Vec<Flag>);
207
208impl Parameter for Flags {
209    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
210        let mut list = Vec::new();
211
212        while let Ok(flag) = args.next_as(pa) {
213            list.push(flag);
214        }
215
216        Ok((Self(list), Some(form::id_of!("param.flag"))))
217    }
218
219    fn arg_name() -> Text {
220        txt!("[param.flag]flags...")
221    }
222}
223
224impl Flags {
225    /// Returns `true` if the `Flags` contains a [`Flag::Word`] with
226    /// the given word
227    pub fn has_word(&self, word: &str) -> bool {
228        self.0
229            .iter()
230            .any(|flag| flag.as_str().as_word() == Ok(word))
231    }
232
233    /// Returns `true` if the `Flags` contains [`Flag::Blob`]s with
234    /// all `char`s in the given blob
235    pub fn has_blob(&self, blob: &str) -> bool {
236        blob.chars().all(|char| {
237            self.0
238                .iter()
239                .filter_map(|flag| flag.as_str().as_blob().ok())
240                .any(|blob| blob.chars().any(|c| c == char))
241        })
242    }
243}
244
245impl std::ops::Deref for Flags {
246    type Target = Vec<Flag>;
247
248    fn deref(&self) -> &Self::Target {
249        &self.0
250    }
251}
252
253impl std::ops::DerefMut for Flags {
254    fn deref_mut(&mut self) -> &mut Self::Target {
255        &mut self.0
256    }
257}
258
259/// Command [`Parameter`]: Global or local scope for commands
260///
261/// This struct captures a [`Flag`] if it exists, if it is `--global`,
262/// then the scope is global. If the flag is something else, it
263/// returns an [`Err`]. If there is no `Flag` then the scope is
264/// [`Scope::Local`].
265#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
266pub enum Scope {
267    /// The scope of this command is meant to be local
268    ///
269    /// This is a loose definition. Usually, it means it should affect
270    /// stuff related only to the current [`Buffer`], but you can
271    /// decide that.
272    ///
273    /// [`Buffer`]: crate::buffer::Buffer
274    Local,
275    /// The scope of this command is meant to be global
276    ///
277    /// This is a loose definition. Usually, it means it should affect
278    /// stuff related to _all_ [`Buffer`]s, but you decide what it
279    /// really means.
280    ///
281    /// [`Buffer`]: crate::buffer::Buffer
282    Global,
283}
284
285impl Parameter for Scope {
286    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
287        if let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa) {
288            if flag.is_word("global") {
289                Ok((Scope::Global, form))
290            } else {
291                Err(txt!("Invalid flag, it can only be [param.info]--global"))
292            }
293        } else {
294            Ok((Scope::Local, None))
295        }
296    }
297
298    fn arg_name() -> Text {
299        txt!("[param.flag]--global[param.punctuation]?")
300    }
301}
302
303impl<P: Parameter> Parameter for Option<P> {
304    /// Will match either [`Parameter`] given, or nothing
305    ///
306    /// This, like other lists, _has_ to be the final argument in the
307    /// [`Parameter`] list, as it will either match correcly, finish
308    /// matching, or match incorrectly in order to give accurate
309    /// feedback.
310    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
311        match args.next_as::<P>(pa) {
312            Ok(arg) => Ok((Some(arg), None)),
313            Err(err) if args.is_forming_param => Err(err),
314            Err(_) => Ok((None, None)),
315        }
316    }
317
318    fn arg_name() -> Text {
319        txt!("{}[param.punctuation]?", P::arg_name())
320    }
321}
322
323impl<P: Parameter> Parameter for Vec<P> {
324    /// Will match a list of [`Parameter`]s
325    ///
326    /// This, like other lists, _has_ to be the final argument in the
327    /// [`Parameter`] list, as it will either match correcly, finish
328    /// matching, or match incorrectly in order to give accurate
329    /// feedback.
330    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
331        let mut returns = Vec::new();
332
333        loop {
334            match args.next_as::<P>(pa) {
335                Ok(ret) => returns.push(ret),
336                Err(err) if args.is_forming_param => return Err(err),
337                Err(_) => break Ok((returns, None)),
338            }
339        }
340    }
341
342    fn arg_name() -> Text {
343        txt!("{}[param.punctuation]...", P::arg_name())
344    }
345}
346
347impl<const N: usize, P: Parameter> Parameter for [P; N] {
348    /// Will match either the argument given, or nothing
349    ///
350    /// This, like other lists, _has_ to be the final argument in the
351    /// [`Parameter`] list, as it will either match correcly, finish
352    /// matching, or match incorrectly in order to give accurate
353    /// feedback.
354    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
355        use std::mem::MaybeUninit;
356        let mut returns = [const { MaybeUninit::uninit() }; N];
357
358        for r in returns.iter_mut() {
359            match args.next_as::<P>(pa) {
360                Ok(ret) => *r = MaybeUninit::new(ret),
361                Err(err) => return Err(err),
362            }
363        }
364
365        Ok((returns.map(|ret| unsafe { ret.assume_init() }), None))
366    }
367
368    fn arg_name() -> Text {
369        let punct = form::id_of!("param.punctuation");
370        txt!(
371            "{punct}[[{}{punct}; [param.count]{N}{punct}]]",
372            P::arg_name()
373        )
374    }
375}
376
377/// Command [`Parameter`]: A list of between `MIN` and `MAX` items
378///
379/// This, like other lists, _has_ to be the final argument in the
380/// [`Parameter`] list, as it will either match correcly, finish
381/// matching, or match incorrectly in order to give accurate
382/// feedback.
383pub struct Between<const MIN: usize, const MAX: usize, P>(pub Vec<P>);
384
385impl<const MIN: usize, const MAX: usize, P: Parameter> Parameter for Between<MIN, MAX, P> {
386    /// Will match between `MIN` and `MAX` [`Parameter`]s
387    ///
388    /// This, like other lists, _has_ to be the final argument in the
389    /// [`Parameter`] list, as it will either match correcly, finish
390    /// matching, or match incorrectly in order to give accurate
391    /// feedback.
392    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
393        let mut returns = Vec::new();
394
395        for _ in 0..MAX {
396            match args.next_as::<P>(pa) {
397                Ok(ret) => returns.push(ret),
398                Err(err) if args.is_forming_param => return Err(err),
399                Err(_) if returns.len() >= MIN => return Ok((Self(returns), None)),
400                Err(err) => return Err(err),
401            }
402        }
403
404        if returns.len() >= MIN {
405            Ok((Self(returns), None))
406        } else {
407            Err(txt!(
408                "List needed at least [param.info]{MIN}[] elements, got only [a]{}",
409                returns.len()
410            ))
411        }
412    }
413
414    fn arg_name() -> Text {
415        let punct = form::id_of!("param.punctuation");
416        let count = form::id_of!("param.count");
417        txt!(
418            "{punct}[[{}{punct}; {count}{MIN}{punct}..{count}{MAX}{punct}]]",
419            P::arg_name()
420        )
421    }
422}
423
424impl<const MIN: usize, const MAX: usize, P> std::ops::Deref for Between<MIN, MAX, P> {
425    type Target = Vec<P>;
426
427    fn deref(&self) -> &Self::Target {
428        &self.0
429    }
430}
431
432impl<const MIN: usize, const MAX: usize, P> std::ops::DerefMut for Between<MIN, MAX, P> {
433    fn deref_mut(&mut self) -> &mut Self::Target {
434        &mut self.0
435    }
436}
437
438impl Parameter for String {
439    fn new(_: &Pass, args: &mut Args) -> Result<(String, Option<FormId>), Text> {
440        Ok((args.next()?.to_string(), None))
441    }
442
443    fn arg_name() -> Text {
444        txt!("[param]arg")
445    }
446}
447
448/// Command [`Parameter`]: The remaining arguments, divided by a space
449///
450/// Fails if the [`String`] would be empty.
451pub struct Remainder(pub String);
452
453impl Parameter for Remainder {
454    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
455        let remainder: String = std::iter::from_fn(|| args.next().ok().map(|arg| arg.value))
456            .collect::<Vec<&str>>()
457            .join(" ");
458        if remainder.is_empty() {
459            Err(txt!("There are no more arguments"))
460        } else {
461            Ok((Self(remainder), None))
462        }
463    }
464
465    fn arg_name() -> Text {
466        txt!("[param]args")
467    }
468}
469implDeref!(Remainder, String);
470
471impl Parameter for Handle {
472    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
473        let buffer_name = args.next()?.value;
474        if let Some(handle) = crate::context::windows()
475            .buffers(pa)
476            .into_iter()
477            .find(|handle| handle.read(pa).name() == buffer_name)
478        {
479            Ok((handle, Some(form::id_of!("param.path.open"))))
480        } else {
481            Err(txt!("No buffer called [a]{buffer_name}[] open"))
482        }
483    }
484
485    fn arg_name() -> Text {
486        txt!("[param]buffer")
487    }
488}
489
490/// Command [`Parameter`]: An open [`Buffer`]'s name, except the
491/// current
492///
493/// [`Buffer`]: crate::buffer::Buffer
494pub struct OtherBuffer(pub Handle);
495
496impl Parameter for OtherBuffer {
497    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
498        let handle = args.next_as::<Handle>(pa)?;
499        let cur_handle = crate::context::current_buffer(pa);
500        if cur_handle == handle {
501            Err(txt!("Argument can't be the current buffer"))
502        } else {
503            Ok((Self(handle), Some(form::id_of!("param.path.open"))))
504        }
505    }
506
507    fn arg_name() -> Text {
508        txt!("[param]buffer")
509    }
510}
511implDeref!(OtherBuffer, Handle);
512
513/// Command [`Parameter`]: A file that _could_ exist
514///
515/// This is the case if the file's path has a parent that exists,
516/// or if the file itself exists.
517///
518/// [`Buffer`]: crate::buffer::Buffer
519pub struct ValidFilePath(pub PathBuf, bool);
520
521impl Parameter for ValidFilePath {
522    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
523        let path = PathBuf::from(
524            shellexpand::full(args.next()?.value)
525                .map_err(|err| txt!("{err}"))?
526                .into_owned(),
527        );
528
529        let canon_path = path.canonicalize();
530        let path = if let Ok(path) = &canon_path {
531            if !path.is_file() {
532                return Err(txt!("Path is not a buffer"));
533            }
534            path.clone()
535        } else if canon_path.is_err()
536            && let Ok(canon_path) = path.with_file_name(".").canonicalize()
537        {
538            canon_path.join(
539                path.file_name()
540                    .ok_or_else(|| txt!("Path has no buffer name"))?,
541            )
542        } else {
543            return Err(txt!("Path was not found"));
544        };
545
546        if let Some(parent) = path.parent()
547            && let Ok(false) | Err(_) = parent.try_exists()
548        {
549            return Err(txt!("Path's parent doesn't exist"));
550        }
551
552        let (form, exists_or_is_open) = if crate::context::windows()
553            .buffers(pa)
554            .into_iter()
555            .map(|handle| handle.read(pa).path())
556            .any(|p| std::path::Path::new(&p) == path)
557        {
558            (form::id_of!("param.path.open"), true)
559        } else if let Ok(true) = path.try_exists() {
560            (form::id_of!("param.path.exists"), true)
561        } else {
562            (form::id_of!("param.path"), false)
563        };
564
565        Ok((Self(path, exists_or_is_open), Some(form)))
566    }
567
568    fn arg_name() -> Text {
569        txt!("[param]path")
570    }
571}
572implDeref!(ValidFilePath, PathBuf);
573
574/////////// Command specific parameters
575
576/// Comand [`Parameter`]: A [`ValidFilePath`], [`Handle`], `--cfg` or
577/// `--cfg-manifest`
578///
579/// This is a generalized way of switching to a [`Handle<Buffer`] on
580/// the `edit` and `open` commands.
581#[doc(hidden)]
582pub enum PathOrBufferOrCfg {
583    Path(PathBuf),
584    Buffer(Handle),
585    Cfg,
586    CfgManifest,
587}
588
589impl Parameter for PathOrBufferOrCfg {
590    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
591        struct DropGuard;
592        impl Drop for DropGuard {
593            fn drop(&mut self) {
594                ONLY_EXISTING.store(false, Ordering::Relaxed);
595            }
596        }
597
598        let _guard = DropGuard;
599
600        args.use_completions_for::<CfgOrManifest>();
601        args.use_completions_for::<Handle>();
602        args.use_completions_for::<ValidFilePath>();
603
604        if let Ok((cfg_or_manifest, form)) = args.next_as_with_form::<CfgOrManifest>(pa) {
605            match cfg_or_manifest {
606                CfgOrManifest::Cfg => Ok((Self::Cfg, form)),
607                CfgOrManifest::Manifest => Ok((Self::CfgManifest, form)),
608            }
609        } else if let Ok((handle, form)) = args.next_as_with_form::<Handle>(pa) {
610            Ok((Self::Buffer(handle), form))
611        } else {
612            let (path, form) = args.next_as_with_form::<ValidFilePath>(pa)?;
613            if !path.1 && ONLY_EXISTING.load(Ordering::Relaxed) {
614                Err(txt!("[a]{path}[]: No such file"))
615            } else {
616                Ok((Self::Path(path.0), form))
617            }
618        }
619    }
620
621    fn arg_name() -> Text {
622        let flag = form::id_of!("param.flag");
623        let punct = form::id_of!("param.punctuation");
624        txt!("[param]path{punct}/[param]buffer{punct}/{flag}--cfg{punct}/{flag}--cfg-manifest")
625    }
626}
627
628static ONLY_EXISTING: AtomicBool = AtomicBool::new(false);
629
630/// Command [`Parameter`]: The `--existing` flag
631pub struct Existing;
632
633impl Parameter for Existing {
634    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
635        let initial = args.clone();
636        let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa) else {
637            return Ok((Self, None));
638        };
639
640        if flag.is_word("existing") {
641            ONLY_EXISTING.store(true, Ordering::Relaxed);
642        } else {
643            *args = initial;
644        }
645
646        Ok((Self, form))
647    }
648
649    fn arg_name() -> Text {
650        txt!("[param.flag]--existing[param.punctuation]?")
651    }
652}
653
654/// Command [`Parameter`]: `--cfg` or `--cfg-manifest`
655///
656/// This is a quick shorthand to get `{duat_config}/src/lib.rs` or
657/// `{duat_config}/Cargo.toml`, respectively
658pub enum CfgOrManifest {
659    /// Represents `{duat_config}/src/lib.rs`
660    Cfg,
661    /// Represents `{duat_config}/Cargo.toml`
662    Manifest,
663}
664
665impl Parameter for CfgOrManifest {
666    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
667        if let Ok((flag, form)) = args.next_as_with_form::<Flag>(pa)
668            && let Some(ret) = match flag.as_word()?.as_str() {
669                "cfg" => Some((Self::Cfg, form)),
670                "cfg-manifest" => Some((Self::Manifest, form)),
671                _ => None,
672            }
673        {
674            Ok(ret)
675        } else {
676            Err(txt!(
677                "Invalid flag, pick [param.flag]cfg[] or [param.flag]cfg-manifest"
678            ))
679        }
680    }
681
682    fn arg_name() -> Text {
683        txt!("[param.flag]--cfg[param.punctuation]/[param.flag]--cfg-manifest")
684    }
685}
686
687/// Command [`Parameter`]: An [`f32`] from a [`u8`] or a percentage
688///
689/// The percentage is of whole divisions of 100, 100 being equivalent
690/// to 255 in [`u8`].
691pub struct F32PercentOfU8(pub f32);
692
693impl Parameter for F32PercentOfU8 {
694    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
695        let arg = args.next()?;
696        if let Some(percentage) = arg.strip_suffix("%") {
697            let percentage: u8 = percentage
698                .parse()
699                .map_err(|_| txt!("[a]{arg}[] is not a valid percentage"))?;
700            if percentage <= 100 {
701                Ok((Self(percentage as f32 / 100.0), None))
702            } else {
703                Err(txt!("[a]{arg}[] is more than [a]100%"))
704            }
705        } else {
706            let byte: u8 = arg
707                .parse()
708                .map_err(|_| txt!("[a]{arg}[] couldn't be parsed"))?;
709            Ok((Self(byte as f32 / 255.0), None))
710        }
711    }
712
713    fn arg_name() -> Text {
714        let punct = form::id_of!("param.punctuation");
715        txt!("[param]u8{punct}/[param]0..=100%")
716    }
717}
718implDeref!(F32PercentOfU8, f32);
719
720impl Parameter for Color {
721    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
722        const fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
723            t = if t < 0.0 { t + 1.0 } else { t };
724            t = if t > 1.0 { t - 1.0 } else { t };
725            if t < 1.0 / 6.0 {
726                p + (q - p) * 6.0 * t
727            } else if t < 1.0 / 2.0 {
728                q
729            } else if t < 2.0 / 3.0 {
730                p + (q - p) * (2.0 / 3.0 - t) * 6.0
731            } else {
732                p
733            }
734        }
735
736        let arg = args.next()?.value;
737        // Expects "#{red:x}{green:x}{blue:x}"
738        if let Some(hex) = arg.strip_prefix("#") {
739            let total = match u32::from_str_radix(hex, 16) {
740                Ok(total) if hex.len() == 6 => total,
741                _ => return Err(txt!("Hexcode does not contain 6 hex values")),
742            };
743            let r = (total >> 16) as u8;
744            let g = (total >> 8) as u8;
745            let b = total as u8;
746            Ok((Color::Rgb { r, g, b }, None))
747            // Expects "hsl {hue%?} {saturation%?} {lightness%?}"
748        } else if arg == "hsl" {
749            let hue = args.next_as::<F32PercentOfU8>(pa)?.0;
750            let sat = args.next_as::<F32PercentOfU8>(pa)?.0;
751            let lit = args.next_as::<F32PercentOfU8>(pa)?.0;
752            let [r, g, b] = if sat == 0.0 {
753                [lit.round() as u8; 3]
754            } else {
755                let q = if lit < 0.5 {
756                    lit * (1.0 + sat)
757                } else {
758                    lit + sat - lit * sat
759                };
760                let p = 2.0 * lit - q;
761                let r = hue_to_rgb(p, q, hue + 1.0 / 3.0);
762                let g = hue_to_rgb(p, q, hue);
763                let b = hue_to_rgb(p, q, hue - 1.0 / 3.0);
764                [r.round() as u8, g.round() as u8, b.round() as u8]
765            };
766            Ok((Color::Rgb { r, g, b }, None))
767        } else {
768            Err(txt!("Color format was not recognized"))
769        }
770    }
771
772    fn arg_name() -> Text {
773        txt!("[param]#{{rgb hex}}[param.punctuation]/[param]hsl {{h}} {{s}} {{l}}")
774    }
775}
776
777/// Command [`Parameter`]: The name of a [`Form`] that has been [set]
778///
779/// [set]: crate::form::set
780/// [`Form`]: crate::form::Form
781pub struct FormName(pub String);
782
783impl Parameter for FormName {
784    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
785        let arg = args.next()?.value;
786        if !arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '.') {
787            return Err(txt!(
788                "Expected identifiers separated by '.'s, found [a]{arg}"
789            ));
790        }
791        if crate::form::exists(arg) {
792            Ok((Self(arg.to_string()), Some(form::id_of_non_static(arg))))
793        } else {
794            Err(txt!("The form [a]{arg}[] has not been set"))
795        }
796    }
797
798    fn arg_name() -> Text {
799        txt!("[param]form")
800    }
801}
802
803implDeref!(FormName, String);
804
805/// Command [`Parameter`]: An existing [`ColorScheme`]'s name
806///
807/// [`ColorScheme`]: crate::form::ColorScheme
808pub struct ColorSchemeArg(pub String);
809
810impl Parameter for ColorSchemeArg {
811    fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
812        let scheme = args.next()?.value;
813        if crate::form::colorscheme_exists(scheme) {
814            Ok((ColorSchemeArg(scheme.to_string()), None))
815        } else {
816            Err(txt!("The colorscheme [a]{scheme}[] was not found"))
817        }
818    }
819
820    fn arg_name() -> Text {
821        txt!("[param]colorscheme")
822    }
823}
824implDeref!(ColorSchemeArg, String);
825
826/// Command [`Parameter`]: Options for reloading
827#[doc(hidden)]
828pub struct ReloadOptions {
829    /// Wether to clean
830    pub clean: bool,
831    /// Wether to update
832    pub update: bool,
833}
834
835impl Parameter for ReloadOptions {
836    fn new(pa: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
837        let flags = args.next_as::<Flags>(pa)?;
838
839        if flags
840            .iter()
841            .any(|flag| !flag.is_word("update") && !flag.is_word("clean"))
842        {
843            Err(txt!("Invalid [a]Flag"))
844        } else {
845            Ok((
846                Self {
847                    clean: flags.has_word("clean"),
848                    update: flags.has_word("update"),
849                },
850                Some(form::id_of!("param.flag")),
851            ))
852        }
853    }
854
855    fn arg_name() -> Text {
856        let punct = form::id_of!("param.punctuation");
857        txt!("[param.flag]--clean{punct}? [param.flag]--update{punct}?")
858    }
859}
860
861/// The list of arguments passed to a command
862#[derive(Clone)]
863pub struct Args<'a> {
864    iter: Peekable<ArgsIter<'a>>,
865    param_range: Range<usize>,
866    has_to_start_param: bool,
867    is_forming_param: bool,
868    // For figuring out what's being parsed atm
869    /// The argument index is used to keep parameters in the
870    /// last_parsed list, even if they failed parsing and another
871    /// parameter was parsed with the same argument instead.
872    arg_n: usize,
873    /// This list will have the TypeId of `T` within the
874    /// `self.next_as::<T>()` call. It serves the purpose of keeping
875    /// track of when a type is done being parsed.
876    currently_parsing: Vec<TypeId>,
877    /// This list will have the TypeId of `T` while `T` is within
878    /// currently_parsing or we haven't succesfully moved on to the
879    /// next argument.
880    /// This is used to aid completion, by keeping track of when we
881    /// have moved on to parsing a new argument.
882    last_parsed: Vec<(usize, TypeId)>,
883}
884
885impl<'arg> Args<'arg> {
886    /// Returns a new instance of `Args`
887    pub(super) fn new(command: &'arg str) -> Self {
888        Self {
889            iter: ArgsIter::new(command).peekable(),
890            param_range: 0..0,
891            has_to_start_param: false,
892            is_forming_param: false,
893            arg_n: 0,
894            currently_parsing: Vec::new(),
895            last_parsed: Vec::new(),
896        }
897    }
898
899    /// Returns the next word or quoted argument
900    #[allow(clippy::should_implement_trait)]
901    pub fn next(&mut self) -> Result<Arg<'arg>, Text> {
902        match self.iter.next() {
903            Some((value, range, is_quoted)) => {
904                self.param_range = range.clone();
905                if self.has_to_start_param {
906                    self.has_to_start_param = false;
907                    self.is_forming_param = true;
908                }
909
910                self.last_parsed.retain(|(start_n, ty)| {
911                    self.currently_parsing.contains(ty) || self.arg_n == *start_n
912                });
913
914                self.arg_n += 1;
915
916                Ok(Arg { value, is_quoted })
917            }
918            None => Err(txt!("Wrong argument count")),
919        }
920    }
921
922    /// Tries to parse the next argument as `P`
923    ///
924    /// If parsing fails, [`Args`] will be reset as if this function
925    /// wasn't called.
926    pub fn next_as<P: Parameter>(&mut self, pa: &Pass) -> Result<P, Text> {
927        self.next_as_with_form(pa).map(|(param, _)| param)
928    }
929
930    /// Tries to parse the next argument as `P`, also returning the
931    /// [`Option<Form>`]
932    ///
933    /// If parsing fails, [`Args`] will be reset as if this function
934    /// wasn't called.
935    pub fn next_as_with_form<P: Parameter>(
936        &mut self,
937        pa: &Pass,
938    ) -> Result<(P, Option<FormId>), Text> {
939        self.currently_parsing.push(TypeId::of::<P>());
940
941        let initial = (
942            self.iter.clone(),
943            self.param_range.clone(),
944            self.has_to_start_param,
945            self.is_forming_param,
946            self.arg_n,
947        );
948
949        self.has_to_start_param = true;
950        let ret = P::new(pa, self);
951        if ret.is_ok() {
952            self.is_forming_param = false;
953        } else {
954            self.iter = initial.0;
955            self.param_range = initial.1;
956            self.has_to_start_param = initial.2;
957            self.is_forming_param = initial.3;
958            self.arg_n = initial.4;
959        }
960
961        self.currently_parsing.retain(|&ty| ty != TypeId::of::<P>());
962
963        ret
964    }
965
966    /// Tries to get the next argument, otherwise returns a [`Text`]
967    pub fn next_else<T: Into<Text>>(&mut self, to_text: T) -> Result<&'arg str, Text> {
968        match self.next() {
969            Ok(arg) => Ok(arg.value),
970            Err(_) => Err(to_text.into()),
971        }
972    }
973
974    /// Returns the char position of the next argument
975    ///
976    /// Mostly used for error feedback by the [`PromptLine`]
977    ///
978    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
979    pub fn next_start(&mut self) -> Option<usize> {
980        self.iter.peek().map(|(_, r, _)| r.start)
981    }
982
983    /// The range of the previous [`Parameter`]
984    ///
985    /// Mostly used for error feedback by the [`PromptLine`]
986    ///
987    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
988    pub fn param_range(&self) -> Range<usize> {
989        self.param_range.clone()
990    }
991
992    /// The last [`Parameter`] type that was parsed
993    ///
994    /// This can/should be used for argument completion.
995    pub fn last_parsed(&self) -> Vec<TypeId> {
996        self.last_parsed.iter().map(|(_, ty)| *ty).collect()
997    }
998
999    /// Declare that you are trying to parse the given [`Parameter`]
1000    /// type
1001    ///
1002    /// This is used on completions, where multiple pools of choices
1003    /// may be selected from in order to get the completions list to
1004    /// show.
1005    pub fn use_completions_for<P: Parameter>(&mut self) {
1006        self.last_parsed.push((self.arg_n, TypeId::of::<P>()));
1007    }
1008}
1009
1010/// An arguemnt that was passed to a command
1011#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1012pub struct Arg<'arg> {
1013    /// The `&str` of that argument
1014    pub value: &'arg str,
1015    /// Wether said argument was quoted
1016    ///
1017    /// This is useful for the [`Flag`] parameter, since it lets you
1018    /// distinguish between `--flag` and `"--flag"`, treating the
1019    /// latter as not a `Flag`
1020    pub is_quoted: bool,
1021}
1022
1023impl<'arg> std::ops::Deref for Arg<'arg> {
1024    type Target = &'arg str;
1025
1026    fn deref(&self) -> &Self::Target {
1027        &self.value
1028    }
1029}
1030
1031impl<'arg, 'other> PartialEq<&'other str> for Arg<'arg> {
1032    fn eq(&self, other: &&'other str) -> bool {
1033        &self.value == other
1034    }
1035}
1036
1037/// A iterator over arguments in a `&str`, useful for the [`cmd`]
1038/// module
1039///
1040/// [`cmd`]: super
1041#[derive(Clone)]
1042pub struct ArgsIter<'a> {
1043    command: &'a str,
1044    chars: std::str::CharIndices<'a>,
1045    start: Option<usize>,
1046    end: Option<usize>,
1047    is_quoting: bool,
1048    last_char: char,
1049}
1050
1051impl<'a> ArgsIter<'a> {
1052    /// Returns a new iterator over arguments in a `&str`
1053    pub fn new(command: &'a str) -> Self {
1054        let mut args_iter = Self {
1055            command,
1056            chars: command.char_indices(),
1057            start: None,
1058            end: None,
1059            is_quoting: false,
1060            // Initial value doesn't matter, as long as it's not '\' or '"'
1061            last_char: 'a',
1062        };
1063
1064        args_iter.next();
1065        args_iter
1066    }
1067}
1068
1069impl<'a> Iterator for ArgsIter<'a> {
1070    type Item = (&'a str, Range<usize>, bool);
1071
1072    fn next(&mut self) -> Option<Self::Item> {
1073        let mut is_quoted = false;
1074        while let Some((b, char)) = self.chars.next() {
1075            let lc = self.last_char;
1076            self.last_char = char;
1077            if self.start.is_some() && char.is_whitespace() && !self.is_quoting {
1078                self.end = Some(b);
1079                break;
1080            } else if char == '\'' && lc != '\\' {
1081                self.is_quoting = !self.is_quoting;
1082                if !self.is_quoting {
1083                    is_quoted = true;
1084                    self.end = Some(b);
1085                    break;
1086                } else {
1087                    self.start = Some(b + 1);
1088                }
1089            } else if !char.is_whitespace() && self.start.is_none() {
1090                self.start = Some(b);
1091            }
1092        }
1093
1094        let e = self.end.take().unwrap_or(self.command.len());
1095        self.start
1096            .take()
1097            .map(|s| (&self.command[s..e], s..e, is_quoted))
1098    }
1099}
1100
1101macro_rules! parse_impl {
1102    ($t:ty, $static_list:expr) => {
1103        impl Parameter for $t {
1104            fn new(_: &Pass, args: &mut Args) -> Result<(Self, Option<FormId>), Text> {
1105                let arg = args.next()?;
1106                let arg = arg.parse().map_err(|_| {
1107                    txt!(
1108                        "[a]{arg}[] couldn't be parsed as [param.info]{}[]",
1109                        stringify!($t)
1110                    )
1111                });
1112                arg.map(|arg| (arg, None))
1113            }
1114
1115            fn arg_name() -> Text {
1116                txt!("[param]{}", stringify!($t))
1117            }
1118        }
1119    };
1120}
1121
1122parse_impl!(bool, Some(&["true", "false"]));
1123parse_impl!(u8, None);
1124parse_impl!(u16, None);
1125parse_impl!(u32, None);
1126parse_impl!(u64, None);
1127parse_impl!(u128, None);
1128parse_impl!(usize, None);
1129parse_impl!(i8, None);
1130parse_impl!(i16, None);
1131parse_impl!(i32, None);
1132parse_impl!(i64, None);
1133parse_impl!(i128, None);
1134parse_impl!(isize, None);
1135parse_impl!(f32, None);
1136parse_impl!(f64, None);
1137parse_impl!(path, Some(&[]));
1138
1139#[allow(non_camel_case_types)]
1140type path = std::path::PathBuf;