brut 0.0.21 → 0.0.22

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 +1 -1
  688. metadata +647 -5
  689. data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -1,9 +1,12 @@
1
1
  # Receives content security policy violations and logs them. This is set up in {Brut::Framework::MCP}, however CSP reporting is
2
2
  # configured in {Brut::FrontEnd::RouteHooks::CSPNoInlineStylesOrScripts::ReportOnly}.
3
3
  class Brut::FrontEnd::Handlers::CspReportingHandler < Brut::FrontEnd::Handler
4
- def handle(body:)
4
+ def initialize(body:)
5
+ @body = body
6
+ end
7
+ def handle
5
8
  begin
6
- parsed = JSON.parse(body.read)
9
+ parsed = JSON.parse(@body.read)
7
10
  Brut.container.instrumentation.add_attributes(parsed.merge(prefix: "brut.csp-reporting"))
8
11
  rescue => ex
9
12
  Brut.container.instrumentation.record_exception(ex)
@@ -65,12 +65,16 @@ class Brut::FrontEnd::Handlers::InstrumentationHandler < Brut::FrontEnd::Handler
65
65
  def as_carrier = { "traceparent" => @value }
66
66
  end
67
67
 
68
- def handle(http_tracestate:, http_traceparent:)
69
- traceparent = TraceParent.from_header(http_traceparent)
70
- span = Span.from_header(http_tracestate)
68
+ def initialize(http_tracestate:, http_traceparent:)
69
+ @http_tracestate = http_tracestate
70
+ @http_traceparent = http_traceparent
71
+ end
72
+ def handle
73
+ traceparent = TraceParent.from_header(@http_traceparent)
74
+ span = Span.from_header(@http_tracestate)
71
75
 
72
76
  if span.nil? || traceparent.nil?
73
- SemanticLogger[self.class].info "Missing traceparent or span: #{http_tracestate}, #{http_traceparent}"
77
+ SemanticLogger[self.class].info "Missing traceparent or span: #{@http_tracestate}, #{@http_traceparent}"
74
78
  return http_status(400)
75
79
  end
76
80
 
@@ -5,9 +5,13 @@
5
5
  # {Brut::FrontEnd::Session#http_accept_language=} *only* if the `Accept-Language` header did not provide a value that is supported by
6
6
  # the app.
7
7
  class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
8
- def handle(body:,session:)
8
+ def initialize(body:,session:)
9
+ @body = body
10
+ @session = session
11
+ end
12
+ def handle
9
13
  begin
10
- parsed = JSON.parse(body.read)
14
+ parsed = JSON.parse(@body.read)
11
15
 
12
16
  Brut.container.instrumentation.add_attributes(
13
17
  prefix: "brut.locale-detection",
@@ -19,9 +23,9 @@ class Brut::FrontEnd::Handlers::LocaleDetectionHandler < Brut::FrontEnd::Handler
19
23
  locale = parsed["locale"]
20
24
  timezone = parsed["timeZone"]
21
25
 
22
- session.timezone_from_browser = timezone
23
- if !session.http_accept_language.known?
24
- session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
26
+ @session.timezone_from_browser = timezone
27
+ if !@session.http_accept_language.known?
28
+ @session.http_accept_language = Brut::I18n::HTTPAcceptLanguage.from_browser(locale)
25
29
  end
26
30
  end
27
31
  rescue => ex
@@ -1,7 +1,10 @@
1
1
  # Used in development to handle a defined route but a missing page. This arranges to render a nicer error page than the default.
2
2
  class Brut::FrontEnd::Handlers::MissingHandler < Brut::FrontEnd::Handler
3
- def handle(route:)
4
- Brut::FrontEnd::Pages::MissingPage.new(route:)
3
+ def initialize(route:)
4
+ @route = route
5
+ end
6
+ def handle
7
+ Brut::FrontEnd::Pages::MissingPage.new(route: @route)
5
8
  end
6
9
 
7
10
  class Form < Brut::FrontEnd::Form
@@ -35,12 +35,12 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
35
35
  # redirect the user or produce an error.
36
36
  #
37
37
  # @return [URI|Brut::FrontEnd::HttpStatus|Object] If you return a `URI` (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#redirect_to}), the user is redirected and no HTML is generated. If you return a {Brut::FrontEnd::HttpStatus} (mostly likely by returning the result of calling {Brut::FrontEnd::HandlingResults#http_status}), HTML generation is skipped and that status is returned with no content. If anything else is returned, HTML is generated normal.
38
- def before_render = nil
38
+ def before_generate = nil
39
39
 
40
- # Core method of this class. Do not override. This handles the use of {#before_render} and is what Brut
40
+ # Core method of this class. Do not override. This handles the use of {#before_generate} and is what Brut
41
41
  # calls to possibly render the page.
42
42
  def handle!
43
- case before_render
43
+ case before_generate
44
44
  in URI => uri
45
45
  uri
46
46
  in Brut::FrontEnd::HttpStatus => http_status
@@ -72,9 +72,6 @@ class Brut::FrontEnd::Page < Brut::FrontEnd::Component
72
72
  # Convienience method for {.page_name}.
73
73
  def page_name = self.class.page_name
74
74
 
75
- # @!visibility private
76
- def component_name = raise Brut::Framework::Errors::Bug,"#{self.class} is not a component"
77
-
78
75
  private
79
76
 
80
77
  # Locates the layout class and uses it to render itself, along
@@ -88,6 +85,7 @@ private
88
85
  "layout"
89
86
  ].join("_")).camelize
90
87
  )
88
+ Brut.container.instrumentation.add_prefixed_attributes("brut", layout_class: layout_class)
91
89
  render layout_class.new(page_name:,&block)
92
90
  end
93
91
 
@@ -112,12 +112,13 @@ class Brut::FrontEnd::RequestContext
112
112
  # @param [Class] klass a class that is to be instantiated entirely by the contents of this `RequestContext`.
113
113
  # @param [Hash] request_params Query string parameters provided by Rack.
114
114
  # @param [Brut::FrontEnd::Routing::Route] route the route that triggered the request.
115
+ # @param [Brut::FrontEnd::Form] form the form, if available
115
116
  # @return [Hash] can be splatted to keyword arguments and passed to the constructor of `klass`
116
117
  #
117
118
  # @raise [ArgumentError] if the constructor has any non-keyword arguments, or if any required keyword argument is
118
119
  # not present in this `RequestContext`.
119
- def as_constructor_args(klass, request_params:, route:nil)
120
- args_for_method(method: klass.instance_method(:initialize), request_params:, form: nil, route:)
120
+ def as_constructor_args(klass, request_params:, route:nil, form: nil)
121
+ args_for_method(method: klass.instance_method(:initialize), request_params:, form: , route:)
121
122
  end
122
123
 
123
124
  # Based on `object`' method, returns a Hash that maps all keywords it requires to the values stored in this
@@ -6,43 +6,66 @@
6
6
  # * {Brut::I18n::ForCLI} for CLI apps
7
7
  # * {Brut::I18n::ForBackEnd} for back-end classes that aren't generating HTML
8
8
  #
9
+ # This module assumes the existence of a three-method protocol that's used for HTML escaping in an
10
+ # HTML-generating web context:
11
+ #
12
+ # * `capture` accepts the block yieled to {#t} and returns whatever it generates. This needs to exist
13
+ # because Phlex's API renders to an internal buffer by default. This module needs to allow Phlex
14
+ # API methods to render to a different buffer.
15
+ # * `safe` accepts a string and returns a string that is presumed to be HTML safe.
16
+ # * `html_escape` accepts a string and returns a string that is HTML escaped.
17
+ #
18
+ # This module does not implement these methods and assumes that either the class using this module will
19
+ # implement them or that a submodule being used does. All submodules provided by Brut provide implementations,
20
+ # so this information is only relevant if you are using this module directly.
9
21
  module Brut::I18n::BaseMethods
10
22
 
11
23
  # Access a translation and insert interpolated elemens as needed. This will use the provided key to determine
