newshound 0.2.2 → 0.2.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65debb3ae756e51f870de0872ff54d2332ee71e9c05aa6ac174c9525336c46b5
4
- data.tar.gz: 8667bbc9ff8695b5191bccbbe066190bb9e6f86f7c7ef0d6508a312420258167
3
+ metadata.gz: 3d9a10844e45f2216a4e2f9a3aa895d72a2cbffb09c157460ce989e7a390564b
4
+ data.tar.gz: e271a3cd7916ec4eee93274e7601f14e4c7dd55d6db92ae55dce2f74f9c31245
5
5
  SHA512:
6
- metadata.gz: 7bb18eaf72c010172a7c80c13f782b6736620cef9855ee81f0faaa2ad32d1659b02d0970a4381adad7b42d72eb80663b8c93e0eefebd58d61ec75d45c2c31081
7
- data.tar.gz: '07722335323043284857b615205ec4def0f167f4f77308ffce3eeb1911afe85c963e6a62987f88d88d59e8385a4998d30e33835211c6c7d70f52e111f46ae5bb'
6
+ metadata.gz: a3c24e8dcf33ef8ea5c346e566f6a5441afa0657ab338b2599806133088f0f19ed36af407cf3d93da087bc76965dc35b3d135b222d6b0aa53f5caa610e015954
7
+ data.tar.gz: 3f2197e8a8bd9d99a2968df964bfd0c471d1c344a365c31a8e092ea9d5c09dfe9f4a87f97da14b3e92e32f9b9219327414998c3f36779b6780ef56b0f03cd9fd
data/CHANGELOG.md CHANGED
@@ -5,45 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.2.2] - 2025-10-23
8
+ ## [0.2.5] - 2025-12-12
9
9
 
10
10
  ### Added
11
11
 
12
- - CI workflow to run tests on Ruby 3.0-3.4 (b3ebc7a)
13
- - Separate linter job for RuboCop (b3ebc7a)
14
- - Coverage reporting with SimpleCov (b3ebc7a)
15
- - CI workflow to run tests on Ruby 3.0-3.4 (782cc57)
16
- - Separate linter job for RuboCop (782cc57)
17
- - Coverage reporting with SimpleCov (782cc57)
18
- - .claude/settings.local.json to .gitignore (701d802)
19
- - Reissue gem dependency for automated versioning (feac98a)
20
- - Git trailer support for changelog management (feac98a)
21
- - Initial CHANGELOG.md with Keep a Changelog format (feac98a)
22
- - CODEOWNERS file for SOFware/engineers (1a0a1f5)
23
- - Newshound::Exceptions::Base for standard API to interact with exception data (9dce846)
24
- - Configuration exception_source to allow for future alternative exception backends (9dce846)
25
- - SolidErrors support (8b5b48a)
12
+ - Add `Newshound::Warnings` module with registry-based adapter pattern for custom warning sources
13
+ - Add `Newshound::Warnings::Base` abstract class for warning adapters
14
+ - Add `Newshound::WarningReporter` for fetching and formatting warnings
15
+ - Display warnings in banner alongside exceptions and job stats
26
16
 
27
- ### Changed
17
+ ### Fixed
28
18
 
29
- - Replaced RuboCop with StandardRB for simpler linting (09292ed)
30
- - Updated GitHub Actions workflow to use standardrb (09292ed)
31
- - Auto-corrected all StandardRB violations (09292ed)
32
- - Simplified test setup by removing unused mocks (701d802)
33
- - Rakefile to use reissue tasks instead of manual versioning (feac98a)
34
- - Removed Ruby 3.0 from test matrix (now testing 3.1-3.4) (4beafd2)
35
- - Updated gemspec required_ruby_version to >= 3.1.0 (4beafd2)
19
+ - Fixed summary badge to show combined warnings and failed jobs count
20
+
21
+ ## [0.2.4] - 2025-12-12
36
22
 
37
23
  ### Fixed
38
24
 
39
- - Workflow now only runs once per PR (removed duplicate triggers) (09292ed)
40
- - Mock ActiveRecord::Base.connection and its methods (701d802)
41
- - Mock connection.execute to return database-like results (701d802)
42
- - Mock connection.quote and connection.select_value properly (701d802)
43
- - Name and email in gemspec authors (ab44f30)
25
+ - Fixed release workflow to properly create version bump PRs using gh pr create instead of peter-evans/create-pull-request action, resolving compatibility issue with reissue gem
44
26
 
