geo_states 0.1.1 → 0.1.3

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: 6a0c6519178af969c2358e5ba7402bdec603bf7cd7dc69dd1afc4b43b4b20d48
4
- data.tar.gz: 0676e02cd3f7d22b384b6a02b4175122e05029ca04b6e7262c085e4da123611e
3
+ metadata.gz: 1b757132bddec9472e730189fa998a1ef255440bb12eec76f407c9242b07b685
4
+ data.tar.gz: 0b5f64fde99b17656d2ced4965c5f766dbcc9b9f533d7ee14389ce83e0c93baa
5
5
  SHA512:
6
- metadata.gz: 3096bf0c3448fd39ad6044d1ef6fc2f75b588b941c43eee34621344480a2770cf2f9ac0a1ef62f6100b396349b838a3da93888d6232e711c8603a3910d537e69
7
- data.tar.gz: dfefb60825e0befbf7414998d982526eff206f22a5dbf1638bbda03252d711f5b76f8e3caee8d612a5db4408444bc8517003d7fc3c2e7c58377032fb24e95f1f
6
+ metadata.gz: 29f3e3ccc65d2b2f7b5c03f2ec807e754dc4f3c3a4d79b940a2bb91dd32b485e7d5da378a62d511c7e3999cb14babdc661f0b310c7c053870dc4e49a81d897fc
7
+ data.tar.gz: b06009b62f7580a61cbaba751652c820963046b116d334c0208e45eec37316e45d031c8974caeffd6bb1051f88ed7f0ac28e2cb3bd25c03421526739099c3196
data/README.md CHANGED
@@ -25,9 +25,9 @@ require "geo_states"
25
25
  GeoStates.all_countries
26
26
  # => [{:country=>"Mexico", :iso2=>"MX", :iso3=>"MEX", :states=>[...]}, ...]
27
27
 
28
- # Find country by ISO 3166-1 alpha-2 code
28
+ # Find country by any identifier (ISO2, ISO3, M49, or name)
29
29
  GeoStates.find_country("MX")
30
- # => {:country=>"Mexico", :m49=>nil, :iso3=>"MEX", :iso2=>"MX", :currency=>"MXN", ...}
30
+ # => {:country=>"Mexico", :m49=>"484", :iso3=>"MEX", :iso2=>"MX", :currency=>"MXN", ...}
31
31
 
32
32
  # Find by ISO3, M49, or name
33
33
  GeoStates.find_country_by_iso3("MEX")
@@ -37,6 +37,14 @@ GeoStates.find_country_by_name("Mexico")
37
37
  # Get states for a country (by iso2, iso3, m49, or name)
38
38
  GeoStates.states_for("MX")
39
39
  # => [{:name=>"Aguascalientes", :state_code=>"AGU", :iso3=>"AGU", :iso2=>"AG"}, ...]
