iparty 0.1.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55ee6dbcc7d44397d35c6acbd8f6121cea24496dd1f436053e3aaa74e53a5fef
4
- data.tar.gz: a8df1dd41f2c5921004a08b944aa0e764d00e7619a3d7dd335b0729b1d86f049
3
+ metadata.gz: 5f040fd93232947aa2be967cac0ad59617cc24e4751c14908466e221d4830fc6
4
+ data.tar.gz: 4b34b52ee226d40b306da7d8dcf7f576ceb37c98d2695c1e2a78db43703ba40a
5
5
  SHA512:
6
- metadata.gz: 7f14e051fb84488a9fd0afff7a413672c7e84e34cb2f326c42799d4feeefa4b70471ca0f5c6184b0fe5ac6497478a8a2f020c6992c338de4ad03c706ce3538e1
7
- data.tar.gz: 9c8c93019f00f2f07165a17f892c2bfc5410d08d2b03eb5e7366f9761eee45c8c7b993f1816c4d6f72f40d353fa0f421f0d1f8ff6a1bc7e97b8fc678df295949
6
+ metadata.gz: 1f319c5dbfcad4f51192b750a973f1de60dbee54db84f176aa67ed921f883770eaf61fb82f1b32a837e93072eb0a10f562bed4e2743cf5df3a4936e957bcf1b1
7
+ data.tar.gz: 67f50c5df51c1c1e06266c780d5b27163b9ee5a7bb45c22f8c846c4de8baa6c8f083db174d09690211780b8edcbc13bb0196acc7355e6e8f24ee18f75d0b7e4a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2026-05-27
4
+
5
+ * Include documentation in gem package
6
+ * Automatically set a network for localhost
7
+ * Add config option (proc) transform_result
8
+ * iso_code fallbacks to code and vice versa so you don't have to think about it (continent has code and rest has iso_code).
9
+ This only applies to method access, `[:code]` does not fallback.
10
+ * Add alias `timezone` for `time_zone`
11
+ * Memoize annotations
12
+ * Add `tag?` helper for annotations
13
+
14
+
15
+
3
16
  ## [0.1.1] - 2026-05-26
4
17
 
5
18
  * Add missing pathname dependency (stdlib)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # IParty
2
2
 
3
- ## 0.1.0 alpha: risk of party crashers
3
+ ## 0.x.x alpha: risk of party crashers
4
4
 
5
5
 
6
6
  Makes (geo) IP fun again! Ain't no party like an IParty, because an IParty don't stop.
data/docs/benchmark.md ADDED
@@ -0,0 +1,40 @@
1
+ # IParty
2
+
3
+ ## Benchmark
4
+
5
+ This is the result of `script/benchmark.rb` on my M1.
6
+
7
+ ```
8
+ RSS-before[uncached]: 30.31 MB
9
+ ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
10
+ Warming up --------------------------------------
11
+ uncached 145.000 i/100ms
12
+ Calculating -------------------------------------
13
+ uncached 1.448k (± 1.9%) i/s (690.59 μs/i) - 14.500k in 10.017464s
14
+ RSS-after[uncached]: 48.75 MB
15
+
16
+
17
+ RSS-before[singletons]: 29.77 MB
18
+ ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
19
+ Warming up --------------------------------------
20
+ singletons 162.000 i/100ms
21
+ Calculating -------------------------------------
22
+ singletons 1.565k (± 8.0%) i/s (638.87 μs/i) - 15.552k in 10.006033s
23
+ RSS-after[singletons]: 31.27 MB
24
+
25
+
26
+ RSS-before[eager_load]: 30.20 MB
27
+ ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
28
+ Warming up --------------------------------------
29
+ eager_load 500.000 i/100ms
30
+ Calculating -------------------------------------
31
+ eager_load 4.940k (± 4.1%) i/s (202.43 μs/i) - 49.500k in 10.038651s
32
+ RSS-after[eager_load]: 112.73 MB
33
+ ```
34
+
35
+ ### Personal conclusion
36
+
37
+ I personally would rather not have mmdb-freshness tied to app-boot.
38
+ Hence I personally use no singletons unless I heavily use IParty in which case I would use the
39
+ CurrentAttributes way of caching non-eagerly-loaded instances per request with an additional per-request ipcache.
40
+ See documentation for more details.
@@ -0,0 +1,132 @@
1
+ # IParty CLI utility
2
+
3
+ ```
4
+ Usage: iparty <IP|host...> [options]
5
+
6
+ # Application options
7
+ -a, --[no-]all full non-summarized output
8
+ -f, --format <FORMATTER> formatter (pretty|json|off) or template string [default: pretty]
9
+ -l, --language <LANG> limit output to language (or all) [default: en]
10
+ -r, --[no-]resolve resolve hosts and include hostnames in data (requires resolv)
11
+ -o, --only key,deep.key,*country* list of key expressions (grep on full key)
12
+ -e, --except key,deep.key,sub* list of key expressions (grep_v on full key)
13
+ ** matches .*
14
+ * matches [^.]*
15
+ --[no-]stdin read from stdin (one IP per line)
16
+
17
+ # (Custom) actions
18
+ -d, --dispatch ACTION Dispatch given action, you may add your own
19
+ --irb IRB repl with iparty context and helpers
20
+
21
+ # MMDB actions
22
+ --mmdb-status Show mmdb file status
23
+ --mmdb-fetch Fetch missing mmdb-editions
24
+ --mmdb-update Update all mmdb-editions
25
+
26
+ # General options
27
+ -h, --help Shows this help
28
+ -v, --version Shows version and mmdb info (and config with --debug)
29
+ -m, --[no-]monochrome Don't or do colorize output
30
+ --[no-]debug Enable debug, raise exceptions and print config with -v
31
+ --no-rc Do not eval config.rb
32
+
33
+ The current config directory is /Users/chaos/.iparty
34
+ ```
35
+
36
+
37
+
38
+ ## Configuration
39
+
40
+ IParty CLI attempts to load a config file (`config.rb`) in `ENV.fetch("IPARTY_CFGDIR", "~/.iparty")` by default, `--no-rc` will skip this.
41
+
42
+ You can configure application defaults or define custom formatters, actions, or do whatever really. The file will be eval'd in the application context.
43
+
44
+ Also look at the action cookbook in `docs/cli/action_cookbook`.
45
+
46
+ ```ruby
47
+ # Create this file as ~/.iparty/config.rb
48
+ # This file is eval'd in the application object's context after it's initialized!
49
+
50
+
51
+ # For IParty options refer to the IParty documentation.
52
+ # https://github.com/2called-chaos/iparty/blob/master/README.md
53
+
54
+ IParty.config.annotate "1.1.1.1", "1.0.0.1", tags: %i[cloudflare_dns]
55
+ IParty.config.annotate_tag %i[loopback], "127.0.0.1/8", "::1"
56
+
57
+
58
+ # Change defaults (arguments will still override)
59
+ # For CLI options refer to application/options.rb#default_options
60
+ # https://github.com/2called-chaos/iparty/blob/master/lib/iparty/cli/application/options.rb
61
+
62
+ # @opts[:summarize] = false
63
+ # @opts[:except] += [
64
+ # "registered_country",
65
+ # "subdivisions",
66
+ # ]
67
+
68
+
69
+
70
+ # =================
71
+ # = Custom Action =
72
+ # =================
73
+ # Also look at the action cookbook in `docs/cli/action_cookbook`!
74
+
75
+ @optparse.separator("\n# Options for --dispatch ban")
76
+ @optparse.on("--reason REASON", String, "ban reason") {|v| @opts[:ban_reason] = v }
77
+
78
+ # iparty -d ban --reason "they suck" 1.2.3.4 c498:81dd:12bc:b812:a2c4:b003:303d:6707
79
+ def dispatch_ban
80
+ each_address do |ip|
81
+ ipp = IParty(ip, significant: false)
82
+ puts "ban #{ipp} (#{ipp.geo.detailed}) because #{@opts[:reason]}" # @todo ban ip
83
+ end
84
+ end
85
+
86
+
87
+
88
+ # ====================
89
+ # = Custom Formatter =
90
+ # ====================
91
+
92
+ # Descendants of CLI::Formatter are accessible by their ::id (compared `fmt.id === input`)
93
+ # i.e. self.id = "foo" # --format foo
94
+ # i.e. self.id = /^foo$/
95
+ # i.e. self.id = ->{ _1 == "foo" }
96
+ # If no id is specified it will default to the class name (unhandy but accessible)
97
+ # You can also list all formatters with `-v --debug`
98
+
99
+ # The app will call #format for single IPs and #format_all for multiple (or stdin)
100
+ # but you may handle them the same way.
101
+
102
+ class MyFormatter < IParty::CLI::Formatter
103
+ # self.id = "my"
104
+
105
+ # def setup
106
+ # # optional, called at the end of initialize if responds_to?(:setup)
107
+ # end
108
+
109
+ # return an array of put-able things (false will be skipped)
110
+ def format_all ips, **kw, &to_data
111
+ ips.map.with_index {|ip, index| format(ip, index: index, **kw, &to_data) }
112
+ end
113
+
114
+ # return a put-able thing (or false to skip output)
115
+ # to_data will turn an IP into filtered data according to app-args and opts
116
+ def format ip, index: 0, **kw, &to_data
117
+ "[#{index}] #{to_data.call(ip)}"
118
+ end
119
+ end
120
+
121
+ class MyJsonFormatter < IParty::CLI::Formatter::JsonFormatter
122
+ self.id = "my_json"
123
+
124
+ def format ip, index: 0, **kw, &to_data
125
+ if @opts[:foo] == "bar"
126
+ format_all(Array(ip), **kw, &to_data)
127
+ else
128
+ super
129
+ end
130
+ end
131
+ end
132
+ ```
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # within ~/.iparty/config.rb context
4
+
5
+ # iparty -d me
6
+ # iparty --dispatch me
7
+ def dispatch_me
8
+ require "net/http"
9
+
10
+ get_ip = proc do |url|
11
+ Net::HTTP.get(URI(url))
12
+ rescue Socket::ResolutionError
13
+ formatter.colorize? ? c("SOCKET_ERROR", :red) : :SOCKET_ERROR
14
+ rescue StandardError
15
+ formatter.colorize? ? c("ERROR", :red) : :ERROR
16
+ end
17
+
18
+ if the_more_clever_way = true
19
+ @argv << get_ip["https://api.ipify.org"]
20
+ @argv << get_ip["https://api6.ipify.org"]
21
+ dispatch_info
22
+ else
23
+ out << formatter.format("my external ips") do
24
+ onlyexcept_data!(
25
+ ipv4: get_ip["https://api.ipify.org"],
26
+ ipv6: get_ip["https://api6.ipify.org"],
27
+ )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,170 @@
1
+ # IParty
2
+
3
+ ## Configuration
4
+
5
+ You may configure and extend IParty in an initializer or in the init part of your non-rails app.
6
+ For basic usage you only really need MaxMind credentials to download the mmdb files.
7
+
8
+ ```ruby
9
+ defined?(IParty) && IParty.configure do |config|
10
+ config.account_id = config.env_value("MAXMIND_ACCOUNT_ID", nil)
11
+ config.license_key = config.env_value("MAXMIND_LICENSE_KEY", nil)
12
+ end
13
+ ```
14
+
15
+
16
+
17
+ ### Easily extend your custom accessors
18
+
19
+ For more info on extending address or result data look at docs/maxmind_result.md
20
+
21
+ ```ruby
22
+ IParty::MaxMind::Result::Geo.class_eval do
23
+ define_attr(:best_tenant, export: true) { country.iso_code == "DE" ? :de : :us }
24
+ end
25
+
26
+ IParty(request.remote_ip).best_tenant # => :us
27
+ ```
28
+
29
+
30
+
31
+ ### Full config
32
+
33
+ This config, minus the CurrentAttributes cache is default config. For basic usage you only need account_id/license_key.
34
+ Please also take a look at docs/maxmind_result.md for extending the address and geo results.
35
+
36
+ ```ruby
37
+ defined?(IParty) && IParty.configure do |config|
38
+ # If set to false drop last half of v6-addresses as they are insignificant for most applications.
39
+ # The then 64-bit addresses also fit into unsigned bigints allowing for easy range representations.
40
+ # Each IParty::Address can overwrite this with
41
+ # * #ipv6_significant accessors
42
+ # * significant: keyword (affected methods only but including new/initialize)
43
+ config.ipv6_significant = config.env_value("IPARTY_IPV6_SIGNIFICANT", true)
44
+
45
+ # Whether to use the low memory file reader or load mmdb into memory as a whole (see docs/benchmark.md)
46
+ config.eager_load = config.env_value("IPARTY_EAGER_LOAD", false)
47
+
48
+ # Use singleton instances of MaxMind::Database readers.
49
+ # These are lazily initialized so in a threaded environment or with eager load enabled you should pre-init them.
50
+ # Side Effect: You will have to reboot your app for mmdb changes to take effect.
51
+ config.singletons = config.env_value("IPARTY_SINGLETONS", false)
52
+ #config.singletons = {} # lazy load singleton instances, not thread-safe
53
+ #config.singletons = true # eagerly init all editions (eager_load into memory if enabled, thread-safe after init)
54
+
55
+ # alternatively you can do something like this:
56
+ # * singleton DB instances (lazily memoized) per-request (eager load should be disabled)
57
+ # * IParty::Address cache (including their geo lookups)
58
+ if defined?(ActiveSupport::CurrentAttributes)
59
+ class IPartyCache < ActiveSupport::CurrentAttributes
60
+ attribute(:databases, default: {})
61
+
62
+ # cached IParty addresses, i.e. IPartyCache.ips["1.2.3.4"]
63
+ # Be wary of mutating (i.e. mask!), use clones
64
+ attribute(:ips, default: -> { Hash.new{|h, ip| h[ip.to_s] = IParty(ip.to_s) } })
65
+ end
66
+ config.singletons = -> { IPartyCache.databases }
67
+ end
68
+
69
+ # An IP that is used instead of local IPs
70
+ config.local_ip_alias = config.env_value("IPARTY_LOCAL_IP_ALIAS", nil)
71
+
72
+ # MaxMind account_id and license_key aka mirror basic-auth
73
+ config.account_id = config.env_value("MAXMIND_ACCOUNT_ID", nil)
74
+ config.license_key = config.env_value("MAXMIND_LICENSE_KEY", nil)
75
+
76
+ # Mirror to download tar.gz compressed mmdb-files from
77
+ config.mirror = config.env_value("MAXMIND_MIRROR", "https://download.maxmind.com/geoip/databases/:edition/download?suffix=tar.gz")
78
+
79
+ # Editions
80
+ config.editions = config.env_value("MAXMIND_EDITIONS", "GeoLite2-ASN GeoLite2-Country GeoLite2-City") {|v| v.split(/\s+|,\s*/) }
81
+
82
+ # Target directory (for mmdb files, will also create subdirectory for updating)
83
+ # Note: Must be a pathname
84
+ config.directory = config.env_value("IPARTY_DIRECTORY", nil) do |dir|
85
+ if dir && !dir.empty?
86
+ Pathname.new(dir)
87
+ elsif defined?(Rails)
88
+ Rails.root.join("vendor", "maxmind")
89
+ else
90
+ Pathname.new(Dir.tmpdir).join("iparty")
91
+ end
92
+ end
93
+
94
+ # Proc to download mmdb files
95
+ config.url_to_mmdb = proc do |url, dir, config|
96
+ auth = %{-u "#{config.account_id}:#{config.license_key}"} if config.account_id && config.license_key
97
+ curl = %{curl -L -s #{"#{auth} " if auth}"#{url}"}
98
+ tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{dir.to_s.shellescape}}
99
+ system("#{curl} | #{tar}")
100
+ end
101
+
102
+ # --- following is example code and not default behaviour ---
103
+
104
+ # Proc to transform geo result data, by default does nothing.
105
+ # yields
106
+ # data the result hash
107
+ # addr the looked up address (as IParty or IPAddr)
108
+ # result_class the class that later will get instantiated with data
109
+ config.transform_result = proc do |data, addr, result_class|
110
+ if result_class != IParty::MaxMind::Result::Asn && addr.loopback?
111
+ data[:country] ||= { iso_code: "ZZ", names: { en: "Local" } }
112
+ data[:continent] ||= { code: "ZZ", names: { en: "Local" } }
113
+ end
114
+ end
115
+
116
+ # For more info on extending look at docs/maxmind_result.md
117
+ IParty::Address.define_method(:detailed) do |*args|
118
+ "#{to_s} -- #{geo.detailed} -- #{asn.detailed}"
119
+ end
120
+
121
+ # You may want to rely on rake task and/or ensure on app boot?
122
+ # fetch_when can be
123
+ # * :always
124
+ # * :missing
125
+ # * (Numeric) maxAge (i.e. (int)seconds or (AS::Duration)14.days)
126
+ IParty.fetch_db_files!(:missing, verbose: true)
127
+ end
128
+ ```
129
+
130
+
131
+
132
+ ### Annotations
133
+
134
+ You can annotate IPs or networks with arbitrary data whereas `tags` has special behaviour (they merge).
135
+ IParty CLI will display `name` along `tags` in the summarized view.
136
+
137
+ ```ruby
138
+ defined?(IParty) && IParty.configure do |config|
139
+ # annotate(*addresses, **data) -- merges data and tags
140
+ IParty.config.annotate "1.0.0.0/8", tags: %i[foo]
141
+ IParty.config.annotate "1.2.3.4", tags: %i[bar]
142
+ IParty.config.annotate "127.0.0.1/8", "::1", name: "loopback", tags: %i[localhost]
143
+
144
+ # annotate_tag(tags, *addresses) -- merges tags
145
+ IParty.config.annotate_tag :one, "1.0.0.0/8"
146
+ IParty.config.annotate_tag %i[local ipv4], "127.0.0.1/8"
147
+ IParty.config.annotate_tag %i[local], "127.0.0.1/8", "::1"
148
+ end
149
+
150
+ IParty("127.1.2.3").annotations # => {:name=>"loopback", :tags=>[:localhost, :local, :ipv4]}
151
+ IParty("127.1.2.3").tag?(:local) # => true
152
+ IParty("69.4.20.0").annotations # => nil
153
+ IParty("69.4.20.0").tag?(:local) # => false
154
+ ```
155
+
156
+
157
+
158
+ ### Check configuration
159
+
160
+ You can use the shipped rake tasks to check your effective configuration.
161
+
162
+ ```
163
+ # shows effective IParty config (including license_key)
164
+ rake iparty:config
165
+ rake iparty:config[inspect]
166
+ rake iparty:config[json]
167
+
168
+ # check mmdb file status
169
+ rake iparty:status
170
+ ```
@@ -0,0 +1,22 @@
1
+ # IParty
2
+
3
+ ## Exceptions
4
+
5
+ These are the exceptions you might encounter using this gem. Only IParty exceptions are added by this gem, the other ones are for your reference.
6
+
7
+ ```ruby
8
+ StandardError
9
+ SystemCallError
10
+ Errno::*
11
+ ArgumentError
12
+ IPAddr::Error
13
+ IPAddr::AddressFamilyError
14
+ IPAddr::InvalidAddressError
15
+ IPAddr::InvalidPrefixError
16
+ IParty::Error
17
+ IParty::MaxMind::Database::Error
18
+ IParty::MaxMind::Database::InvalidFileFormatError
19
+ ```
20
+
21
+ IParty::Address will generally raise IPAddr exceptions.
22
+ To guard against IP errors, if you do not already rescue broader, you typically want to rescue IPAddr::Error and IParty::Error.
@@ -0,0 +1,82 @@
1
+ # IParty
2
+
3
+ ## MaxMind Result
4
+
5
+ Result is_a?(Hash) with some magic sprinkled in, party!
6
+
7
+ * Defined attributes can be retrieved via method(chaining)
8
+ * Even without ActiveSupport core_ext, Result(Hash) and Subdivisions(Array) implements blank?/present?/presence (values do not however)
9
+ * Dynamically compares
10
+ * result == Numeric => geoname_id
11
+ * result == String => name.en
12
+ * Dynamically inquires
13
+ * result.us? (2-char checks against code on continent or iso_code otherwise)
14
+ * result.foo? (!2-char checks against english name (lowercased, underscored))
15
+
16
+
17
+
18
+ ### Class Structure
19
+
20
+ ```ruby
21
+ IParty::MaxMind::Result < Hash
22
+ Asn < Result
23
+ Geo < Result
24
+ GeoCountry < Geo
25
+ GeoCity < Geo
26
+
27
+ Subdivisions < Array
28
+ Location < Result
29
+ NamedLocation < Result
30
+ Continent < NamedLocation
31
+ Country < NamedLocation
32
+ City < NamedLocation
33
+ Subdivision < NamedLocation
34
+ Postal < Result
35
+ Traits < Result
36
+ ```
37
+
38
+ Note: There's little reason to define attributes on GeoCountry or GeoCity since by default country is the fallback of city lookups.
39
+ You should therefore define attributes on Geo and make sure they work with nil data to preserve safe navigation access.
40
+
41
+
42
+
43
+ ### Add methods
44
+
45
+ You can (in an initializer) add your own representations and helpers to the result classes or address class.
46
+
47
+ ```ruby
48
+ # define_attr is a helper to define accessors on the result data with the following synopsis:
49
+ # define_attr(
50
+ # name, # The method name
51
+ # attribute = name, # The attribute to dig
52
+ # type: nil, # sugar for transform ->(v) { type.new(v) }
53
+ # aliases: nil, # Symbol or Array[Symbol] of alias method names
54
+ # memoize: nil, # Memoize the value (true/false, nil: true if transform block is given)
55
+ # export: nil, # True, Symbol or Array[Symbol] of method names to export to Address (def_delegate)
56
+ # # Note: Only works on Result/Geo(Country/City)/Asn
57
+ # &transform # The value to return, if omitted it's the digged value
58
+ # )
59
+
60
+ IParty::MaxMind::Result::Geo.define_attr(:best_tenant, export: true) { continent.eu? ? :eu : :us }
61
+ IParty::MaxMind::Result::Geo.define_attr(:short, export: true) { [continent.code, country.name].compact.join(" / ") }
62
+ IParty::MaxMind::Result::Asn.define_attr(:short, export: :asn_short) { "AS#{number} #{organization}" }
63
+ IParty::Address.define_method(:detailed) { "#{to_s} -- #{geo.detailed} -- #{asn_short}" }
64
+ # IParty(ip).short/asn_short/detailed
65
+
66
+ # Delegate geo/asn methods so you can do `ip.METHOD` instead of `ip.geo.METHOD`
67
+ IParty::Address.def_delegators(:geo, *%i[registered_country])
68
+ IParty::Address.def_delegators(:asn, *%i[network])
69
+ # Note that some are already exported, namely:
70
+ # * city
71
+ # * continent
72
+ # * country
73
+ # * in_european_union?
74
+ # * latitude
75
+ # * longitude
76
+ # * time_zone
77
+ # * postal_code
78
+ # * (asn) autonomous_system_network
79
+ # * (asn) autonomous_system_number
80
+ # * (asn) autonomous_system_organization
81
+ # * (asn) autonomous_system_detailed
82
+ ```
@@ -0,0 +1,84 @@
1
+ # IParty
2
+
3
+ ## Download of mmdb-files
4
+
5
+ Note: You need to configure valid credentials and/or mirror, see Configuration.
6
+
7
+
8
+ ---
9
+
10
+
11
+ ### Download method
12
+
13
+ By default IParty will use this proc (and you may change it) to turn a URL (to a gz compressed mmdb-file) to a mmdb-file inside the temporary directory.
14
+
15
+ Note: This requires a unix-oid environment with curl, tar and gzip available.
16
+
17
+ ```ruby
18
+ IParty::MaxMind.download_proc = proc do |url, temp_dir|
19
+ auth = %{-u "#{account_id}:#{license_key}"} if account_id && license_key
20
+ curl = %{curl -L -s #{"#{auth} " if auth}"#{url}"}
21
+ tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{temp_dir.to_s.shellescape}}
22
+ system("#{curl} | #{tar}")
23
+ end
24
+ ```
25
+
26
+
27
+ The file fetching process will
28
+
29
+ * download all editions (`IParty::MaxMind.editions`) in sequence, extract them flat into the temporary directory
30
+ * the MaxMind account_id and license_key are HTTP Basic Auth username and password (this also works for your mirror)
31
+ * move all mmdb files inside the temp directory into the data directory
32
+ * this is essentially an atomic-ish update and will not nuke your existing files on error
33
+ * files that failed to download or are no longer requested will remain (no cleanup)
34
+ * remove the temp directory
35
+
36
+
37
+
38
+ ### Download
39
+
40
+ #### Ruby
41
+
42
+ In your application startup, or in a rake task:
43
+
44
+ ```ruby
45
+ # only download missing or expired files
46
+ IParty.fetch_db_files!(14 * 24 * 60 * 60) # or 14.days
47
+
48
+ # only download missing files, verbose will print files downloaded to stderr
49
+ IParty.fetch_db_files!(:missing, verbose: true)
50
+
51
+ # always download a fresh copy
52
+ IParty.fetch_db_files! # (:always)
53
+ ```
54
+
55
+
56
+ #### Rake tasks
57
+
58
+ You may also use the shipped rake tasks for this purpose:
59
+
60
+ ```ruby
61
+ rake iparty:fetch
62
+ rake iparty:fetch[14.days] # or int-seconds without AS
63
+ rake iparty:update
64
+ ```
65
+
66
+ There are also these rake tasks
67
+ ```ruby
68
+ # prints mmdb file status, expiry check if max_age is provided
69
+ # will exit(1) if any file is missing, invalid or expired
70
+ rake iparty:status
71
+ rake iparty:status[14.days]
72
+
73
+ # shows effective IParty config (including license_key)
74
+ rake iparty:config
75
+ rake iparty:config[inspect]
76
+ rake iparty:config[json]
77
+ ```
78
+
79
+ Outside of Rails you need to register them manually in your Rakefile:
80
+
81
+ ```ruby
82
+ require "iparty/rake_task"
83
+ IParty::RakeTask.new
84
+ ```
@@ -122,6 +122,8 @@ module IParty
122
122
  end
