brut 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (689) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +24 -3
  3. data/.nvim.lua +1 -0
  4. data/Dockerfile.dx +12 -3
  5. data/Gemfile.lock +9 -7
  6. data/README.md +0 -7
  7. data/Rakefile +6 -4
  8. data/bin/dev +20 -0
  9. data/bin/docs +27 -0
  10. data/bin/setup +47 -1
  11. data/brut-css/.nvim.lua +1 -0
  12. data/brut-css/README.md +28 -0
  13. data/brut-css/bin/build +31 -0
  14. data/brut-css/bin/dev +1 -0
  15. data/brut-css/bin/docs +15 -0
  16. data/brut-css/bin/setup +5 -0
  17. data/brut-css/config/media-queries-all.css +15 -0
  18. data/brut-css/config/media-queries-minimal.css +5 -0
  19. data/brut-css/config/postcss.config.cjs +7 -0
  20. data/brut-css/config/pseudo-classes-all.css +9 -0
  21. data/brut-css/dx +1 -0
  22. data/brut-css/package-lock.json +3217 -0
  23. data/brut-css/package.json +36 -0
  24. data/brut-css/src/css/appearance.css +145 -0
  25. data/brut-css/src/css/border.css +522 -0
  26. data/brut-css/src/css/colors.css +3502 -0
  27. data/brut-css/src/css/dimensions.css +548 -0
  28. data/brut-css/src/css/flex.css +179 -0
  29. data/brut-css/src/css/index.css +13 -0
  30. data/brut-css/src/css/layout.css +120 -0
  31. data/brut-css/src/css/list.css +41 -0
  32. data/brut-css/src/css/positioning.css +354 -0
  33. data/brut-css/src/css/properties/colors.css +455 -0
  34. data/brut-css/src/css/properties/index.css +3 -0
  35. data/brut-css/src/css/properties/spacing.css +140 -0
  36. data/brut-css/src/css/properties/typography.css +224 -0
  37. data/brut-css/src/css/reset.css +107 -0
  38. data/brut-css/src/css/spacing.css +585 -0
  39. data/brut-css/src/css/typography.css +519 -0
  40. data/brut-css/src/css/utils.css +104 -0
  41. data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
  42. data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
  43. data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
  44. data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
  45. data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
  46. data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
  47. data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
  48. data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
  49. data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
  50. data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
  51. data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
  52. data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
  53. data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
  54. data/brut-css/src/docs/docs.css +98 -0
  55. data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
  56. data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
  57. data/brut-css/src/docs/includes/head.html.ejs +5 -0
  58. data/brut-css/src/docs/includes/nav.html.ejs +10 -0
  59. data/brut-css/src/docs/index.html.ejs +32 -0
  60. data/brut-css/src/docs/prism-twilight.min.css +1 -0
  61. data/brut-css/src/js/Logger.js +71 -0
  62. data/brut-css/src/js/build.js +111 -0
  63. data/brut-css/src/js/cli/CLIArgError.js +7 -0
  64. data/brut-css/src/js/cli/Debug.js +27 -0
  65. data/brut-css/src/js/cli/DocsDir.js +16 -0
  66. data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
  67. data/brut-css/src/js/cli/InputFile.js +31 -0
  68. data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
  69. data/brut-css/src/js/cli/OutputFile.js +22 -0
  70. data/brut-css/src/js/cli/ParsedArg.js +17 -0
  71. data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
  72. data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
  73. data/brut-css/src/js/cli.js +108 -0
  74. data/brut-css/src/js/docGenerator.js +467 -0
  75. data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
  76. data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
  77. data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
  78. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
  79. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
  80. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
  81. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
  82. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
  83. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
  84. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
  85. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
  86. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
  87. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
  88. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
  89. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
  90. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
  91. data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
  92. data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
  93. data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
  94. data/brut-js/.projections.json +10 -0
  95. data/brut-js/README.md +118 -0
  96. data/brut-js/bin/build +10 -0
  97. data/brut-js/bin/ci +5 -0
  98. data/brut-js/bin/setup +5 -0
  99. data/brut-js/docs/README.md +8 -0
  100. data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
  101. data/brut-js/docs/jsdoc-theme/publish.js +692 -0
  102. data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
  103. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  104. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
  105. data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
  106. data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
  107. data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
  108. data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
  109. data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
  110. data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
  111. data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
  112. data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
  113. data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
  114. data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
  115. data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
  116. data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
  117. data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
  118. data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
  119. data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
  120. data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
  121. data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
  122. data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
  123. data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
  124. data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
  125. data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
  126. data/brut-js/docs/jsdoc.config.json +23 -0
  127. data/brut-js/docs/package-lock.json +343 -0
  128. data/brut-js/docs/package.json +7 -0
  129. data/brut-js/package-lock.json +2171 -0
  130. data/brut-js/package.json +32 -0
  131. data/brut-js/specs/AjaxSubmit.spec.js +256 -0
  132. data/brut-js/specs/Autosubmit.spec.js +127 -0
  133. data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
  134. data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
  135. data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
  136. data/brut-js/specs/CopyToClipboard.spec.js +35 -0
  137. data/brut-js/specs/Form.spec.js +181 -0
  138. data/brut-js/specs/I18nTranslation.spec.js +19 -0
  139. data/brut-js/specs/LocaleDetection.spec.js +22 -0
  140. data/brut-js/specs/Message.spec.js +15 -0
  141. data/brut-js/specs/SpecHelper.js +23 -0
  142. data/brut-js/specs/Tabs.spec.js +41 -0
  143. data/brut-js/specs/config/asset_metadata.json +7 -0
  144. data/brut-js/src/AjaxSubmit.js +384 -0
  145. data/brut-js/src/Autosubmit.js +63 -0
  146. data/brut-js/src/BaseCustomElement.js +261 -0
  147. data/brut-js/src/ConfirmSubmit.js +116 -0
  148. data/brut-js/src/ConfirmationDialog.js +143 -0
  149. data/brut-js/src/ConstraintViolationMessage.js +125 -0
  150. data/brut-js/src/ConstraintViolationMessages.js +98 -0
  151. data/brut-js/src/CopyToClipboard.js +96 -0
  152. data/brut-js/src/Form.js +151 -0
  153. data/brut-js/src/I18nTranslation.js +61 -0
  154. data/brut-js/src/LocaleDetection.js +117 -0
  155. data/brut-js/src/Logger.js +90 -0
  156. data/brut-js/src/Message.js +56 -0
  157. data/brut-js/src/RichString.js +113 -0
  158. data/brut-js/src/Tabs.js +168 -0
  159. data/brut-js/src/Tracing.js +247 -0
  160. data/brut-js/src/appForTestingOnly.js +15 -0
  161. data/brut-js/src/index.js +130 -0
  162. data/brut-js/src/testing/AssetMetadata.js +35 -0
  163. data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
  164. data/brut-js/src/testing/CustomElementTest.js +235 -0
  165. data/brut-js/src/testing/DOMCreator.js +45 -0
  166. data/brut-js/src/testing/index.js +48 -0
  167. data/brutrb.com/.vitepress/config.mjs +106 -0
  168. data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
  169. data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
  170. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  171. data/brutrb.com/.vitepress/theme/index.js +18 -0
  172. data/brutrb.com/.vitepress/theme/style.css +149 -0
  173. data/brutrb.com/ai.md +68 -0
  174. data/brutrb.com/assets.md +138 -0
  175. data/brutrb.com/bin/build +5 -0
  176. data/brutrb.com/bin/deploy +7 -0
  177. data/brutrb.com/bin/dev +5 -0
  178. data/brutrb.com/bin/setup +5 -0
  179. data/brutrb.com/brut-js.md +117 -0
  180. data/brutrb.com/business-logic.md +55 -0
  181. data/brutrb.com/cli.md +278 -0
  182. data/brutrb.com/components.md +243 -0
  183. data/brutrb.com/configuration.md +257 -0
  184. data/brutrb.com/css.md +103 -0
  185. data/brutrb.com/custom-element-tests.md +149 -0
  186. data/brutrb.com/database-access.md +201 -0
  187. data/brutrb.com/database-schema.md +312 -0
  188. data/brutrb.com/deployment.md +66 -0
  189. data/brutrb.com/dev-environment.md +179 -0
  190. data/brutrb.com/doc-conventions.md +39 -0
  191. data/brutrb.com/end-to-end-tests.md +174 -0
  192. data/brutrb.com/flash-and-session.md +224 -0
  193. data/brutrb.com/forms.md +866 -0
  194. data/brutrb.com/getting-started.md +66 -0
  195. data/brutrb.com/handlers.md +153 -0
  196. data/brutrb.com/hooks.md +178 -0
  197. data/brutrb.com/i18n.md +188 -0
  198. data/brutrb.com/images/Makefile +10 -0
  199. data/brutrb.com/images/dev-env-overview.dot +54 -0
  200. data/brutrb.com/images/dev-env-overview.png +0 -0
  201. data/brutrb.com/images/dev-env-protocol.dot +37 -0
  202. data/brutrb.com/images/dev-env-protocol.png +0 -0
  203. data/brutrb.com/images/logo-300.png +0 -0
  204. data/brutrb.com/images/logo.png +0 -0
  205. data/brutrb.com/images/overview.graffle +0 -0
  206. data/brutrb.com/images/overview.png +0 -0
  207. data/brutrb.com/images/spa.dot +19 -0
  208. data/brutrb.com/images/spa.png +0 -0
  209. data/brutrb.com/images/workspace-protocol.dot +44 -0
  210. data/brutrb.com/images/workspace-protocol.png +0 -0
  211. data/brutrb.com/index.md +36 -0
  212. data/brutrb.com/instrumentation.md +183 -0
  213. data/brutrb.com/javascript.md +122 -0
  214. data/brutrb.com/jobs.md +14 -0
  215. data/brutrb.com/keyword-injection.md +237 -0
  216. data/brutrb.com/markdown-examples.md +85 -0
  217. data/brutrb.com/middleware.md +80 -0
  218. data/brutrb.com/not-released.md +5 -0
  219. data/brutrb.com/overview.md +404 -0
  220. data/brutrb.com/package-lock.json +2404 -0
  221. data/brutrb.com/package.json +11 -0
  222. data/brutrb.com/pages.md +378 -0
  223. data/brutrb.com/public/images/logo-300.png +0 -0
  224. data/brutrb.com/public/images/logo.png +0 -0
  225. data/brutrb.com/routes.md +215 -0
  226. data/brutrb.com/security.md +105 -0
  227. data/brutrb.com/seed-data.md +63 -0
  228. data/brutrb.com/space-time-continuum.md +85 -0
  229. data/brutrb.com/tutorial.md +3 -0
  230. data/brutrb.com/unit-tests.md +148 -0
  231. data/docker-compose.dx.yml +6 -3
  232. data/docs/404.html +21 -0
  233. data/docs/CNAME +1 -0
  234. data/docs/ai.html +24 -0
  235. data/docs/api/Brut/BackEnd/SeedData.html +493 -0
  236. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
  237. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
  238. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
  239. data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
  240. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
  241. data/docs/api/Brut/BackEnd/Validators.html +128 -0
  242. data/docs/api/Brut/BackEnd.html +132 -0
  243. data/docs/api/Brut/CLI/App.html +1576 -0
  244. data/docs/api/Brut/CLI/AppRunner.html +491 -0
  245. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
  246. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
  247. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
  248. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
  249. data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
  250. data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
  251. data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
  252. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
  253. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
  254. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
  255. data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
  256. data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
  257. data/docs/api/Brut/CLI/Apps/DB.html +183 -0
  258. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
  259. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
  260. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
  261. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
  262. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
  263. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
  264. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
  265. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
  266. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
  267. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
  268. data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
  269. data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
  270. data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
  271. data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
  272. data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
  273. data/docs/api/Brut/CLI/Apps/Test.html +253 -0
  274. data/docs/api/Brut/CLI/Apps.html +125 -0
  275. data/docs/api/Brut/CLI/Command.html +2342 -0
  276. data/docs/api/Brut/CLI/Error.html +139 -0
  277. data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
  278. data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
  279. data/docs/api/Brut/CLI/Executor.html +430 -0
  280. data/docs/api/Brut/CLI/InvalidOption.html +245 -0
  281. data/docs/api/Brut/CLI/Options.html +753 -0
  282. data/docs/api/Brut/CLI/Output.html +699 -0
  283. data/docs/api/Brut/CLI/SystemExecError.html +451 -0
  284. data/docs/api/Brut/CLI.html +263 -0
  285. data/docs/api/Brut/FactoryBot.html +225 -0
  286. data/docs/api/Brut/Framework/App.html +1097 -0
  287. data/docs/api/Brut/Framework/Config.html +1045 -0
  288. data/docs/api/Brut/Framework/Container.html +1379 -0
  289. data/docs/api/Brut/Framework/Error.html +140 -0
  290. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
  291. data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
  292. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
  293. data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
  294. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
  295. data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
  296. data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
  297. data/docs/api/Brut/Framework/Errors.html +328 -0
  298. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
  299. data/docs/api/Brut/Framework/MCP.html +861 -0
  300. data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
  301. data/docs/api/Brut/Framework.html +129 -0
  302. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
  303. data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
  304. data/docs/api/Brut/FrontEnd/Component.html +365 -0
  305. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
  306. data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
  307. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
  308. data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
  309. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
  310. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
  311. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
  312. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
  313. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
  314. data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
  315. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
  316. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
  317. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
  318. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
  319. data/docs/api/Brut/FrontEnd/Components.html +135 -0
  320. data/docs/api/Brut/FrontEnd/Download.html +467 -0
  321. data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
  322. data/docs/api/Brut/FrontEnd/Form.html +1157 -0
  323. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
  324. data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
  325. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
  326. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
  327. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
  328. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
  329. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
  330. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
  331. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
  332. data/docs/api/Brut/FrontEnd/Forms.html +127 -0
  333. data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
  334. data/docs/api/Brut/FrontEnd/Handler.html +442 -0
  335. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
  336. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
  337. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
  338. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
  339. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
  340. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
  341. data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
  342. data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
  343. data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
  344. data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
  345. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
  346. data/docs/api/Brut/FrontEnd/Layout.html +318 -0
  347. data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
  348. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
  349. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
  350. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
  351. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
  352. data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
  353. data/docs/api/Brut/FrontEnd/Page.html +773 -0
  354. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
  355. data/docs/api/Brut/FrontEnd/Pages.html +125 -0
  356. data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
  357. data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
  358. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
  359. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
  360. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
  361. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
  362. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
  363. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
  364. data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
  365. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
  366. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
  367. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
  368. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
  369. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
  370. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
  371. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
  372. data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
  373. data/docs/api/Brut/FrontEnd/Routing.html +927 -0
  374. data/docs/api/Brut/FrontEnd/Session.html +1195 -0
  375. data/docs/api/Brut/FrontEnd.html +134 -0
  376. data/docs/api/Brut/I18n/BaseMethods.html +931 -0
  377. data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
  378. data/docs/api/Brut/I18n/ForCLI.html +302 -0
  379. data/docs/api/Brut/I18n/ForHTML.html +296 -0
  380. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
  381. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
  382. data/docs/api/Brut/I18n.html +127 -0
  383. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
  384. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
  385. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
  386. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
  387. data/docs/api/Brut/Instrumentation.html +126 -0
  388. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
  389. data/docs/api/Brut/SinatraHelpers.html +281 -0
  390. data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
  391. data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
  392. data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
  393. data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
  394. data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
  395. data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
  396. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
  397. data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
  398. data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
  399. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
  400. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
  401. data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
  402. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
  403. data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
  404. data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
  405. data/docs/api/Brut/SpecSupport.html +129 -0
  406. data/docs/api/Brut.html +225 -0
  407. data/docs/api/Clock.html +603 -0
  408. data/docs/api/RichString.html +968 -0
  409. data/docs/api/SemanticLogger/Appender/Async.html +219 -0
  410. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
  411. data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
  412. data/docs/api/Sequel/Extensions.html +117 -0
  413. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
  414. data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
  415. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
  416. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
  417. data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
  418. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
  419. data/docs/api/Sequel/Plugins/FindBang.html +125 -0
  420. data/docs/api/Sequel/Plugins.html +117 -0
  421. data/docs/api/Sequel.html +117 -0
  422. data/docs/api/_index.html +1553 -0
  423. data/docs/api/class_list.html +54 -0
  424. data/docs/api/css/common.css +1 -0
  425. data/docs/api/css/full_list.css +58 -0
  426. data/docs/api/css/style.css +503 -0
  427. data/docs/api/file.README.html +127 -0
  428. data/docs/api/file_list.html +59 -0
  429. data/docs/api/frames.html +22 -0
  430. data/docs/api/index.html +127 -0
  431. data/docs/api/js/app.js +344 -0
  432. data/docs/api/js/full_list.js +242 -0
  433. data/docs/api/js/jquery.js +4 -0
  434. data/docs/api/method_list.html +3998 -0
  435. data/docs/api/top-level-namespace.html +112 -0
  436. data/docs/assets/ai.md.tZrjP9im.js +1 -0
  437. data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
  438. data/docs/assets/app.D_yaTITQ.js +1 -0
  439. data/docs/assets/assets.md.D3wunzLx.js +19 -0
  440. data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
  441. data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
  442. data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
  443. data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
  444. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
  445. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
  446. data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
  447. data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
  448. data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
  449. data/docs/assets/cli.md.RmeA2b0i.js +127 -0
  450. data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
  451. data/docs/assets/components.md.eCttGlN-.js +104 -0
  452. data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
  453. data/docs/assets/configuration.md.BRriU0cL.js +78 -0
  454. data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
  455. data/docs/assets/css.md.DJgj2clw.js +21 -0
  456. data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
  457. data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
  458. data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
  459. data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
  460. data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
  461. data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
  462. data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
  463. data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
  464. data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
  465. data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
  466. data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
  467. data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
  468. data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
  469. data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
  470. data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
  471. data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
  472. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
  473. data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
  474. data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
  475. data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
  476. data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
  477. data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
  478. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
  479. data/docs/assets/handlers.md.089DVD3v.js +69 -0
  480. data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
  481. data/docs/assets/hooks.md.C4-moMny.js +80 -0
  482. data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
  483. data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
  484. data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
  485. data/docs/assets/index.md.B28EwVpq.js +1 -0
  486. data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
  487. data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
  488. data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
  489. data/docs/assets/javascript.md.GWbhRS51.js +31 -0
  490. data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
  491. data/docs/assets/jobs.md.S-2amAYp.js +1 -0
  492. data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
  493. data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
  494. data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
  495. data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
  496. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
  497. data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
  498. data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
  499. data/docs/assets/not-released.md.BBy28McC.js +1 -0
  500. data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
  501. data/docs/assets/overview.Da81cB9R.png +0 -0
  502. data/docs/assets/overview.md.CDalkuxV.js +133 -0
  503. data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
  504. data/docs/assets/pages.md.BE3kfOc5.js +122 -0
  505. data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
  506. data/docs/assets/routes.md.BMM7peut.js +29 -0
  507. data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
  508. data/docs/assets/security.md.C668yXCi.js +1 -0
  509. data/docs/assets/security.md.C668yXCi.lean.js +1 -0
  510. data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
  511. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
  512. data/docs/assets/spa.qejUdp-5.png +0 -0
  513. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
  514. data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
  515. data/docs/assets/style.D73IYGCX.css +1 -0
  516. data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
  517. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
  518. data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
  519. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
  520. data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
  521. data/docs/assets.html +42 -0
  522. data/docs/brut-css/brut.css +1 -0
  523. data/docs/brut-css/brut.max.css +22372 -0
  524. data/docs/brut-css/classes/appearances.html +783 -0
  525. data/docs/brut-css/classes/background-colors.html +3529 -0
  526. data/docs/brut-css/classes/border-colors.html +3529 -0
  527. data/docs/brut-css/classes/borders.html +2293 -0
  528. data/docs/brut-css/classes/dimensions.html +2581 -0
  529. data/docs/brut-css/classes/flex.html +917 -0
  530. data/docs/brut-css/classes/foreground-colors.html +3261 -0
  531. data/docs/brut-css/classes/junk-drawer.html +431 -0
  532. data/docs/brut-css/classes/layout.html +668 -0
  533. data/docs/brut-css/classes/lists.html +331 -0
  534. data/docs/brut-css/classes/positioning.html +1751 -0
  535. data/docs/brut-css/classes/spacings.html +2633 -0
  536. data/docs/brut-css/classes/typography.html +2206 -0
  537. data/docs/brut-css/customization/advanced-configuration.html +204 -0
  538. data/docs/brut-css/customization/breakpoints.html +227 -0
  539. data/docs/brut-css/customization/design-system.html +197 -0
  540. data/docs/brut-css/customization/pseudo-classes.html +228 -0
  541. data/docs/brut-css/docs.css +98 -0
  542. data/docs/brut-css/getting-started/core-concepts.html +234 -0
  543. data/docs/brut-css/getting-started/installation.html +190 -0
  544. data/docs/brut-css/getting-started/overview.html +210 -0
  545. data/docs/brut-css/getting-started/simple-example.html +285 -0
  546. data/docs/brut-css/index.html +193 -0
  547. data/docs/brut-css/prism-twilight.min.css +1 -0
  548. data/docs/brut-css/properties/colors.html +1548 -0
  549. data/docs/brut-css/properties/spacings.html +614 -0
  550. data/docs/brut-css/properties/typography.html +777 -0
  551. data/docs/brut-js/api/AjaxSubmit.html +374 -0
  552. data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
  553. data/docs/brut-js/api/Autosubmit.html +192 -0
  554. data/docs/brut-js/api/Autosubmit.js.html +114 -0
  555. data/docs/brut-js/api/BaseCustomElement.html +1091 -0
  556. data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
  557. data/docs/brut-js/api/BrutCustomElements.html +172 -0
  558. data/docs/brut-js/api/BufferedLogger.html +173 -0
  559. data/docs/brut-js/api/ConfirmSubmit.html +278 -0
  560. data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
  561. data/docs/brut-js/api/ConfirmationDialog.html +425 -0
  562. data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
  563. data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
  564. data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
  565. data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
  566. data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
  567. data/docs/brut-js/api/CopyToClipboard.html +345 -0
  568. data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
  569. data/docs/brut-js/api/Form.html +294 -0
  570. data/docs/brut-js/api/Form.js.html +202 -0
  571. data/docs/brut-js/api/I18nTranslation.html +409 -0
  572. data/docs/brut-js/api/I18nTranslation.js.html +112 -0
  573. data/docs/brut-js/api/LocaleDetection.html +312 -0
  574. data/docs/brut-js/api/LocaleDetection.js.html +168 -0
  575. data/docs/brut-js/api/Logger.html +702 -0
  576. data/docs/brut-js/api/Logger.js.html +141 -0
  577. data/docs/brut-js/api/Message.html +238 -0
  578. data/docs/brut-js/api/Message.js.html +107 -0
  579. data/docs/brut-js/api/PrefixedLogger.html +369 -0
  580. data/docs/brut-js/api/RichString.html +1049 -0
  581. data/docs/brut-js/api/RichString.js.html +164 -0
  582. data/docs/brut-js/api/Tabs.html +295 -0
  583. data/docs/brut-js/api/Tabs.js.html +219 -0
  584. data/docs/brut-js/api/Tracing.html +277 -0
  585. data/docs/brut-js/api/Tracing.js.html +298 -0
  586. data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
  587. data/docs/brut-js/api/external-Performance.html +138 -0
  588. data/docs/brut-js/api/external-Promise.html +138 -0
  589. data/docs/brut-js/api/external-ValidityState.html +138 -0
  590. data/docs/brut-js/api/external-Window.html +233 -0
  591. data/docs/brut-js/api/external-fetch.html +138 -0
  592. data/docs/brut-js/api/global.html +400 -0
  593. data/docs/brut-js/api/index.html +168 -0
  594. data/docs/brut-js/api/index.js.html +181 -0
  595. data/docs/brut-js/api/module-testing.html +383 -0
  596. data/docs/brut-js/api/scripts/linenumber.js +25 -0
  597. data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
  598. data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
  599. data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
  600. data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
  601. data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
  602. data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
  603. data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
  604. data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
  605. data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
  606. data/docs/brut-js/api/testing.DOMCreator.html +171 -0
  607. data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
  608. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
  609. data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
  610. data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
  611. data/docs/brut-js/api/testing_index.js.html +99 -0
  612. data/docs/brut-js.html +35 -0
  613. data/docs/business-logic.html +24 -0
  614. data/docs/cli.html +150 -0
  615. data/docs/components.html +127 -0
  616. data/docs/configuration.html +101 -0
  617. data/docs/css.html +44 -0
  618. data/docs/custom-element-tests.html +92 -0
  619. data/docs/database-access.html +86 -0
  620. data/docs/database-schema.html +86 -0
  621. data/docs/deployment.html +24 -0
  622. data/docs/dev-environment.html +34 -0
  623. data/docs/doc-conventions.html +24 -0
  624. data/docs/end-to-end-tests.html +49 -0
  625. data/docs/flash-and-session.html +116 -0
  626. data/docs/forms.html +402 -0
  627. data/docs/getting-started.html +25 -0
  628. data/docs/handlers.html +92 -0
  629. data/docs/hashmap.json +1 -0
  630. data/docs/hooks.html +103 -0
  631. data/docs/i18n.html +46 -0
  632. data/docs/images/logo-300.png +0 -0
  633. data/docs/images/logo.png +0 -0
  634. data/docs/index.html +24 -0
  635. data/docs/instrumentation.html +58 -0
  636. data/docs/javascript.html +54 -0
  637. data/docs/jobs.html +24 -0
  638. data/docs/keyword-injection.html +48 -0
  639. data/docs/markdown-examples.html +56 -0
  640. data/docs/middleware.html +43 -0
  641. data/docs/not-released.html +24 -0
  642. data/docs/overview.html +156 -0
  643. data/docs/pages.html +145 -0
  644. data/docs/routes.html +52 -0
  645. data/docs/security.html +24 -0
  646. data/docs/seed-data.html +37 -0
  647. data/docs/space-time-continuum.html +24 -0
  648. data/docs/tutorial.html +24 -0
  649. data/docs/unit-tests.html +36 -0
  650. data/docs/vp-icons.css +1 -0
  651. data/lib/brut/cli/app_runner.rb +1 -1
  652. data/lib/brut/cli/apps/test.rb +5 -0
  653. data/lib/brut/framework/mcp.rb +0 -4
  654. data/lib/brut/front_end/component.rb +59 -84
  655. data/lib/brut/front_end/components/constraint_violations.rb +1 -4
  656. data/lib/brut/front_end/components/form_tag.rb +1 -1
  657. data/lib/brut/front_end/components/input.rb +3 -3
  658. data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
  659. data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +6 -8
  660. data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
  661. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
  662. data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
  663. data/lib/brut/front_end/components/time_tag.rb +2 -1
  664. data/lib/brut/front_end/form.rb +4 -4
  665. data/lib/brut/front_end/forms/input.rb +2 -1
  666. data/lib/brut/front_end/forms/input_definition.rb +5 -2
  667. data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
  668. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
  669. data/lib/brut/front_end/forms/select_input.rb +2 -4
  670. data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
  671. data/lib/brut/front_end/handler.rb +28 -26
  672. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
  673. data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
  674. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
  675. data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
  676. data/lib/brut/front_end/page.rb +4 -6
  677. data/lib/brut/front_end/request_context.rb +3 -2
  678. data/lib/brut/i18n/base_methods.rb +136 -82
  679. data/lib/brut/i18n/for_back_end.rb +1 -0
  680. data/lib/brut/i18n/for_cli.rb +1 -0
  681. data/lib/brut/i18n/for_html.rb +32 -4
  682. data/lib/brut/instrumentation/open_telemetry.rb +12 -2
  683. data/lib/brut/sinatra_helpers.rb +10 -3
  684. data/lib/brut/spec_support/component_support.rb +18 -18
  685. data/lib/brut/spec_support/e2e_test_server.rb +3 -0
  686. data/lib/brut/version.rb +1 -1
  687. data/lib/sequel/extensions/brut_migrations.rb +12 -9
  688. metadata +647 -5
  689. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -28,7 +28,6 @@ end
