duat_core/cmd/
parameters.rs

1//! Defines the processing of parameters in commands
2//!
3//! This processing first separates the [`Flags`] and [`Args`] of the
4//! call, and then transforms the list of arguments into a list of
5//! [`Parameter`]s, as defined by the command. Each [`Parameter`] may
6//! take multiple 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::{iter::Peekable, marker::PhantomData, ops::Range, path::PathBuf};
10
11use crossterm::style::Color;
12
13use crate::{
14    buffer::Buffer,
15    context::Handle,
16    data::Pass,
17    form::{self, FormId},
18    text::{Text, txt},
19};
20
21/// A parameter for commands that can be called
22///
23/// This parameter must be parseable from [`Args`], which come from a
24/// `&str`. It can take multiple words, and can be composed of other
25/// [`Parameter`]s. An example of this is the [`Form`], which is
26/// composed of multiple [`Color`] parameters, which are then composed
27/// of some format (rgb, hsl), which is then composed of more
28/// parameters, like rgb values, for example.
29///
30/// Other types of [`Parameter`] are just a "list" of other
31/// [`Parameter`]s. For example, [`Vec<P>`] can be used as a
32/// [`Parameter`] to capture any number of `P` arguments.
33/// Additionally, there is the [`Between<MIN, MAX, P>`], which is
34/// _like_ [`Vec<P>`], but takes at least `MIN` `P`s and at most `MAX`
35/// `P`s.
36///
37/// [`Form`]: crate::form::Form
38pub trait Parameter<'a>: Sized {
39    /// The type that is returned
40    type Returns;
41    /// Tries to consume arguments until forming a parameter
42    ///
43    /// Since parameters shouldn't mutate data, pa is just a regular
44    /// shared reference.
45    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text>;
46}
47
48impl<'a, P: Parameter<'a>> Parameter<'a> for Option<P> {
49    type Returns = Option<P::Returns>;
50
51    /// Will match either [`Parameter`] given, or nothing
52    ///
53    /// This, like other lists, _has_ to be the final argument in the
54    /// [`Parameter`] list, as it will either match correcly, finish
55    /// matching, or match incorrectly in order to give accurate
56    /// feedback.
57    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
58        match args.next_as::<P>(pa) {
59            Ok(arg) => Ok((Some(arg), None)),
60            Err(err) if args.is_forming_param => Err(err),
61            Err(_) => Ok((None, None)),
62        }
63    }
64}
65
66impl<'a, P: Parameter<'a>> Parameter<'a> for Vec<P> {
67    type Returns = Vec<P::Returns>;
68
69    /// Will match a list of [`Parameter`]s
70    ///
71    /// This, like other lists, _has_ to be the final argument in the
72    /// [`Parameter`] list, as it will either match correcly, finish
73    /// matching, or match incorrectly in order to give accurate
74    /// feedback.
75    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
76        let mut returns = Vec::new();
77
78        loop {
79            match args.next_as::<P>(pa) {
80                Ok(ret) => returns.push(ret),
81                Err(err) if args.is_forming_param => return Err(err),
82                Err(_) => break Ok((returns, None)),
83            }
84        }
85    }
86}
87
88impl<'a, const N: usize, P: Parameter<'a>> Parameter<'a> for [P; N] {
89    type Returns = [P::Returns; N];
90
91    /// Will match either the argument given, or nothing
92    ///
93    /// This, like other lists, _has_ to be the final argument in the
94    /// [`Parameter`] list, as it will either match correcly, finish
95    /// matching, or match incorrectly in order to give accurate
96    /// feedback.
97    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
98        use std::mem::MaybeUninit;
99        let mut returns = [const { MaybeUninit::uninit() }; N];
100
101        for r in returns.iter_mut() {
102            match args.next_as::<P>(pa) {
103                Ok(ret) => *r = MaybeUninit::new(ret),
104                Err(err) => return Err(err),
105            }
106        }
107
108        Ok((returns.map(|ret| unsafe { ret.assume_init() }), None))
109    }
110}
111
112/// Command [`Parameter`]: A list of between `MIN` and `MAX` items
113///
114/// This, like other lists, _has_ to be the final argument in the
115/// [`Parameter`] list, as it will either match correcly, finish
116/// matching, or match incorrectly in order to give accurate
117/// feedback.
118pub struct Between<const MIN: usize, const MAX: usize, P>(PhantomData<P>);
119
120impl<'a, const MIN: usize, const MAX: usize, P: Parameter<'a>> Parameter<'a>
121    for Between<MIN, MAX, P>
122{
123    type Returns = Vec<P::Returns>;
124
125    /// Will match between `MIN` and `MAX` [`Parameter`]s
126    ///
127    /// This, like other lists, _has_ to be the final argument in the
128    /// [`Parameter`] list, as it will either match correcly, finish
129    /// matching, or match incorrectly in order to give accurate
130    /// feedback.
131    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
132        let mut returns = Vec::new();
133
134        for _ in 0..MAX {
135            match args.next_as::<P>(pa) {
136                Ok(ret) => returns.push(ret),
137                Err(err) if args.is_forming_param => return Err(err),
138                Err(_) if returns.len() >= MIN => return Ok((returns, None)),
139                Err(err) => return Err(err),
140            }
141        }
142
143        if returns.len() >= MIN {
144            Ok((returns, None))
145        } else {
146            Err(txt!(
147                "List needed at least [a]{MIN}[] elements, got only [a]{}",
148                returns.len()
149            ))
150        }
151    }
152}
153
154impl<'a> Parameter<'a> for &'a str {
155    type Returns = &'a str;
156
157    fn new(_: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
158        args.next().map(|arg| (arg, None))
159    }
160}
161
162impl Parameter<'_> for String {
163    type Returns = String;
164
165    fn new(_: &Pass, args: &mut Args) -> Result<(Self::Returns, Option<FormId>), Text> {
166        Ok((args.next()?.to_string(), None))
167    }
168}
169
170/// Command [`Parameter`]: The remaining arguments, divided by a space
171///
172/// Fails if the [`String`] would be empty.
173pub struct Remainder;
174
175impl Parameter<'_> for Remainder {
176    type Returns = String;
177
178    fn new(_: &Pass, args: &mut Args) -> Result<(Self::Returns, Option<FormId>), Text> {
179        let remainder: String = std::iter::from_fn(|| args.next().ok())
180            .collect::<Vec<&str>>()
181            .join(" ");
182        if remainder.is_empty() {
183            Err(txt!("There are no more arguments"))
184        } else {
185            Ok((remainder, None))
186        }
187    }
188}
189
190/// Command [`Parameter`]: An existing [`ColorScheme`]'s name
191///
192/// [`ColorScheme`]: crate::form::ColorScheme
193pub struct ColorSchemeArg;
194
195impl<'a> Parameter<'a> for ColorSchemeArg {
196    type Returns = &'a str;
197
198    fn new(_: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
199        let scheme = args.next()?;
200        if crate::form::colorscheme_exists(scheme) {
201            Ok((scheme, None))
202        } else {
203            Err(txt!("The colorscheme [a]{scheme}[] was not found"))
204        }
205    }
206}
207
208impl<'a> Parameter<'a> for Buffer {
209    type Returns = Handle;
210
211    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
212        let buffer_name = args.next()?;
213        if let Some(handle) = crate::context::windows()
214            .buffers(pa)
215            .find(|handle| handle.read(pa).name() == buffer_name)
216        {
217            Ok((handle, Some(form::id_of!("param.buffer.open"))))
218        } else {
219            Err(txt!("No buffer called [a]{buffer_name}[] open"))
220        }
221    }
222}
223
224/// Command [`Parameter`]: An open [`Buffer`]'s name, except the
225/// current
226///
227/// [`Buffer`]: crate::buffer::Buffer
228pub struct OtherBuffer;
229
230impl<'a> Parameter<'a> for OtherBuffer {
231    type Returns = Handle;
232
233    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
234        let handle = args.next_as::<Buffer>(pa)?;
235        let cur_handle = crate::context::current_buffer(pa);
236        if *cur_handle == handle {
237            Err(txt!("Argument can't be the current buffer"))
238        } else {
239            Ok((handle, Some(form::id_of!("param.buffer.open"))))
240        }
241    }
242}
243
244/// Command [`Parameter`]: A [`Buffer`] whose parent is real
245///
246/// [`Buffer`]: crate::buffer::Buffer
247pub struct ValidBuffer;
248
249impl Parameter<'_> for ValidBuffer {
250    type Returns = PathBuf;
251
252    fn new(pa: &Pass, args: &mut Args) -> Result<(Self::Returns, Option<FormId>), Text> {
253        let path = args.next_as::<PathBuf>(pa)?;
254
255        let canon_path = path.canonicalize();
256        let path = if let Ok(path) = &canon_path {
257            if !path.is_file() {
258                return Err(txt!("Path is not a buffer"));
259            }
260            path.clone()
261        } else if canon_path.is_err()
262            && let Ok(canon_path) = path.with_file_name(".").canonicalize()
263        {
264            canon_path.join(
265                path.file_name()
266                    .ok_or_else(|| txt!("Path has no buffer name"))?,
267            )
268        } else {
269            return Err(txt!("Path was not found"));
270        };
271
272        if let Some(parent) = path.parent()
273            && let Ok(false) | Err(_) = parent.try_exists()
274        {
275            return Err(txt!("Path's parent doesn't exist"));
276        }
277
278        let form = if crate::context::windows()
279            .buffers(pa)
280            .map(|handle| handle.read(pa).path())
281            .any(|p| std::path::Path::new(&p) == path)
282        {
283            form::id_of!("param.buffer.open")
284        } else if let Ok(true) = path.try_exists() {
285            form::id_of!("param.buffer.exists")
286        } else {
287            form::id_of!("param.buffer")
288        };
289
290        Ok((path, Some(form)))
291    }
292}
293
294/// A [`ValidBuffer`] or `--opts` or `--opts-manifest`
295pub(super) enum PathOrBufferOrCfg {
296    Path(PathBuf),
297    Buffer(Handle),
298    Cfg,
299    CfgManifest,
300}
301
302impl Parameter<'_> for PathOrBufferOrCfg {
303    type Returns = Self;
304
305    fn new(pa: &Pass, args: &mut Args<'_>) -> Result<(Self::Returns, Option<FormId>), Text> {
306        if args.flags.word("cfg") {
307            Ok((Self::Cfg, None))
308        } else if args.flags.word("cfg-manifest") {
309            Ok((Self::CfgManifest, None))
310        } else if let Ok((handle, form)) = args.next_as_with_form::<Buffer>(pa) {
311            Ok((Self::Buffer(handle), form))
312        } else {
313            let (path, form) = args.next_as_with_form::<ValidBuffer>(pa)?;
314            Ok((Self::Path(path), form))
315        }
316    }
317}
318
319/// Command [`Parameter`]: An [`f32`] from a [`u8`] or a percentage
320///
321/// The percentage is of whole divisions of 100, 100 being equivalent
322/// to 255 in [`u8`].
323pub struct F32PercentOfU8;
324
325impl Parameter<'_> for F32PercentOfU8 {
326    type Returns = f32;
327
328    fn new(_: &Pass, args: &mut Args) -> Result<(Self::Returns, Option<FormId>), Text> {
329        let arg = args.next()?;
330        if let Some(percentage) = arg.strip_suffix("%") {
331            let percentage: u8 = percentage
332                .parse()
333                .map_err(|_| txt!("[a]{arg}[] is not a valid percentage"))?;
334            if percentage <= 100 {
335                Ok((percentage as f32 / 100.0, None))
336            } else {
337                Err(txt!("[a]{arg}[] is more than [a]100%"))
338            }
339        } else {
340            let byte: u8 = arg
341                .parse()
342                .map_err(|_| txt!("[a]{arg}[] couldn't be parsed"))?;
343            Ok((byte as f32 / 255.0, None))
344        }
345    }
346}
347
348impl<'a> Parameter<'a> for Color {
349    type Returns = Color;
350
351    fn new(pa: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
352        const fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
353            t = if t < 0.0 { t + 1.0 } else { t };
354            t = if t > 1.0 { t - 1.0 } else { t };
355            if t < 1.0 / 6.0 {
356                p + (q - p) * 6.0 * t
357            } else if t < 1.0 / 2.0 {
358                q
359            } else if t < 2.0 / 3.0 {
360                p + (q - p) * (2.0 / 3.0 - t) * 6.0
361            } else {
362                p
363            }
364        }
365
366        let arg = args.next()?;
367        // Expects "#{red:x}{green:x}{blue:x}"
368        if let Some(hex) = arg.strip_prefix("#") {
369            let total = match u32::from_str_radix(hex, 16) {
370                Ok(total) if hex.len() == 6 => total,
371                _ => return Err(txt!("Hexcode does not contain 6 hex values")),
372            };
373            let r = (total >> 16) as u8;
374            let g = (total >> 8) as u8;
375            let b = total as u8;
376            Ok((Color::Rgb { r, g, b }, None))
377            // Expects "rgb {red} {green} {blue}"
378        } else if arg == "rgb" {
379            let r = args.next_as::<u8>(pa)?;
380            let g = args.next_as::<u8>(pa)?;
381            let b = args.next_as::<u8>(pa)?;
382            Ok((Color::Rgb { r, g, b }, None))
383            // Expects "hsl {hue%?} {saturation%?} {lightness%?}"
384        } else if arg == "hsl" {
385            let hue = args.next_as::<F32PercentOfU8>(pa)?;
386            let sat = args.next_as::<F32PercentOfU8>(pa)?;
387            let lit = args.next_as::<F32PercentOfU8>(pa)?;
388            let [r, g, b] = if sat == 0.0 {
389                [lit.round() as u8; 3]
390            } else {
391                let q = if lit < 0.5 {
392                    lit * (1.0 + sat)
393                } else {
394                    lit + sat - lit * sat
395                };
396                let p = 2.0 * lit - q;
397                let r = hue_to_rgb(p, q, hue + 1.0 / 3.0);
398                let g = hue_to_rgb(p, q, hue);
399                let b = hue_to_rgb(p, q, hue - 1.0 / 3.0);
400                [r.round() as u8, g.round() as u8, b.round() as u8]
401            };
402            Ok((Color::Rgb { r, g, b }, None))
403        } else {
404            Err(txt!("Color format was not recognized"))
405        }
406    }
407}
408
409/// Command [`Parameter`]: The name of a [`Form`] that has been [set]
410///
411/// [set]: crate::form::set
412/// [`Form`]: crate::form::Form
413pub struct FormName;
414
415impl<'a> Parameter<'a> for FormName {
416    type Returns = &'a str;
417
418    fn new(_: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
419        let arg = args.next()?;
420        if !arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '.') {
421            return Err(txt!("Expected identifiers separated by '.'s, found [a]{arg}"));
422        }
423        if crate::form::exists(arg) {
424            Ok((arg, Some(form::id_of_non_static(arg))))
425        } else {
426            Err(txt!("The form [a]{arg}[] has not been set"))
427        }
428    }
429}
430
431impl<'a> Parameter<'a> for Flags<'a> {
432    type Returns = Flags<'a>;
433
434    fn new(_: &Pass, args: &mut Args<'a>) -> Result<(Self::Returns, Option<FormId>), Text> {
435        Ok((args.flags.clone(), None))
436    }
437}
438
439/// The list of arguments passed to a command
440///
441/// This list excludes [`Flags`], and separates arguments either by
442/// whitespace, or by non escaped double quotes.
443///
444/// ```rust
445/// # use duat_core::cmd;
446/// # fn test() {
447/// //                    cmd │      flags      │         arguments
448/// //                   ┌   ┐│┌               ┐│┌  ┐ ┌        ┐ ┌   ┐ ┌   ┐
449/// cmd::queue_notify(r#"mycmd --flags -moreflag arg1 "more arg" \"arg arg\""#);
450/// # }
451/// ```
452pub struct Args<'a> {
453    args: Peekable<ArgsIter<'a>>,
454    param_range: Range<usize>,
455    has_to_start_param: bool,
456    is_forming_param: bool,
457    flags: Flags<'a>,
458}
459
460impl<'a> Args<'a> {
461    /// Returns the next word or quoted argument
462    #[allow(clippy::should_implement_trait)]
463    pub fn next(&mut self) -> Result<&'a str, Text> {
464        match self.args.next() {
465            Some((arg, range)) => {
466                self.param_range = range.clone();
467                if self.has_to_start_param {
468                    self.has_to_start_param = false;
469                    self.is_forming_param = true;
470                }
471                Ok(arg)
472            }
473            None => Err(txt!("Wrong argument count")),
474        }
475    }
476
477    /// Tries to parse the next argument as `P`
478    ///
479    /// If parsing fails, [`Args`] will be reset as if this function
480    /// wasn't called.
481    pub fn next_as<P: Parameter<'a>>(&mut self, pa: &Pass) -> Result<P::Returns, Text> {
482        let initial_args = self.args.clone();
483        self.has_to_start_param = true;
484        let ret = P::new(pa, self);
485        if ret.is_ok() {
486            self.is_forming_param = false;
487        } else {
488            self.args = initial_args
489        }
490        ret.map(|(arg, _)| arg)
491    }
492
493    /// Tries to parse the next argument as `P`
494    ///
495    /// If parsing fails, [`Args`] will be reset as if this function
496    /// wasn't called.
497    pub fn next_as_with_form<P: Parameter<'a>>(
498        &mut self,
499        pa: &Pass,
500    ) -> Result<(P::Returns, Option<FormId>), Text> {
501        let initial_args = self.args.clone();
502        self.has_to_start_param = true;
503        let ret = P::new(pa, self);
504        if ret.is_ok() {
505            self.is_forming_param = false;
506        } else {
507            self.args = initial_args
508        }
509        ret
510    }
511
512    /// Tries to get the next argument, otherwise returns a [`Text`]
513    pub fn next_else<T: Into<Text>>(&mut self, to_text: T) -> Result<&'a str, Text> {
514        match self.args.next() {
515            Some((arg, _)) => Ok(arg),
516            None => Err(to_text.into()),
517        }
518    }
519
520    /// Returns the char position of the next argument
521    ///
522    /// Mostly used for error feedback by the [`PromptLine`]
523    ///
524    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
525    pub fn next_start(&mut self) -> Option<usize> {
526        self.args.peek().map(|(_, r)| r.start)
527    }
528
529    /// The range of the previous [`Parameter`]
530    ///
531    /// Mostly used for error feedback by the [`PromptLine`]
532    ///
533    /// [`PromptLine`]: docs.rs/duat/latest/duat/widgets/struct.PromptLine.html
534    pub fn param_range(&self) -> Range<usize> {
535        self.param_range.clone()
536    }
537
538    /// A private [`Clone`]
539    pub(super) fn clone(&self) -> Self {
540        Self {
541            args: self.args.clone(),
542            param_range: self.param_range.clone(),
543            has_to_start_param: self.has_to_start_param,
544            is_forming_param: self.is_forming_param,
545            flags: self.flags.clone(),
546        }
547    }
548}
549
550/// The flags passed to a command
551///
552/// They work just like flags on regular Linux commands, i.e., you
553/// have word flags, like `"--global"`, and glob flags, like `"-aBc"`.
554#[derive(Clone)]
555pub struct Flags<'a> {
556    blob: String,
557    word: Vec<&'a str>,
558}
559
560impl Flags<'_> {
561    /// Checks if all of the [`char`]s in the `blob` passed.
562    pub fn blob(&self, blob: impl AsRef<str>) -> bool {
563        let mut all_chars = true;
564        for char in blob.as_ref().chars() {
565            all_chars &= self.blob.contains(char);
566        }
567        all_chars
568    }
569
570    /// Returns `true` if the `word` flag was passed.
571    pub fn word(&self, flag: impl AsRef<str>) -> bool {
572        self.word.contains(&flag.as_ref())
573    }
574
575    /// Returns `true` if no flags have been passed.
576    pub fn is_empty(&self) -> bool {
577        self.blob.is_empty() && self.word.is_empty()
578    }
579}
580
581/// Splits a command into [`Args`] and [`Flags`]
582///
583/// [`Args`]: super::Args
584/// [`Flags`]: super::Flags
585pub fn get_args(command: &str) -> super::Args<'_> {
586    let mut blob = String::new();
587    let mut word = Vec::new();
588
589    let args = args_iter(command);
590    let mut args = args.peekable();
591    let mut byte = 0;
592
593    while let Some((arg, range)) = args.peek() {
594        if let Some(word_arg) = arg.strip_prefix("--") {
595            if !word_arg.is_empty() {
596                args.next();
597                if !word.contains(&word_arg) {
598                    word.push(word_arg)
599                }
600            } else {
601                args.next();
602                break;
603            }
604        } else if let Some(blob_arg) = arg.strip_prefix('-') {
605            args.next();
606            for char in blob_arg.chars() {
607                if !blob.contains(char) {
608                    blob.push(char)
609                }
610            }
611        } else {
612            byte = range.start;
613            break;
614        }
615    }
616
617    super::Args {
618        args,
619        param_range: byte..byte,
620        has_to_start_param: false,
621        is_forming_param: false,
622        flags: super::Flags { blob, word },
623    }
624}
625
626/// The [`Iterator`] over the [`Parameter`]s of the command
627#[define_opaque(ArgsIter)]
628pub fn args_iter(command: &str) -> ArgsIter<'_> {
629    let mut chars = command.char_indices();
630    let mut start = None;
631    let mut end = None;
632    let mut is_quoting = false;
633    // Initial value doesn't matter, as long as it's not '\'
634    let mut last_char = 'a';
635    let mut args: ArgsIter = std::iter::from_fn(move || {
636        while let Some((b, char)) = chars.next() {
637            let lc = last_char;
638            last_char = char;
639            if start.is_some() && char.is_whitespace() && !is_quoting {
640                end = Some(b);
641                break;
642            } else if char == '"' && lc != '\\' {
643                is_quoting = !is_quoting;
644                if !is_quoting {
645                    end = Some(b + 1);
646                    break;
647                } else {
648                    start = Some(b);
649                }
650            } else if !char.is_whitespace() && start.is_none() {
651                start = Some(b);
652            }
653        }
654
655        let e = end.take().unwrap_or(command.len());
656        start.take().map(|s| (&command[s..e], s..e))
657    });
658    args.next();
659    args
660}
661
662/// An [`Iterator`] over the arguments in a command call
663#[doc(hidden)]
664pub type ArgsIter<'a> = impl Iterator<Item = (&'a str, std::ops::Range<usize>)> + Clone;
665
666parse_impl!(bool);
667parse_impl!(u8);
668parse_impl!(u16);
669parse_impl!(u32);
670parse_impl!(u64);
671parse_impl!(u128);
672parse_impl!(usize);
673parse_impl!(i8);
674parse_impl!(i16);
675parse_impl!(i32);
676parse_impl!(i64);
677parse_impl!(i128);
678parse_impl!(isize);
679parse_impl!(f32);
680parse_impl!(f64);
681parse_impl!(std::path::PathBuf);
682
683macro parse_impl($t:ty) {
684    impl Parameter<'_> for $t {
685        type Returns = Self;
686
687        fn new(_: &Pass, args: &mut Args) -> Result<(Self::Returns, Option<FormId>), Text> {
688            let arg = args.next()?;
689            let arg = arg.parse().map_err(|_| {
690                txt!("[a]{arg}[] couldn't be parsed as [a]{}[]", stringify!($t))
691            });
692            arg.map(|arg| (arg, None))
693        }
694    }
695}