12
- # the actual full key to the translation, as described below. The value returned is not HTML escaped,
13
- # assuming that you have not placed HTML injections in your own translation files. Interpolated
14
- # values *are* HTML escaped, so external input is safe to provide.
15
- #
16
- # This method also may take a block, and the results of the block are inserted into the `%{block}`
17
- # interpolation value in the i18n string, if it's present.
24
+ # the actual full key to the translation, as described below. See {Brut::I18n::ForHTML#t} for details
25
+ # on how this works in the context of a {Brut::FrontEnd::Component} or {Brut::FrontEnd::Page}.
18
26
  #
19
27
  # Any missing interpolation will result in an exception, *except* for the value `field`. When
20
28
  # a string has `%{field}` in it, but `field:` is omitted in this call, the value for
21
- # `"general.cv.this_field"` is used. This value, in English, is "this field", so a call
29
+ # `"cv.this_field"` is used. This value, in English, is "this field", so a call
22
30
  # to `t("email.required")` would generate `"This field is required"`, while a call
23
31
  # to `t("email.required", field: "E-mail address")` would generate `"E-mail address is required"`.
24
32
  #
25
33
  # @param [String,Symbol,Array<String>,Array<Symbol>] key used to create one or more keys to be translated.
26
- # This value's behavior is designed to balance predictabilitiy in what actual key is chosen
27
- # but without needless repetition on a page. If this value is provided, and is an array, the values
28
- # are joined with "." to form a key. If the value is not an array, that value is used directly.
29
- # Given this key, two values are checked for a translation: the key itself and
30
- # the key inside "general.". If this value is *not* provided, it is expected
31
- # that the `**rest` hash includes page: or component:. See that parameter and the example.
32
- #
33
- # @param [Hash] rest values to use for interpolation of the key's translation. If `key` is omitted,
34
- # this hash should have a value for either `page:` or `component:` (not both). If
35
- # `page:` is present, it is assumed that the class that has included this module
36
- # is a `Brut::FrontEnd::Page` or is a page component. It's `page_name` will be used to create
37
- # a key based on the value of `page:`: `pages.«page_name».«page: value»`.
38
- # if `component:` is included, the behavior is the same but for `component` instead of `page`.
39
- # Note that if the page– or component–specific key is not found, this will check
40
- # `generalpage: value»`.
34
+ # This value's behavior balances predictabilitiy with what key is used and some flexibilty
35
+ # to allow page– or component–specific translations when needed, and fallbacks when not.
36
+ #
37
+ # When an array if given, the values are turned into strings and joined with a "." to
38
+ # form a full key.
39
+ #
40
+ # Depending on what class this module is mixed into, additional keys will be tried:
41
+ #
42
+ # * If this is a page, `pages.«page_name».` will be prepended and tried before the key passed in.
43
+ # If the `pages.«page_name»` version is not found, the exact key passed in is checked.
44
+ # * If this is a page private component, `pages.«page_name».` will be prepended and tried before
45
+ # the key passed in. The value for page_name»` is determined by the outer class that this
46
+ # component is a part of.
47
+ # * If this is a component (include if it is a page private component),
48
+ # `componentscomponent_name».` will be prepended and tried before the key passed in.
49
+ # If the `components.«component_name»` version is not found, the exact key passed in is checked.
50
+ #
51
+ # The priority of the keys are as follows:
52
+ #
53
+ # 1. Component key is checked (unless this is a page)
54
+ # 2. Page key is checked (unless this is a non-page private component)
55
+ # 3. The literal key passed-in is checked
56
+ #
57
+ # If no value is found for any key an exception is raised.
58
+ #
59
+ # @param [Hash] interpolated_values values to use for interpolation of the key's translation. Note that if
60
+ # `:block` is part of this has, you may not pass a block to this method. Note also
61
+ # that `:count` can be used if the key is expected to be pluralized. This value
62
+ # is required for keys that are designed for pluralization. See examples below.
41
63
  # @option interpolated_values [Numeric] count Special interpolation to control pluralization.