28
28
  #
29
29
  # Becuase this is a Phlex component, you must implement `view_template` and make calls to Phlex's API to create
30
30
  # the markup for your component.
31
- #
32
31
  class Brut::FrontEnd::Component < Phlex::HTML
33
32
 
34
33
  include Brut::Framework::Errors
@@ -51,96 +50,72 @@ class Brut::FrontEnd::Component < Phlex::HTML
51
50
  register_element :brut_tabs
52
51
  register_element :brut_tracing
53
52
 
54
- # Inline an SVG that is part of your app.
55
- #
56
- # @param [String] svg path to the SVG file, relative to where SVGs are
57
- # stored, which is `app/src/front_end/svgs` or where `Brut.container.svg_locator` is
58
- # looking
59
- #
60
- # @see Brut::FrontEnd::InlineSvgLocator
61
- def inline_svg(svg)
62
- Brut.container.svg_locator.locate(svg).then { |svg_file|
63
- File.read(svg_file)
64
- }.then { |svg_content|
65
- raw(safe(svg_content))
66
- }
67
- end
68
-
69
- # Include a {Brut::FrontEnd::Components::TimeTag} in your markup.
70
- def time_tag(timestamp:nil,**component_options, &contents)
71
- args = component_options.merge(timestamp:)
72
- render Brut::FrontEnd::Components::TimeTag.new(**args,&contents)
73
- end
74
-
75
- # Include a {Brut::FrontEnd::Components::FormTag} in your markup.
76
- def form_tag(**args, &block)
77
- render Brut::FrontEnd::Components::FormTag.new(**args,&block)
78
- end
79
-
80
- # Include a component in your markup that you would like Brut to instantiate.
81
- # This will use keyword injection to create the component, which means that if the component
82
- # doesn't require any data from this component, you do not need to pass through those values.
83
- # For example, you may have a component that renders the flash message. To avoid requiring your component to
84
- # be passed the flash, a global component can be injected with it from Brut.
85
- def global_component(component_klass)
86
- render Brut::FrontEnd::RequestContext.inject(component_klass)
87
- end
53
+ # Module for the various "free methods" available to all components.
54
+ # Generally, we don't want to build up mega-classes with lots of modules, but
55
+ # this provides a nice, singular namespace to document the helpers as apart
56
+ # from the various methods of the component.
57
+ module Helpers
58
+ # Render an inline an SVG that is part of your app. **Note** this does not
59
+ # return the SVG's contents, but it renders it into the current Phlex
60
+ # context.
61
+ #
62
+ # @param [String] svg path to the SVG file, relative to where SVGs are
63
+ # stored, which is `app/src/front_end/svgs` or where `Brut.container.svg_locator` is
64
+ # looking
65
+ #
66
+ # @see Brut::FrontEnd::InlineSvgLocator
67
+ def inline_svg(svg)
68
+ Brut.container.svg_locator.locate(svg).then { |svg_file|
69
+ File.read(svg_file)
70
+ }.then { |svg_content|
71
+ raw(safe(svg_content))
72
+ }
73
+ end
88
74
 