123
123
 
124
124
  def annotations
125
+ return @_annotations if defined?(@_annotations)
126
+
125
127
  result = {}
126
128
  IParty.config.annotations&.each do |ipp, adata|
127
129
  next unless ipp.include?(self)
@@ -129,7 +131,13 @@ module IParty
129
131
  result.merge!(adata.merge(tags: result.fetch(:tags, []) | adata.fetch(:tags, [])))
130
132
  end
131
133
 
132
- result unless result.empty?
134
+ @_annotations = result.empty? ? nil : result
135
+ end
136
+
137
+ def tag? tag
138
+ return false unless tags = annotations&.fetch(:tags, nil)
139
+
140
+ tags.include?(tag)
133
141
  end
134
142
 
135
143
  def as_json
data/lib/iparty/config.rb CHANGED
@@ -12,6 +12,7 @@ module IParty
12
12
  :local_ip_alias,
13
13
  :ipv6_significant,
14
14
  :url_to_mmdb,
15
+ :transform_result,
15
16
  :annotations,
16
17
  keyword_init: true,
17
18
  ) do
@@ -135,6 +136,18 @@ module IParty
135
136
  tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{dir.to_s.shellescape}}
136
137
  system("#{curl} | #{tar}")
137
138
  end,
139
+
140
+ # Proc to transform geo result data, by default does nothing.
141
+ # yields
142
+ # data the result hash
143
+ # addr the looked up address (as IParty or IPAddr)
144
+ # result_class the class that later will get instantiated with data
145
+ # transform_result: proc do |data, addr, result_class|
146
+ # if addr.loopback?
147
+ # data[:country] ||= { iso_code: "ZZ", names: { en: "Local" } }
148
+ # data[:continent] ||= { code: "ZZ", names: { en: "Local" } }
149
+ # end
150
+ # end,
138
151
  )
