Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 81 additions & 7 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,87 @@
# .rubocop.yml
require:
- rubocop-minitest

AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 3.0
NewCops: enable

# Allow development dependencies in gemspec
Gemspec/DevelopmentDependencies:
Enabled: false

# Make documentation optional for most files
Style/Documentation:
Enabled: false

# Allow unused arguments (often needed for API compatibility)
Lint/UnusedMethodArgument:
Enabled: false

# Allow missing super in inherited callbacks
Lint/MissingSuper:
Enabled: false

# Allow private class methods to be defined normally
Lint/IneffectiveAccessModifier:
Enabled: false

# Allow longer classes and modules for better organization
Metrics/ClassLength:
Max: 200
Exclude:
- 'test/**/*'

Metrics/ModuleLength:
Max: 200

# Allow more reasonable method lengths
Metrics/MethodLength:
Max: 40
Exclude:
- 'test/**/*'

Metrics/AbcSize:
Max: 50
Exclude:
- 'test/**/*'

Metrics/CyclomaticComplexity:
Max: 12

Metrics/PerceivedComplexity:
Max: 12

# Allow longer parameter lists for API methods
Metrics/ParameterLists:
Max: 10
CountKeywordArgs: false

# Allow longer lines, especially for documentation
Layout/LineLength:
Max: 150
Exclude:
- 'test/**/*'

# Allow larger blocks in tests
Metrics/BlockLength:
Exclude:
- 'test/**/*'
- '*.gemspec'

# Other style preferences
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes

Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes
Lint/DuplicateMethods:
Exclude:
- 'test/**/*'

Style/FrozenStringLiteralComment:
Enabled: false

Lint/Void:
Exclude:
- test/cdss/test_analysis.rb
- test/cdss/test_structures.rb

Layout/LineLength:
Max: 120
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ gem "rake", "~> 13.0"

group :development, :test do
gem "minitest", "~> 5.0"
gem "rubocop", "~> 1.21"
gem "rubocop-minitest"
gem "pry"
gem "pry-byebug"
gem "rubocop", "~> 1.21"
gem "rubocop-minitest"
gem "webrick" # for Ruby 3.0+ if needed
gem "yard"
end
3 changes: 2 additions & 1 deletion lib/usgs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ module Usgs
@loader.setup

extend Dry::Configurable

setting :user_agent, default: -> { "Usgs Ruby Gem/#{VERSION}" }
setting :timeout, default: 30
setting :base_url, default: "https://waterservices.usgs.gov/nwis"
setting :default_parameter, default: "DISCHRG"
setting :debug, default: true
setting :debug, default: false

class << self
attr_reader :loader
Expand Down
18 changes: 8 additions & 10 deletions lib/usgs/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ class Client
include Site
include Statistics

attr_reader :timeout, :user_agent
attr_reader :timeout, :user_agent, :debug

def initialize(timeout: 30, user_agent: "usgs-ruby/#{Usgs::VERSION}", debug: false)
def initialize(timeout: 30, user_agent: "usgs-ruby/#{Usgs::VERSION}", debug: nil)
@timeout = timeout
@user_agent = user_agent
@debug = debug
@debug = debug.nil? ? Usgs.config.debug : debug
end

# Base URL for USGS Water Services
Expand All @@ -32,9 +32,9 @@ def api_get(path, query = {})

def fetch_url(url, query: {}, timeout: 30, user_agent: nil)
uri = URI(url)
uri.query = URI.encode_www_form(query).gsub('+', '%20') unless query.empty?
uri.query = URI.encode_www_form(query).gsub("+", "%20") unless query.empty?

puts "\n=== USGS Request ===\n#{uri}\n====================\n" if $DEBUG || ENV["USGS_DEBUG"]
puts "\n=== USGS Request ===\n#{uri}\n====================\n" if @debug

http_get(uri, timeout: timeout, user_agent: user_agent)
end
Expand All @@ -53,11 +53,9 @@ def http_get(uri, timeout: 30, user_agent: nil)

response = http.request(request)

if response.is_a?(Net::HTTPSuccess)
response
else
raise "USGS API Error #{response.code}: #{response.message}\n#{response.body}"
end
raise "USGS API Error #{response.code}: #{response.message}\n#{response.body}" unless response.is_a?(Net::HTTPSuccess)

