hyper_scripter/query/
mod.rs

1use crate::error::{
2    Contextable, DisplayError, DisplayResult,
3    FormatCode::{Regex as RegexCode, ScriptQuery as ScriptQueryCode},
4    Result,
5};
6use crate::script::{ConcreteScriptName, IntoScriptName, ScriptName};
7use crate::util::impl_ser_by_to_string;
8use regex::Regex;
9use std::num::NonZeroUsize;
10use std::str::FromStr;
11
12mod util;
13pub use util::*;
14mod range_query;
15pub use range_query::*;
16mod list_query_handler;
17mod the_multifuzz_algo;
18pub use list_query_handler::*;
19
20#[derive(Debug, Eq, PartialEq, Display)]
21pub enum EditQuery<Q> {
22    #[display(fmt = "?")]
23    NewAnonimous,
24    #[display(fmt = "{}", _0)]
25    Query(Q),
26}
27impl<Q: FromStr<Err = DisplayError>> FromStr for EditQuery<Q> {
28    type Err = DisplayError;
29    fn from_str(s: &str) -> DisplayResult<Self> {
30        Ok(if s == "?" {
31            EditQuery::NewAnonimous
32        } else {
33            EditQuery::Query(s.parse()?)
34        })
35    }
36}
37impl_ser_by_to_string!(EditQuery<ScriptOrDirQuery>);
38impl_ser_by_to_string!(EditQuery<ListQuery>);
39
40#[derive(Debug, Display, Clone)]
41pub enum DirQuery {
42    #[display(fmt = "/")]
43    Root,
44    #[display(fmt = "{}/", _0)]
45    NonRoot(ConcreteScriptName),
46}
47impl DirQuery {
48    /// 接上另一個 `ScriptName`
49    ///
50    /// ```
51    /// use hyper_scripter::query::*;
52    /// use hyper_scripter::script::*;
53    ///
54    /// let root = DirQuery::Root;
55    /// let joined = root.clone().join(&".42".to_owned().into_script_name().unwrap());
56    /// assert_eq!(joined.to_string(), "42");
57    ///
58    /// let joined = root.join(&"a/b/c".to_owned().into_script_name().unwrap());
59    /// assert_eq!(joined.to_string(), "c");
60    ///
61    /// let dir = DirQuery::NonRoot(ConcreteScriptName::new("dir".into()).unwrap());
62    /// let joined = dir.clone().join(&".42".to_owned().into_script_name().unwrap());
63    /// assert_eq!(joined.to_string(), "dir/42");
64    ///
65    /// let joined = dir.join(&"a/b/c".to_owned().into_script_name().unwrap());
66    /// assert_eq!(joined.to_string(), "dir/c");
67    /// ```
68    pub fn join(self, other: &ScriptName) -> ConcreteScriptName {
69        match (self, other) {
70            (Self::Root, ScriptName::Anonymous(id)) => ConcreteScriptName::new_id(*id),
71            (Self::Root, ScriptName::Named(n)) => n.stem(),
72            (Self::NonRoot(mut dir), ScriptName::Anonymous(id)) => {
73                dir.join_id(*id);
74                dir
75            }
76            (Self::NonRoot(mut dir), ScriptName::Named(n)) => {
77                dir.join(n);
78                dir
79            }
80        }
81    }
82}
83
84#[derive(Debug, Display)]
85pub enum ScriptOrDirQuery {
86    #[display(fmt = "{}", _0)]
87    Script(ScriptName),
88    #[display(fmt = "{}", _0)]
89    Dir(DirQuery),
90}
91impl FromStr for ScriptOrDirQuery {
92    type Err = DisplayError;
93    fn from_str(s: &str) -> DisplayResult<Self> {
94        Ok(if s == "/" {
95            ScriptOrDirQuery::Dir(DirQuery::Root)
96        } else if s.ends_with('/') {
97            let s = &s[0..s.len() - 1];
98            ScriptOrDirQuery::Dir(DirQuery::NonRoot(ConcreteScriptName::new(s.into())?))
99        } else {
100            ScriptOrDirQuery::Script(s.parse()?)
101        })
102    }
103}
104impl_ser_by_to_string!(ScriptOrDirQuery);
105
106#[derive(Debug, Display)]
107pub enum ListQuery {
108    #[display(fmt = "{}", _1)]
109    Pattern(Regex, String, bool),
110    #[display(fmt = "{}", _0)]
111    Query(ScriptQuery),
112}
113impl FromStr for ListQuery {
114    type Err = DisplayError;
115    fn from_str(s: &str) -> DisplayResult<Self> {
116        if s.contains('*') {
117            // TODO: 好好檢查
118            let s = s.to_owned();
119            let re = s.replace(".", r"\.");
120            let re = re.replace("*", ".*");
121            let (re, bang) = if re.ends_with('!') {
122                (&re[0..re.len() - 1], true)
123            } else {
124                (&re[..], false)
125            };
126            match Regex::new(&format!("^{re}$",)) {
127                Ok(re) => Ok(ListQuery::Pattern(re, s, bang)),
128                Err(e) => {
129                    log::error!("正規表達式錯誤:{}", e);
130                    RegexCode.to_display_res(s)
131                }
132            }
133        } else {
134            Ok(ListQuery::Query(s.parse()?))
135        }
136    }
137}
138impl_ser_by_to_string!(ListQuery);
139
140#[derive(Debug, Clone, Eq, PartialEq)]
141pub struct ScriptQuery {
142    inner: ScriptQueryInner,
143    bang: bool,
144}
145impl Default for ScriptQuery {
146    fn default() -> Self {
147        ScriptQuery {
148            inner: ScriptQueryInner::Prev(none0_usize(1)),
149            bang: false,
150        }
151    }
152}
153impl std::fmt::Display for ScriptQuery {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        match &self.inner {
156            ScriptQueryInner::Fuzz(fuzz) => write!(f, "{}", fuzz),
157            ScriptQueryInner::Exact(e) => write!(f, "={}", e),
158            ScriptQueryInner::Prev(p) => write!(f, "^{}", p),
159        }?;
160        if self.bang {
161            write!(f, "!")?;
162        }
163        Ok(())
164    }
165}
166impl_ser_by_to_string!(ScriptQuery);
167
168#[derive(Debug, Clone, Eq, PartialEq)]
169enum ScriptQueryInner {
170    Fuzz(String),
171    Exact(ScriptName),
172    Prev(NonZeroUsize),
173}
174impl IntoScriptName for ScriptQuery {
175    fn into_script_name(self) -> Result<ScriptName> {
176        match self.inner {
177            ScriptQueryInner::Fuzz(s) => s.into_script_name(),
178            ScriptQueryInner::Exact(name) => Ok(name),
179            _ => panic!("歷史查詢沒有名字"),
180        }
181    }
182}
183
184fn none0_usize(n: usize) -> NonZeroUsize {
185    NonZeroUsize::new(n).unwrap()
186}
187fn parse_prev(s: &str) -> Result<NonZeroUsize> {
188    // NOTE: 解析 `^^^^ = Prev(4)`
189    let mut is_pure_prev = true;
190    for ch in s.chars() {
191        if ch != '^' {
192            is_pure_prev = false;
193            break;
194        }
195    }
196    if is_pure_prev {
197        return Ok(none0_usize(s.len()));
198    }
199    // NOTE: 解析 `^4 = Prev(4)`
200    match s[1..s.len()].parse::<NonZeroUsize>() {
201        Ok(prev) => Ok(prev),
202        Err(e) => ScriptQueryCode
203            .to_res(s.to_owned())
204            .context(format!("解析整數錯誤:{}", e)),
205    }
206}
207impl FromStr for ScriptQuery {
208    type Err = DisplayError;
209    fn from_str(mut s: &str) -> DisplayResult<Self> {
210        let bang = if s.ends_with('!') {
211            if s == "!" {
212                return Ok(ScriptQuery {
213                    inner: ScriptQueryInner::Prev(none0_usize(1)),
214                    bang: true,
215                });
216            }
217            s = &s[..s.len() - 1];
218            true
219        } else {
220            false
221        };
222        let inner = if s.starts_with('=') {
223            s = &s[1..s.len()];
224            let name = s.to_owned().into_script_name()?;
225            ScriptQueryInner::Exact(name)
226        } else if s == "-" {
227            ScriptQueryInner::Prev(none0_usize(1))
228        } else if s.starts_with('^') {
229            ScriptQueryInner::Prev(parse_prev(s)?)
230        } else {
231            ScriptName::valid(s, true, true, true).context("模糊搜尋仍需符合腳本名格式!")?; // NOTE: 單純檢查用
232            ScriptQueryInner::Fuzz(s.to_owned())
233        };
234        Ok(ScriptQuery { inner, bang })
235    }
236}