139
152
  end
140
153
  end
@@ -49,8 +49,8 @@ module IParty
49
49
  addr = IPAddr.new(addr) unless addr.is_a?(IPAddr)
50
50
  addr = IParty.config.local_ip_alias if IParty.config.local_ip_alias && addr.loopback?
51
51
  addr = IPAddr.new(addr) unless addr.is_a?(IPAddr)
52
- addr = addr.ipv4_compat if addr.ipv4?
53
- long = addr.is_a?(IParty::Address) ? addr.to_i(significant: true) : addr.to_i
52
+ compat_addr = addr.ipv4? ? addr.ipv4_compat : addr
53
+ long = compat_addr.is_a?(IParty::Address) ? compat_addr.to_i(significant: true) : compat_addr.to_i
54
54
  node_no = 0
55
55
 
56
56
  (@start_idx...128).each do |i|
@@ -62,7 +62,12 @@ module IParty
62
62
  elsif next_node_no >= @node_count
63
63
  pos = (next_node_no - @node_count) - DATA_SECTION_SEPARATOR_SIZE
64
64
  result = decode(@data_section_start, pos)[1]
65
- result[:network] = cidr_from_long(long, i) unless result.empty?
65
+ result[:network] = if !result.empty?
66
+ cidr_from_long(long, i)
67
+ elsif addr.loopback?
68
+ @family == Socket::AF_INET6 ? "::1/128" : "127.0.0.0/8"
69
+ end
70
+ IParty.config.transform_result&.call(result, addr, result_class)
66
71
  return result_class.new(result)