45
- ### Removed
27
+ ## [0.2.3] - 2025-10-29
46
28
 
47
- - Dependency on exception-track (8b5b48a)
29
+ ### Fixed
30
+
31
+ - Updated styling in banner injector to attempt to keep newshound banner above other application's menus, instead of hovering over them.
32
+
33
+ ### Changed
48
34
 
49
- ## [0.2.2] - Unreleased
35
+ - Consolidated exception data extraction in ExceptionTrack and SolidErrors (55cffd3)
data/README.md CHANGED
@@ -203,18 +203,12 @@ bin/console
203
203
 
204
204
  ## Release Management
205
205
 
206
- This gem uses [Reissue](https://github.com/rails/reissue) for release management. To release a new version, perform
207
- the following steps as you would with any other ruby gem:
206
+ This gem uses [Reissue](https://github.com/rails/reissue) for release management. Releases are automated via GitHub Actions:
208
207
 
209
- ```bash
210
- bundle exec rake bump:checksum
211
- ```
212
- And then create a new release:
213
- ```bash
214
- bundle exec rake release
215
- ```
216
-
217
- The final step is to push your version bump branch, open a PR, and merge it.
208
+ 1. Navigate to Actions → "Release gem to RubyGems.org"
209
+ 2. Click "Run workflow"
210
+ 3. Select version segment to bump (patch, minor, major)
211
+ 4. Review and merge the auto-generated PR
218
212
 
219
213
  ## Dependencies
220
214
 
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ require "standard/rake"
8
8
  Reissue::Task.create :reissue do |task|
9
9
  task.version_file = "lib/newshound/version.rb"
10
10
  task.fragment = :git
11
+ task.commit_finalize = false # Let workflow handle commits
11
12
  end
12
13
 
13
14
  RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1 @@
1
+ 4b38b6628a214ff2cf66a400e184a5659a2ba82ccf46efde37293d93dd77534ffe87d3dd41220e0cac43aabaed66318cbdb70b8cbf66988f744f04ead452bbc5
@@ -0,0 +1 @@
1
+ e17f9781dfbe16702fdda1809ec0e69eacbb3ed762d8df8fb0c471b79860989a0dfedebbb22923bf699c4b243ad8a543bdab374b3ab6c296b67cae3c4b5c305a
@@ -3,7 +3,8 @@
3
3
  module Newshound
4
4
  class Configuration
5
5
  attr_accessor :exception_limit, :enabled, :authorized_roles,
6
- :current_user_method, :authorization_block, :exception_source
6
+ :current_user_method, :authorization_block, :exception_source,
7
+ :warning_source, :warning_limit
7
8
 
8
9
  def initialize
9
10
  @exception_limit = 10
@@ -12,6 +13,8 @@ module Newshound
12
13
  @current_user_method = :current_user
13
14
  @authorization_block = nil
14
15
  @exception_source = :exception_track
16
+ @warning_source = nil
17
+ @warning_limit = 10
15
18
  end
16
19
 
17
20
  # Allow custom authorization logic
@@ -12,53 +12,41 @@ module Newshound
12
12
  details = parse_exception_details(exception)
13
13
 
14
14
  <<~TEXT
15
- *#{number}. #{exception_title(exception)}*
15
+ *#{number}. #{details[:title]}*
16
16
  • *Time:* #{exception.created_at.strftime("%I:%M %p")}
17
17
  #{format_controller(details)}
18
- #{format_message(exception, details)}
18
+ #{format_message(details)}
19
19
  TEXT
20
20
  end
21
21
 
22
22
  def format_for_banner(exception)
23
23
  details = parse_exception_details(exception)
24
24
 
25
- # Extract message
26
- message = if details["message"].present?
27
- details["message"].to_s
28
- elsif exception.respond_to?(:message) && exception.message.present?
29
- exception.message.to_s
30
- else
31
- +""
32
- end
33
-
34
- # Extract location
35
- location = if details["controller_name"] && details["action_name"]
36
- "#{details["controller_name"]}##{details["action_name"]}"
37
- else
38
- +""
39
- end
40
-
41
25
  {
42
- title: exception_title(exception),
43
- message: message.truncate(100),
44
- location: location,
26
+ title: details[:title],
27
+ message: details[:message].truncate(100),
28
+ location: details[:location],
45
29
  time: exception.created_at.strftime("%I:%M %p")
46
30
  }
47
31
  end
48
32
 
49
33
  private
50
34
 
51
- def exception_title(exception)
52
- if exception.respond_to?(:title) && exception.title.present?
53
- exception.title
54
- elsif exception.respond_to?(:exception_class) && exception.exception_class.present?
55
- exception.exception_class
56
- else
57
- "Unknown Exception"
58
- end
35
+ def parse_exception_details(exception)
36
+ body_data = parse_body(exception)
37
+ controller_name = body_data["controller_name"]
38
+ action_name = body_data["action_name"]
39
+
40
+ {
41
+ title: exception.try(:title).presence || exception.try(:exception_class).presence || "Unknown Exception",
42
+ message: body_data["message"].presence&.to_s || exception.try(:message).presence&.to_s || +"",
43
+ location: (controller_name && action_name) ? "#{controller_name}##{action_name}" : +"",
44
+ controller_name: controller_name,
45
+ action_name: action_name
46
+ }
59
47
  end
60
48
 
61
- def parse_exception_details(exception)
49
+ def parse_body(exception)
62
50
  return {} unless exception.respond_to?(:body) && exception.body.present?
63
51
 
64
52
  JSON.parse(exception.body)
@@ -67,24 +55,15 @@ module Newshound
67
55
  end
68
56
 
69
57
  def format_controller(details)
70
- return +"" unless details["controller_name"] && details["action_name"]
58
+ return +"" unless details in {controller_name: String, action_name: String}
71
59
 
72
- "• *Controller:* #{details["controller_name"]}##{details["action_name"]}\n"
60
+ "• *Controller:* #{details[:location]}\n"
73
61
  end
74
62
 
75
- def format_message(exception, details = nil)
76
- details ||= parse_exception_details(exception)
77
-
78
- # Try to get message from different sources
79
- message = if details["message"].present?
80
- details["message"]
81
- elsif exception.respond_to?(:message) && exception.message.present?
82
- exception.message
83
- end
84
-
85
- return +"" unless message.present?
63
+ def format_message(details)
64
+ return +"" unless details in {message: String}
86
65
 
87
- message = message.to_s.truncate(100)
66
+ message = details[:message].to_s.truncate(100)
88
67
  "• *Message:* `#{message}`"
89
68
  end
90
69
  end
@@ -9,60 +9,50 @@ module Newshound
9
9
  end
10
10
 
11
11
  def format_for_report(exception, number)
12
- context = parse_context(exception)
12
+ details = parse_exception_details(exception)
13
13
 
14
14
  <<~TEXT
15
- *#{number}. #{exception_title(exception)}*
15
+ *#{number}. #{details[:title]}*
16
16
  • *Time:* #{exception.created_at.strftime("%I:%M %p")}
17
- #{format_controller(context)}
18
- #{format_message(exception, context)}
17
+ #{format_controller(details)}
18
+ #{format_message(details)}
19
19
  TEXT
20
20
  end
21
21
 
22
22
  def format_for_banner(exception)
23
- context = parse_context(exception)
24
-
25
- # Extract message
26
- message = if exception.respond_to?(:message) && exception.message.present?
27
- exception.message.to_s
28
- elsif context["message"].present?
29
- context["message"].to_s
30
- else
31
- +""
32
- end
33
-
34
- # Extract location from context
35
- location = if context["controller"] && context["action"]
36
- "#{context["controller"]}##{context["action"]}"
37
- else
38
- +""
39
- end
23
+ details = parse_exception_details(exception)
40
24
 
41
25
  {
42
- title: exception_title(exception),
43
- message: message.truncate(100),
44
- location: location,
26
+ title: details[:title],
27
+ message: details[:message].truncate(100),
28
+ location: details[:location],
45
29
  time: exception.created_at.strftime("%I:%M %p")
46
30
  }
47
31
  end
48
32
 
49
33
  private
50
34
 
51
- def exception_title(exception)
52
- if exception.respond_to?(:error_class) && exception.error_class.present?
53
- exception.error_class
54
- else
55
- "Unknown Exception"
56
- end
35
+ def parse_exception_details(exception)
36
+ context_data = parse_context(exception)
37
+ controller = context_data["controller"]
38
+ action = context_data["action"]
39
+
40
+ {
41
+ title: exception.try(:error_class).presence || "Unknown Exception",
42
+ message: exception.try(:message).presence&.to_s || context_data["message"].presence&.to_s || +"",
43
+ location: (controller && action) ? "#{controller}##{action}" : +"",
44
+ controller: controller,
45
+ action: action
46
+ }
57
47
  end
58
48
 
59
49
  def parse_context(exception)
60
50
  return {} unless exception.respond_to?(:context) && exception.context.present?
61
51
 
62
- # SolidErrors context might be a hash or JSON string
63
- if exception.context.is_a?(Hash)
52
+ case exception.context
53
+ when Hash
64
54
  exception.context
65
- elsif exception.context.is_a?(String)
55
+ when String
66
56
  JSON.parse(exception.context)
67
57
  else
68
58
  {}
@@ -71,25 +61,16 @@ module Newshound
71
61
  {}
72
62
  end
73
63
 
74
- def format_controller(context)
75
- return +"" unless context["controller"] && context["action"]
64
+ def format_controller(details)
65
+ return +"" unless details in {controller: String, action: String}
76
66
 
77
- "• *Controller:* #{context["controller"]}##{context["action"]}\n"
67
+ "• *Controller:* #{details[:location]}\n"
78
68
  end
79
69
 
80
- def format_message(exception, context = nil)
81
- context ||= parse_context(exception)
82
-
83
- # Try to get message from different sources
84
- message = if exception.respond_to?(:message) && exception.message.present?
85
- exception.message
86
- elsif context["message"].present?
87
- context["message"]
88
- end
89
-
90
- return +"" unless message.present?
70
+ def format_message(details)
71
+ return +"" unless details in {message: String}
91
72
 
92
- message = message.to_s.truncate(100)
73
+ message = details[:message].to_s.truncate(100)
93
74
  "• *Message:* `#{message}`"
94
75
  end
95
76
  end
@@ -56,27 +56,30 @@ module Newshound
56
56
  def generate_banner_html
57
57
  exception_reporter = Newshound::ExceptionReporter.new
58
58
  que_reporter = Newshound::QueReporter.new
59
+ warning_reporter = Newshound::WarningReporter.new
59
60
 
60
61
  exception_data = exception_reporter.banner_data
61
62
  job_data = que_reporter.banner_data
63
+ warning_data = warning_reporter.banner_data
62
64
 
63
65
  # Generate HTML from template
64
- render_banner(exception_data, job_data)
66
+ render_banner(exception_data, job_data, warning_data)
65
67
  end
66
68
 
67
- def render_banner(exception_data, job_data)
69
+ def render_banner(exception_data, job_data, warning_data = {})
68
70
  <<~HTML
69
71
  <div id="newshound-banner" class="newshound-banner newshound-collapsed">
70
72
  #{render_styles}
71
73
  <div class="newshound-header" onclick="document.getElementById('newshound-banner').classList.toggle('newshound-collapsed'); window.newshoundUpdatePadding();">
72
74
  <span class="newshound-title">
73
75
  🐕 Newshound
74
- #{summary_badge(exception_data, job_data)}
76
+ #{summary_badge(exception_data, job_data, warning_data)}
75
77
  </span>
76
78
  <span class="newshound-toggle">▼</span>
77
79
  </div>
78
80
  <div class="newshound-content">
79
81
  #{render_exceptions(exception_data)}
82
+ #{render_warnings(warning_data)}
80
83
  #{render_jobs(job_data)}
81
84
  </div>
82
85
  </div>
@@ -117,7 +120,7 @@ module Newshound
117
120
  <<~CSS
118
121
  <style>
119
122
  body {
120
- padding-top: 0;
123
+ padding-top: 50px;
121
124
  transition: padding-top 0.3s ease-out;
122
125
  }
123
126
  .newshound-banner {
@@ -236,16 +239,20 @@ module Newshound
236
239
  CSS
237
240
  end
238
241
 
239
- def summary_badge(exception_data, job_data)
242
+ def summary_badge(exception_data, job_data, warning_data = {})
240
243
  exception_count = exception_data[:exceptions]&.length || 0
241
244
  failed_jobs = job_data.dig(:queue_stats, :failed) || 0
245
+ warning_count = warning_data[:warnings]&.length || 0
242
246
 
243
247
  if exception_count > 0 || failed_jobs > 10
244
248
  badge_class = "error"
245
249
  text = "#{exception_count} exceptions, #{failed_jobs} failed jobs"
246
- elsif failed_jobs > 5
250
+ elsif warning_count > 0 || failed_jobs > 5
247
251
  badge_class = "warning"
248
- text = "#{failed_jobs} failed jobs"
252
+ parts = []
253
+ parts << "#{warning_count} warnings" if warning_count > 0
254
+ parts << "#{failed_jobs} failed jobs" if failed_jobs > 5
255
+ text = parts.join(", ")
249
256
  else
250
257
  badge_class = "success"
251
258
  text = "All clear"
@@ -280,6 +287,30 @@ module Newshound
280
287
  HTML
281
288
  end
282
289
 
290
+ def render_warnings(data)
291
+ warnings = data[:warnings] || []
292
+
293
+ return "" if warnings.empty?
294
+
295
+ items = warnings.take(5).map do |w|
296
+ <<~HTML
297
+ <div class="newshound-item">
298
+ <div class="newshound-item-title">#{escape_html(w[:title])}</div>
299
+ <div class="newshound-item-detail">
300
+ #{escape_html(w[:message])} • #{escape_html(w[:location])} • #{escape_html(w[:time])}
301
+ </div>
302
+ </div>
303
+ HTML
304
+ end.join
305
+
306
+ <<~HTML
307
+ <div class="newshound-section">
308
+ <div class="newshound-section-title">⚠️ Warnings (#{warnings.length})</div>
309
+ #{items}
310
+ </div>
311
+ HTML
312
+ end
313
+
283
314
  def render_jobs(data)
284
315
  stats = data[:queue_stats] || {}
285
316
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Newshound
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.5"
5
5
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Newshound
4
+ class WarningReporter
5
+ attr_reader :warning_source, :configuration, :time_range
6
+
7
+ def initialize(warning_source: nil, configuration: nil, time_range: 24.hours)
8
+ @configuration = configuration || Newshound.configuration
9
+ @warning_source = resolve_warning_source(warning_source)
10
+ @time_range = time_range
11
+ end
12
+
13
+ def generate_report
14
+ return no_warnings_block if recent_warnings.empty?
15
+
16
+ [
17
+ {
18
+ type: "section",
19
+ text: {
20
+ type: "mrkdwn",
21
+ text: "*⚠️ Recent Warnings (Last 24 Hours)*"
22
+ }
23
+ },
24
+ *format_warnings
25
+ ]
26
+ end
27
+ alias_method :report, :generate_report
28
+
29
+ # Returns data formatted for the banner UI
30
+ def banner_data
31
+ return {warnings: []} unless warning_source
32
+
33
+ {
34
+ warnings: recent_warnings.map { |warning| warning_source.format_for_banner(warning) }
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def resolve_warning_source(source)
41
+ source ||= @configuration.warning_source
42
+ return nil if source.nil?
43
+
44
+ source.is_a?(Symbol) ? Warnings.source(source) : source
45
+ end
46
+
47
+ def recent_warnings
48
+ return [] unless warning_source
49
+
50
+ @recent_warnings ||= warning_source.recent(
51
+ time_range: time_range,
52
+ limit: configuration.warning_limit
53
+ )
54
+ end
55
+
56
+ def format_warnings
57
+ recent_warnings.map.with_index do |warning, index|
58
+ {
59
+ type: "section",
60
+ text: {
61
+ type: "mrkdwn",
62
+ text: warning_source.format_for_report(warning, index + 1)
63
+ }
64
+ }
65
+ end
66
+ end
67
+
68
+ def no_warnings_block
69
+ [
70
+ {
71
+ type: "section",
72
+ text: {
73
+ type: "mrkdwn",
74
+ text: "*✅ No Warnings in the Last 24 Hours*"
75
+ }
76
+ }
77
+ ]
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Newshound
4
+ module Warnings
5
+ # Base class for warning source adapters
6
+ # Each adapter is responsible for:
7
+ # 1. Fetching recent warnings from its specific data source
8
+ # 2. Formatting warning data for reports and banners
9
+ #
10
+ # Subclasses must implement:
11
+ # - #recent(time_range:, limit:) - Returns a collection of warning records
12
+ # - #format_for_report(warning, number) - Formats a single warning for Slack/report display
13
+ # - #format_for_banner(warning) - Formats a single warning for banner UI
14
+ class Base
15
+ # Fetches recent warnings from the data source
16
+ #
17
+ # @param time_range [ActiveSupport::Duration] Time duration to look back (e.g., 24.hours)
18
+ # @param limit [Integer] Maximum number of warnings to return
19
+ # @return [Array] Collection of warning records
20
+ def recent(time_range:, limit:)
21
+ raise NotImplementedError, "#{self.class} must implement #recent"
22
+ end
23
+
24
+ # Formats a warning for report/Slack display
25
+ #
26
+ # @param warning [Object] Warning record from the data source
27
+ # @param number [Integer] Position number in the list
28
+ # @return [String] Formatted markdown text for display
29
+ def format_for_report(warning, number)
30
+ raise NotImplementedError, "#{self.class} must implement #format_for_report"
31
+ end
32
+
33
+ # Formats a warning for banner UI display
34
+ #
35
+ # @param warning [Object] Warning record from the data source
36
+ # @return [Hash] Hash with keys: :title, :message, :location, :time
37
+ def format_for_banner(warning)
38
+ raise NotImplementedError, "#{self.class} must implement #format_for_banner"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(__dir__, "warnings", "*.rb")].each { |file| require file }
4
+
5
+ module Newshound
6
+ module Warnings
7
+ # Registry for warning adapters
8
+ # Allows applications to register custom warning sources without
9
+ # needing to create classes within the Newshound namespace
10
+ @registry = {}
11
+
12
+ class << self
13
+ # Access the registry of registered adapters
14
+ # @return [Hash] The registry hash mapping names to adapter classes
15
+ attr_reader :registry
16
+
17
+ # Register a warning adapter class with a symbolic name
18
+ #
19
+ # @param name [Symbol, String] The name to register the adapter under
20
+ # @param adapter_class [Class] The adapter class (must inherit from Warnings::Base)
21
+ # @example
22
+ # Newshound::Warnings.register(:unprocessable_events, MyApp::UnprocessableEventsWarning)
23
+ def register(name, adapter_class)
24
+ @registry[name.to_sym] = adapter_class
25
+ end
26
+
27
+ # Get a warning source adapter instance
28
+ #
29
+ # @param source [Symbol, Object] Either a symbolic name to look up, or an adapter instance
30
+ # @return [Warnings::Base] An instance of the warning adapter
31
+ # @raise [RuntimeError] If the source symbol cannot be resolved
32
+ # @example
33
+ # Newshound::Warnings.source(:unprocessable_events)
34
+ # Newshound::Warnings.source(MyAdapter.new)
35
+ def source(source)
36
+ return source unless source.is_a?(Symbol)
37
+
38
+ # First check the registry
39
+ if @registry.key?(source)
40
+ return @registry[source].new
41
+ end
42
+
43
+ # Fall back to constant lookup (like Exceptions module)
44
+ constant = constants.find { |c| c.to_s.gsub(/(?<!^)([A-Z])/, "_\\1").downcase == source.to_s }
45
+ raise "Invalid warning source: #{source}" unless constant
46
+
47
+ const_get(constant).new
48
+ end
49
+
50
+ # Clear the registry (primarily for testing)
51
+ def clear_registry!
52
+ @registry = {}
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/newshound.rb CHANGED
@@ -4,6 +4,8 @@ require_relative "newshound/version"
4
4
  require_relative "newshound/configuration"
5
5
  require_relative "newshound/exceptions"
6
6
  require_relative "newshound/exception_reporter"
7
+ require_relative "newshound/warnings"
8
+ require_relative "newshound/warning_reporter"
7
9
  require_relative "newshound/que_reporter"
8
10
  require_relative "newshound/authorization"
9
11
  require_relative "newshound/middleware/banner_injector"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newshound
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Savannah Moore
@@ -52,6 +52,8 @@ files:
52
52
  - README.md
53
53
  - Rakefile
54
54
  - TRANSPORT_USAGE.md
55
+ - checksums/newshound-0.2.3.gem.sha512
56
+ - checksums/newshound-0.2.5.gem.sha512
55
57
  - lib/generators/newshound/install/install_generator.rb
56
58
  - lib/generators/newshound/install/templates/newshound.rb
57
59
  - lib/newshound.rb
@@ -66,6 +68,9 @@ files:
66
68
  - lib/newshound/que_reporter.rb
67
69
  - lib/newshound/railtie.rb
68
70
  - lib/newshound/version.rb
71
+ - lib/newshound/warning_reporter.rb
72
+ - lib/newshound/warnings.rb
73
+ - lib/newshound/warnings/base.rb
69
74
  - newshound.gemspec
70
75
  homepage: https://github.com/SOFware/newshound
71
76
  licenses: