system2_macros/lib.rs
1//! The macros of `system2`.
2//!
3//! Use them through [`system2`](https://docs.rs/system2), which re-exports each one.
4
5mod answers;
6mod bbox;
7mod choice;
8mod derive;
9mod image;
10mod json;
11mod noul;
12mod part;
13mod questions;
14mod score;
15
16use proc_macro::TokenStream;
17use quote::ToTokens;
18use syn::{DeriveInput, parse_macro_input};
19
20/// Makes a [`Noul`] that the compiler checks.
21///
22/// The macro never fails at run time: every mistake is a compile error, pointing at the bad
23/// token. The instructions come first, in one of three forms:
24///
25/// - A string literal. It works as a format string, like `format!`, where each placeholder
26/// names a variable, as in `{name}`. The text outside the placeholders can't be only
27/// whitespace. A literal with no placeholders makes a constant.
28/// - A `&'static str` constant, such as `include_str!("rule.txt")`. `cargo check` validates it.
29/// - A JSON literal, `{ … }` or `[ … ]`, that is not empty, with no key repeated. Each string
30/// value is a format string. Any other value is a literal, or a variable of a type that
31/// always serializes: text, a number, a `bool`, a `serde_json::Value`, a `serde_json::Map`, or
32/// a collection or string-keyed map of those.
33///
34/// The optional criteria come second: an object with a `"true"` key, a `"false"` key, or both,
35/// where each criterion takes any of the forms above. If the instructions come from data, use
36/// [`Noul::new`] instead.
37///
38/// # Examples
39///
40/// ```
41/// use system2::{Noul, noul};
42///
43/// const URGENT: Noul = noul!("Is the ticket urgent?");
44/// const REFUND: Noul = noul!("Does the customer ask for a refund?", {
45/// "true": "The customer wants the money back.",
46/// "false": "The customer wants no money.",
47/// });
48/// const POLICY: Noul = noul!({
49/// "question": "Does the reply obey the policy?",
50/// "policy": "A reply never promises a date.",
51/// });
52///
53/// let product = "the mobile app";
54/// let about = noul!("Is the ticket about {product}?");
55/// ```
56///
57/// [`Noul`]: ../system2/struct.Noul.html
58/// [`Noul::new`]: ../system2/struct.Noul.html#method.new
59#[proc_macro]
60pub fn noul(input: TokenStream) -> TokenStream {
61 let noul = parse_macro_input!(input as noul::Noul);
62 noul.into_token_stream().into()
63}
64
65/// Makes a [`Choice`] that the compiler checks.
66///
67/// The instructions come first, in a form that [`noul!`] takes. The options come second:
68///
69/// - A type that derives [`Options`]. The answer gives a variant.
70/// - An object of names and descriptions. The answer gives the name.
71/// - An array of names. The answer gives the name.
72/// - An array of `(name, value)` or `(name, description, value)`. The answer gives the value.
73///
74/// Each name is a string literal, so two equal names are a compile error. Each description has
75/// a form of the instructions. Each value is a constant expression of any type, and it is not
76/// sent. A Choice has 1 to 255 options. For options from data, use [`Choice::new`],
77/// [`Choice::with_descriptions`], or [`Choice::with_values`].
78///
79/// # Examples
80///
81/// ```
82/// use std::cmp::Ordering;
83/// use system2::{Choice, Options, choice};
84///
85/// #[derive(Debug, Clone, Copy, PartialEq, Options)]
86/// enum Team {
87/// /// Payments, invoices, and refunds.
88/// Billing,
89/// /// Bugs and outages.
90/// Engineering,
91/// }
92///
93/// const TEAM: Choice<Team> = choice!("Which team must answer the ticket?", Team);
94/// const NEXT: Choice<&str> = choice!("What must the agent do?", {
95/// "reply": "Reply to the customer.",
96/// "escalate": "Give the ticket to an engineer.",
97/// });
98/// const LANGUAGE: Choice<&str> = choice!("Which language is the ticket in?", ["en", "de", "fr"]);
99/// const BETTER: Choice<Ordering> = choice!("Which reply is better?", [
100/// ("first", "The first reply is better.", Ordering::Less),
101/// ("second", "The second reply is better.", Ordering::Greater),
102/// ]);
103/// ```
104///
105/// [`noul!`]: ../system2/macro.noul.html
106/// [`Choice`]: ../system2/struct.Choice.html
107/// [`Choice::new`]: ../system2/struct.Choice.html#method.new
108/// [`Choice::with_descriptions`]: ../system2/struct.Choice.html#method.with_descriptions
109/// [`Choice::with_values`]: ../system2/struct.Choice.html#method.with_values
110/// [`Options`]: ../system2/trait.Options.html
111#[proc_macro]
112pub fn choice(input: TokenStream) -> TokenStream {
113 let choice = parse_macro_input!(input as choice::Choice);
114 choice.into_token_stream().into()
115}
116
117/// Makes a [`Score`] that the compiler checks.
118///
119/// The instructions come first, in a form that [`noul!`] takes. The levels come second, from the
120/// lowest to the highest:
121///
122/// - A type that derives [`Levels`]. The answer gives a variant.
123/// - An array of levels as text. The answer gives the text.
124/// - An array of `(level, value)`. The answer gives the value.
125///
126/// Each level is a string literal, so two equal levels are a compile error. Each value is a
127/// constant expression of any type, and it is not sent. A Score has 1 to 10 levels. For levels
128/// from data, use [`Score::new`] or [`Score::with_values`].
129///
130/// # Examples
131///
132/// ```
133/// use system2::{Levels, Score, score};
134///
135/// #[derive(Debug, Clone, Copy, PartialEq, Levels)]
136/// enum Urgency {
137/// Low,
138/// Medium,
139/// High,
140/// }
141///
142/// const URGENCY: Score<Urgency> = score!("How urgent is the ticket?", Urgency);
143/// const SIZE: Score<&str> = score!("How big is the fix?", ["A line.", "A file.", "A module."]);
144/// const DAYS: Score<u32> = score!("How long does the fix take?", [("A day.", 1), ("A week.", 5)]);
145/// ```
146///
147/// [`noul!`]: ../system2/macro.noul.html
148/// [`Score`]: ../system2/struct.Score.html
149/// [`Score::new`]: ../system2/struct.Score.html#method.new
150/// [`Score::with_values`]: ../system2/struct.Score.html#method.with_values
151/// [`Levels`]: ../system2/trait.Levels.html
152#[proc_macro]
153pub fn score(input: TokenStream) -> TokenStream {
154 let score = parse_macro_input!(input as score::Score);
155 score.into_token_stream().into()
156}
157
158/// Makes a [`Bbox`] that the compiler checks.
159///
160/// It takes only the instructions, in a form that [`noul!`] takes. The instructions describe one
161/// region of an image in the state. A Bbox needs the model `jev-1.14.0` or a later version. For
162/// instructions from data, use [`Bbox::new`].
163///
164/// # Examples
165///
166/// ```
167/// use system2::{Bbox, bbox};
168///
169/// const DIALOG: Bbox = bbox!("The error dialog.");
170///
171/// let button = "Retry";
172/// let found = bbox!("The button with the text `{button}`.");
173/// ```
174///
175/// [`noul!`]: ../system2/macro.noul.html
176/// [`Bbox`]: ../system2/struct.Bbox.html
177/// [`Bbox::new`]: ../system2/struct.Bbox.html#method.new
178#[proc_macro]
179pub fn bbox(input: TokenStream) -> TokenStream {
180 let bbox = parse_macro_input!(input as bbox::Bbox);
181 bbox.into_token_stream().into()
182}
183
184/// Makes a question set with a name for each question set in it.
185///
186/// Its answers are a struct with the same field names. A field with only a name is short for the
187/// variable of that name, as in a struct literal. Each field must be a question set, and an error
188/// at the field tells the fix for each other value. A tag takes other data along. The struct of
189/// the answers derives `Debug`, `Clone`, and `Serialize` when the types of its fields do.
190/// `questions! {}` sends no request.
191///
192/// The set has no type name, so it cannot be a constant. A named set can: see [`Answers`].
193///
194/// # Examples
195///
196/// ```no_run
197/// use system2::prelude::*;
198///
199/// # async fn run() -> system2::Result<()> {
200/// let triage = questions! {
201/// urgent: noul!("Is the ticket urgent?"),
202/// angry: noul!("Is the customer angry?"),
203/// }
204/// .ask("The site is down again.")
205/// .await?;
206/// if triage.urgent.is_likely() && triage.angry.is_likely() { /* … */ }
207/// # Ok(())
208/// # }
209/// ```
210///
211/// [`Answers`]: ../system2/derive.Answers.html
212#[proc_macro]
213pub fn questions(input: TokenStream) -> TokenStream {
214 let questions = parse_macro_input!(input as questions::Questions);
215 questions.into_token_stream().into()
216}
217
218#[doc(hidden)]
219#[proc_macro]
220pub fn include_image(input: TokenStream) -> TokenStream {
221 let path = parse_macro_input!(input as syn::LitStr);
222 image::include_image(&path).into()
223}
224
225/// Derives [`Options`] for an enum, so that its variants are the options of a [`Choice`].
226///
227/// Each variant must be a unit variant. The name of an option is the name of its variant in snake
228/// case, with no `r#`. Its description is the doc comment of the variant, as rustdoc shows it. Two
229/// attributes change them:
230///
231/// - `#[system2(rename = "…")]` gives the name.
232/// - `#[system2(description = "…")]` gives the description. Then the derive ignores the doc
233/// comment, so the doc comment can hold text for rustdoc only.
234///
235/// An option with no description sends its name only. The enum has 1 to 255 variants, and each
236/// name is different. Each error is a compile error at its variant.
237///
238/// # Examples
239///
240/// ```
241/// use system2::{Choice, Options, choice};
242///
243/// #[derive(Debug, Clone, Copy, PartialEq, Options)]
244/// enum Team {
245/// /// Payments, invoices, and refunds.
246/// Billing,
247/// /// Bugs and outages.
248/// Engineering,
249/// /// The team for all other tickets.
250/// #[system2(rename = "support", description = "Each other ticket.")]
251/// Help,
252/// }
253///
254/// const TEAM: Choice<Team> = choice!("Which team must answer the ticket?", Team);
255/// ```
256///
257/// [`Options`]: ../system2/trait.Options.html
258/// [`Choice`]: ../system2/struct.Choice.html
259#[proc_macro_derive(Options, attributes(system2))]
260pub fn derive_options(input: TokenStream) -> TokenStream {
261 let input = parse_macro_input!(input as DeriveInput);
262 derive::options(&input)
263 .unwrap_or_else(syn::Error::into_compile_error)
264 .into()
265}
266
267/// Derives [`Levels`] for an enum, so that its variants are the levels of a [`Score`].
268///
269/// The variants are the levels, from the lowest to the highest. Each variant must be a unit
270/// variant. The API knows a level only by its text. Thus a level sends its description: the doc
271/// comment of the variant, or the value of `#[system2(description = "…")]`. The doc comment is
272/// the text that rustdoc shows. If no variant has a description, each level is the name of its
273/// variant in snake case, with no `r#`. If only some variants have one, each other variant is a
274/// compile error. The enum has 1 to 10 variants, and each level is different.
275///
276/// # Examples
277///
278/// ```
279/// use system2::{Levels, Score, score};
280///
281/// #[derive(Debug, Clone, Copy, PartialEq, Levels)]
282/// enum Severity {
283/// /// Nothing breaks.
284/// Minor,
285/// /// A feature breaks for some customers.
286/// Major,
287/// /// The level that pages the engineer on call.
288/// #[system2(description = "Data is lost.")]
289/// Critical,
290/// }
291///
292/// const SEVERITY: Score<Severity> = score!("How severe is the bug?", Severity);
293/// ```
294///
295/// [`Levels`]: ../system2/trait.Levels.html
296/// [`Score`]: ../system2/struct.Score.html
297#[proc_macro_derive(Levels, attributes(system2))]
298pub fn derive_levels(input: TokenStream) -> TokenStream {
299 let input = parse_macro_input!(input as DeriveInput);
300 derive::levels(&input)
301 .unwrap_or_else(syn::Error::into_compile_error)
302 .into()
303}
304
305/// Derives [`Answers`] for a struct, so that [`Questions`] asks the questions of its fields.
306///
307/// The struct has named fields, and no generic parameters. Each field names its question set with
308/// `#[system2(question = …)]`. The type of the field is the answer set of that question set. The
309/// question set must be a constant expression, such as `URGENT` or `noul!("…")`. A named set in a
310/// named set is `Questions::new()`. For questions from data, use [`questions!`]. The derive adds
311/// no method to the struct. A public struct can hold the answer of a private type, such as a
312/// `ChoiceAnswer` of a private enum.
313///
314/// # Examples
315///
316/// ```no_run
317/// use system2::prelude::*;
318/// use system2::{ChoiceAnswer, NoulAnswer};
319///
320/// #[derive(Debug, Clone, Copy, PartialEq, Options)]
321/// enum Team {
322/// Billing,
323/// Engineering,
324/// }
325///
326/// #[derive(Debug, Answers)]
327/// struct Triage {
328/// #[system2(question = noul!("Is the ticket urgent?"))]
329/// urgent: NoulAnswer,
330/// #[system2(question = choice!("Which team must answer the ticket?", Team))]
331/// team: ChoiceAnswer<Team>,
332/// }
333///
334/// const TRIAGE: Questions<Triage> = Questions::new();
335///
336/// # async fn run(ticket: &str) -> system2::Result<()> {
337/// let triage = TRIAGE.ask(ticket).await?;
338/// if triage.urgent.is_likely() && *triage.team.most_likely() == Team::Engineering { /* … */ }
339/// # Ok(())
340/// # }
341/// ```
342///
343/// [`Answers`]: ../system2/trait.Answers.html
344/// [`Questions`]: ../system2/struct.Questions.html
345/// [`questions!`]: ../system2/macro.questions.html
346#[proc_macro_derive(Answers, attributes(system2))]
347pub fn derive_answers(input: TokenStream) -> TokenStream {
348 let input = parse_macro_input!(input as DeriveInput);
349 answers::answers(&input)
350 .unwrap_or_else(syn::Error::into_compile_error)
351 .into()
352}