brut 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Gemfile.lock +66 -1
  4. data/README.md +36 -0
  5. data/Rakefile +22 -0
  6. data/brut.gemspec +7 -0
  7. data/doc-src/architecture.md +102 -0
  8. data/doc-src/assets.md +98 -0
  9. data/doc-src/forms.md +214 -0
  10. data/doc-src/handlers.md +83 -0
  11. data/doc-src/javascript.md +265 -0
  12. data/doc-src/keyword-injection.md +183 -0
  13. data/doc-src/pages.md +210 -0
  14. data/doc-src/route-hooks.md +59 -0
  15. data/docs-todo.md +32 -0
  16. data/lib/brut/back_end/seed_data.rb +5 -1
  17. data/lib/brut/back_end/validator.rb +1 -1
  18. data/lib/brut/back_end/validators/form_validator.rb +31 -6
  19. data/lib/brut/cli/app.rb +100 -4
  20. data/lib/brut/cli/app_runner.rb +38 -5
  21. data/lib/brut/cli/apps/build_assets.rb +4 -6
  22. data/lib/brut/cli/apps/db.rb +2 -7
  23. data/lib/brut/cli/apps/scaffold.rb +413 -7
  24. data/lib/brut/cli/apps/test.rb +14 -1
  25. data/lib/brut/cli/command.rb +141 -6
  26. data/lib/brut/cli/error.rb +30 -3
  27. data/lib/brut/cli/execution_results.rb +44 -6
  28. data/lib/brut/cli/executor.rb +21 -1
  29. data/lib/brut/cli/options.rb +29 -2
  30. data/lib/brut/cli/output.rb +47 -0
  31. data/lib/brut/cli.rb +26 -0
  32. data/lib/brut/factory_bot.rb +2 -0
  33. data/lib/brut/framework/app.rb +38 -5
  34. data/lib/brut/framework/config.rb +97 -54
  35. data/lib/brut/framework/container.rb +97 -33
  36. data/lib/brut/framework/errors/abstract_method.rb +3 -7
  37. data/lib/brut/framework/errors/missing_parameter.rb +12 -0
  38. data/lib/brut/framework/errors/no_class_for_path.rb +25 -0
  39. data/lib/brut/framework/errors/not_found.rb +12 -2
  40. data/lib/brut/framework/errors/not_implemented.rb +14 -0
  41. data/lib/brut/framework/errors.rb +18 -0
  42. data/lib/brut/framework/fussy_type_enforcement.rb +17 -12
  43. data/lib/brut/framework/mcp.rb +106 -49
  44. data/lib/brut/framework/project_environment.rb +9 -0
  45. data/lib/brut/framework.rb +1 -0
  46. data/lib/brut/front_end/asset_metadata.rb +7 -1
  47. data/lib/brut/front_end/component.rb +129 -38
  48. data/lib/brut/front_end/components/constraint_violations.rb +57 -0
  49. data/lib/brut/front_end/components/form_tag.rb +23 -32
  50. data/lib/brut/front_end/components/i18n_translations.rb +34 -1
  51. data/lib/brut/front_end/components/input.rb +3 -0
  52. data/lib/brut/front_end/components/inputs/csrf_token.rb +2 -0
  53. data/lib/brut/front_end/components/inputs/radio_button.rb +41 -0
  54. data/lib/brut/front_end/components/inputs/select.rb +26 -2
  55. data/lib/brut/front_end/components/inputs/text_field.rb +27 -5
  56. data/lib/brut/front_end/components/inputs/textarea.rb +24 -4
  57. data/lib/brut/front_end/components/locale_detection.rb +8 -1
  58. data/lib/brut/front_end/components/page_identifier.rb +2 -0
  59. data/lib/brut/front_end/components/time.rb +95 -0
  60. data/lib/brut/front_end/components/traceparent.rb +22 -0
  61. data/lib/brut/front_end/download.rb +11 -0
  62. data/lib/brut/front_end/flash.rb +32 -0
  63. data/lib/brut/front_end/form.rb +109 -106
  64. data/lib/brut/front_end/forms/constraint_violation.rb +16 -11
  65. data/lib/brut/front_end/forms/input.rb +30 -42
  66. data/lib/brut/front_end/forms/input_declarations.rb +90 -0
  67. data/lib/brut/front_end/forms/input_definition.rb +45 -30
  68. data/lib/brut/front_end/forms/radio_button_group_input.rb +46 -0
  69. data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +29 -0
  70. data/lib/brut/front_end/forms/select_input.rb +47 -0
  71. data/lib/brut/front_end/forms/select_input_definition.rb +27 -0
  72. data/lib/brut/front_end/forms/validity_state.rb +23 -9
  73. data/lib/brut/front_end/handler.rb +27 -8
  74. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +4 -2
  75. data/lib/brut/front_end/handlers/instrumentation_handler.rb +99 -0
  76. data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -4
  77. data/lib/brut/front_end/handlers/missing_handler.rb +9 -0
  78. data/lib/brut/front_end/handling_results.rb +14 -4
  79. data/lib/brut/front_end/http_method.rb +13 -1
  80. data/lib/brut/front_end/http_status.rb +10 -0
  81. data/lib/brut/front_end/layouts/_internal.html.erb +68 -0
  82. data/lib/brut/front_end/middleware.rb +5 -0
  83. data/lib/brut/front_end/middlewares/annotate_brut_owned_paths.rb +15 -0
  84. data/lib/brut/front_end/middlewares/favicon.rb +16 -0
  85. data/lib/brut/front_end/middlewares/open_telemetry_span.rb +12 -0
  86. data/lib/brut/front_end/middlewares/reload_app.rb +27 -9
  87. data/lib/brut/front_end/page.rb +50 -11
  88. data/lib/brut/front_end/pages/_missing_page.html.erb +17 -0
  89. data/lib/brut/front_end/pages/missing_page.rb +36 -0
  90. data/lib/brut/front_end/request_context.rb +117 -8
  91. data/lib/brut/front_end/route_hook.rb +43 -1
  92. data/lib/brut/front_end/route_hooks/age_flash.rb +3 -2
  93. data/lib/brut/front_end/route_hooks/csp_no_inline_scripts.rb +8 -0
  94. data/lib/brut/front_end/route_hooks/csp_no_inline_styles_or_scripts.rb +18 -10
  95. data/lib/brut/front_end/route_hooks/locale_detection.rb +11 -5
  96. data/lib/brut/front_end/route_hooks/setup_request_context.rb +7 -4
  97. data/lib/brut/front_end/routing.rb +138 -31
  98. data/lib/brut/front_end/session.rb +86 -7
  99. data/lib/brut/front_end/template.rb +17 -2
  100. data/lib/brut/front_end/templates/block_filter.rb +4 -3
  101. data/lib/brut/front_end/templates/erb_parser.rb +1 -1
  102. data/lib/brut/front_end/templates/escapable_filter.rb +2 -0
  103. data/lib/brut/front_end/templates/html_safe_string.rb +30 -2
  104. data/lib/brut/front_end/templates/locator.rb +60 -0
  105. data/lib/brut/i18n/base_methods.rb +4 -0
  106. data/lib/brut/i18n.rb +1 -0
  107. data/lib/brut/instrumentation/logger_span_exporter.rb +75 -0
  108. data/lib/brut/instrumentation/open_telemetry.rb +107 -0
  109. data/lib/brut/instrumentation.rb +4 -6
  110. data/lib/brut/junk_drawer.rb +54 -4
  111. data/lib/brut/sinatra_helpers.rb +42 -38
  112. data/lib/brut/spec_support/clock_support.rb +6 -0
  113. data/lib/brut/spec_support/component_support.rb +53 -26
  114. data/lib/brut/spec_support/e2e_test_server.rb +82 -0
  115. data/lib/brut/spec_support/enhanced_node.rb +45 -0
  116. data/lib/brut/spec_support/general_support.rb +14 -3
  117. data/lib/brut/spec_support/handler_support.rb +2 -0
  118. data/lib/brut/spec_support/matcher.rb +6 -3
  119. data/lib/brut/spec_support/matchers/be_page_for.rb +3 -2
  120. data/lib/brut/spec_support/matchers/have_constraint_violation.rb +22 -9
  121. data/lib/brut/spec_support/matchers/have_html_attribute.rb +9 -2
  122. data/lib/brut/spec_support/matchers/have_i18n_string.rb +24 -0
  123. data/lib/brut/spec_support/matchers/have_link_to.rb +14 -0
  124. data/lib/brut/spec_support/matchers/have_redirected_to.rb +30 -0
  125. data/lib/brut/spec_support/matchers/have_rendered.rb +4 -11
  126. data/lib/brut/spec_support/matchers/have_returned_http_status.rb +16 -8
  127. data/lib/brut/spec_support/rspec_setup.rb +182 -0
  128. data/lib/brut/spec_support.rb +8 -3
  129. data/lib/brut/version.rb +2 -1
  130. data/lib/brut.rb +28 -5
  131. data/lib/sequel/extensions/brut_instrumentation.rb +5 -27
  132. data/lib/sequel/extensions/brut_migrations.rb +18 -8
  133. data/lib/sequel/plugins/created_at.rb +2 -0
  134. data/lib/sequel/plugins/external_id.rb +39 -1
  135. data/lib/sequel/plugins/find_bang.rb +4 -1
  136. metadata +140 -13
  137. data/lib/brut/back_end/action.rb +0 -3
  138. data/lib/brut/back_end/result.rb +0 -46
  139. data/lib/brut/front_end/components/timestamp.rb +0 -33
  140. data/lib/brut/instrumentation/basic.rb +0 -66
  141. data/lib/brut/instrumentation/event.rb +0 -19
  142. data/lib/brut/instrumentation/http_event.rb +0 -5
  143. data/lib/brut/instrumentation/subscriber.rb +0 -41
