max / makenotwork
| 1 | //! Forward fence, the hand-written `hx-confirm` ratchet. |
| 2 | //! |
| 3 | //! No template writes `hx-confirm` by hand. Asking before a destructive |
| 4 | //! act is a property of the act, so it belongs in the description |
| 5 | //! (`quasi_router::Act::confirm`, `screen.rs:2787` with its builder at `:2932`), |
| 6 | //! where a terminal host asks in its own way and no renderer can forget to ask. |
| 7 | //! An attribute typed into Askama says it to one host only. |
| 8 | //! |
| 9 | //! Each remaining site leaves as its screen is described, so the condition is |
| 10 | //! met by the conversion program rather than by an edit. What this seal does is |
| 11 | //! make that monotone: a new template writing the attribute by hand fails here, |
| 12 | //! and every conversion lowers the number. |
| 13 | //! |
| 14 | //! # What this seal is not |
| 15 | //! |
| 16 | //! It does not, and cannot, say which acts destroy something. A danger class |
| 17 | //! near a confirm proves nothing in either direction: |
| 18 | //! `templates/partials/tabs/item_details.html` carries four confirms and no |
| 19 | //! danger class while three of its four acts are destructive, and |
| 20 | //! `templates/partials/admin_user_entries.html` carries four danger classes for |
| 21 | //! four confirms while two of those acts restore rather than destroy. Some |
| 22 | //! confirms are non-destructive and keep their confirm without taking |
| 23 | //! `Tone::Danger`. So a converting change reads its own sites; the markup is not |
| 24 | //! evidence. |
| 25 | //! |
| 26 | //! The converted side is asserted where it is written, not here: each described |
| 27 | //! screen's own tests check that its destructive acts carry both marks and that |
| 28 | //! its non-destructive ones carry neither (`src/quasi/ssh_keys.rs`, |
| 29 | //! `library_contacts.rs`, `forum_memberships.rs`). |
| 30 | |
| 31 | use fs; |
| 32 | use ; |
| 33 | |
| 34 | /// Ratchets down only, never up. |
| 35 | /// |
| 36 | /// The seal counts template source, so deduplicating two templates lowers it |
| 37 | /// without describing anything. Read it against what moved before treating it |
| 38 | /// as a progress figure. |
| 39 | /// |
| 40 | /// Two sites are deliberately not converted: the Remove buttons in |
| 41 | /// `partials/link_row.html` and `partials/tag.html` both target |
| 42 | /// `hx-target="closest .<class>"`, and a relative selector is not something |
| 43 | /// `Action::replaces` can say. Filed rather than approximated. |
| 44 | /// |
| 45 | /// `partials/ssh_keys_list.html` and `partials/git_tokens_list.html` are not |
| 46 | /// this program's to remove either: they are not tab renderings but the |
| 47 | /// fragments `routes::api::ssh_keys` and the git-token routes answer with, and |
| 48 | /// the described screens still call those. |
| 49 | const HIGH_WATER: usize = 43; |
| 50 | |
| 51 | /// Every template, recursively. Walked rather than listed for the reason the |
| 52 | /// frontend-globals seal walks `frontend/src`: a fence that has to be told about |
| 53 | /// each new file silently stops covering the tree. |
| 54 | |
| 55 | let mut files = Vecnew; |
| 56 | collect |
| 57 | &new.join, |
| 58 | &mut files, |
| 59 | ; |
| 60 | files.sort; |
| 61 | files |
| 62 | |
| 63 | |
| 64 | |
| 65 | let Ok = read_dir else |
| 66 | return; |
| 67 | ; |
| 68 | for entry in entries |
| 69 | let path = entry.expect.path; |
| 70 | if path.is_dir |
| 71 | collect; |
| 72 | else if path.extension.and_then == Some |
| 73 | out.push; |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | /// Where the attribute is written, one line per site, sorted by path. |
| 79 | /// |
| 80 | /// The failure message carries the list rather than a bare number, because a |
| 81 | /// ratchet that only says "it rose" leaves the reader to find the site. |
| 82 | |
| 83 | let root = new; |
| 84 | let mut found = Vecnew; |
| 85 | |
| 86 | for path in templates |
| 87 | let src = read_to_string.unwrap_or_default; |
| 88 | let shown = path |
| 89 | .strip_prefix |
| 90 | .unwrap_or |
| 91 | .display |
| 92 | .to_string; |
| 93 | for in src.lines.enumerate |
| 94 | for _ in 0..line.matches.count |
| 95 | found.push; |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | found |
| 101 | |
| 102 | |
| 103 | |
| 104 | |
| 105 | let found = sites; |
| 106 | let count = found.len; |
| 107 | |
| 108 | assert! |
| 109 | count <= HIGH_WATER, |
| 110 | "hand-written hx-confirm rose to {count} (HIGH_WATER {HIGH_WATER}).\n\ |
| 111 | Asking before an act is the act's own property: say it with \ |
| 112 | Act::confirm in the described screen, not as an attribute in Askama. \ |
| 113 | If you CONVERTED sites, lower HIGH_WATER to {count}.\n{}", |
| 114 | found.join |
| 115 | ; |
| 116 | |
| 117 | assert_eq! |
| 118 | count, HIGH_WATER, |
| 119 | "hand-written hx-confirm fell to {count}. Lower HIGH_WATER to {count}, \ |
| 120 | and say in its doc comment which screen took them.", |
| 121 | ; |
| 122 | |
| 123 | |
| 124 | |
| 125 | |
| 126 | // Four files hold more than one, so a fence counting files rather than |
| 127 | // occurrences would report a conversion that removed two of four as |
| 128 | // progress on neither. The count is of sites. |
| 129 | let found = sites; |
| 130 | |
| 131 | assert! |
| 132 | found.iter.any, |
| 133 | "item_details holds four of them: {found:?}" |
| 134 | ; |
| 135 | assert_eq! |
| 136 | found |
| 137 | .iter |
| 138 | .filter |
| 139 | .count, |
| 140 | 4, |
| 141 | "all four, not the file once: {found:?}" |
| 142 | ; |
| 143 | |
| 144 |