gull 1.0.0 → 1.0.1

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: eb20ff9550e66bb6897227d930069551cc9882c1eef9f285d7a1ba74e0b2133e
4
- data.tar.gz: b50b4e87e416dce952046c357e2c06d1d826b84e451141e237570418569d4b1d
3
+ metadata.gz: 7924e8188cee18f8b12e1a420d6ea68d1e0951c1c7c7e53f9d87b11b528d23f0
4
+ data.tar.gz: 517bb2c4a28d250207ddd22c51173b2be3392e0f1059b89110cc2af11a48af65
5
5
  SHA512:
6
- metadata.gz: b06376a56d00f2fa3ee684f4897b02439d24cd910722779a1f696e4d2a596550fa82cd7efb2e83c94934550fdaf406a266cddcab78a97b41d9fbdf6a13f65373
7
- data.tar.gz: ca1f9f07f27e928571b1d0ed54bbe2275bccd8389995073f0892b4c5e469d176ea5084fa09bf0bdbca923b225105627da4f45e986fd5cd06e245a82a4515a577
6
+ metadata.gz: 79015c143ade595b2d021986b48c0e7dbe78c83f2f42b787e9b7027914263631b3a6581a7491c607cef8037240465e056d8200f4eb9770016a9e88c83b022b71
7
+ data.tar.gz: 61d48682d8434559a7b14347765b3537cfc32c78f7c23e5ff137dcfe7d5571e3e98bbd8a8c79be198959351e3bd8913da7e0c02f82ad8310bf7a33ad7784d974
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ 1.0.1 (03/07/2026) - Add YARD doc comments to all public classes and methods. Expand README with polygon, error handling, and client usage examples. Handle malformed JSON responses (raise `HttpError` instead of `JSON::ParserError`). Handle missing time fields in alert data (return `nil` instead of raising `TypeError`).
2
+
3
+ ***
4
+
1
5
  1.0.0 (03/07/2026) - Major rewrite: migrate from defunct `alerts.weather.gov` XML feed to `api.weather.gov/alerts` JSON API. Drop all runtime dependencies (`httpclient`, `nokogiri`) in favor of Ruby stdlib (`net/http`, `json`). Require Ruby >= 3.1. Add `area` option for state-based filtering. Replace Travis CI with GitHub Actions. Add RuboCop. **Breaking:** remove `url` option (use `area` instead), remove `Polygon#image_url`, remove `strict` option.
2
6
 
3
7
  ***
data/README.md CHANGED
@@ -36,7 +36,7 @@ alert.effective_at
36
36
  alert.expires_at
37
37
  alert.published_at
38
38
  alert.area
39
- alert.polygon
39
+ alert.polygon # Gull::Polygon or nil
40
40
  alert.geocode.fips6
41
41
  alert.geocode.ugc
42
42
  alert.urgency
@@ -51,8 +51,47 @@ To get alerts for a single state or territory, pass the area option:
51
51
  alerts = Gull::Alert.fetch(area: 'OK')
