1use crate::executor::{AGENT, HUMAN, RULE};
8use serde_yaml::{Mapping, Value as Yaml};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum RuleKind {
12 Path,
13 Absent,
14 Contains,
15 Run,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Criterion {
24 PathExists {
25 path: String,
26 description: String,
27 },
28 PathAbsent {
29 absent: String,
30 description: String,
31 },
32 FileContains {
33 file: String,
34 contains: String,
35 description: String,
36 },
37 CommandRun {
38 run: String,
39 description: String,
40 },
41 AgentJudgement {
42 description: String,
43 },
44 HumanGate {
45 description: String,
46 },
47}
48
49impl Criterion {
50 pub fn executor(&self) -> &'static str {
52 match self {
53 Criterion::PathExists { .. }
54 | Criterion::PathAbsent { .. }
55 | Criterion::FileContains { .. }
56 | Criterion::CommandRun { .. } => RULE,
57 Criterion::AgentJudgement { .. } => AGENT,
58 Criterion::HumanGate { .. } => HUMAN,
59 }
60 }
61
62 pub fn description(&self) -> &str {
64 match self {
65 Criterion::PathExists { description, .. }
66 | Criterion::PathAbsent { description, .. }
67 | Criterion::FileContains { description, .. }
68 | Criterion::CommandRun { description, .. }
69 | Criterion::AgentJudgement { description }
70 | Criterion::HumanGate { description } => description,
71 }
72 }
73
74 pub fn text(&self) -> String {
76 if !self.description().is_empty() {
77 return self.description().to_string();
78 }
79 match self {
80 Criterion::PathExists { path, .. } => format!("存在:{path}"),
81 Criterion::PathAbsent { absent, .. } => format!("不存在:{absent}"),
82 Criterion::FileContains { file, contains, .. } => {
83 format!("含「{contains}」:{file}")
84 }
85 Criterion::CommandRun { run, .. } => format!("跑通:{run}"),
86 Criterion::AgentJudgement { .. } | Criterion::HumanGate { .. } => String::new(),
87 }
88 }
89
90 pub fn to_yaml(&self) -> Yaml {
92 let mut pairs: Vec<(&str, &str)> = Vec::new();
93 match self {
94 Criterion::PathExists { path, description } => {
95 pairs.push(("executor", RULE));
96 pairs.push(("path", path));
97 if !description.is_empty() {
98 pairs.push(("description", description));
99 }
100 }
101 Criterion::PathAbsent {
102 absent,
103 description,
104 } => {
105 pairs.push(("executor", RULE));
106 pairs.push(("absent", absent));
107 if !description.is_empty() {
108 pairs.push(("description", description));
109 }
110 }
111 Criterion::FileContains {
112 file,
113 contains,
114 description,
115 } => {
116 pairs.push(("executor", RULE));
117 pairs.push(("file", file));
118 pairs.push(("contains", contains));
119 if !description.is_empty() {
120 pairs.push(("description", description));
121 }
122 }
123 Criterion::CommandRun { run, description } => {
124 pairs.push(("executor", RULE));
125 pairs.push(("run", run));
126 if !description.is_empty() {
127 pairs.push(("description", description));
128 }
129 }
130 Criterion::AgentJudgement { description } => {
131 pairs.push(("executor", AGENT));
132 pairs.push(("description", description));
133 }
134 Criterion::HumanGate { description } => {
135 pairs.push(("executor", HUMAN));
136 pairs.push(("description", description));
137 }
138 }
139 let mut map = Mapping::new();
140 for (key, value) in pairs {
141 map.insert(
142 Yaml::String(key.to_string()),
143 Yaml::String(value.to_string()),
144 );
145 }
146 Yaml::Mapping(map)
147 }
148
149 pub fn expanded<F>(&self, expand: F) -> Criterion
151 where
152 F: Fn(&str) -> String,
153 {
154 let ex = |value: &str| {
155 if value.contains("{{") {
156 expand(value)
157 } else {
158 value.to_string()
159 }
160 };
161 match self {
162 Criterion::PathExists { path, description } => Criterion::PathExists {
163 path: ex(path),
164 description: ex(description),
165 },
166 Criterion::PathAbsent {
167 absent,
168 description,
169 } => Criterion::PathAbsent {
170 absent: ex(absent),
171 description: ex(description),
172 },
173 Criterion::FileContains {
174 file,
175 contains,
176 description,
177 } => Criterion::FileContains {
178 file: ex(file),
179 contains: ex(contains),
180 description: ex(description),
181 },
182 Criterion::CommandRun { run, description } => Criterion::CommandRun {
183 run: ex(run),
184 description: ex(description),
185 },
186 Criterion::AgentJudgement { description } => Criterion::AgentJudgement {
187 description: ex(description),
188 },
189 Criterion::HumanGate { description } => Criterion::HumanGate {
190 description: ex(description),
191 },
192 }
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct RuleItem {
199 pub description: String,
200 pub kind: Option<RuleKind>,
201 pub args: Vec<String>,
202}
203
204impl RuleItem {
205 pub fn machine(&self) -> bool {
206 self.kind.is_some()
207 }
208}
209
210pub fn items_of(criteria: &[Criterion]) -> Vec<RuleItem> {
212 criteria
213 .iter()
214 .map(|criterion| {
215 let description = criterion.text();
216 let (kind, args) = match criterion {
217 Criterion::PathExists { path, .. } => (Some(RuleKind::Path), vec![path.clone()]),
218 Criterion::PathAbsent { absent, .. } => {
219 (Some(RuleKind::Absent), vec![absent.clone()])
220 }
221 Criterion::FileContains { file, contains, .. } => (
222 Some(RuleKind::Contains),
223 vec![file.clone(), contains.clone()],
224 ),
225 Criterion::CommandRun { run, .. } => (Some(RuleKind::Run), vec![run.clone()]),
226 Criterion::AgentJudgement { .. } | Criterion::HumanGate { .. } => {
227 (None, Vec::new())
228 }
229 };
230 RuleItem {
231 description,
232 kind,
233 args,
234 }
235 })
236 .collect()
237}