67
72
  else
68
73
  node_no = next_node_no
@@ -41,14 +41,14 @@ module IParty
41
41
  define_attr(:latitude)
42
42
  define_attr(:longitude)
43
43
  define_attr(:metro_code)
44
- define_attr(:time_zone)
44
+ define_attr(:time_zone, aliases: :timezone)
45
45
  end
46
46
 
47
47
  class NamedLocation < Result
48
- define_attr(:code)
48
+ define_attr(:code, memoize: false) {|v| v || self[:iso_code] }
49
49
  define_attr(:geoname_id)
50
50
  define_attr(:is_in_european_union, aliases: :in_european_union?)
51
- define_attr(:iso_code)
51
+ define_attr(:iso_code, memoize: false) {|v| v || self[:code] }
52
52
  define_attr(:names, type: Result)
53
53
 
54
54
  def name(locale = :en, fallback_locale: :en)
@@ -75,7 +75,7 @@ module IParty
75
75
 
76
76
  # dynamic inquiry
77
77
  define_attr(:inquire_on_name) { name&.downcase&.tr(" ", "_") }
78
- define_attr(:inquire_on_code) { (iso_code || code)&.downcase }
78
+ define_attr(:inquire_on_code) { iso_code&.downcase }
79
79
 
80
80
  def respond_to_missing? method_name, include_private = false