@@ -1,19 +1,51 @@
1
1
  module Sequel
2
2
  module Plugins
3
+ # Configures models with an external id that can be safely shared publicly and safely rotated. The external
4
+ # id is random, however it is prefixed with configurable strings that can indicate what type of model it represents.
5
+ # This disambiguates ids when shared pubnlically or in internal tooling.
6
+ #
7
+ # This extension accepts a configuration option `global_prefix:` that is a string prepended to all external IDs. This ensures
8
+ # that your app's IDs clearly come from your app and not some other app that may be using external ids.
9
+ #
10
+ # To use external ids you must then call {ClassMethods#has_external_id} in your model. This method accepts a string which is
11
+ # appended to the global prefix. This is then *prepended* to a unique identifier. The per-model external id prefix should
12
+ # indicate the type of the model. Note that it's not intended to be used programmatically—it's only for humans to quickly see
13
+ # what type of thing an identifier represents.
14
+ #
15
+ # When {ClassMethods#has_external_id} is called on your model, the column `external_id` will be set on save if it has no value.
16
+ #
17
+ # @example
18
+ # class DB::Widget < AppDataModel
19
+ # has_external_id "wg"
20
+ # end
21
+ #
22
+ # Sequel::Model.plugin :external_id, global_prefix: "my"
23
+ #
24
+ # widget = DB::Widget.new(name: "flux capacitor")
25
+ # widget.external_id # => mywg_43792834f9c3a7
3
26
  module ExternalId