89
- # include a {Brut::FrontEnd::Components::ConstraintViolations} in your markup.
90
- def constraint_violations(form:, input_name:, index: nil, message_html_attributes: {}, **html_attributes)
91
- render(
92
- Brut::FrontEnd::Components::ConstraintViolations.new(
93
- form:,
94
- input_name:,
95
- index:,
96
- message_html_attributes:,
97
- **html_attributes
98
- )
99
- )
100
- end
101
-
102
- # Create an HTML input tag for the given input of a form. This is a convieniece method
103
- # that calls {Brut::FrontEnd::Components::Inputs::TextField.for_form_input}.
104
- def input_tag(form:, input_name:, index: nil, **html_attributes)
105
- render(
106
- Brut::FrontEnd::Components::Inputs::TextField.for_form_input(
107
- form:,
108
- input_name:,
109
- index:,
110
- html_attributes:)
111
- )
75
+ # Return a component that you would like Brut to instantiate.
76
+ # This will use keyword injection to create the component, which means that if the component
77
+ # doesn't require any data from this component, you do not need to pass through those values.
78
+ # For example, you may have a component that renders the flash message. To avoid requiring your component to
79
+ # be passed the flash, a global component can be injected with it from Brut.
80
+ #
81
+ # @return [Object] instance of `component_klass`, as created by Brut. This will
82
+ # not render the component.
83
+ def global_component(component_klass)
84
+ Brut::FrontEnd::RequestContext.inject(component_klass)
85
+ end
112
86
  end