42
- # @yield Nothing is yielded if a block is given, however the value returned is used for the `%{block}`
43
- # interpolation value.
64
+ # @option interpolated_values [String] block Value to use for `%{block}`. If this is used, a block may not be
65
+ # yielded.
66
+ # @yield If a block is passed, it is used for the value of `%{block}`. No parameters are yielded to the block.
44
67
  # @yieldreturn [String] The value to use for the `%{block}` interpolation value. There is some nuance to
45
- # how this works. The value returned is given to `capture`, and that value
68
+ # how this works. The value returned is given to `capture`, and *that* value
46
69
  # is given to `safe`. Outside of an HTML-rendering context, these methods
47
70
  # simply pass through the contents of the block. In an HTML-rendering
48
71
  # context, however, these methods are assumed to be from
@@ -57,25 +80,17 @@ module Brut::I18n::BaseMethods
57
80
  # @example Simplest usage
58
81
  # # in your translations file
59
82
  # en: {
60
- # general: {
61
- # hello: "Hi!"
62
- # },
63
- # formalized: {
64
- # hello: "Greetings!"
65
- # }
83
+ # hello: "Hi!"
66
84
  # }
67
85
  # # in your code
68
86
  # t(:hello) # => Hi!
69
- # t("formalized.hello") # => Greetings!
70
87
  #
71
88
  # @example Using an array for the key
72
89
  # # in your translations file
73
90
  # en: {
74
- # general: {
75
- # actions: {
76
- # edit: "Make an edit"
77
- # }
78
- # },
91
+ # actions: {
92
+ # edit: "Make an edit"
93
+ # }
79
94
  # }
80
95
  # # in your code
81
96
  # t([:actions, :edit]) # => Make an edit
@@ -83,9 +98,7 @@ module Brut::I18n::BaseMethods
83
98
  # @example Using page:
84
99
  # # in your translations file
85
100
  # en: {
86
- # general: {
87
- # new_widget: "Make a New Widget",
88
- # },
101
+ # new_widget: "Make a New Widget",
89
102
  # pages: {
90
103
  # HomePage: {
91
104
  # new_widget: "Create new Widget"
@@ -96,28 +109,61 @@ module Brut::I18n::BaseMethods
96
109
  # },
97
110
  # }
98
111
  # # in your code for HomePage
99
- # t(page: :new_widget) # => Create new Widget
112
+ # t(:new_widget) # => Create new Widget
100
113
  # # in your code for WidgetsPage
101
- # t(page: :new_widget) # => Create New
114
+ # t(:new_widget) # => Create New
102
115
  # # in your code for SomeOtherPage
103
- # t(page: :new_widget) # => Make a New Widget
116
+ # t(:new_widget) # => Make a New Widget
104
117
  #
105
- # @example Using page: with an array
118
+ # @example Using in a component
106
119
  # # in your translations file
107
120
  # en: {
121
+ # status: {
122
+ # ready: "Available",
123
+ # stalled: "Stalled",
124
+ # completed: "Done",
125
+ # },
126
+ # components: {
127
+ # TagComponent: {
128
+ # status: {
129
+ # ready: "Ready",
130
+ # stalled: "Waiting",
131
+ # }
132
+ # },
133
+ # },
134
+ # }
135
+ # # in your code for TagComponent
136
+ # t(page: [ :status, :ready ]) # => Ready
137
+ # t(page: [ :status, :completed ]) # => Done
138
+ # # in your code for StatusComponent
139
+ # t(page: [ :status, :ready ]) # => Available
140
+ # t(page: [ :status, :completed ]) # => Done
141
+ #
142
+ # @example Using in a page-private component
143
+ # # in your translations file
144
+ # en: {
145
+ # status: {
146
+ # ready: "Available",
147
+ # stalled: "Stalled",
148
+ # completed: "Done",
149
+ # },
108
150
  # pages: {
109
151
  # WidgetsPage: {