27
+ # @!visibility private
4
28
  def self.apply(model,*args,&block)
5
29
  @global_prefix = (args.first || {})[:global_prefix]
6
30
  end
31
+ def self.global_prefix = @global_prefix
7
32
 
8
33
  module ClassMethods
9
- attr_reader :global_prefix
34
+ # @!visibility private
35
+ def global_prefix = Sequel::Plugins::ExternalId.global_prefix
36
+ # Called inside a model's body to indicate that this model has an `external_id` that this plugin should manage and what
37
+ # prefix should be used. Calling this will also alter {InstanceMethods#to_s} to include this id in the string representation.
38
+ #
39
+ # @param prefix [String] a short string identfying the type of this model. It will be prepended to your external_id.
10
40
  def has_external_id(prefix)
11
41
  global_prefix = find_global_prefix
12
42
  @external_id_prefix = RichString.new("#{global_prefix}#{prefix}").to_s_or_nil
13
43
  end
14
44
 
45
+ # @!visibility private
15
46
  def external_id_prefix = @external_id_prefix
16
47
 
48
+ # @!visibility private
17
49
  def find_global_prefix(receiver=self)
18
50
  if receiver.respond_to?(:global_prefix)
19
51
  if receiver.global_prefix.nil?
@@ -30,6 +62,7 @@ module Sequel
30
62
  end
31
63
 