87
+ include Helpers
113
88
 
114
89
  # The name of this component, used for debugging and other purposes. Do not
115
90
  # override this.
116
91
  def self.component_name = self.name
117
-
118
- # Calls {.component_name} as a convienience. Do not override this.
92
+ # Convenience method to get the component name. This just calls the class
93
+ # method {.component_name}.
119
94
  def component_name = self.class.component_name
120
95
 
121
- # For page components (components that are private/nested to a page), this returns
122
- # the name of the page in which they are nested. This is mostly useful for
123
- # locating page-specific I18n translations.
124
- #
125
- # @raise If this component is not nested inside a page
126
- # @see Brut::I18n::BaseMethods#t
127
- def page_name
128
- @page_name ||= begin
129
- page = self.class.name.split(/::/).reduce(Module) { |accumulator,class_path_part|
130
- if accumulator.ancestors.include?(Brut::FrontEnd::Page)
131
- accumulator
132
- else
133
- accumulator.const_get(class_path_part)
134
- end
135
- }
136
- if page.ancestors.include?(Brut::FrontEnd::Page)
137
- page.name
138
- elsif page.respond_to?(:page_name)
139
- page.page_name
140
- else
141
- raise "#{self.class} is not nested inside a page, so #page_name should not have been called"
142
- end
143
- end
144
- end
96
+ # True if this component is page private.
97
+ # @!visibility private
98
+ def page_private? = !!self.containing_page_class
145
99
 