response
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/usgs/daily_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module Usgs
module DailyValues
include Utils

# Fetch daily values (DV) from USGS NWIS
#
# @param sites [String, Array<String>] USGS site ID(s)
Expand Down
1 change: 1 addition & 0 deletions lib/usgs/instantaneous_values.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Usgs
module InstantaneousValues
include Utils

# Fetch instantaneous values (IV) from USGS NWIS
#
# @param sites [String, Array<String>] One or more USGS site IDs
Expand Down
2 changes: 1 addition & 1 deletion lib/usgs/models/site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Site
attr_accessor(*ATTRIBUTES) # :nodoc:

def initialize(attrs = {})
attrs = attrs.is_a?(Hash) ? attrs : {}
attrs = {} unless attrs.is_a?(Hash)
attrs[:metadata] ||= {}
ATTRIBUTES.each do |attr|
instance_variable_set(:"@#{attr}", attrs[attr]) if attrs.key?(attr)
Expand Down
11 changes: 6 additions & 5 deletions lib/usgs/parsers/rdb_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def parse(text)
column_names = field_names_line.split("\t")
data_lines[data_start_index..].map do |line|
next if line.empty? || line.start_with?("#")

values = line.split("\t")
Hash[column_names.zip(values)].transform_keys { |k| k.strip.to_sym }
column_names.zip(values).to_h.transform_keys { |k| k.strip.to_sym }
end.compact
end

Expand All @@ -40,10 +41,10 @@ def parse_time_series(text)
value = nil if value.nil? || value == "" || value == "-999999" || value.downcase.include?("ice")

{
site_no: row[:site_no]&.strip,
datetime: row[:datetime] || row[:dateTime],
value: value&.to_f,
code: row[:value_cd] || row[:qualifiers],
site_no: row[:site_no]&.strip,
datetime: row[:datetime] || row[:dateTime],
value: value&.to_f,
code: row[:value_cd] || row[:qualifiers],
parameter_cd: row[:parameter_cd] || row[:parm_cd]
}.compact
end
Expand Down
42 changes: 21 additions & 21 deletions lib/usgs/parsers/statistics_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ def parse(rdb_text)

rows.map do |row|
{
site_no: row[:site_no]&.strip,
site_no: row[:site_no]&.strip,
parameter_cd: row[:parameter_cd]&.strip,
month_nu: row[:month_nu]&.to_i,
day_nu: row[:day_nu]&.to_i,
begin_yr: row[:begin_yr]&.to_i,
end_yr: row[:end_yr]&.to_i,
count_nu: row[:count_nu]&.to_i,
mean_va: row[:mean_va],
max_va: row[:max_va],
max_va_yr: row[:max_va_yr]&.to_i,
min_va: row[:min_va],
min_va_yr: row[:min_va_yr]&.to_i,
p05_va: row[:p05_va],
p10_va: row[:p10_va],
p20_va: row[:p20_va],
p25_va: row[:p25_va],
p50_va: row[:p50_va],
p75_va: row[:p75_va],
p80_va: row[:p80_va],
p90_va: row[:p90_va],
p95_va: row[:p95_va],
metadata: {}
month_nu: row[:month_nu]&.to_i,
day_nu: row[:day_nu]&.to_i,
begin_yr: row[:begin_yr]&.to_i,
end_yr: row[:end_yr]&.to_i,
count_nu: row[:count_nu]&.to_i,
mean_va: row[:mean_va],
max_va: row[:max_va],
max_va_yr: row[:max_va_yr]&.to_i,
min_va: row[:min_va],
min_va_yr: row[:min_va_yr]&.to_i,
p05_va: row[:p05_va],
p10_va: row[:p10_va],
p20_va: row[:p20_va],
p25_va: row[:p25_va],
p50_va: row[:p50_va],
p75_va: row[:p75_va],
p80_va: row[:p80_va],
p90_va: row[:p90_va],
p95_va: row[:p95_va],
metadata: {}
}
end
end
Expand Down
12 changes: 6 additions & 6 deletions lib/usgs/parsers/time_series_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ def parse_series(series)
datetime = datetime[0..9] if datetime&.include?("T00:00:00.000")