32
64
  module InstanceMethods
65
+ # @!visibility private
33
66
  def before_save
34
67
  if self.class.external_id_prefix
35
68
  if self.external_id.nil?
@@ -39,6 +72,11 @@ module Sequel
39
72
  end
40
73
  super
41
74
  end
75
+ # Includes the external id in the super class' representation. This means you can include
76
+ # models in Log messages without having to explicitly fetch the id.
77
+ def to_s
78
+ super.to_s + "[external_id:#{self.external_id}]"
79
+ end
42
80
  end
43
81
  end
44
82
  end
@@ -1,11 +1,14 @@
1
1
  module Sequel
2
2
  module Plugins
3
+ # Adds {ClassMethods#find!} to all models, which behaves as it does in Rails.
3
4
  module FindBang
4
5
  module ClassMethods
6
+ # Calls `first!`, but provides a more helpful error message when no records are found.
7
+ # @raise [Brut::Framework::Errors::NotFound]
5
8
  def find!(**args)
6
9
  self.first!(**args)
7
10
  rescue Sequel::NoMatchingRow => ex
8
- raise Sequel::NoMatchingRow.new(ex.message + "; #{args.inspect}")
11
+ raise Brut::Framework::Errors::NotFound.new(resource_name: self.name,search_terms: args.inspect,context: ex.message)
9
12
  end
10
13
  end
11
14
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-11-14 00:00:00.000000000 Z
10
+ date: 2025-02-18 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: irb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: dotenv
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +65,20 @@ dependencies:
52
65
  - - ">="
53
66
  - !ruby/object:Gem::Version
54
67
  version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: concurrent-ruby
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
55
82
  - !ruby/object:Gem::Dependency
56
83
  name: faker
57
84
  requirement: !ruby/object:Gem::Requirement
@@ -262,6 +289,34 @@ dependencies:
262
289
  - - ">="
263
290
  - !ruby/object:Gem::Version
264
291
  version: '0'
292
+ - !ruby/object:Gem::Dependency
293
+ name: opentelemetry-sdk
294
+ requirement: !ruby/object:Gem::Requirement
295
+ requirements:
296
+ - - ">="
297
+ - !ruby/object:Gem::Version
298
+ version: '0'
299
+ type: :runtime
300
+ prerelease: false
301
+ version_requirements: !ruby/object:Gem::Requirement
302
+ requirements:
303
+ - - ">="
304
+ - !ruby/object:Gem::Version
305
+ version: '0'
306
+ - !ruby/object:Gem::Dependency
307
+ name: opentelemetry-exporter-otlp
308
+ requirement: !ruby/object:Gem::Requirement
309
+ requirements:
310
+ - - ">="
311
+ - !ruby/object:Gem::Version
312
+ version: '0'
313
+ type: :runtime
314
+ prerelease: false
315
+ version_requirements: !ruby/object:Gem::Requirement
316
+ requirements:
317
+ - - ">="
318
+ - !ruby/object:Gem::Version
319
+ version: '0'
265
320
  - !ruby/object:Gem::Dependency
266
321
  name: activesupport
267
322
  requirement: !ruby/object:Gem::Requirement
@@ -318,6 +373,48 @@ dependencies:
318
373
  - - ">="
319
374
  - !ruby/object:Gem::Version
320
375
  version: '0'
376
+ - !ruby/object:Gem::Dependency
377
+ name: yard
378
+ requirement: !ruby/object:Gem::Requirement
379
+ requirements:
380
+ - - ">="
381
+ - !ruby/object:Gem::Version
382
+ version: '0'
383
+ type: :development
384
+ prerelease: false
385
+ version_requirements: !ruby/object:Gem::Requirement
386
+ requirements:
387
+ - - ">="
388
+ - !ruby/object:Gem::Version
389
+ version: '0'
390
+ - !ruby/object:Gem::Dependency
391
+ name: rdiscount
392
+ requirement: !ruby/object:Gem::Requirement
393
+ requirements:
394
+ - - ">="
395
+ - !ruby/object:Gem::Version
396
+ version: '0'
397
+ type: :development
398
+ prerelease: false
399
+ version_requirements: !ruby/object:Gem::Requirement
400
+ requirements:
401
+ - - ">="
402
+ - !ruby/object:Gem::Version
403
+ version: '0'
404
+ - !ruby/object:Gem::Dependency
405
+ name: rdoc
406
+ requirement: !ruby/object:Gem::Requirement
407
+ requirements:
408
+ - - ">="
409
+ - !ruby/object:Gem::Version
410
+ version: '0'
411
+ type: :development
412
+ prerelease: false
413
+ version_requirements: !ruby/object:Gem::Requirement
414
+ requirements:
415
+ - - ">="
416
+ - !ruby/object:Gem::Version
417
+ version: '0'
321
418
  description: NOT YET RELEASED - An opinionated web framework build on web standards