110
- # new_widget: "Create New"
111
- # captions: {
112
- # new: "New Widgets"
113
- # }
152
+ # new_widget: "Create New",
153
+ # nevermind: "Don't Create One",
154
+ # },
155
+ # }
156
+ # components: {
157
+ # "WidgetsPage::WidgetComponent": {
158
+ # new_widget: "Make New Widget",
114
159
  # },
115
160
  # },
116
161
  # }
117
- # # in your code for HomePage
118
- # t(page: [ :captions, :new ]) # => New Widgets
162
+ # # in your code for WidgetsPage::WidgetComponent
163
+ # t(page: :new_widget) # => Make New Widget
164
+ # t(page: :nevermind) # => Don't Create One
119
165
  #
120
- # @example Using a block with Phlex
166
+ # @example Using a block in a page or component
121
167
  # # in your translations file
122
168
  # en: {
123
169
  # greeting: "Hello there %{name}, you may %{block}",
@@ -140,42 +186,35 @@ module Brut::I18n::BaseMethods
140
186
  # contact support
141
187
  # </a>
142
188
  # </h1>
143
- def t(key=:look_in_rest,**rest,&block)
144
- if key == :look_in_rest
145
-
146
- page = rest.delete(:page)
147
- component = rest.delete(:component)
189
+ def t(key,**interpolated_values,&block)
190
+ keys_to_check = []
191
+ key = Array(key).join('.')
192
+ is_page_private_component = self.kind_of?(Brut::FrontEnd::Component) &&
193
+ self.page_private?
194
+ is_page = self.kind_of?(Brut::FrontEnd::Page)
195
+ is_component = self.kind_of?(Brut::FrontEnd::Component) && !is_page
148
196
 
149
- if !page.nil? && !component.nil?
150
- raise ArgumentError, "You may only specify page or component, not both"
151
- end
152
-
153
- subkey = nil
154
- if page
155
- subkey = Array(page).join(".")
156
- key = ["pages.#{self.page_name}.#{subkey}"]
157
- elsif component
158
- subkey = Array(component).join(".")
159
- key = ["components.#{self.component_name}.#{subkey}"]
160
- else
161
- raise ArgumentError, "If you omit an explicit key, you must specify page or component"
162
- end
163
- key << "general.#{subkey}"
164
- else
165
- key = Array(key).join('.')
166
- key = [key,"general.#{key}"]
197
+ if is_component
198
+ keys_to_check << "components.#{self.component_name}.#{key}"
167
199
  end
200
+ if is_page
201
+ keys_to_check << "pages.#{self.page_name}.#{key}"
202
+ elsif is_page_private_component
203
+ keys_to_check << "pages.#{self.containing_page_name}.#{key}"
204
+ end
205
+ keys_to_check << key
206
+
168
207
  if !block.nil?
169
- if rest[:block]
208
+ if interpolated_values[:block]
170
209
  raise ArgumentError,"t was given a block and a block: param. You can't do both "
171
210
  end
172
211
  block_contents = safe(capture(&block))
173
- rest[:block] = block_contents
212
+ interpolated_values[:block] = block_contents
174
213
  end
175
- t_direct(key,**rest)
214
+ t_direct(keys_to_check,**interpolated_values.merge(key_given: key))
176
215
  rescue I18n::MissingInterpolationArgument => ex
177
216
  if ex.key.to_s == "block"
178
- raise ArgumentError,"One of the keys #{key.join(", ")} contained a %{block} interpolation value: '#{ex.string}'. This means you must use t_html *and* yield a block to it"
217
+ raise ArgumentError,"One of the keys #{key.join(", ")} contained a %{block} interpolation value: '#{ex.string}'. This means you must yield a block to `t`"
179
218
  else
180
219
  raise
181
220
  end
@@ -186,7 +225,7 @@ module Brut::I18n::BaseMethods
186
225
  end
187
226
 
188
227
  def this_field_value
189
- @__this_field_value ||= ::I18n.t("general.cv.this_field", raise: true)
228
+ @__this_field_value ||= ::I18n.t("cv.this_field", raise: true)
190
229
  end
191
230
 
