brut 0.0.1 → 0.0.2

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 (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