322
419
  email:
323
420
  - davec@thirdtank.com
@@ -337,7 +434,16 @@ files:
337
434
  - bin/rake
338
435
  - bin/setup
339
436
  - brut.gemspec
437
+ - doc-src/architecture.md
438
+ - doc-src/assets.md
439
+ - doc-src/forms.md
440
+ - doc-src/handlers.md
441
+ - doc-src/javascript.md
442
+ - doc-src/keyword-injection.md
443
+ - doc-src/pages.md
444
+ - doc-src/route-hooks.md
340
445
  - docker-compose.dx.yml
446
+ - docs-todo.md
341
447
  - dx/build
342
448
  - dx/docker-compose.env
343
449
  - dx/dx.sh.lib
@@ -348,8 +454,6 @@ files:
348
454
  - dx/start
349
455
  - dx/stop
350
456
  - lib/brut.rb
351
- - lib/brut/back_end/action.rb
352
- - lib/brut/back_end/result.rb
353
457
  - lib/brut/back_end/seed_data.rb
354
458
  - lib/brut/back_end/validator.rb
355
459
  - lib/brut/back_end/validators/form_validator.rb
@@ -374,38 +478,57 @@ files:
374
478
  - lib/brut/framework/errors.rb
375
479
  - lib/brut/framework/errors/abstract_method.rb
376
480
  - lib/brut/framework/errors/bug.rb
481
+ - lib/brut/framework/errors/missing_parameter.rb
482
+ - lib/brut/framework/errors/no_class_for_path.rb
377
483
  - lib/brut/framework/errors/not_found.rb
484
+ - lib/brut/framework/errors/not_implemented.rb
378
485
  - lib/brut/framework/fussy_type_enforcement.rb
379
486
  - lib/brut/framework/mcp.rb
380
487
  - lib/brut/framework/project_environment.rb
381
488
  - lib/brut/front_end/asset_metadata.rb
382
489
  - lib/brut/front_end/component.rb
490
+ - lib/brut/front_end/components/constraint_violations.rb
383
491
  - lib/brut/front_end/components/form_tag.rb
384
492
  - lib/brut/front_end/components/i18n_translations.rb
385
493
  - lib/brut/front_end/components/input.rb
386
494
  - lib/brut/front_end/components/inputs/csrf_token.rb
495
+ - lib/brut/front_end/components/inputs/radio_button.rb
387
496
  - lib/brut/front_end/components/inputs/select.rb
388
497
  - lib/brut/front_end/components/inputs/text_field.rb
389
498
  - lib/brut/front_end/components/inputs/textarea.rb
390
499
  - lib/brut/front_end/components/locale_detection.rb
391
500
  - lib/brut/front_end/components/page_identifier.rb
392
- - lib/brut/front_end/components/timestamp.rb
501
+ - lib/brut/front_end/components/time.rb
502
+ - lib/brut/front_end/components/traceparent.rb
393
503
  - lib/brut/front_end/download.rb
394
504
  - lib/brut/front_end/flash.rb
395
505
  - lib/brut/front_end/form.rb
396
506
  - lib/brut/front_end/forms/constraint_violation.rb
397
507
  - lib/brut/front_end/forms/input.rb
508
+ - lib/brut/front_end/forms/input_declarations.rb
398
509
  - lib/brut/front_end/forms/input_definition.rb
510
+ - lib/brut/front_end/forms/radio_button_group_input.rb
511
+ - lib/brut/front_end/forms/radio_button_group_input_definition.rb
512
+ - lib/brut/front_end/forms/select_input.rb
513
+ - lib/brut/front_end/forms/select_input_definition.rb
399
514
  - lib/brut/front_end/forms/validity_state.rb