52
52
  ```
53
53
 
54
- ##Notes, Caveats
55
- This library fetches active alerts from the [NWS API](https://api.weather.gov) (`api.weather.gov/alerts/active`), which returns GeoJSON. No authentication is required but the API does require a `User-Agent` header (set automatically by the gem).
54
+ ### Polygons
55
+
56
+ Alerts with geographic boundaries include a `Polygon` object:
57
+
58
+ ```ruby
59
+ alert.polygon.coordinates
60
+ # => [[34.57, -97.56], [34.77, -97.38], ...]
61
+
62
+ alert.polygon.to_s
63
+ # => "34.57,-97.56 34.77,-97.38 ..."
64
+
65
+ alert.polygon.to_wkt
66
+ # => "POLYGON((-97.56 34.57, -97.38 34.77, ...))"
67
+ ```
68
+
69
+ ### Error Handling
70
+
71
+ ```ruby
72
+ begin
73
+ alerts = Gull::Alert.fetch
74
+ rescue Gull::TimeoutError => e
75
+ # request timed out
76
+ rescue Gull::HttpError => e
77
+ # non-success response or connection failure
78
+ e.original # wrapped exception, if any
79
+ end
80
+ ```
81
+
82
+ ### Advanced: Client
83
+
84
+ For direct access to unparseable features, use `Client`:
85
+
86
+ ```ruby
87
+ client = Gull::Client.new(area: 'OK')
88
+ alerts = client.fetch
89
+ client.errors # features that could not be parsed
90
+ ```
91
+
92
+ ## Notes, Caveats
93
+
94
+ This library fetches active alerts from the [NWS API](https://api.weather.gov) (`api.weather.gov/alerts/active`), which returns GeoJSON. No authentication is required but the API does require a `User-Agent` header (set automatically by the gem). See the [NWS API docs](https://www.weather.gov/documentation/services-web-api) for more details.
56
95
 
57
96
  The NWS will often cancel or update alerts before their expiration time. The API only returns currently active alerts.
58
97
 
data/lib/gull/alert.rb CHANGED
@@ -1,7 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gull
4
+ # Represents a single NWS weather alert (warning, watch, or
5
+ # advisory). Use +Alert.fetch+ to retrieve active alerts from the
6
+ # NWS API.
4
7
  class Alert
8
+ # @!attribute id [rw] NWS alert identifier
9
+ # @!attribute title [rw] Alert headline
10
+ # @!attribute summary [rw] Full alert description text
11
+ # @!attribute link [rw] Canonical URL for this alert
12
+ # @!attribute alert_type [rw] Event type (e.g. "Tornado Warning")
13
+ # @!attribute polygon [rw] Alert area as a Polygon, or nil
14
+ # @!attribute area [rw] Human-readable area description
15
+ # @!attribute effective_at [rw] Time the alert takes effect
16
+ # @!attribute expires_at [rw] Time the alert expires
17
+ # @!attribute updated_at [rw] Onset time, or sent time if absent
18
+ # @!attribute published_at [rw] Time the alert was sent
19
+ # @!attribute urgency [rw] Urgency level as a Symbol
20
+ # @!attribute severity [rw] Severity level as a Symbol
21
+ # @!attribute certainty [rw] Certainty level as a Symbol
22
+ # @!attribute geocode [rw] Geocode with UGC and FIPS codes
23
+ # @!attribute vtec [rw] VTEC string, or nil
5
24
  attr_accessor :id, :title, :summary, :link, :alert_type, :polygon,
6
25
  :area, :effective_at, :expires_at, :updated_at,
7
26
  :published_at, :urgency, :severity, :certainty,
@@ -11,11 +30,13 @@ module Gull
11
30
  self.geocode = Geocode.new
12
31
  end
13
32
 
33
+ # Fetches active alerts from the NWS API.
14
34
  def self.fetch(options = {})
15
35
  client = Client.new(options)
16
36
  client.fetch
17
37
  end
18
38
 
39
+ # Populates this alert from a GeoJSON feature hash.
19
40
  def parse(feature)
20
41
  props = feature['properties']
21
42
  parse_core_attributes(feature, props)
@@ -38,10 +59,14 @@ module Gull
38
59
  end
39
60
 
40
61
  def parse_times(props)
41
- self.effective_at = Time.parse(props['effective'])
42
- self.expires_at = Time.parse(props['expires'])
43
- self.published_at = Time.parse(props['sent'])
44
- self.updated_at = Time.parse(props['onset'] || props['sent'])
62
+ self.effective_at = parse_time(props['effective'])
63
+ self.expires_at = parse_time(props['expires'])
64
+ self.published_at = parse_time(props['sent'])
65
+ self.updated_at = parse_time(props['onset'] || props['sent'])
66
+ end
67
+
68
+ def parse_time(value)
69
+ value ? Time.parse(value) : nil
45
70
  end
46
71
 
47
72
  def parse_categories(props)
data/lib/gull/client.rb CHANGED
@@ -1,21 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gull
4
+ # Low-level HTTP client for the NWS alerts API. Handles
5
+ # fetching, parsing, and error wrapping. Most callers should
6
+ # use +Alert.fetch+ instead.
4
7
  class Client
5
8
  URL = 'https://api.weather.gov/alerts/active'
6
9
  USER_AGENT = "gull/#{VERSION} (Ruby #{RUBY_VERSION})".freeze
7
10
 
11
+ # Features that could not be parsed are collected here.
8
12
  attr_accessor :errors
9
13
 
10
14
  def initialize(options = {})
11
15
  @options = options
12
16
  end
13
17
 
18
+ # Fetches active alerts and returns an Array of Alert objects.
14
19
  def fetch
15
20
  self.errors = []
16
21
  json = response
17
22
  data = JSON.parse(json)
18
23
  process(data['features'] || [])
24
+ rescue JSON::ParserError
25
+ raise HttpError, 'Unexpected response from NWS API'
19
26
  end
20
27
 
21
28
  private
data/lib/gull/error.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'English'
4
4
 
5
5
  module Gull
6
+ # Raised when the NWS API returns a non-success response or the
7
+ # connection fails. Wraps the original exception, if any.
6
8
  class HttpError < StandardError
7
9
  attr_reader :original
8
10
 
@@ -12,6 +14,7 @@ module Gull
12
14
  end
13
15
  end
14
16
 
17
+ # Raised when the NWS API request times out.
15
18
  class TimeoutError < HttpError
16
19
  end
17
20
  end
data/lib/gull/geocode.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gull
4
+ # Holds UGC zone codes and FIPS county codes for an alert area.
4
5
  class Geocode
6
+ # @!attribute fips6 [rw] Space-separated FIPS 6 codes
7
+ # @!attribute ugc [rw] Space-separated UGC zone codes
5
8
  attr_accessor :fips6, :ugc
6
9
  end
7
10
  end
data/lib/gull/polygon.rb CHANGED
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gull
4
+ # Represents the geographic boundary of an alert as a series of
5
+ # lat/lon coordinate pairs. Coordinates are stored in [lat, lon]
6
+ # order.
4
7
  class Polygon
5
8
  attr_accessor :coordinates
6
9
 
10
+ # Accepts GeoJSON coordinates ([lon, lat]) and stores them as
11
+ # [lat, lon].
7
12
  def initialize(coords)
8
13
  self.coordinates = coords.map { |point| [point[1], point[0]] }
9
14
  end
10
15
 
16
+ # Returns coordinates as "lat,lon lat,lon ..." string.
11
17
  def to_s
12
18
  coordinates.map { |pair| pair.join(',') }.join(' ')
13
19
  end
14
20
 
21
+ # Returns coordinates in Well-Known Text format.
15
22
  def to_wkt
16
23
  pairs = coordinates.map { |pair| "#{pair.last} #{pair.first}" }
17
24
  .join(', ')
data/lib/gull/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gull
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
data/spec/alert_spec.rb CHANGED
@@ -154,6 +154,18 @@ describe Gull::Alert do
154
154
  expect(alerts.size).to eq(0)
155
155
  end
156
156
 
157
+ it 'should handle missing onset time' do
158
+ feature = load_feature('missing_times.json')
159
+ stub_alerts(wrap_features(feature))
160
+
161
+ alert = Gull::Alert.fetch.first
162
+
163
+ expect(alert.effective_at).to be_nil
164
+ expect(alert.expires_at).to eq Time.parse('2026-03-07T08:15:00-06:00')
165
+ expect(alert.updated_at).to eq Time.parse('2026-03-07T07:48:00-06:00')
166
+ expect(alert.published_at).to eq Time.parse('2026-03-07T07:48:00-06:00')
167
+ end
168
+
157
169
  it 'should handle missing event' do
158
170
  json = File.read 'spec/fixtures/missing_event.json'
159
171
  stub_alerts(json)
data/spec/client_spec.rb CHANGED
@@ -71,6 +71,18 @@ describe Gull::Client do
71
71
  end
72
72
  end
73
73
 
74
+ it 'should raise error on malformed JSON response' do
75
+ stub_request(:get, 'https://api.weather.gov/alerts/active')
76
+ .to_return(status: 200, body: '<html>Bad Gateway</html>',
77
+ headers: {})
78
+
79
+ client = Gull::Client.new
80
+ expect { client.fetch }
81
+ .to raise_error(Gull::HttpError, /Unexpected response/) do |e|
82
+ expect(e.original).to be_a(JSON::ParserError)
83
+ end
84
+ end
85
+
74
86
  it 'should filter by area' do
75
87
  json = File.read 'spec/fixtures/alerts.json'
76
88
 
@@ -0,0 +1,36 @@
1
+ {
2
+ "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.missing-times-test",
3
+ "type": "Feature",
4
+ "geometry": null,
5
+ "properties": {
6
+ "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.missing-times-test",
7
+ "@type": "wx:Alert",
8
+ "id": "urn:oid:2.49.0.1.840.0.missing-times-test",
9
+ "areaDesc": "Test County",
10
+ "geocode": {
11
+ "SAME": ["005001"],
12
+ "UGC": ["ARZ001"]
13
+ },
14
+ "affectedZones": [],
15
+ "references": [],
16
+ "sent": "2026-03-07T07:48:00-06:00",
17
+ "effective": null,
18
+ "onset": null,
19
+ "expires": "2026-03-07T08:15:00-06:00",
20
+ "ends": null,
21
+ "status": "Actual",
22
+ "messageType": "Alert",
23
+ "category": "Met",
24
+ "severity": "Moderate",
25
+ "certainty": "Likely",
26
+ "urgency": "Expected",
27
+ "event": "Special Weather Statement",
28
+ "sender": "w-nws.webmaster@noaa.gov",
29
+ "senderName": "NWS Test",
30
+ "headline": "Test alert with missing onset",
31
+ "description": "Test description.",
32
+ "instruction": null,
33
+ "response": "Monitor",
34
+ "parameters": {}
35
+ }
36
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gull
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Deckard
@@ -157,6 +157,7 @@ files:
157
157
  - spec/fixtures/features/empty_geocode.json
158
158
  - spec/fixtures/features/flood_advisory.json
159
159
  - spec/fixtures/features/flood_warning.json
160
+ - spec/fixtures/features/missing_times.json
160
161
  - spec/fixtures/features/multipolygon.json
161
162
  - spec/fixtures/features/null_geometry.json
162
163
  - spec/fixtures/features/polygon_no_vtec.json