81
81
  method_name.end_with?("?") || super
@@ -170,7 +170,7 @@ module IParty
170
170
  define_attr(:latitude, memoize: false, export: true) { location.latitude }
171
171
  define_attr(:longitude, memoize: false, export: true) { location.longitude }
172
172
  define_attr(:metro_code, memoize: false) { location.metro_code }
173
- define_attr(:time_zone, memoize: false, export: true) { location.time_zone }
173
+ define_attr(:time_zone, memoize: false, aliases: :timezone, export: true) { location.time_zone }
174
174
  define_attr(:postal_code, aliases: :zip, export: true, memoize: false) { postal.code }
175
175
 
176
176
  define_attr(:detailed_parts, memoize: false) { [continent.code, country.name, city.name].compact }
@@ -111,7 +111,14 @@ module IParty
111
111
  puts IParty.config.inspect
112
112
  else
113
113
  IParty.config.each_pair do |key, value|
114
- puts "#{key.to_s.rjust(16)}: #{value.inspect}"
114
+ if key == :annotations && value
115
+ puts "#{key.to_s.rjust(16)}:"
116
+ value.each do |ipp, adata|
117
+ puts "#{"".rjust(16)} #{ipp.to_cidr}: #{adata.inspect}"
118
+ end
119
+ else
120
+ puts "#{key.to_s.rjust(16)}: #{value.inspect}"
121
+ end
115
122
  end
116
123
  end
117
124
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IParty
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iparty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Pachnit
@@ -91,6 +91,13 @@ files:
91
91
  - LICENSE.txt
92
92
  - README.md
93
93
  - Rakefile
94
+ - docs/benchmark.md
95
+ - docs/cli/README.md
96
+ - docs/cli/action_cookbook/show_my_remote_ips.rb
97
+ - docs/configuration.md
98
+ - docs/exceptions.md
99
+ - docs/maxmind_result.md
100
+ - docs/mmdb_download.md
94
101
  - exe/iparty
95
102
  - lib/iparty.rb
96
103
  - lib/iparty/address.rb