400
515
  - lib/brut/front_end/handler.rb
401
516
  - lib/brut/front_end/handlers/csp_reporting_handler.rb
517
+ - lib/brut/front_end/handlers/instrumentation_handler.rb
402
518
  - lib/brut/front_end/handlers/locale_detection_handler.rb
519
+ - lib/brut/front_end/handlers/missing_handler.rb
403
520
  - lib/brut/front_end/handling_results.rb
404
521
  - lib/brut/front_end/http_method.rb
405
522
  - lib/brut/front_end/http_status.rb
523
+ - lib/brut/front_end/layouts/_internal.html.erb
406
524
  - lib/brut/front_end/middleware.rb
525
+ - lib/brut/front_end/middlewares/annotate_brut_owned_paths.rb
526
+ - lib/brut/front_end/middlewares/favicon.rb
527
+ - lib/brut/front_end/middlewares/open_telemetry_span.rb
407
528
  - lib/brut/front_end/middlewares/reload_app.rb
408
529
  - lib/brut/front_end/page.rb
530
+ - lib/brut/front_end/pages/_missing_page.html.erb
531
+ - lib/brut/front_end/pages/missing_page.rb
409
532
  - lib/brut/front_end/request_context.rb
410
533
  - lib/brut/front_end/route_hook.rb
411
534
  - lib/brut/front_end/route_hooks/age_flash.rb
@@ -421,20 +544,22 @@ files:
421
544
  - lib/brut/front_end/templates/erb_parser.rb
422
545
  - lib/brut/front_end/templates/escapable_filter.rb
423
546
  - lib/brut/front_end/templates/html_safe_string.rb
547
+ - lib/brut/front_end/templates/locator.rb
424
548
  - lib/brut/i18n.rb
425
549
  - lib/brut/i18n/base_methods.rb
426
550
  - lib/brut/i18n/for_cli.rb
427
551
  - lib/brut/i18n/for_html.rb
428
552
  - lib/brut/i18n/http_accept_language.rb
429
553
  - lib/brut/instrumentation.rb
430
- - lib/brut/instrumentation/basic.rb
431
- - lib/brut/instrumentation/event.rb
432
- - lib/brut/instrumentation/http_event.rb
433
- - lib/brut/instrumentation/subscriber.rb
554
+ - lib/brut/instrumentation/logger_span_exporter.rb
555
+ - lib/brut/instrumentation/open_telemetry.rb
434
556
  - lib/brut/junk_drawer.rb
435
557
  - lib/brut/sinatra_helpers.rb
436
558
  - lib/brut/spec_support.rb
559
+ - lib/brut/spec_support/clock_support.rb
437
560
  - lib/brut/spec_support/component_support.rb
561
+ - lib/brut/spec_support/e2e_test_server.rb
562
+ - lib/brut/spec_support/enhanced_node.rb
438
563
  - lib/brut/spec_support/flash_support.rb
439
564
  - lib/brut/spec_support/general_support.rb
440
565
  - lib/brut/spec_support/handler_support.rb
@@ -444,8 +569,12 @@ files:
444
569
  - lib/brut/spec_support/matchers/be_routing_for.rb
445
570
  - lib/brut/spec_support/matchers/have_constraint_violation.rb
446
571
  - lib/brut/spec_support/matchers/have_html_attribute.rb
572
+ - lib/brut/spec_support/matchers/have_i18n_string.rb
573
+ - lib/brut/spec_support/matchers/have_link_to.rb
574
+ - lib/brut/spec_support/matchers/have_redirected_to.rb
447
575
  - lib/brut/spec_support/matchers/have_rendered.rb
448
576
  - lib/brut/spec_support/matchers/have_returned_http_status.rb
577
+ - lib/brut/spec_support/rspec_setup.rb
449
578
  - lib/brut/spec_support/session_support.rb
450
579
  - lib/brut/version.rb
451
580
  - lib/sequel/extensions/brut_instrumentation.rb
@@ -461,7 +590,6 @@ metadata:
461
590
  homepage_uri: https://naildrivin5.com
462
591
  source_code_uri: https://naildrivin5.com
463
592
  changelog_uri: https://naildrivin5.com
464
- post_install_message:
465
593
  rdoc_options: []
466
594
  require_paths:
467
595
  - lib
@@ -476,8 +604,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
476
604
  - !ruby/object:Gem::Version
477
605
  version: '0'
478
606
  requirements: []
479
- rubygems_version: 3.5.23
480
- signing_key:
607
+ rubygems_version: 3.6.4
481
608
  specification_version: 4
482
609
  summary: NOT YET RELEASED - Web Framework Built around Ruby, Web Standards, Simplicity,
483
610
  and Object-Orientation
@@ -1,3 +0,0 @@
1
- class Brut::BackEnd::Action
2
- include SemanticLogger::Loggable
3
- end
@@ -1,46 +0,0 @@
1
- # The result of back-end processing, which is essentially
2
- # a container for constraint violations and arbitrary context.
3
- class Brut::BackEnd::Result
4
- attr_reader :constraint_violations, :context
5
-
6
- def initialize
7
- @constraint_violations = {}
8
- @context = {}
9
- end
10
-
11
- def constraint_violation!(object:,
12
- field:,
13
- key:,
14
- context: {})
15
- @constraint_violations[object] ||= {}
16
- @constraint_violations[object][field] ||= {}
17
- @constraint_violations[object][field][key] = context
18
- end
19
-
20
- def each_violation(&block)
21
- @constraint_violations.each do |object,fields|
22
- fields.each do |field,keys|
23
- keys.each do |key,context|
24
- block.(object,field,key,context)
25
- end
26
- end
27
- end
28
- end
29
-
30
- def []=(key_in_context,object)
31
- @context[key_in_context] = object
32
- end
33
-
34
- def [](key_in_context)
35
- @context.fetch(key_in_context)
36
- rescue KeyError => ex
37
- raise KeyError.new(
38
- "Context did not contain '#{key_in_context}' (#{key_in_context.class}). Context has these keys: #{@context.keys.join(',')}",
39
- receiver: ex.receiver,
40
- key: ex.key)
41
- end
42
-
43
-
44
- def constraint_violations? = self.constraint_violations.any?
45
-
46
- end
@@ -1,33 +0,0 @@
1
- require "rexml"
2
- class Brut::FrontEnd::Components::Timestamp < Brut::FrontEnd::Component
3
- include Brut::I18n::ForHTML
4
- def initialize(timestamp:, format: :full, skip_year_if_same: true, attribute_format: :iso_8601, **only_contains_class)
5
- @timestamp = timestamp
6
- formats = [ format ]
7
- if @timestamp.year == Time.now.year && skip_year_if_same
8
- formats.unshift("#{format}_no_year")
9
- end
10
- format_keys = formats.map { |f| "time.formats.#{f}" }
11
- found_format = formats.zip(::I18n.t(format_keys)).detect { |(key,value)|
12
- value !~ /^Translation missing/
13
- }.first
14
- if found_format.nil?
15
- raise ArgumentError,"format #{format} is not a known time format"
16
- end
17
- @format = found_format.to_sym
18
-
19
- if ::I18n.t("time.formats.#{attribute_format}") =~ /^Translation missing/
20
- raise ArgumentError,"attribute_format #{attribute_format} is not a known time format"
21
- end
22
- @attribute_format = attribute_format.to_sym
23
- @class_attribute = only_contains_class[:class] || ""
24
- end
25
-
26
-
27
- def render(clock:)
28
- timestamp_in_time_zone = clock.in_time_zone(@timestamp)
29
- html_tag(:time, class: @class_attribute, datetime: ::I18n.l(timestamp_in_time_zone,format: @attribute_format)) do
30
- ::I18n.l(timestamp_in_time_zone,format: @format)
31
- end
32
- end
33
- end
@@ -1,66 +0,0 @@
1
- class Brut::Instrumentation::Basic
2
- def initialize
3
- @subscribers = Concurrent::Set.new
4
- end
5
-
6
- class TypeChecking < Brut::Instrumentation::Basic
7
- def instrument(event,&block)
8
- if !event.kind_of?(Brut::Instrumentation::Event)
9
- raise "You cannot instrument a #{event.class} - it must be a Brut::Instrumentation::Event or subclass"
10
- end
11
- super
12
- end
13
- end
14
-
15
- def instrument(event,&block)
16
- block ||= ->() {}
17
-
18
- start = Time.now
19
- result = nil
20
- exception = nil
21
-
22
- begin
23
- result = block.(event)
24
- rescue => ex
25
- exception = ex
26
- end
27
- stop = Time.now
28
- notify(event:,start:,stop:,exception:)
29
- if exception
30
- raise exception
31
- else
32
- result
33
- end
34
- end
35
-
36
- def subscribe(subscriber=:use_block,&block)
37
- if block.nil? && subscriber == :use_block
38
- raise ArgumentError,"subscriber requires a Brut::Instrumentation::Subscriber or a block"
39
- end
40
- if !block.nil? && subscriber != :use_block
41
- raise ArgumentError,"subscriber requires a Brut::Instrumentation::Subscriber or a block, not both"
42
- end
43
- if block.nil?
44
- if subscriber.kind_of?(Proc)
45
- subscriber = Brut::Instrumentation::Subscriber.from_proc(subscriber)
46
- elsif !subscriber.kind_of?(Brut::Instrumentation::Subscriber)
47
- raise ArgumentError, "subscriber must be a Proc or Brut::Instrumentation::Subscriber, not a #{subscriber.class}"
48
- end
49
- else
50
- subscriber = Brut::Instrumentation::Subscriber.from_proc(block)
51
- end
52
- @subscribers << subscriber
53
- end
54
-
55
- def notify(event:,start:,stop:,exception:)
56
- Thread.new do
57
- @subscribers.each do |subscriber|
58
- begin
59
- subscriber.(event:,start:,stop:,exception:)
60
- rescue => ex
61
- warn "#{subscriber} raised #{ex}"
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,19 +0,0 @@
1
- class Brut::Instrumentation::Event
2
- include Brut::Framework::FussyTypeEnforcement
3
-
4
- attr_reader :category,
5
- :subcategory,
6
- :name,
7
- :details
8
-
9
- def initialize(category:,
10
- subcategory:nil,
11
- name:,
12
- details:{})
13
- @category = type!(category,String,"category",required: true, coerce: :to_s)
14
- @subcategory = type!(subcategory,String,"subcategory",required: false, coerce: :to_s)
15
- @name = type!(name,String,"name",required:true,coerce: :to_s)
16
- @details = type!(details,Hash,"details",required:false) || {}
17
- end
18
-
19
- end
@@ -1,5 +0,0 @@
1
- class Brut::Instrumentation::HTTPEvent < Brut::Instrumentation::Event
2
- def initialize(http_method:,name:,path:,details:{})
3
- super(category: "http", subcategory: http_method, name: name, details: details.merge(path:path))
4
- end
5
- end
@@ -1,41 +0,0 @@
1
- class Brut::Instrumentation::Subscriber
2
- def self.from_proc(block)
3
- required_parameter_names_found = self.instance_method(:call).parameters.map { |(type,name)| [ name, false ] }.to_h
4
- unexpected_parameter_names_error = {}
5
-
6
- block.parameters.each do |(type,name)|
7
- if required_parameter_names_found.key?(name)
8
- if type == :key || type == :keyreq
9
- required_parameter_names_found[name] = true
10
- else
11
- unexpected_parameter_names[name] = "Not a keyword arg"
12
- end
13
- elsif type != :key
14
- if type == :keyreq
15
- unexpected_parameter_names[name] = "keyword arg without a default value"
16
- else
17
- unexpected_parameter_names[name] = "Not a keyword arg"
18
- end
19
- end
20
- end
21
- errors = []
22
- if unexpected_parameter_names_error.any?
23
- messages = unexpected_parameter_names_error.map { |name,problem|
24
- "#{name} - #{problem}"
25
- }.join(", ")
26
- errors << "Unexpected parameters were required, so this cannot be used as a subscriber: #{messages}"
27
- end
28
- if required_parameter_names_found.any? { |_name,found| !found }
29
- messages = required_parameter_names_found.select { |_name,found| !found }.map { |name,_found| "#{name} must be a keyword argument" }.join(",")
30
- errors << "Required parameters were missing, so this cannot be used as a subscriber: #{messages}"
31
- end
32
- if errors.any?
33
- raise ArgumentError,errors.join(", ")
34
- end
35
- block
36
- end
37
-
38
- def call(event:,start:,stop:,exception:)
39
- end
40
-
41
- end