1use serde::{Deserialize, Serialize};
5
6pub const MACRO_VERSION: u32 = 1;
8pub const MAX_MACROS: usize = 1_000;
10pub const MAX_MACRO_STEPS: usize = 10_000;
12pub const MAX_MACRO_DEPTH: usize = 4;
14pub const MAX_MACRO_NAME: usize = 255;
16pub const MAX_MACRO_TEXT: usize = 4_096;
18pub const MAX_MACRO_REF: usize = 1_024;
21pub const MAX_MACRO_SHORTCUT: usize = 64;
23pub const MAX_MACRO_PERMISSIONS: usize = 256;
25pub const MAX_MACRO_COLUMNS: usize = 16_384;
27
28#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33#[serde(default, rename_all = "camelCase")]
34pub struct Macro {
35 pub v: u32,
37 pub name: String,
38 pub description: String,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub shortcut: Option<String>,
42 pub inputs: Vec<NamedInput>,
44 pub steps: Vec<Step>,
45 pub output: OutputRule,
47 pub permissions: Vec<String>,
49}
50
51impl Default for Macro {
52 fn default() -> Self {
53 Macro {
54 v: MACRO_VERSION,
55 name: String::new(),
56 description: String::new(),
57 shortcut: None,
58 inputs: vec![],
59 steps: vec![],
60 output: OutputRule::default(),
61 permissions: vec![],
62 }
63 }
64}
65
66impl Macro {
67 pub fn step_count(&self) -> usize {
69 fn count(steps: &[Step]) -> usize {
70 steps.iter().map(|s| 1usize.saturating_add(s.kind.children().map(|b| count(b)).sum::<usize>())).sum()
71 }
72 count(&self.steps)
73 }
74
75 pub fn check_limits(&self) -> Result<(), String> {
83 let Macro { v, name, description, shortcut, inputs, steps, output, permissions } = self;
84 check_version(u64::from(*v))?;
85 let name = name.trim();
86 if name.is_empty() {
87 return Err("A macro needs a name.".into());
88 }
89 if name.chars().count() > MAX_MACRO_NAME {
90 return Err(format!("A macro name can be at most {MAX_MACRO_NAME} characters long."));
91 }
92 if name.chars().any(char::is_control) {
93 return Err("A macro name can't hold line breaks or other control characters.".into());
94 }
95 check_text("A macro description", description, MAX_MACRO_TEXT, false)?;
96 if let Some(shortcut) = shortcut {
97 check_text("A shortcut", shortcut, MAX_MACRO_SHORTCUT, true)?;
98 }
99 let OutputRule { anchor, on_conflict: _ } = output;
100 if let Some(anchor) = anchor {
101 check_text("The output anchor", anchor, MAX_MACRO_REF, true)?;
102 }
103 if permissions.len() > MAX_MACRO_PERMISSIONS {
104 return Err(format!("A macro can list at most {MAX_MACRO_PERMISSIONS} permissions."));
105 }
106 for p in permissions {
107 check_id("A permission", p)?;
108 }
109 fn walk(steps: &[Step], level: usize, count: &mut usize) -> Result<(), String> {
112 for s in steps {
113 *count = count.saturating_add(1);
114 if *count > MAX_MACRO_STEPS {
115 return Err(format!("A macro can have at most {MAX_MACRO_STEPS} steps."));
116 }
117 let Step { kind, enabled: _, breakpoint: _, note } = s;
118 check_text("A step note", note, MAX_MACRO_TEXT, false)?;
119 match kind {
120 StepKind::Command { id, params: _ } => check_id("A step's command", id)?,
121 StepKind::Repeat { over, of: _, max: _, body: _ } => check_text("A Repeat step's range", over, MAX_MACRO_REF, true)?,
122 StepKind::If { cell, op: _, value, then: _, otherwise: _ } => {
123 check_text("An If step's cell", cell, MAX_MACRO_REF, true)?;
124 if serde_json::to_string(value).map_or(usize::MAX, |t| t.chars().count()) > MAX_MACRO_TEXT {
126 return Err(format!("An If step's value can be at most {MAX_MACRO_TEXT} characters long."));
127 }
128 }
129 StepKind::Stop { message, rollback: _ } => check_text("A Stop message", message, MAX_MACRO_TEXT, false)?,
130 }
131 if kind.children().next().is_some() && level > MAX_MACRO_DEPTH {
132 return Err(format!("Repeat and If steps can nest at most {MAX_MACRO_DEPTH} deep."));
133 }
134 for body in kind.children() {
135 walk(body, level + 1, count)?;
136 }
137 }
138 Ok(())
139 }
140 walk(steps, 1, &mut 0)?;
141 if inputs.len() > 64 {
142 return Err("A macro can declare at most 64 inputs.".into());
143 }
144 for (k, i) in inputs.iter().enumerate() {
145 let NamedInput { name, kind, rows, cols, header, col_types, non_empty: _, default_ref } = i;
146 let n = name.as_str();
147 let ident = n.chars().next().is_some_and(char::is_alphabetic) && n.chars().all(|c| c.is_alphanumeric() || c == '_');
148 if !ident || n.chars().count() > MAX_MACRO_NAME {
149 return Err(format!("'{n}' can't name an input: use letters, digits and _, starting with a letter."));
150 }
151 if ["Output", "Row", "Column", "Name"].iter().any(|r| r.eq_ignore_ascii_case(n)) {
152 return Err(format!("'{n}' is a token of its own and can't name an input."));
153 }
154 if crate::base::RangeRef::parse(n).is_some() {
156 return Err(format!("'{n}' reads as a cell reference and can't name an input."));
157 }
158 if inputs.iter().take(k).any(|o| o.name.eq_ignore_ascii_case(n)) {
159 return Err(format!("Two inputs are called '{n}'."));
160 }
161 for (what, [least, most]) in [("rows", rows), ("columns", cols)] {
162 if *most != 0 && least > most {
163 return Err(format!("Input '{n}' allows {least} to {most} {what}: the least can't be more than the most."));
164 }
165 }
166 let one = |b: &[u32; 2]| *b == [1, 1] || *b == [1, 0];
168 if *kind == InputKind::Cell && (!one(rows) || !one(cols) || *header || col_types.len() > 1) {
169 return Err(format!("Input '{n}' is a single cell: one row and one column, no header, at most one column type."));
170 }
171 let most = if cols[1] == 0 { MAX_MACRO_COLUMNS } else { usize::try_from(cols[1]).unwrap_or(usize::MAX).min(MAX_MACRO_COLUMNS) };
172 if col_types.len() > most {
173 return Err(format!("Input '{n}' lists the types of {} columns, but has at most {most}.", col_types.len()));
174 }
175 if let Some(r) = default_ref {
176 check_text(&format!("The default range of input '{n}'"), r, MAX_MACRO_REF, true)?;
177 }
178 }
179 Ok(())
180 }
181}
182
183fn check_text(what: &str, text: &str, max: usize, one_line: bool) -> Result<(), String> {
186 if text.chars().count() > max {
187 return Err(format!("{what} can be at most {max} characters long."));
188 }
189 if one_line && text.chars().any(char::is_control) {
190 return Err(format!("{what} can't hold line breaks or other control characters."));
191 }
192 Ok(())
193}
194
195fn check_id(what: &str, id: &str) -> Result<(), String> {
198 let ok = !id.is_empty() && id.len() <= MAX_MACRO_NAME && id.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_');
199 if ok { Ok(()) } else { Err(format!("{what} must be a command id (letters, digits, . and _): '{}'", id.chars().take(64).collect::<String>())) }
200}
201
202#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
207#[serde(transparent)]
208pub struct Macros(std::sync::Arc<Vec<Macro>>);
209
210impl Macros {
211 pub fn is_empty(&self) -> bool {
212 self.0.is_empty()
213 }
214
215 pub fn shares(&self, other: &Macros) -> bool {
217 std::sync::Arc::ptr_eq(&self.0, &other.0)
218 }
219
220 pub fn estimated_bytes(&self) -> usize {
225 struct Count(usize);
227 impl std::io::Write for Count {
228 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
229 self.0 = self.0.saturating_add(buf.len());
230 Ok(buf.len())
231 }
232 fn flush(&mut self) -> std::io::Result<()> {
233 Ok(())
234 }
235 }
236 self.0.iter().fold(0usize, |bytes, m| {
237 let mut json = Count(0);
238 let _ = serde_json::to_writer(&mut json, m);
240 let steps = m.step_count().saturating_mul(std::mem::size_of::<Step>());
241 bytes.saturating_add(std::mem::size_of::<Macro>()).saturating_add(json.0).saturating_add(steps)
242 })
243 }
244}
245
246impl From<Vec<Macro>> for Macros {
247 fn from(list: Vec<Macro>) -> Macros {
248 Macros(std::sync::Arc::new(list))
249 }
250}
251
252impl std::ops::Deref for Macros {
253 type Target = Vec<Macro>;
254 fn deref(&self) -> &Vec<Macro> {
255 &self.0
256 }
257}
258
259impl std::ops::DerefMut for Macros {
260 fn deref_mut(&mut self) -> &mut Vec<Macro> {
261 std::sync::Arc::make_mut(&mut self.0)
262 }
263}
264
265impl IntoIterator for Macros {
266 type Item = Macro;
267 type IntoIter = std::vec::IntoIter<Macro>;
268 fn into_iter(self) -> Self::IntoIter {
269 std::sync::Arc::unwrap_or_clone(self.0).into_iter()
270 }
271}
272
273impl<'a> IntoIterator for &'a Macros {
274 type Item = &'a Macro;
275 type IntoIter = std::slice::Iter<'a, Macro>;
276 fn into_iter(self) -> Self::IntoIter {
277 self.0.iter()
278 }
279}
280
281impl FromIterator<Macro> for Macros {
282 fn from_iter<I: IntoIterator<Item = Macro>>(iter: I) -> Macros {
283 Macros(std::sync::Arc::new(iter.into_iter().collect()))
284 }
285}
286
287pub fn check_version(v: u64) -> Result<(), String> {
290 if v > u64::from(MACRO_VERSION) {
291 return Err(format!("This macro was made by a newer version of GridCraft (macro format {v}); this version reads format {MACRO_VERSION}."));
292 }
293 Ok(())
294}
295
296pub(crate) fn entries<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<serde_json::Value>, D::Error> {
299 Ok(match serde_json::Value::deserialize(d)? {
300 serde_json::Value::Array(list) => list,
301 serde_json::Value::Null => vec![],
302 other => vec![other],
303 })
304}
305
306#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
308#[serde(default, rename_all = "camelCase")]
309pub struct NamedInput {
310 pub name: String,
312 pub kind: InputKind,
313 pub rows: [u32; 2],
315 pub cols: [u32; 2],
317 pub header: bool,
319 pub col_types: Vec<ColType>,
321 pub non_empty: bool,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub default_ref: Option<String>,
326}
327
328impl Default for NamedInput {
329 fn default() -> Self {
330 NamedInput {
331 name: "Input".into(),
332 kind: InputKind::Range,
333 rows: [1, 0],
334 cols: [1, 0],
335 header: false,
336 col_types: vec![],
337 non_empty: false,
338 default_ref: None,
339 }
340 }
341}
342
343#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub enum InputKind {
346 Cell,
348 #[default]
349 Range,
350}
351
352#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub enum ColType {
356 Number,
357 Text,
358 Date,
360 Bool,
361 #[default]
362 Any,
363}
364
365impl ColType {
366 pub fn noun(self) -> &'static str {
368 match self {
369 ColType::Number => "numbers",
370 ColType::Text => "text",
371 ColType::Date => "dates",
372 ColType::Bool => "TRUE or FALSE",
373 ColType::Any => "anything",
374 }
375 }
376}
377
378#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
380pub struct Step {
381 #[serde(flatten)]
382 pub kind: StepKind,
383 #[serde(default = "yes", skip_serializing_if = "is_true")]
385 pub enabled: bool,
386 #[serde(default, skip_serializing_if = "is_false")]
388 pub breakpoint: bool,
389 #[serde(default, skip_serializing_if = "String::is_empty")]
390 pub note: String,
391}
392
393fn yes() -> bool {
394 true
395}
396fn is_true(b: &bool) -> bool {
397 *b
398}
399fn is_false(b: &bool) -> bool {
400 !*b
401}
402
403impl Step {
404 pub fn new(kind: StepKind) -> Step {
405 Step { kind, enabled: true, breakpoint: false, note: String::new() }
406 }
407
408 pub fn command(id: impl Into<String>, params: serde_json::Value) -> Step {
410 Step::new(StepKind::Command { id: id.into(), params })
411 }
412}
413
414#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
415#[serde(tag = "type", rename_all = "camelCase")]
416pub enum StepKind {
417 Command {
419 id: String,
420 #[serde(default)]
421 params: serde_json::Value,
422 },
423 Repeat {
426 over: String,
427 #[serde(default)]
428 of: RepeatOf,
429 #[serde(default = "repeat_max")]
430 max: u32,
431 #[serde(default)]
432 body: Vec<Step>,
433 },
434 If {
437 cell: String,
438 op: CompareOp,
439 #[serde(default)]
440 value: serde_json::Value,
441 #[serde(default)]
442 then: Vec<Step>,
443 #[serde(default, rename = "else")]
444 otherwise: Vec<Step>,
445 },
446 Stop {
448 #[serde(default)]
449 message: String,
450 #[serde(default, skip_serializing_if = "is_false")]
451 rollback: bool,
452 },
453}
454
455fn repeat_max() -> u32 {
456 10_000
457}
458
459impl StepKind {
460 pub fn children(&self) -> impl Iterator<Item = &Vec<Step>> {
462 let (a, b) = match self {
463 StepKind::Repeat { body, .. } => (Some(body), None),
464 StepKind::If { then, otherwise, .. } => (Some(then), Some(otherwise)),
465 _ => (None, None),
466 };
467 a.into_iter().chain(b)
468 }
469}
470
471#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub enum RepeatOf {
474 #[default]
475 Rows,
476 Columns,
477}
478
479#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub enum CompareOp {
482 Eq,
483 Ne,
484 Gt,
485 Ge,
486 Lt,
487 Le,
488 Empty,
490 NotEmpty,
491 Contains,
493}
494
495#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
497#[serde(default, rename_all = "camelCase")]
498pub struct OutputRule {
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub anchor: Option<String>,
502 pub on_conflict: OnConflict,
504}
505
506#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub enum OnConflict {
509 #[default]
511 Ask,
512 Fail,
513 Overwrite,
514 ShiftDown,
515 ShiftRight,
516}
517
518impl OnConflict {
519 pub fn parse(s: &str) -> Option<OnConflict> {
520 Some(match s.to_ascii_lowercase().replace(['_', '-', ' '], "").as_str() {
521 "ask" => OnConflict::Ask,
522 "fail" | "cancel" => OnConflict::Fail,
523 "overwrite" => OnConflict::Overwrite,
524 "shiftdown" => OnConflict::ShiftDown,
525 "shiftright" => OnConflict::ShiftRight,
526 _ => return None,
527 })
528 }
529
530 pub fn name(self) -> &'static str {
531 match self {
532 OnConflict::Ask => "ask",
533 OnConflict::Fail => "fail",
534 OnConflict::Overwrite => "overwrite",
535 OnConflict::ShiftDown => "shiftDown",
536 OnConflict::ShiftRight => "shiftRight",
537 }
538 }
539}