{
site_no: site_no,
site_no: site_no,
parameter_cd: parameter_cd,
datetime: datetime,
value: value,
qualifiers: v["qualifiers"],
unit: unit,
metadata: {}
datetime: datetime,
value: value,
qualifiers: v["qualifiers"],
unit: unit,
metadata: {}
}
end
end
Expand Down
9 changes: 3 additions & 6 deletions lib/usgs/site.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@ module Site
# @example
# Usgs.client.get_sites(state_cd: "CO", parameter_cd: :discharge)
#
def get_sites(state_cd: nil, county_cd: nil, huc: nil, bBox: nil, site_name: nil,
def get_sites(state_cd: nil, county_cd: nil, huc: nil, b_box: nil, site_name: nil,
site_type: nil, site_status: "all", parameter_cd: nil)

query = {
format: "rdb,1.0",
stateCd: state_cd,
countyCd: county_cd,
huc: huc,
bBox: bBox,
bBox: b_box,
siteName: site_name,
siteType: site_type,
siteStatus: site_status
Expand All @@ -42,9 +41,7 @@ def get_sites(state_cd: nil, county_cd: nil, huc: nil, bBox: nil, site_name: nil

# Validate for at least one major filter
major_keys = %i[stateCd countyCd huc bBox].map(&:to_sym)
if (query.keys & major_keys).empty?
raise ArgumentError, "You must provide at least one major filter: state_cd, county_cd, huc or bBox"
end
raise ArgumentError, "You must provide at least one major filter: state_cd, county_cd, huc or b_box" if (query.keys & major_keys).empty?

raw = api_get("/site/", query)
Parser.parse_sites(raw.body).map { |row| Models::Site.new(row) }
Expand Down
8 changes: 2 additions & 6 deletions lib/usgs/statistics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ def get_stats(sites:, parameter_cd: nil, report_type: :daily, stat_year_type: ni
param_list = resolve_parameter_codes(parameter_cd)
type_str = report_type.to_s

unless %w[daily monthly annual].include?(type_str)
raise ArgumentError, "report_type must be :daily, :monthly, or :annual"
end
raise ArgumentError, "report_type must be :daily, :monthly, or :annual" unless %w[daily monthly annual].include?(type_str)

if type_str != "annual" && stat_year_type
raise ArgumentError, "stat_year_type is only valid when report_type: :annual"
end
raise ArgumentError, "stat_year_type is only valid when report_type: :annual" if type_str != "annual" && stat_year_type

query = {
format: "rdb,1.0",
Expand Down
28 changes: 16 additions & 12 deletions lib/usgs/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ def resolve_parameter_codes(codes)
return nil if codes.nil?

mapping = {
discharge: "00060",
gage_height: "00065",
temperature: "00010",
precipitation: "00045",
do: "00300",
conductivity: "00095",
ph: "00400"
discharge: "00060",
gage_height: "00065",
temperature: "00010",
precipitation: "00045",
do: "00300",
conductivity: "00095",
ph: "00400"
}

Array(codes).map do |c|
if c.is_a?(Symbol)
mapped = mapping[c]
raise ArgumentError, "Invalid parameter code: #{c}. Valid codes are: #{mapping.keys.join(', ')}" if mapped.nil?
if mapped.nil?
raise ArgumentError,
"Invalid parameter code: #{c}. Valid codes are: #{mapping.keys.join(', ')}"
end

mapped
else
Expand All @@ -34,13 +37,14 @@ def resolve_parameter_codes(codes)

def format_date(date)
Date.parse(date.to_s).strftime("%Y-%m-%d")
rescue
rescue StandardError
Date.today.strftime("%Y-%m-%d")
end

def format_datetime(dt)
return nil unless dt
Time.parse(dt.to_s).utc.strftime("%Y-%m-%dT%H:%M")
def format_datetime(datetime)
return nil unless datetime

Time.parse(datetime.to_s).utc.strftime("%Y-%m-%dT%H:%M")
end
end
end
1 change: 0 additions & 1 deletion lib/usgs/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
module Usgs
VERSION = "1.0.0"
end

Loading