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 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}
82impl 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); }
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]; }
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 pub fn to_file_path(&self, ty: &ScriptType) -> Result<PathBuf> {
153 self.to_file_path_inner(ty, false).map(|t| t.0)
154 }
155 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 name: ScriptName,
231 pub tags: HashSet<Tag>,
232 pub ty: ScriptType,
233 pub created_time: ScriptTime,
234}
235#[derive(Debug, Deref, Clone)]
236pub struct ScriptInfo {
237 pub humble_time: Option<NaiveDateTime>,
238 pub read_time: ScriptTime,
239 pub write_time: ScriptTime,
240 pub neglect_time: Option<ScriptTime>,
241 pub exec_time: Option<ScriptTime<(String, String, String, Option<PathBuf>)>>,
243 pub exec_done_time: Option<ScriptTime<(i32, i64)>>,
245 pub exec_count: u64,
246 #[deref]
247 timeless_info: TimelessScriptInfo,
249}
250impl DerefMut for ScriptInfo {
251 fn deref_mut(&mut self) -> &mut Self::Target {
252 self.timeless_info.changed = true;
253 &mut self.timeless_info
254 }
255}
256
257fn map<T: Deref<Target = NaiveDateTime>>(time: &Option<T>) -> NaiveDateTime {
258 match time {
259 Some(time) => **time,
260 _ => Default::default(),
261 }
262}
263impl ScriptInfo {
264 pub fn set_id(&mut self, id: i64) {
265 assert_eq!(self.id, 0, "只有 id=0(代表新腳本)時可以設定 id");
266 self.timeless_info.id = id;
267 }
268 pub fn append_tags(&mut self, tags: TagSelector) {
269 if tags.append {
270 log::debug!("附加上標籤:{:?}", tags);
271 tags.fill_allowed_map(&mut self.tags);
272 } else {
273 log::debug!("設定標籤:{:?}", tags);
274 self.tags = tags.into_allowed_iter().collect();
275 }
276 }
277 pub fn cp(&self, new_name: ScriptName) -> Self {
278 let builder = ScriptInfo::builder(0, new_name, self.ty.clone(), self.tags.iter().cloned());
279 builder.build()
280 }
281 pub fn last_major_time(&self) -> NaiveDateTime {
283 max!(
284 *self.write_time,
285 map(&self.humble_time.as_ref()),
286 map(&self.exec_time),
287 map(&self.exec_done_time)
288 )
289 }
290 pub fn last_time(&self) -> NaiveDateTime {
292 max!(
293 *self.read_time,
294 *self.write_time,
295 map(&self.exec_time),
296 map(&self.exec_done_time)
297 )
298 }
299 pub fn file_path_fallback(&self) -> PathBuf {
300 self.name.to_file_path_fallback(&self.ty).0
301 }
302 pub fn read(&mut self) {
303 self.read_time = ScriptTime::now(());
304 }
305 pub fn write(&mut self) {
306 let now = ScriptTime::now(());
307 self.read_time = now.clone();
308 self.write_time = now;
309 }
310 pub fn exec(
311 &mut self,
312 content: String,
313 args: &[String],
314 env_record: String,
315 dir: Option<PathBuf>,
316 ) {
317 log::trace!("{:?} 執行內容為 {}", self, content);
318 let args_ser = serde_json::to_string(args).unwrap();
319 self.exec_time = Some(ScriptTime::now((content, args_ser, env_record, dir)));
320 self.exec_count += 1;
322 }
323 pub fn exec_done(&mut self, code: i32, main_event_id: i64) {
324 log::trace!("{:?} 執行結果為 {}", self, code);
325 self.exec_done_time = Some(ScriptTime::now((code, main_event_id)));
326 }
327 pub fn neglect(&mut self) {
328 self.neglect_time = Some(ScriptTime::now(()))
329 }
330 pub fn builder(
331 id: i64,
332 name: ScriptName,
333 ty: ScriptType,
334 tags: impl Iterator<Item = Tag>,
335 ) -> ScriptBuilder {
336 ScriptBuilder {
337 id,
338 name,
339 ty,
340 tags: tags.collect(),
341 read_time: None,
342 created_time: None,
343 exec_time: None,
344 write_time: None,
345 exec_done_time: None,
346 neglect_time: None,
347 humble_time: None,
348 exec_count: 0,
349 }
350 }
351}
352
353pub trait IntoScriptName {
354 fn into_script_name(self) -> Result<ScriptName>;
355 fn into_script_name_unchecked(self) -> Result<ScriptName>
356 where
357 Self: Sized,
358 {
359 self.into_script_name()
360 }
361}
362
363impl IntoScriptName for u32 {
364 fn into_script_name(self) -> Result<ScriptName> {
365 Ok(ScriptName::Anonymous(self))
366 }
367}
368impl IntoScriptName for ConcreteScriptName {
369 fn into_script_name(self) -> Result<ScriptName> {
370 Ok(ScriptName::Named(self))
371 }
372}
373#[inline]
374fn string_into_script_name(s: String, check: bool) -> Result<ScriptName> {
375 log::debug!("解析腳本名:{} {}", s, check);
376 if let Some(id) = ScriptName::valid(&s, false, false, check)? {
377 id.into_script_name()
378 } else {
379 Ok(ScriptName::Named(ConcreteScriptName::new_unchecked(s))) }
381}
382impl IntoScriptName for String {
383 fn into_script_name(self) -> Result<ScriptName> {
384 string_into_script_name(self, true)
385 }
386 fn into_script_name_unchecked(self) -> Result<ScriptName> {
387 string_into_script_name(self, false)
388 }
389}
390impl IntoScriptName for ScriptName {
391 fn into_script_name(self) -> Result<ScriptName> {
392 Ok(self)
393 }
394}
395
396#[derive(Debug)]
397pub struct ScriptBuilder {
398 pub name: ScriptName,
399 read_time: Option<NaiveDateTime>,
400 created_time: Option<NaiveDateTime>,
401 write_time: Option<NaiveDateTime>,
402 exec_time: Option<NaiveDateTime>,
403 neglect_time: Option<NaiveDateTime>,
404 humble_time: Option<NaiveDateTime>,
405 exec_done_time: Option<NaiveDateTime>,
406 exec_count: u64,
407 id: i64,
408 tags: HashSet<Tag>,
409 ty: ScriptType,
410}
411
412impl ScriptBuilder {
413 pub fn exec_count(&mut self, count: u64) -> &mut Self {
414 self.exec_count = count;
415 self
416 }
417 pub fn exec_time(&mut self, time: NaiveDateTime) -> &mut Self {
418 self.exec_time = Some(time);
419 self
420 }
421 pub fn exec_done_time(&mut self, time: NaiveDateTime) -> &mut Self {
422 self.exec_done_time = Some(time);
423 self
424 }
425 pub fn read_time(&mut self, time: NaiveDateTime) -> &mut Self {
426 self.read_time = Some(time);
427 self
428 }
429 pub fn write_time(&mut self, time: NaiveDateTime) -> &mut Self {
430 self.write_time = Some(time);
431 self
432 }
433 pub fn neglect_time(&mut self, time: NaiveDateTime) -> &mut Self {
434 self.neglect_time = Some(time);
435 self
436 }
437 pub fn humble_time(&mut self, time: NaiveDateTime) -> &mut Self {
438 self.humble_time = Some(time);
439 self
440 }
441 pub fn created_time(&mut self, time: NaiveDateTime) -> &mut Self {
442 self.created_time = Some(time);
443 self
444 }
445 pub fn build(self) -> ScriptInfo {
446 let created_time = ScriptTime::new_or(self.created_time, ScriptTime::now(()));
447 ScriptInfo {
448 write_time: ScriptTime::new_or(self.write_time, created_time),
449 read_time: ScriptTime::new_or(self.read_time, created_time),
450 exec_time: self.exec_time.map(ScriptTime::new),
451 exec_done_time: self.exec_done_time.map(ScriptTime::new),
452 neglect_time: self.neglect_time.map(ScriptTime::new),
453 humble_time: self.humble_time,
454 exec_count: self.exec_count,
455 timeless_info: TimelessScriptInfo {
456 changed: false,
457 id: self.id,
458 name: self.name,
459 ty: self.ty,
460 tags: self.tags,
461 created_time,
462 },
463 }
464 }
465}