192
231
  # Directly access translations without trying to be smart about deriving the key. This is useful
@@ -194,28 +233,43 @@ module Brut::I18n::BaseMethods
194
233
  #
195
234
  # @param [Array<String>,Array<Symbol>] keys list of keys representing what is to be translated. The
196
235
  # first key found will be used. If no key in the list is found
197
- # will raise a I18n::MissingTranslation
236
+ # will raise a I18n::MissingTranslation.
198
237
  # @param [Hash] interpolated_values value to use for interpolation of the key's translation
199
238
  # @option interpolated_values [Numeric] count Special interpolation to control pluralization.
239
+ # @option interpolated_values [String|Symbol] key_given If included, this is not used for interpolation, but
240
+ # will be used in error messages to represent the key
241
+ # given to `t`.
200
242
  #
201
243
  # @raise [I18n::MissingTranslation] if no translation is found
202
244
  # @raise [I18n::MissingInterpolationArgument] if interpolation arguments are missing, or if the key
203
245
  # has pluralizations and no count: was given
204
246
  def t_direct(keys,interpolated_values={})
205
247
  keys = Array(keys).map(&:to_sym)
248
+ key_given = interpolated_values.delete(:key_given)
206
249
  default_interpolated_values = {
207
250
  field: this_field_value,
208
251
  }
209
252
  escaped_interpolated_values = interpolated_values.map { |key,value|
210
253
  if value.kind_of?(String)
211
- [ key, CGI.escapeHTML(value) ]
254
+ [ key, html_escape(value) ]
212
255
  else
213
256
  [ key, value ]
214
257
  end
215
258
  }.to_h
216
259
  result = ::I18n.t(keys.first, default: keys[1..-1],raise: true, **default_interpolated_values.merge(escaped_interpolated_values))
217
260
  if result.kind_of?(Hash)
218
- raise I18n::MissingInterpolationArgument.new(:count,interpolated_values,keys.join(","))
261
+ incorrect_pluralization = result.keys.none? { |key| key == :one }
262
+ if incorrect_pluralization
263
+ key_message = if key_given
264
+ "Key '#{key_given}'"
265
+ else
266
+ "One of the keys"
267
+ end
268
+ raise Brut::Framework::Errors::Bug,
269
+ "#{key_message} resulted in a Hash that doesn't appear to be created for pluralizations. This means that you may have given a key expecting it to map to a translation but it is actually a namespace for other keys. Please adjust your translations file to avoid this situation. Keys checked:\n#{keys.join(", ")}\nSub keys found:\n#{result.keys.join(', ')}"
270
+ else
271
+ raise I18n::MissingInterpolationArgument.new(:count,interpolated_values,keys.join(","))
272
+ end
219
273
  end
220
274
  result
221
275
  end
@@ -5,4 +5,5 @@ module Brut::I18n::ForBackEnd
5
5
  include Brut::I18n::BaseMethods
6
6
  def safe(string) = string
7
7
  def capture(&block) = block.()
8
+ def html_escape(value) = value
8
9
  end
@@ -5,4 +5,5 @@ module Brut::I18n::ForCLI
5
5
  include Brut::I18n::BaseMethods
6
6
  def safe(string) = string
7
7
  def capture(&block) = block.()
8
+ def html_escape(value) = value
8
9
  end
@@ -1,12 +1,40 @@
1
1
  # I18n for components or pages, which are assumed to be Phlex components.