100
+ # Returns the {Brut::FrontEnd::Page.page_name} of the page containing this component,
101
+ # if it is {#page_private?}. Do not call if it's not.
102
+ # @!visibility private
103
+ def containing_page_name = self.containing_page_class.page_name
104
+
105
+ private
106
+
107
+ def containing_page_class
108
+ page_class = self.class.name.split(/::/).reduce(Module) { |accumulator,class_path_part|
109
+ if accumulator.ancestors.include?(Brut::FrontEnd::Page)
110
+ accumulator
111
+ else
112
+ accumulator.const_get(class_path_part)
113
+ end
114
+ }
115
+ if page_class.ancestors.include?(Brut::FrontEnd::Page)
116
+ page_class
117
+ else
118
+ nil
119
+ end
120
+ end
146
121
  end
@@ -14,10 +14,7 @@
14
14
  # </brut-cv-messages>
15
15
  # ```
16
16
  #
17
- # Note that if you are using `<brut-form>` then `<brut-cv>` elements will be inserted into the `<brut-cv-messages>` element, however
18
- # they will not have the `server-side` attribute.
19
- #
20
- # You will most commonly use this component via {Brut::FrontEnd::Component#constraint_violations}.
17
+ # Note that if you are using `<brut-form>` then `<brut-cv>` elements will be inserted into the `<brut-cv-messages>` element, however they will not have the `server-side` attribute.
21
18
  class Brut::FrontEnd::Components::ConstraintViolations < Brut::FrontEnd::Component
