hyper_scripter/
script.rs

1use crate::config::Config;
2use crate::error::{
3    Contextable, DisplayError, DisplayResult, Error, FormatCode::ScriptName as ScriptNameCode,
4    Result,
5};
6use crate::script_time::ScriptTime;
7use crate::script_type::ScriptType;
8use crate::tag::{Tag, TagSelector};
9use crate::util::illegal_name;
10use chrono::NaiveDateTime;
11use fxhash::FxHashSet as HashSet;
12use std::borrow::Cow;
13use std::cmp::Ordering;
14use std::fmt::Write;
15use std::ops::{Deref, DerefMut};
16use std::path::PathBuf;
17use std::str::FromStr;
18
19pub const ANONYMOUS: &str = ".anonymous";
20
21macro_rules! max {
22    ($x:expr) => ( $x );
23    ($x:expr, $($xs:expr),+) => {
24        {
25            use std::cmp::max;
26            max($x, max!( $($xs),+ ))
27        }
28    };
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Deref, Display, Hash)]
32#[display(fmt = "{}", inner)]
33pub struct ConcreteScriptName {
34    #[deref]
35    inner: String,
36}
37impl ConcreteScriptName {
38    fn valid(s: &str) -> Result {
39        // FIXME: 好好想想什麼樣的腳本名可行,並補上單元測試
40        for s in s.split('/') {
41            if illegal_name(s) {
42                return ScriptNameCode.to_res(s.to_owned());
43            }
44        }
45        Ok(())
46    }
47    pub fn new(s: String) -> Result<Self> {
48        Self::valid(&s)?;
49        Ok(ConcreteScriptName { inner: s })
50    }
51    pub fn new_id(id: u32) -> Self {
52        ConcreteScriptName {
53            inner: id.to_string(),
54        }
55    }
56    fn new_unchecked(s: String) -> Self {
57        ConcreteScriptName { inner: s }
58    }
59    fn stem_inner(&self) -> &str {
60        if let Some((_, stem)) = self.inner.rsplit_once('/') {
61            stem
62        } else {
63            &self.inner
64        }
65    }
66    pub fn stem(&self) -> ConcreteScriptName {
67        Self::new_unchecked(self.stem_inner().to_owned())
68    }
69    pub fn join(&mut self, other: &ConcreteScriptName) {
70        write!(&mut self.inner, "/{}", other.stem_inner()).unwrap();
71    }
72    pub fn join_id(&mut self, other: u32) {
73        write!(&mut self.inner, "/{}", other).unwrap();
74    }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub enum ScriptName {
79    Anonymous(u32),
80    Named(ConcreteScriptName),
81}
82/// ```
83/// use hyper_scripter::script::*;
84///
85/// let name: ScriptName = ".42".parse().unwrap();
86/// assert_eq!(name, ScriptName::Anonymous(42));
87///
88/// let name: ScriptName = "name".parse().unwrap();
89/// assert_eq!(name.to_string(), "name");
90///
91/// let res: Result<ScriptName, _> = ".0".parse();
92/// res.unwrap_err();
93/// ```
94impl FromStr for ScriptName {
95    type Err = DisplayError;
96    fn from_str(s: &str) -> DisplayResult<Self> {
97        let n = s.to_owned().into_script_name()?;
98        Ok(n)
99    }
100}
101impl ScriptName {
102    #[inline]
103    pub fn valid(
104        mut s: &str,
105        allow_endwith_slash: bool,
106        allow_dot: bool,
107        check: bool,
108    ) -> Result<Option<u32>> {
109        log::debug!("檢查腳本名:{}", s);
110
111        if s.starts_with('.') {
112            if s.len() == 1 && allow_dot {
113                log::info!("特殊規則:允許單一個`.`");
114                return Ok(None); // NOTE: 讓匿名腳本可以直接用 `.` 來搜
115            }
116            match s[1..].parse::<std::num::NonZeroU32>() {
117                Ok(id) => Ok(Some(id.get())),
118                Err(e) => ScriptNameCode.to_res(s.to_owned()).context(e),
119            }
120        } else if check {
121            if s.ends_with('/') && allow_endwith_slash {
122                log::info!("特殊規則:允許以`/`結尾");
123                s = &s[..s.len() - 1]; // NOTE: 有了補全,很容易補出帶著`/`結尾的指令,放寬標準吧
124            }
125            ConcreteScriptName::valid(&s)?;
126            Ok(None)
127        } else {
128            Ok(None)
129        }
130    }
131    pub fn namespaces(&self) -> Vec<&'_ str> {
132        match self {
133            ScriptName::Anonymous(_) => vec![],
134            ScriptName::Named(s) => {
135                let mut v: Vec<_> = s.split('/').collect();
136                v.pop();
137                v
138            }
139        }
140    }
141    pub fn is_anonymous(&self) -> bool {
142        log::debug!("判斷是否為匿名:{:?}", self);
143        matches!(self, ScriptName::Anonymous(_))
144    }
145    pub fn key(&self) -> Cow<'_, str> {
146        match self {
147            ScriptName::Anonymous(id) => Cow::Owned(format!(".{}", id)),
148            ScriptName::Named(s) => Cow::Borrowed(s),
149        }
150    }
151    /// 回傳值是相對於 HS_HOME 的路徑
152    pub fn to_file_path(&self, ty: &ScriptType) -> Result<PathBuf> {
153        self.to_file_path_inner(ty, false).map(|t| t.0)
154    }
155    /// 回傳值是相對於 HS_HOME 的路徑,對未知的類別直接用類別名作擴展名
156    pub fn to_file_path_fallback(&self, ty: &ScriptType) -> (PathBuf, Option<Error>) {
157        self.to_file_path_inner(ty, true).unwrap()
158    }
159    fn to_file_path_inner(
160        &self,
161        ty: &ScriptType,
162        fallback: bool,
163    ) -> Result<(PathBuf, Option<Error>)> {
164        fn add_ext(
165            name: &mut String,
166            ty: &ScriptType,
167            fallback: bool,
168            err: &mut Option<Error>,
169        ) -> Result {
170            let ext = match Config::get().get_script_conf(ty) {
171                Err(e) => {
172                    if !fallback {
173                        return Err(e);
174                    }
175                    log::warn!(
176                        "取腳本路徑時找不到類別設定:{},直接把類別名當擴展名試試",
177                        e,
178                    );
179                    *err = Some(e);
180                    Some(ty.as_ref())
181                }
182                Ok(c) => c.ext.as_ref().map(|s| s.as_ref()),
183            };
184            if let Some(ext) = ext {
185                write!(name, ".{}", ext).unwrap();
186            }
187            Ok(())
188        }
189        let mut err = None;
190        match self {
191            ScriptName::Anonymous(id) => {
192                let mut file_name = id.to_string();
193                let dir: PathBuf = ANONYMOUS.into();
194                add_ext(&mut file_name, ty, fallback, &mut err)?;
195                Ok((dir.join(file_name), err))
196            }
197            ScriptName::Named(name) => {
198                let mut file_name = name.to_string();
199                add_ext(&mut file_name, ty, fallback, &mut err)?;
200                Ok((file_name.into(), err))
201            }
202        }
203    }
204}
205impl PartialOrd for ScriptName {
206    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
207        match (self, other) {
208            (ScriptName::Named(n1), ScriptName::Named(n2)) => n1.inner.partial_cmp(&n2.inner),
209            (ScriptName::Anonymous(i1), ScriptName::Anonymous(i2)) => i1.partial_cmp(i2),
210            (ScriptName::Named(_), ScriptName::Anonymous(_)) => Some(Ordering::Less),
211            (ScriptName::Anonymous(_), ScriptName::Named(_)) => Some(Ordering::Greater),
212        }
213    }
214}
215impl Ord for ScriptName {
216    fn cmp(&self, other: &Self) -> Ordering {
217        self.partial_cmp(other).unwrap()
218    }
219}
220impl std::fmt::Display for ScriptName {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        write!(f, "{}", self.key())
223    }
224}
225
226#[derive(Debug, Clone)]
227pub struct TimelessScriptInfo {
228    pub changed: bool,
229    pub id: i64,
230    pub hash: i64,
231    pub name: ScriptName,
232    pub tags: HashSet<Tag>,
233    pub ty: ScriptType,
234    pub created_time: ScriptTime,
235}
236#[derive(Debug, Deref, Clone)]
237pub struct ScriptInfo {
238    pub humble_time: Option<NaiveDateTime>,
239    pub read_time: ScriptTime,
240    pub write_time: ScriptTime,
241    pub neglect_time: Option<ScriptTime>,
242    /// (content, args, env_record, dir)
243    pub exec_time: Option<ScriptTime<(String, String, String, Option<PathBuf>)>>,
244    /// (return code, main event id)
245    pub exec_done_time: Option<ScriptTime<(i32, i64)>>,
246    pub exec_count: u64,
247    #[deref]
248    /// 用來區隔「時間資料」和「其它元資料」,並偵測其它元資料的修改
249    timeless_info: TimelessScriptInfo,
250}
251impl DerefMut for ScriptInfo {
252    fn deref_mut(&mut self) -> &mut Self::Target {
253        self.timeless_info.changed = true;
254        &mut self.timeless_info
255    }
256}
257
258fn map<T: Deref<Target = NaiveDateTime>>(time: &Option<T>) -> NaiveDateTime {
259    match time {
260        Some(time) => **time,
261        _ => Default::default(),
262    }
263}
264impl ScriptInfo {
265    pub fn set_id(&mut self, id: i64) {
266        assert_eq!(self.id, 0, "只有 id=0(代表新腳本)時可以設定 id");
267        self.timeless_info.id = id;
268    }
269    pub fn append_tags(&mut self, tags: TagSelector) {
270        if tags.append {
271            log::debug!("附加上標籤:{:?}", tags);
272            tags.fill_allowed_map(&mut self.tags);
273        } else {
274            log::debug!("設定標籤:{:?}", tags);
275            self.tags = tags.into_allowed_iter().collect();
276        }
277    }
278    pub fn cp(&self, new_name: ScriptName) -> Self {
279        let builder = ScriptInfo::builder(
280            0,
281            self.hash,
282            new_name,
283            self.ty.clone(),
284            self.tags.iter().cloned(),
285        );
286        builder.build()
287    }
288    /// `major time` 即不包含 `read` 事件的時間,但包含 `humble`
289    pub fn last_major_time(&self) -> NaiveDateTime {
290        max!(
291            *self.write_time,
292            map(&self.humble_time.as_ref()),
293            map(&self.exec_time),
294            map(&self.exec_done_time)
295        )
296    }
297    /// 不包含 `humble`
298    pub fn last_time(&self) -> NaiveDateTime {
299        max!(
300            *self.read_time,
301            *self.write_time,
302            map(&self.exec_time),
303            map(&self.exec_done_time)
304        )
305    }
306    pub fn file_path_fallback(&self) -> PathBuf {
307        self.name.to_file_path_fallback(&self.ty).0
308    }
309    pub fn read(&mut self) {
310        self.read_time = ScriptTime::now(());
311    }
312    pub fn write(&mut self) {
313        let now = ScriptTime::now(());
314        self.read_time = now.clone();
315        self.write_time = now;
316    }
317    pub fn exec(
318        &mut self,
319        content: String,
320        args: &[String],
321        env_record: String,
322        dir: Option<PathBuf>,
323    ) {
324        log::trace!("{:?} 執行內容為 {}", self, content);
325        let args_ser = serde_json::to_string(args).unwrap();
326        self.exec_time = Some(ScriptTime::now((content, args_ser, env_record, dir)));
327        // NOTE: no readtime, otherwise it will be hard to tell what event was caused by what operation.
328        self.exec_count += 1;
329    }
330    pub fn exec_done(&mut self, code: i32, main_event_id: i64) {
331        log::trace!("{:?} 執行結果為 {}", self, code);
332        self.exec_done_time = Some(ScriptTime::now((code, main_event_id)));
333    }
334    pub fn neglect(&mut self) {
335        self.neglect_time = Some(ScriptTime::now(()))
336    }
337    pub fn builder(
338        id: i64,
339        hash: i64,
340        name: ScriptName,
341        ty: ScriptType,
342        tags: impl Iterator<Item = Tag>,
343    ) -> ScriptBuilder {
344        ScriptBuilder {
345            id,
346            hash,
347            name,
348            ty,
349            tags: tags.collect(),
350            read_time: None,
351            created_time: None,
352            exec_time: None,
353            write_time: None,
354            exec_done_time: None,
355            neglect_time: None,
356            humble_time: None,
357            exec_count: 0,
358        }
359    }
360}
361
362pub trait IntoScriptName {
363    fn into_script_name(self) -> Result<ScriptName>;
364    fn into_script_name_unchecked(self) -> Result<ScriptName>
365    where
366        Self: Sized,
367    {
368        self.into_script_name()
369    }
370}
371
372impl IntoScriptName for u32 {
373    fn into_script_name(self) -> Result<ScriptName> {
374        Ok(ScriptName::Anonymous(self))
375    }
376}
377impl IntoScriptName for ConcreteScriptName {
378    fn into_script_name(self) -> Result<ScriptName> {
379        Ok(ScriptName::Named(self))
380    }
381}
382#[inline]
383fn string_into_script_name(s: String, check: bool) -> Result<ScriptName> {
384    log::debug!("解析腳本名:{} {}", s, check);
385    if let Some(id) = ScriptName::valid(&s, false, false, check)? {
386        id.into_script_name()
387    } else {
388        Ok(ScriptName::Named(ConcreteScriptName::new_unchecked(s))) // NOTE: already checked by `ScriptName::valid`
389    }
390}
391impl IntoScriptName for String {
392    fn into_script_name(self) -> Result<ScriptName> {
393        string_into_script_name(self, true)
394    }
395    fn into_script_name_unchecked(self) -> Result<ScriptName> {
396        string_into_script_name(self, false)
397    }
398}
399impl IntoScriptName for ScriptName {
400    fn into_script_name(self) -> Result<ScriptName> {
401        Ok(self)
402    }
403}
404
405#[derive(Debug)]
406pub struct ScriptBuilder {
407    pub name: ScriptName,
408    read_time: Option<NaiveDateTime>,
409    created_time: Option<NaiveDateTime>,
410    write_time: Option<NaiveDateTime>,
411    exec_time: Option<NaiveDateTime>,
412    neglect_time: Option<NaiveDateTime>,
413    humble_time: Option<NaiveDateTime>,
414    exec_done_time: Option<NaiveDateTime>,
415    exec_count: u64,
416
417    hash: i64,
418    id: i64,
419    tags: HashSet<Tag>,
420    ty: ScriptType,
421}
422
423impl ScriptBuilder {
424    pub fn exec_count(&mut self, count: u64) -> &mut Self {
425        self.exec_count = count;
426        self
427    }
428    pub fn exec_time(&mut self, time: NaiveDateTime) -> &mut Self {
429        self.exec_time = Some(time);
430        self
431    }
432    pub fn exec_done_time(&mut self, time: NaiveDateTime) -> &mut Self {
433        self.exec_done_time = Some(time);
434        self
435    }
436    pub fn read_time(&mut self, time: NaiveDateTime) -> &mut Self {
437        self.read_time = Some(time);
438        self
439    }
440    pub fn write_time(&mut self, time: NaiveDateTime) -> &mut Self {
441        self.write_time = Some(time);
442        self
443    }
444    pub fn neglect_time(&mut self, time: NaiveDateTime) -> &mut Self {
445        self.neglect_time = Some(time);
446        self
447    }
448    pub fn humble_time(&mut self, time: NaiveDateTime) -> &mut Self {
449        self.humble_time = Some(time);
450        self
451    }
452    pub fn created_time(&mut self, time: NaiveDateTime) -> &mut Self {
453        self.created_time = Some(time);
454        self
455    }
456    pub fn build(self) -> ScriptInfo {
457        let created_time = ScriptTime::new_or(self.created_time, ScriptTime::now(()));
458        ScriptInfo {
459            write_time: ScriptTime::new_or(self.write_time, created_time),
460            read_time: ScriptTime::new_or(self.read_time, created_time),
461            exec_time: self.exec_time.map(ScriptTime::new),
462            exec_done_time: self.exec_done_time.map(ScriptTime::new),
463            neglect_time: self.neglect_time.map(ScriptTime::new),
464            humble_time: self.humble_time,
465            exec_count: self.exec_count,
466            timeless_info: TimelessScriptInfo {
467                changed: false,
468                id: self.id,
469                hash: self.hash,
470                name: self.name,
471                ty: self.ty,
472                tags: self.tags,
473                created_time,
474            },
475        }
476    }
477}