max / quasi
6 files changed,
+87 insertions,
-12 deletions
| @@ -3522,6 +3522,15 @@ | |||
| 3522 | 3522 | "ratatui", | |
| 3523 | 3523 | ] | |
| 3524 | 3524 | ||
| 3525 | + | [[package]] | |
| 3526 | + | name = "quasi-declare" | |
| 3527 | + | version = "0.1.0" | |
| 3528 | + | dependencies = [ | |
| 3529 | + | "proc-macro2", | |
| 3530 | + | "quote", | |
| 3531 | + | "syn 2.0.119", | |
| 3532 | + | ] | |
| 3533 | + | ||
| 3525 | 3534 | [[package]] | |
| 3526 | 3535 | name = "quasi-http" | |
| 3527 | 3536 | version = "0.101.1" | |
| @@ -3544,7 +3553,7 @@ | |||
| 3544 | 3553 | ||
| 3545 | 3554 | [[package]] | |
| 3546 | 3555 | name = "quasi-notifs" | |
| 3547 | - | version = "0.101.1" | |
| 3556 | + | version = "0.101.2" | |
| 3548 | 3557 | dependencies = [ | |
| 3549 | 3558 | "quasi-router", | |
| 3550 | 3559 | "synckit-config", | |
| @@ -3554,7 +3563,7 @@ | |||
| 3554 | 3563 | ||
| 3555 | 3564 | [[package]] | |
| 3556 | 3565 | name = "quasi-router" | |
| 3557 | - | version = "0.101.1" | |
| 3566 | + | version = "0.101.11" | |
| 3558 | 3567 | dependencies = [ | |
| 3559 | 3568 | "makeover-layout", | |
| 3560 | 3569 | ] | |
| @@ -6270,6 +6279,10 @@ | |||
| 6270 | 6279 | "winnow 1.0.4", | |
| 6271 | 6280 | ] | |
| 6272 | 6281 | ||
| 6282 | + | [[patch.unused]] | |
| 6283 | + | name = "synckit-client" | |
| 6284 | + | version = "0.10.0" | |
| 6285 | + | ||
| 6273 | 6286 | [[patch.unused]] | |
| 6274 | 6287 | name = "kberg" | |
| 6275 | 6288 | version = "0.1.0" | |
| @@ -6286,10 +6299,6 @@ | |||
| 6286 | 6299 | name = "tagtree" | |
| 6287 | 6300 | version = "0.4.1" | |
| 6288 | 6301 | ||
| 6289 | - | [[patch.unused]] | |
| 6290 | - | name = "synckit-client" | |
| 6291 | - | version = "0.10.0" | |
| 6292 | - | ||
| 6293 | 6302 | [[patch.unused]] | |
| 6294 | 6303 | name = "quasi-type" | |
| 6295 | 6304 | version = "0.1.3" |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "quasi-notifs" | |
| 3 | - | version = "0.101.1" | |
| 3 | + | version = "0.101.2" | |
| 4 | 4 | description = "Declared notification kinds and the registry that holds them: what a notification says and what it is for, apart from how it reaches a person" | |
| 5 | 5 | edition.workspace = true | |
| 6 | 6 | rust-version.workspace = true |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "quasi-router" | |
| 3 | - | version = "0.101.10" | |
| 3 | + | version = "0.101.11" | |
| 4 | 4 | description = "Host-agnostic router: a request in, a renderer-agnostic description out" | |
| 5 | 5 | edition.workspace = true | |
| 6 | 6 | rust-version.workspace = true |
| @@ -966,10 +966,18 @@ | |||
| 966 | 966 | } | |
| 967 | 967 | ||
| 968 | 968 | /// One form: where it writes, what its button says, and what it asks. | |
| 969 | + | /// | |
| 970 | + | /// The fields are pushed rather than written into a `vec![]` literal because a | |
| 971 | + | /// field may be guarded, which is what `settings::email`'s advanced block asked | |
| 972 | + | /// for: six server questions that are on the form only while the disclosure is | |
| 973 | + | /// open. R9 holds here as everywhere else -- the field is built whether or not | |
| 974 | + | /// it is asked -- so a supplier feeding a question nobody sees is still called | |
| 975 | + | /// and still answers. | |
| 969 | 976 | fn form(action: &Action, body: &[Item]) -> Result<TokenStream> { | |
| 970 | 977 | let mut submit = None; | |
| 971 | - | let mut fields = Vec::new(); | |
| 972 | - | for item in body { | |
| 978 | + | let mut asked = Vec::new(); | |
| 979 | + | let held = format_ident!("asked", span = Span::call_site()); | |
| 980 | + | for (index, item) in body.iter().enumerate() { | |
| 973 | 981 | match item { | |
| 974 | 982 | Item::Attribute { name, args, guard } if name == "submit" => { | |
| 975 | 983 | let [label] = args.as_slice() else { | |
| @@ -988,7 +996,33 @@ | |||
| 988 | 996 | name, | |
| 989 | 997 | label, | |
| 990 | 998 | body, | |
| 991 | - | }) => fields.push(field(kind, name, label, body)?), | |
| 999 | + | }) => { | |
| 1000 | + | let built = field(kind, name, label, body)?; | |
| 1001 | + | asked.push(quote!(#held.push(#built);)); | |
| 1002 | + | } | |
| 1003 | + | Item::Emit(Emission::Guarded { guard, inner }) => { | |
| 1004 | + | let Emission::Field { | |
| 1005 | + | kind, | |
| 1006 | + | name, | |
| 1007 | + | label, | |
| 1008 | + | body, | |
| 1009 | + | } = &**inner | |
| 1010 | + | else { | |
| 1011 | + | return Err(syn::Error::new( | |
| 1012 | + | emission_span(inner), | |
| 1013 | + | "a form holds fields, and only a field may be guarded here", | |
| 1014 | + | )); | |
| 1015 | + | }; | |
| 1016 | + | let built = field(kind, name, label, body)?; | |
| 1017 | + | let test = predicate(guard)?; | |
| 1018 | + | let question = format_ident!("question_{index}", span = Span::call_site()); | |
| 1019 | + | asked.push(quote!({ | |
| 1020 | + | let #question = #built; | |
| 1021 | + | if #test { | |
| 1022 | + | #held.push(#question); | |
| 1023 | + | } | |
| 1024 | + | })); | |
| 1025 | + | } | |
| 992 | 1026 | Item::Attribute { name, .. } => { | |
| 993 | 1027 | return Err(syn::Error::new( | |
| 994 | 1028 | name.span(), | |
| @@ -1020,7 +1054,11 @@ | |||
| 1020 | 1054 | ::quasi_router::Node::Form { | |
| 1021 | 1055 | action: #action, | |
| 1022 | 1056 | submit: ::std::convert::Into::into(#submit), | |
| 1023 | - | fields: ::std::vec![#(#fields),*], | |
| 1057 | + | fields: { | |
| 1058 | + | let mut #held = ::std::vec::Vec::new(); | |
| 1059 | + | #(#asked)* | |
| 1060 | + | #held | |
| 1061 | + | }, | |
| 1024 | 1062 | } | |
| 1025 | 1063 | }) | |
| 1026 | 1064 | } |
| @@ -189,6 +189,18 @@ | |||
| 189 | 189 | } | |
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | + | /// A map an app has already read the whole of. | |
| 193 | + | /// | |
| 194 | + | /// The blanket closure impl covers a store that is asked one key at a time. An | |
| 195 | + | /// app that reads its config table in one query holds the answers before the | |
| 196 | + | /// pane is drawn, and a closure over the map is a wrapper that says nothing: | |
| 197 | + | /// GoingsOn's settings screen reads `all_config` once for the whole section. | |
| 198 | + | impl Settings for std::collections::HashMap<String, String> { | |
| 199 | + | fn get(&self, key: &str) -> Option<String> { | |
| 200 | + | self.get(key).cloned() | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | ||
| 192 | 204 | /// Nothing has been set. Every key reads as its default. | |
| 193 | 205 | /// | |
| 194 | 206 | /// What an app has on its very first run, and what a test wants when it is |
| @@ -3607,6 +3607,22 @@ | |||
| 3607 | 3607 | } | |
| 3608 | 3608 | } | |
| 3609 | 3609 | ||
| 3610 | + | /// Offer these themes, chaining. | |
| 3611 | + | /// | |
| 3612 | + | /// The accreting half of [`theme`](Self::theme), which takes the whole list | |
| 3613 | + | /// as an argument, and the same gap [`options`](Self::options) fills beside | |
| 3614 | + | /// [`select`](Self::select): a caller with no expression to hold a list in | |
| 3615 | + | /// still has to be able to offer themes. GoingsOn's Appearance section is | |
| 3616 | + | /// the site. | |
| 3617 | + | /// | |
| 3618 | + | /// Says nothing about the kind, exactly as `options` does not. A renderer | |
| 3619 | + | /// reads these only for [`layout::FieldKind::Theme`]. | |
| 3620 | + | #[must_use] | |
| 3621 | + | pub fn themes(mut self, themes: impl IntoIterator<Item = ThemeChoice>) -> Self { | |
| 3622 | + | self.themes.extend(themes); | |
| 3623 | + | self | |
| 3624 | + | } | |
| 3625 | + | ||
| 3610 | 3626 | /// The same picker, offering a row that tracks the ambient mode. | |
| 3611 | 3627 | /// | |
| 3612 | 3628 | /// The [`Choice`] carries the value the app's own store spells it with — |