22
19
  # Create a new ConstraintViolations component
23
20
  #
@@ -1,4 +1,4 @@
1
- # Represents a `<form>` HTML element that includes a CSRF token as needed. You likely want to use this class via the {Brut::FrontEnd::Component#form_tag} method.
1
+ # Represents a `<form>` HTML element that includes a CSRF token as needed.
2
2
  class Brut::FrontEnd::Components::FormTag < Brut::FrontEnd::Component
3
3
  # Creates the form surrounding the contents of the block yielded to it. If the form's action is a POST, it will include a CSRF token.
4
4
  # If the form's action is GET, it will not.
@@ -2,10 +2,10 @@ module Brut::FrontEnd::Components
2
2
 
3
3
  # Holds components designed to render HTML `<input>` and other form components.
4
4
  module Inputs
5
- autoload(:TextField,"brut/front_end/components/inputs/text_field")
5
+ autoload(:InputTag,"brut/front_end/components/inputs/input_tag")
6
6
  autoload(:RadioButton,"brut/front_end/components/inputs/radio_button")
7
- autoload(:Select,"brut/front_end/components/inputs/select")
8
- autoload(:Textarea,"brut/front_end/components/inputs/textarea")
7
+ autoload(:SelectTagWithOptions,"brut/front_end/components/inputs/select_tag_with_options")
8
+ autoload(:TextareaTag,"brut/front_end/components/inputs/textarea_tag")
9
9
  autoload(:CsrfToken,"brut/front_end/components/inputs/csrf_token")
10
10
  end
11
11
 
@@ -1,5 +1,5 @@
1
1
  # Renders a hidden field for a form that contains the current CSRF token. You only need
2
- # to use this directly if you are building a form without {Brut::FrontEnd::Component#form_tag}.
2
+ # to use this directly if you are building a form without {Brut::FrontEnd::Components::FormTag}
3
3
  class Brut::FrontEnd::Components::Inputs::CsrfToken < Brut::FrontEnd::Components::Input
4
4
  def initialize(csrf_token:)
5
5
  @csrf_token = csrf_token
@@ -1,5 +1,5 @@
1
1
  # Generates an HTML `<input>` field.
2
- class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components::Input
2
+ class Brut::FrontEnd::Components::Inputs::InputTag < Brut::FrontEnd::Components::Input
3
3
  # Creates the appropriate input for the given {Brut::FrontEnd::Form} and input name.
4
4
  # Generally, you want to use this method over the initializer.
5
5
  #
@@ -7,10 +7,10 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
7
7
  # @param [String] input_name the name of the input, which should be a member of `form`
8
8
  # @param [Integer] index if this input is part of an array, this is the index into that array. This is used to get the input's value.
9
9
  # @param [Hash] html_attributes any additional HTML attributes to include on the `<input>` element.
10
- def self.for_form_input(form:, input_name:, index: nil, html_attributes: {})
10
+ def self.for_form_input(form:, input_name:, index: nil, **html_attributes)
11
+ input = form.input(input_name, index:)
11
12
  default_html_attributes = {}
12
13
  html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
13
- input = form.input(input_name, index:)
14
14
 
15
15
  default_html_attributes[:required] = input.required
16
16
  default_html_attributes[:pattern] = input.pattern
@@ -45,13 +45,11 @@ class Brut::FrontEnd::Components::Inputs::TextField < Brut::FrontEnd::Components
45
45
  end
46
46
  if !form.new? && !input.valid?
47
47
  default_html_attributes["data-invalid"] = true
48
- input.validity_state.each do |constraint,violated|
49
- if violated
50
- default_html_attributes["data-#{constraint}"] = true
51
- end
48
+ input.validity_state.each do |constraint|
49
+ default_html_attributes["data-#{constraint}"] = true
52
50
  end
53
51
  end
54
- Brut::FrontEnd::Components::Inputs::TextField.new(default_html_attributes.merge(html_attributes))
52
+ Brut::FrontEnd::Components::Inputs::InputTag.new(default_html_attributes.merge(html_attributes))
55
53
  end
56
54
 
57
55
  def invalid? = @attributes["data-invalid"] == true
@@ -3,7 +3,7 @@
3
3
  # internal to the {Brut::FrontEnd::Form} treat the radio button group as a single input with
4
4
  # a single name and value. When it comes time to generate HTML, this class is used
5
5
  # to generate a single radio button from a group.
6
- class Brut::FrontEnd::Components::Inputs::RadioButton < Brut::FrontEnd::Components::Inputs::TextField
6
+ class Brut::FrontEnd::Components::Inputs::RadioButton < Brut::FrontEnd::Components::Inputs::InputTag
7
7
  # Creates a radio button that is part of a radio button group. You should call this
8
8
  # method once for each radio button in the group.
9
9
  #