2
- # To use this outside of a Phlex context, you must define these two
3
- # methods to ensure proper HTML escaping happens:
2
+ # This will do HTML escaping as follows:
3
+ #
4
+ # * Interpolated values are always HTML-escaped
5
+ # * When a block is used, that value is assumed to be safe HTML,
6
+ # and generated outside the current Phlex context. It's value is
7
+ # captured (via `#capture`) and then declared HTML safe by being
8
+ # passed to `#safe`.
9
+ #
10
+ # Unless you put HTML injections into your translations file, this should result
11
+ # in safe HTML in any translation.
12
+ #
13
+ # This module provides two features that aren't part of {#Brut::I18n::BaseMethods}:
14
+ #
15
+ # * {#t} calls `#safe` on its return value, indicating that the string is safe. To use
16
+ # this string in a Phlex view, you *must* call `#raw` and pass it the string.
17
+ # * {#html_escape} is implemented to escape values it's given.
18
+ #
19
+ #
20
+ # @example
21
+ # class StatusComponent < AppComponent
22
+ # def initialize(status:)
23
+ # @status = status
24
+ # end
25
+ # def view_template
26
+ # div do
27
+ # raw(
28
+ # t( [ :status, @status ] )
29
+ # )
30
+ # end
31
+ # end
32
+ # end
4
33
  #
5
- # * `safe` to accept a string and return a string.
6
- # * `capture` to accept a block and return its contents as a string.
7
34
  module Brut::I18n::ForHTML
8
35
  include Brut::I18n::BaseMethods
9
36
  def t(...)
10
37
  safe(super)
11
38
  end
39
+ def html_escape(value) = CGI.escapeHTML(value)
12
40
  end
@@ -63,7 +63,7 @@ class Brut::Instrumentation::OpenTelemetry
63
63
  # @param [Exception] ex the exception to record.
64
64
  # @param [Hash] attributes any attributes to attach that will show up in your OTel provider
65
65
  # @raise [Exception] the exception passed in.
66
- def record_and_reraise_exception(ex,attributes=nil)
66
+ def record_and_reraise_exception!(ex,attributes=nil)
67
67
  reecord_exception(ex,attributes)
68
68
  raise ex
69
69
  end
@@ -71,13 +71,23 @@ class Brut::Instrumentation::OpenTelemetry
71
71
 
72
72
  # Adds attributes to the span, converting the hash or keyword arguments to strings. This will use
73
73
  # the app's Otel prefix for all attributes, so you do not have to prefix them.
74
- # If you need to set standard attributes, you should use {Brut::Instrumentation::OpenTelemetry::Span#add_prefixed_attributes} instead.
74
+ # If you need to set standard attributes, you should use {#add_prefixed_attributes} instead.
75
75
  # @param [Hash] attributes any attributes to attach to the event.
76
76
  def add_attributes(attributes)
77
77
  current_span = OpenTelemetry::Trace.current_span
78
78
  current_span.add_attributes(NormalizedAttributes.new(:detect,attributes).to_h)
79
79
  end
80
80
 
81
+ # Adds attributes to the span, prefixing each key with the given prefix, then converting the hash or keyword arguments to strings. For example, if the prefix is 'my_app' and you add the attributes 'type' and 'reason', the actual attribute names will be 'my_app.type' and 'my_app.reason'.
82
+ #
83
+ # @see #add_attributes
84
+ def add_prefixed_attributes(prefix,attributes)
85
+ current_span = OpenTelemetry::Trace.current_span
86
+ current_span.add_attributes(
87
+ NormalizedAttributes.new(prefix,attributes).to_h
88
+ )
89
+ end
90
+
81
91
  private
82
92
 
83
93
  class NormalizedAttributes
@@ -179,16 +179,23 @@ module Brut::SinatraHelpers
179
179
  form_class: form_class,
180
180
  )
181
181
 
182
- handler = handler_class.new
183
182
  form = if form_class.nil?
184
183
  nil
185
184
  else
186
185
  form_class.new(params: params)
187
186
  end
188
187
 
189
- process_args = Brut::FrontEnd::RequestContext.current.as_method_args(handler,:handle,request_params: params,form: form,route:brut_route)
188
+ constructor_args = Brut::FrontEnd::RequestContext.current.as_constructor_args(
189
+ handler_class,
190
+ request_params: params,
191
+ route: brut_route,
192
+ form: form,
193
+ )
194
+
195
+ handler = handler_class.new(**constructor_args)
190
196
 
191
- result = handler.handle!(**process_args)
197
+ result = handler.handle!
198
+ span.add_prefixed_attributes("brut", result_class: result.class)
192
199
 
193
200
  case result
194
201
  in URI => uri