40
+
41
+ # Find a specific state
42
+ GeoStates.find_state("MX", "CMX")
43
+ # => {:name=>"Mexico City", :state_code=>"CMX", :iso3=>"CMX", :iso2=>"CM"}
44
+
45
+ # With user config: CDMX, DF, etc. resolve to Mexico City (see User config below)
46
+ GeoStates.find_state("MX", "CDMX")
47
+ # => {:name=>"Mexico City", :state_code=>"CMX", ...}
40
48
  ```
41
49
 
42
50
  ### Country format
@@ -57,6 +65,60 @@ Each state has:
57
65
  - `:iso3` — same as state_code if not provided
58
66
  - `:iso2` — derived from state_code if not provided
59
67
 
68
+ ### Example: what the data looks like
69
+
70
+ **Country** (e.g. `GeoStates.find_country("MX")`):
71
+
72
+ ```ruby
73
+ {
74
+ country: "Mexico",
75
+ m49: "484",
76
+ iso3: "MEX",
77
+ iso2: "MX",
78
+ currency: "MXN",
79
+ currency_symbol: "$",
80
+ phone_code: "+52",
81
+ flag: "https://upload.wikimedia.org/wikipedia/commons/f/fc/Flag_of_Mexico.svg",
82
+ states: [
83
+ { name: "Aguascalientes", state_code: "AGU", iso3: "AGU", iso2: "AG" },
84
+ { name: "Baja California", state_code: "BCN", iso3: "BCN", iso2: "BC" },
85
+ # ... 30 more states
86
+ ]
87
+ }
88
+ ```
89
+
90
+ **States** (e.g. `GeoStates.states_for("MX")` — first two):
91
+
92
+ ```ruby
93
+ [
94
+ { name: "Aguascalientes", state_code: "AGU", iso3: "AGU", iso2: "AG" },
95
+ { name: "Baja California", state_code: "BCN", iso3: "BCN", iso2: "BC" }
96
+ ]
97
+ ```
98
+
99
+ ### User config for state aliases
100
+
101
+ You can add a YAML file to define aliases (e.g. CDMX, DF for Mexico City):
102
+
103
+ 1. Create `config/geo_states.yml` or `geo_states.yml` in your project root
104
+ 2. Or set `GEO_STATES_CONFIG_PATH` to the config file path
105
+
106
+ Example `config/geo_states.yml`:
107
+
108
+ ```yaml
109
+ MX:
110
+ state_aliases:
111
+ CDMX: DF
112
+ DF: DF
113
+ DISTRITO FEDERAL: DF
114
+ CIUDAD DE MEXICO: DF
115
+ MEXICO CITY: DF
116
+ ```
117
+
118
+ Values map to the `state_code` in the bundled data. For Mexico City the data uses `CMX`; `DF` is a built-in alias that maps to `CMX`. So `CDMX` → `DF` → `CMX` works when you have the config.
119
+
120
+ See `config/geo_states.example.yml` for a copy-paste template.
121
+
60
122
  ## Data sources
61
123
 
62
124
  The bundled data in `lib/geo_states/data/countries.json` is the single source. It includes:
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
8
+ Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
9
9
 
10
10
  task default: :spec
@@ -0,0 +1,14 @@
1
+ # GeoStates: user config for state aliases
2
+ # Copy to config/geo_states.yml or geo_states.yml in your project root.
3
+ # Or set GEO_STATES_CONFIG_PATH to the config file path.
4
+ #
5
+ # Values must map to state_code in bundled data (e.g. CMX for Mexico City).
6
+ # DF is supported as built-in alias for Mexico City (CMX).
7
+
8
+ MX:
9
+ state_aliases:
10
+ CDMX: DF
11
+ DF: DF
12
+ DISTRITO FEDERAL: DF
13
+ CIUDAD DE MEXICO: DF
14
+ MEXICO CITY: DF
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module GeoStates
6
+ # Loads user-provided YAML config for state aliases (e.g. CDMX => CMX for Mexico).
7
+ module ConfigLoader
8
+ CONFIG_FILENAMES = %w[geo_states.yml geo_states.yaml].freeze
9
+
10
+ class << self
11
+ # @return [Hash] country_iso2 => { alias => state_code }
12
+ def state_aliases
13
+ @state_aliases ||= load_state_aliases
14
+ end
15
+
16
+ # Reset cached config (useful for tests).
17
+ def reset!
18
+ @state_aliases = nil
19
+ @config_path = nil
20
+ end
21
+
22
+ # @param path [String] Explicit config file path
23
+ def config_path=(path)
24
+ @config_path = path
25
+ @state_aliases = nil
26
+ end
27
+
28
+ # Resolve state identifier via user config. Returns canonical state_code or nil.
29
+ # @param country_iso2 [String] e.g. "MX"
30
+ # @param state_id [String] e.g. "CDMX", "DF"
31
+ # @return [String, nil] resolved state_code (e.g. "CMX") or nil
32
+ def resolve_alias(country_iso2, state_id)
33
+ return nil if country_iso2.nil? || state_id.nil?
34
+
35
+ iso2 = country_iso2.to_s.upcase
36
+ id = state_id.to_s.strip
37
+
38
+ aliases = state_aliases[iso2]
39
+ return nil unless aliases.is_a?(Hash)
40
+
41
+ # Support both flat and nested structure:
42
+ # MX: { CDMX: CMX } or MX: { state_aliases: { CDMX: CMX } }
43
+ map = aliases['state_aliases'] || aliases
44
+ return nil unless map.is_a?(Hash)
45
+
46
+ val = map[id] || map[id.upcase]
47
+ val.to_s if val
48
+ end
49
+
50
+ private
51
+
52
+ def load_state_aliases
53
+ path = locate_config
54
+ return {} unless path && File.file?(path)
55
+
56
+ raw = YAML.load_file(path)
57
+ return {} unless raw.is_a?(Hash)
58
+
59
+ raw.transform_keys { |k| k.to_s.upcase }
60
+ end
61
+
62
+ def locate_config
63
+ return @config_path if defined?(@config_path) && @config_path
64
+
65
+ if ENV['GEO_STATES_CONFIG_PATH']
66
+ p = ENV['GEO_STATES_CONFIG_PATH'].strip
67
+ return p if !p.empty? && File.file?(p)
68
+ end
69
+
70
+ search_paths.each do |dir|
71
+ CONFIG_FILENAMES.each do |name|
72
+ path = File.join(dir, name)
73
+ return path if File.file?(path)
74
+ end
75
+ end
76
+
77
+ nil
78
+ end
79
+
80
+ def search_paths
81
+ paths = []
82
+ paths << Dir.pwd
83
+ paths << File.join(Dir.pwd, 'config') if Dir.pwd
84
+ paths.uniq
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
3
+ require 'json'
4
4
 
5
5
  module GeoStates
6
6
  # Loads country/state data from bundled JSON (no external API at runtime).
7
7
  module DataLoader
8
- DATA_DIR = File.expand_path("data", __dir__)
9
- COUNTRIES_FILE = File.join(DATA_DIR, "countries.json")
8
+ DATA_DIR = File.expand_path('data', __dir__)
9
+ COUNTRIES_FILE = File.join(DATA_DIR, 'countries.json')
10
10
 
11
11
  class << self
12
12
  def load_countries
@@ -35,35 +35,36 @@ module GeoStates
35
35
  def find_by_m49(m49)
36
36
  code = m49.to_s.strip
37
37
  return nil if code.empty?
38
- code = code.rjust(3, "0") if code.length < 3
38
+
39
+ code = code.rjust(3, '0') if code.length < 3
39
40
  countries.find { |c| c[:m49].to_s == code }
40
41
  end
41
42
 
42
43
  private
43
44
 
44
45
  def normalize_country(raw)
45
- states = (raw["states"] || []).map { |s| normalize_state(s) }
46
+ states = (raw['states'] || []).map { |s| normalize_state(s) }
46
47
  {
47
- country: raw["name"],
48
- m49: raw["m49"],
49
- iso3: raw["iso3"],
50
- iso2: raw["iso2"],
51
- currency: raw["currency"],
52
- currency_symbol: raw["currency_symbol"],
53
- phone_code: raw["phone_code"],
54
- flag: raw["flag"],
48
+ country: raw['name'],
49
+ m49: raw['m49'],
50
+ iso3: raw['iso3'],
51
+ iso2: raw['iso2'],
52
+ currency: raw['currency'],
53
+ currency_symbol: raw['currency_symbol'],
54
+ phone_code: raw['phone_code'],
55
+ flag: raw['flag'],
55
56
  states: states
56
57
  }
57
58
  end
58
59
 
59
60
  def normalize_state(raw)
60
- code = raw["state_code"].to_s
61
+ code = raw['state_code'].to_s
61
62
  iso2 = code.length >= 2 ? code[0, 2].upcase : code.upcase
62
63
  {
63
- name: raw["name"],
64
+ name: raw['name'],
64
65
  state_code: code,
65
- iso3: raw["iso3"] || code,
66
- iso2: raw["iso2"] || iso2
66
+ iso3: raw['iso3'] || code,
67
+ iso2: raw['iso2'] || iso2
67
68
  }
68
69
  end
69
70
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GeoStates
4
- VERSION = "0.1.1"
4
+ VERSION = '0.1.3'
5
5
  end
data/lib/geo_states.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "geo_states/version"
4
- require "geo_states/data_loader"
3
+ require_relative 'geo_states/version'
4
+ require_relative 'geo_states/config_loader'
5
+ require 'geo_states/data_loader'
5
6
 
6
7
  module GeoStates
7
8
  class Error < StandardError; end
@@ -12,20 +13,37 @@ module GeoStates
12
13
  DataLoader.countries
13
14
  end
14
15
 
15
- # Find country by ISO 3166-1 alpha-2 (e.g. "MX", "US").
16
+ # Find country by any identifier (ISO2, ISO3, M49, or name).
17
+ # @param identifier [String] e.g. "MX", "MEX", "484", "Mexico"
18
+ # @return [Hash, nil]
19
+ def self.find_country(identifier)
20
+ find_country_by_iso2(identifier) ||
21
+ find_country_by_iso3(identifier) ||
22
+ find_country_by_m49(identifier) ||
23
+ find_country_by_name(identifier)
24
+ end
25
+
26
+ # Find country by ISO 3166-1 alpha-2 code (e.g. "MX", "US").
16
27
  # @param iso2 [String]
17
28
  # @return [Hash, nil]
18
- def self.find_country(iso2)
29
+ def self.find_country_by_iso2(iso2)
19
30
  DataLoader.find_by_iso2(iso2)
20
31
  end
21
32
 
22
- # Find country by ISO 3166-1 alpha-3 (e.g. "MEX", "USA").
33
+ # Find country by ISO 3166-1 alpha-3 code (e.g. "MEX", "USA").
23
34
  # @param iso3 [String]
24
35
  # @return [Hash, nil]
25
36
  def self.find_country_by_iso3(iso3)
26
37
  DataLoader.find_by_iso3(iso3)
27
38
  end
28
39
 
40
+ # Find country by UN M49 numeric code (e.g. "484", 484).
41
+ # @param m49 [String, Integer]
42
+ # @return [Hash, nil]
43
+ def self.find_country_by_m49(m49)
44
+ DataLoader.find_by_m49(m49)
45
+ end
46
+
29
47
  # Find country by name (case-insensitive).
30
48
  # @param name [String]
31
49
  # @return [Hash, nil]
@@ -33,13 +51,45 @@ module GeoStates
33
51
  DataLoader.find_by_name(name)
34
52
  end
35
53
 
36
- # States for a country (by iso2, iso3, or country name).
37
- # @param identifier [String] ISO2, ISO3, or country name
54
+ # Return states (subdivisions) for a country. Accepts ISO2, ISO3, M49, or name.
55
+ # @param identifier [String] ISO2, ISO3, M49, or country name
38
56
  # @return [Array<Hash>]
39
57
  def self.states_for(identifier)
40
- country = find_country(identifier) ||
41
- find_country_by_iso3(identifier) ||
42
- find_country_by_name(identifier)
58
+ country = find_country(identifier)
43
59
  country&.dig(:states) || []
44
60
  end
61
+
62
+ # Find a state within a country. Accepts user YAML config for aliases (e.g. CDMX => CMX).
63
+ # Config file: config/geo_states.yml or geo_states.yml, or GEO_STATES_CONFIG_PATH env.
64
+ # @param country_id [String] ISO2, ISO3, M49, or country name (e.g. "MX", "Mexico")
65
+ # @param state_id [String] state_code, name, or alias from user config (e.g. "CMX", "CDMX", "Mexico City")
66
+ # @return [Hash, nil] state hash with :name, :state_code, :iso2, :iso3, or nil
67
+ def self.find_state(country_id, state_id)
68
+ country = find_country(country_id)
69
+ return nil unless country
70
+
71
+ canonical = resolve_state_id(country[:iso2], state_id.to_s)
72
+ return nil if canonical.nil? || canonical.empty?
73
+
74
+ states = country[:states] || []
75
+ states.find do |s|
76
+ s[:state_code].to_s.casecmp(canonical) == 0 ||
77
+ s[:name].to_s.casecmp(canonical) == 0
78
+ end
79
+ end
80
+
81
+ # Resolve state identifier: user config first, then built-in aliases (e.g. DF -> CMX for Mexico).
82
+ # @param iso2 [String] country ISO2
83
+ # @param state_id [String] user input
84
+ # @return [String] canonical state_code for lookup
85
+ def self.resolve_state_id(iso2, state_id)
86
+ id = state_id.to_s.strip
87
+ return nil if id.empty?
88
+
89
+ canonical = ConfigLoader.resolve_alias(iso2, id) || id
90
+ # Built-in: Mexico DF -> CMX (bundled data uses CMX; user config may map CDMX => DF)
91
+ return 'CMX' if iso2.to_s.upcase == 'MX' && canonical.to_s.upcase == 'DF'
92
+
93
+ canonical
94
+ end
45
95
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geo_states
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alberto Osnaya (@elosnaya)
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-17 00:00:00.000000000 Z
11
+ date: 2026-02-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Lists countries and their states (or provinces, regions) in a consistent
14
14
  format. Data is bundled with the gem.
@@ -21,9 +21,11 @@ files:
21
21
  - LICENSE.txt
22
22
  - README.md
23
23
  - Rakefile
24
+ - config/geo_states.example.yml
24
25
  - geo_states-0.1.0.gem
25
26
  - geo_states/data/countries.json
26
27
  - lib/geo_states.rb
28
+ - lib/geo_states/config_loader.rb
27
29
  - lib/geo_states/data/countries.json
28
30
  - lib/geo_states/data_loader.rb
29
31
  - lib/geo_states/version.rb