@@ -0,0 +1,187 @@
1
+ # Renders an HTML `<select>`.
2
+ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd::Components::Input
3
+ # Creates the appropriate select input for the given {Brut::FrontEnd::Form} and input name.
4
+ # Generally, you want to use this method over the initializer.
5
+ #
6
+ # @param [Brut::FrontEnd::Form} form The form that is being rendered.
7
+ # This method will consult this class to understand the requirements
8
+ # on this select so its HTML is generated correctly.
9
+ # @param [String] input_name the name of the input, which should be a member of `form`
10
+ # @param [Array<Object>] options An array of objects represented what is being selected.
11
+ # These can be any object and are ideally whatever domain object or
12
+ # data type you want on the backend to represent this selection.
13
+ # @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
14
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
15
+ # `value` attribute. The value returned by `value_attribute` should be unique amongst the
16
+ # `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
17
+ # @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
18
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
19
+ # text content. The value returned by `option_text_attribute` need not be unique, though if it
20
+ # is not unique, it will certainly be confusing.
21
+ # @param [Integer] index if this input is part of an array, this is the index into that array.
22
+ # This is used to get the input's value.
23
+ # @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
24
+ # @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
25
+ # If this is false, there will be no blank element. If it's `true`, there will be one with
26
+ # no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
27
+ # to be used as the `value` attribute and option text content, respectively.
28
+ #
29
+ # @return [Brut::FrontEnd::Components::Inputs::SelectTagWithOptions] the select input ready to be placed into a view.
30
+ def self.for_form_input(form:,
31
+ input_name:,
32
+ options:,
33
+ include_blank: false,
34
+ value_attribute:,
35
+ option_text_attribute:,
36
+ index: nil,
37
+ html_attributes: {})
38
+ html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
39
+ default_html_attributes = {}
40
+ index ||= 0
41
+ input = form.input(input_name, index:)
42
+ default_html_attributes[:required] = input.required
43
+ if !form.new? && !input.valid?
44
+ default_html_attributes["data-invalid"] = true
45
+ input.validity_state.each do |constraint,violated|
46
+ if violated
47
+ default_html_attributes["data-#{constraint}"] = true
48
+ end
49
+ end
50
+ end
51
+ name = if input.array?
52
+ "#{input.name}[]"
53
+ else
54
+ input.name
55
+ end
56
+
57
+ Brut::FrontEnd::Components::Inputs::SelectTagWithOptions.new(
58
+ name: name,
59
+ options:,
60
+ selected_value: input.value,
61
+ value_attribute:,
62
+ option_text_attribute:,
63
+ include_blank:,
64
+ html_attributes: default_html_attributes.merge(html_attributes)
65
+ )
66
+ end
67
+
68
+ # Create the element.
69
+ #
70
+ # @param [String] name the name of the input
71
+ # @param [Array<Object>] options An array of objects represented what is being selected.
72
+ # These can be any object and are ideally whatever domain object or
73
+ # data type you want on the backend to represent this selection.
74
+ # @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
75
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
76
+ # `value` attribute. The value returned by `value_attribute` should be unique amongst the
77
+ # `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
78
+ # @param [String] selected_value the value of the selected option. Note that this is the *value*
79
+ # of the selected option, not the selected option itself. To set the selected value
80
+ # based on a selected option, omit this and use `selected_option`
81
+ # @param [String] selected_option the selected option. Note that `value_attribute` will be called
82
+ # on this to determine the selected value to use when generating HTML. Also note that
83
+ # this object must be in `options` or an exeception is raised.
84
+ # @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
85
+ # This will be called on each element of `options` to get the value used for the `<option>`'s
86
+ # text content. The value returned by `option_text_attribute` need not be unique, though if it
87
+ # is not unique, it will certainly be confusing.
88
+ # @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
89
+ # @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
90
+ # If this is false, there will be no blank element. If it's `true`, there will be one with
91
+ # no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
92
+ # to be used as the `value` attribute and option text content, respectively.
93
+ #
94
+ # @raise ArgumentError if `selected_option` is present, but not in `options` or if `selected_value` is
95
+ # present, but no option's value for `value_attribute` is that value.
96
+ #
97
+ # XXX: Why does this not ask the form for the selected_value?
98
+ # XXX: This doesn't do well when values are strings
99
+ def initialize(name:,
100
+ options:,
101
+ value_attribute:,
102
+ selected_value: nil,
103
+ selected_option: nil,
104
+ option_text_attribute:,
105
+ include_blank: false,
106
+ html_attributes:)
107
+ @options = options
108
+ @include_blank = IncludeBlank.from_param(include_blank)
109
+ @value_attribute = value_attribute
110
+ @option_text_attribute = option_text_attribute
111
+ @html_attributes = html_attributes
112
+ @html_attributes[:name] = name
113
+
114
+ if selected_value.nil?
115
+ if selected_option.nil?
116
+ @selected_value = nil # explicitly nothing is selected
117
+ else
118
+ option = options.detect { |option|
119
+ option.send(@value_attribute) == selected_option.send(@value_attribute)
120
+ }
121
+ if option.nil?
122
+ raise ArgumentError, "selected_option #{selected_option} (with #{value_attribute} '#{selected_option.send(value_attribute)}') was not found in options"
123
+ end
124
+ @selected_value = option.send(@value_attribute)
125
+ end
126
+ else
127
+ if selected_value.kind_of?(Array)
128
+ raise "WTF: #{name}"
129
+ end
130
+ option = options.detect { |option|
131
+ selected_value == option.send(@value_attribute)
132
+ }
133
+ if option.nil?
134
+ raise ArgumentError, "selected_value #{selected_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
135
+ end
136
+ @selected_value = option.send(@value_attribute)
137
+ end
138
+ end
139
+
140
+ def view_template
141
+ select(**@html_attributes) {
142
+ if @include_blank
143
+ option(**@include_blank.option_attributes) {
144
+ @include_blank.text_content
145
+ }
146
+ end
147
+ options = @options.each do |option|
148
+ value = option.send(@value_attribute)
149
+ option_attributes = { value: value }
150
+ if value == @selected_value
151
+ option_attributes[:selected] = true
152
+ end
153
+ option(**option_attributes) {
154
+ option.send(@option_text_attribute)
155
+ }
156
+ end
157
+ }
158
+ end
159
+ private
160
+
161
+ # @!visibility private
162
+ class IncludeBlank
163
+ attr_reader :text_content, :option_attributes
164
+ def self.from_param(include_blank)
165
+ if !include_blank
166
+ return nil
167
+ else
168
+ self.new(include_blank)
169
+ end
170
+ end
171
+ def initialize(include_blank)
172
+ if include_blank == true
173
+ @text_content = ""
174
+ @option_attributes = {}
175
+ elsif include_blank.kind_of?(Hash)
176
+ if include_blank.key?(:value) && include_blank.key?(:text_content)
177
+ @text_content = include_blank[:text_content]
178
+ @option_attributes = { value: include_blank[:value] }
179
+ else
180
+ raise ArgumentError, "when include_blank: is a Hash, it must include both :value and :text_content as keys. Got: #{include_blank.keys.join(", ")}"
181
+ end
182
+ else
183
+ raise ArgumentError,"include_blank: was a #{include_blank.class}. It should be true, false, nil, or a Hash"
184
+ end
185
+ end
186
+ end
187
+ end
@@ -1,5 +1,5 @@
1
1
  # Generates an HTML `<textarea>` field.
2
- class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components::Input
2
+ class Brut::FrontEnd::Components::Inputs::TextareaTag < Brut::FrontEnd::Components::Input
3
3
  # Creates the appropriate textarea for the given {Brut::FrontEnd::Form} and input name.
4
4
  # Generally, you want to use this method over the initializer.
5
5
  #
@@ -35,7 +35,7 @@ class Brut::FrontEnd::Components::Inputs::Textarea < Brut::FrontEnd::Components:
35
35
  end
36
36
  end
37
37
  value = input.value
38
- Brut::FrontEnd::Components::Inputs::Textarea.new(default_html_attributes.merge(html_attributes), value)
38
+ Brut::FrontEnd::Components::Inputs::TextareaTag.new(default_html_attributes.merge(html_attributes), value)
39
39
  end
40
40
  # Create an instance
41
41
  #
@@ -1,4 +1,5 @@
1
- # Renders a date or timestamp accessibly, using the `<time>` element. Likely you will use this via the {Brut::FrontEnd::Component#time_tag} method. This will account for the current request's time zone. See {Clock}.
1
+ # Renders a date or timestamp accessibly, using the `<time>` element.
2
+ # This will account for the current request's time zone. See {Clock}.
2
3
  class Brut::FrontEnd::Components::TimeTag < Brut::FrontEnd::Component
3
4
  include Brut::I18n::ForHTML
4
5
  # Creates the component
@@ -68,12 +68,12 @@ class Brut::FrontEnd::Form
68
68
  @inputs = self.class.input_definitions.map { |name,input_definition|
69
69
  value = @params[name] || @params[name.to_sym]
70
70
  inputs = if value.kind_of?(Array)
71
- value.map { |one_value|
72
- input_definition.make_input(value: one_value)
71
+ value.map.with_index { |one_value, index|
72
+ input_definition.make_input(value: one_value, index:)
73
73
  }
74
74
  else
75
75
  [
76
- input_definition.make_input(value:)
76
+ input_definition.make_input(value:, index: nil)
77
77
  ]
78
78
  end
79
79
 
@@ -98,7 +98,7 @@ class Brut::FrontEnd::Form
98
98
  input = inputs[index]
99
99
  if input.nil?
100
100
  input_definition = self.class.input_definitions.fetch(input_name.to_s)
101
- input = input_definition.make_input(value:"")
101
+ input = input_definition.make_input(value:"", index:)
102
102
  inputs[index] = input
103
103
  end
104
104
  input
@@ -13,9 +13,10 @@ class Brut::FrontEnd::Forms::Input
13
13
  # Create the input with the given definition and value
14
14
  # @param [Brut::FrontEnd::Forms::InputDefinition] input_definition
15
15
  # @param [String] value
16
- def initialize(input_definition:, value:)
16
+ def initialize(input_definition:, value:, index:)
17
17
  @input_definition = input_definition
18
18
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
19
+ @index = index
19
20
  self.value=(value)
20
21
  end
21
22
 
@@ -109,7 +109,10 @@ class Brut::FrontEnd::Forms::InputDefinition
109
109
 
110
110
 
111
111
  # Create an Input based on this definition, initializing it with the given value.
112
- def make_input(value:)
113
- Brut::FrontEnd::Forms::Input.new(input_definition: self, value: value)
112
+ # @param [String] value the value to give this input initially.
113
+ # @param [Integer] index the index of this input, if it is part of an array of
114
+ # inputs. `nil` is allowed only if the input definition is not for an array.
115
+ def make_input(value:, index:)
116
+ Brut::FrontEnd::Forms::Input.new(input_definition: self, value:, index:)
114
117
  end
115
118
  end
@@ -8,9 +8,10 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInput
8
8
  attr_reader :validity_state
9
9
 
10
10
  # (see Brut::FrontEnd::Forms::Input#initialize)
11
- def initialize(input_definition:, value:)
11
+ def initialize(input_definition:, value:, index:)
12
12
  @input_definition = input_definition
13
13
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
14
+ @index = index
14
15
  if input_definition.array?
15
16
  value ||= []
16
17
  end
@@ -23,7 +23,7 @@ class Brut::FrontEnd::Forms::RadioButtonGroupInputDefinition
23
23
  def array? = @array
24
24
 
25
25
  # Create an Input based on this defitition, initializing it with the given value.
26
- def make_input(value:)
27
- Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value: value)
26
+ def make_input(value:, index:)
27
+ Brut::FrontEnd::Forms::RadioButtonGroupInput.new(input_definition: self, value:, index:)
28
28
  end
29
29
  end
@@ -9,12 +9,10 @@ class Brut::FrontEnd::Forms::SelectInput
9
9
  attr_reader :validity_state
10
10
 
11
11
  # (see Brut::FrontEnd::Forms::Input#initialize)
12
- def initialize(input_definition:, value:)
12
+ def initialize(input_definition:, value:, index:)
13
13
  @input_definition = input_definition
14
14
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new
15
- if input_definition.array?
16
- value ||= []
17
- end
15
+ @index = index
18
16
  self.value=(value)
19
17
  end
20
18
 
@@ -21,7 +21,7 @@ class Brut::FrontEnd::Forms::SelectInputDefinition
21
21
  def array? = @array
22
22
 
23
23
  # Create an Input based on this defitition, initializing it with the given value.
24
- def make_input(value:)
25
- Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value: value)
24
+ def make_input(value:, index:)
25
+ Brut::FrontEnd::Forms::SelectInput.new(input_definition: self, value:, index:)
26
26
  end
27
27
  end
@@ -1,12 +1,24 @@
1
1
  module Brut::FrontEnd
2
- # A handler responds to all HTTP requests other than those that render a page. It will be given any data it needs
3
- # to handle the request to its {#handle} method, which you must implement.
4
- # You define this method to accept the parameters you expect. See {Brut::FrontEnd::RequestContext} for how that works.
2
+ # A handler responds to all HTTP requests other than those that render a page.
3
+ # Like a page, the handler is initialized with any of the data it needs. The {#handle} method will
4
+ # be called to perform whatever action is needed, and its return value will determine what
5
+ # ther esponse will be.
5
6
  #
6
- # You may also define `before_handle` which will be given any subset of those parameters and can perform logic before
7
- # handle is called. This is most useful in a base class to check for permissions or other cross-cutting concerns.
7
+ # To create a handler, after defining a route or form,
8
+ # subclass this class (or, more likely, your app's `AppHandler`) and
9
+ # provide an initializer that accepts keyword arguments. The names of these arguments will be used
10
+ # to locate the values that Brut will pass in when creating your page object. If your handler
11
+ # is for a form, be sure to include the `form:` keyword argument.
8
12
  #
9
- # The primary method of this class is {#handle!} which you should not override, but *should* call in a test.
13
+ # Consult Brut's documentation on keyword injection to know what values you may use and how values are located.
14
+ #
15
+ # Then, implement {#handle} to perform whatever logic is needed to handle the request.
16
+ #
17
+ # You may also define `before_handle` which will be called before {#handle} to potentially abort
18
+ # the request. This is mostly useful if you have a base class for some of your handlers and want to
19
+ # share cross-cutting logic.
20
+ #
21
+ # Note that the public API for handlers is {#handle!}, which is what you should call in a test.
10
22
  class Handler
11
23
  include Brut::FrontEnd::HandlingResults
12
24
  include Brut::Framework::Errors
@@ -27,32 +39,22 @@ module Brut::FrontEnd
27
39
  abstract_method!
28
40
  end
29
41
 
42
+ # Override this to performa any checks before {#handle} is called. This should
43
+ # return `nil` if {#handle} should proceed to be called. Generally, you don't need to override
44
+ # this as {#handle} can include the logic. Where this is useful is to share cross-cutting logic
45
+ # across other handlers.
46
+ #
47
+ # @return [URI|Brut::FrontEnd::Component,Array,Brut::FrontEnd::HttpStatus,Brut::FrontEnd::Download] See
48
+ # {#handle} for what each return value means.
49
+ def before_handle = nil
50
+
30
51
  # Called by Brut to handle the request. Do not override this. If your handler responds to `before_handle` that is called with the
31
52
  # same args as you have defined for {#handle}. If `before_handle` returns anything other than `nil`, that value is returned and
32
53
  # should be one of the values documented in {#handle}. If `before_handle` returns `nil`, {#handle} is called and whatever it
33
54
  # returns is returned here.
34
55
  def handle!(**args)
35
56
  result = nil
36
- if self.respond_to?(:before_handle)
37
- before_handle_args = self.method(:before_handle).parameters.map { |(type,name)|
38
- if type == :keyreq
39
- if args.key?(name)
40
- [ name, args[name] ]
41
- else
42
- raise ArgumentError,"before_handle requires keyword arg '#{name}' but `handle` did not receive it. It must"
43
- end
44
- elsif type == :key
45
- if args.key?(name)
46
- [ name, args[name] ]
47
- else
48
- nil
49
- end
50
- else
51
- raise ArgumentError,"before_handle must only have keyword args. Got '#{name}' of type '#{type}'"
52
- end
53
- }.compact.to_h
54
- result = self.before_handle(**before_handle_args)
55
- end
57
+ result = self.before_handle
56
58
  if result.nil?
57
59
  result = self.handle(**args)
58
60
  end