pico_phone 0.8.3-aarch64-linux → 0.8.5-aarch64-linux
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 +4 -4
- data/README.md +561 -0
- data/lib/pico_phone/3.1/pico_phone.so +0 -0
- data/lib/pico_phone/3.2/pico_phone.so +0 -0
- data/lib/pico_phone/3.3/pico_phone.so +0 -0
- data/lib/pico_phone/3.4/pico_phone.so +0 -0
- data/lib/pico_phone/4.0/pico_phone.so +0 -0
- data/lib/pico_phone/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7c595f4f55a3be9f1cfb723ec5114fb98bd71a651ffadc7558faf35070d0523
|
|
4
|
+
data.tar.gz: 0e3b33720bce4842e22bfabcfecc10df3fb199c0023143541b558d3d7fea8508
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bfa31b236a879ea543b6376c6cd805ad227ba0cac86806cf65cc4fee741ce307510cdf6514bf5b57f6b02660a9a529257c4500ebcd65a0bee6299261c483f04
|
|
7
|
+
data.tar.gz: 5d421a51557c6ced42063e9d87e52930fecb93984209fd0825c8cb936793145fb1992ab04949a8d84f8d242e189fd3c0906071584aefde10845708db3c3481d8
|
data/README.md
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
# PicoPhone
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gjack/pico_phone/actions/workflows/ci.yml)
|
|
4
|
+
[](https://badge.fury.io/rb/pico_phone)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A thin Ruby wrapper around Google's [libphonenumber](https://github.com/google/libphonenumber) C++ library, built with [Rice](https://github.com/ruby-rice/rice). It uses the same engine as Android's dialer for parsing, validating, and formatting phone numbers in any country.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'pico_phone'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install directly:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
gem install pico_phone
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Pre-compiled native gems are available for `arm64-darwin` (Apple Silicon Macs), `x86_64-linux` and `aarch64-linux` (glibc 2.34 or newer: Debian 12+, Ubuntu 22.04+, and similar), and `x86_64-linux-musl` and `aarch64-linux-musl` (Alpine and similar), for Ruby 3.1–3.4 and 4.0. On these platforms and Ruby versions, no system libraries or compiler are required — Bundler will select the right binary automatically.
|
|
24
|
+
|
|
25
|
+
RubyGems and Bundler pick a gem by CPU, OS and libc name only, so on a glibc distro older than the versions above (for example RHEL/Alma/Rocky 9 or Amazon Linux 2023) they will still select the precompiled gem, which then fails to load. Force the source build there instead — `gem install pico_phone --platform ruby`, or `bundle config set force_ruby_platform true` for Bundler — after installing libphonenumber as described below.
|
|
26
|
+
|
|
27
|
+
On other platforms the gem compiles from source and requires libphonenumber:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
# macOS
|
|
31
|
+
brew install libphonenumber
|
|
32
|
+
|
|
33
|
+
# Ubuntu/Debian
|
|
34
|
+
sudo apt-get install libphonenumber-dev libicu-dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Checking if a phone number is valid
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
PicoPhone.valid?("+15102745656") #true
|
|
43
|
+
PicoPhone.valid_for_country?("(510) 274-5556", "BR") #false
|
|
44
|
+
|
|
45
|
+
phone = PicoPhone.parse("(510) 274-5556", "BR")
|
|
46
|
+
phone.valid? #false
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Checking if a phone number is possible
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
PicoPhone.possible?("+15102745556") #true
|
|
53
|
+
PicoPhone.possible?("It's my number") #false
|
|
54
|
+
|
|
55
|
+
phone = PicoPhone.parse("-245")
|
|
56
|
+
phone.possible? #false
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`possible_with_reason` returns a symbol explaining why a number is or is not possible, which is more useful than a boolean when generating validation messages.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
phone = PicoPhone.parse("+15102745656", "US")
|
|
63
|
+
phone.possible_with_reason # :is_possible
|
|
64
|
+
|
|
65
|
+
phone = PicoPhone.parse("123", "US")
|
|
66
|
+
phone.possible_with_reason # :too_short
|
|
67
|
+
|
|
68
|
+
phone = PicoPhone.parse("123456789000", "US")
|
|
69
|
+
phone.possible_with_reason # :too_long
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Possible return values are `:is_possible`, `:is_possible_local_only`, `:too_short`, `:too_long`, `:invalid_country_code`, and `:invalid_length`.
|
|
73
|
+
|
|
74
|
+
### Formatting a phone number
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
phone = PicoPhone.parse("5102745656", "US")
|
|
78
|
+
|
|
79
|
+
phone.national # (510) 274-5656
|
|
80
|
+
phone.international # +1 510-274-5656
|
|
81
|
+
phone.e164 # +15102745656
|
|
82
|
+
phone.raw_national # 5102745656
|
|
83
|
+
phone.raw_international # 15102745656
|
|
84
|
+
|
|
85
|
+
phone = PicoPhone.parse("0435582008", "AU")
|
|
86
|
+
|
|
87
|
+
phone.national # 0435 582 008
|
|
88
|
+
phone.international # +61 435 582 008
|
|
89
|
+
phone.e164 # +61435582008
|
|
90
|
+
phone.raw_national # 435582008
|
|
91
|
+
phone.raw_international # 61435582008
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Country codes, area codes, and local number
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
phone = PicoPhone.parse("5102745656", "US")
|
|
98
|
+
|
|
99
|
+
phone.country_code # 1
|
|
100
|
+
phone.area_code # "510"
|
|
101
|
+
phone.local_number # "2745656"
|
|
102
|
+
|
|
103
|
+
phone = PicoPhone.parse("0435582008", "AU")
|
|
104
|
+
|
|
105
|
+
phone.country_code # 61
|
|
106
|
+
phone.area_code # "" (AU mobile numbers have no geographical area code)
|
|
107
|
+
phone.local_number # "435582008" (full national number when there is no area code)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`country_calling_code` returns the international calling code for a region code, without needing to parse a number first. Returns `0` for unknown or invalid region codes.
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
PicoPhone.country_calling_code("US") # 1
|
|
114
|
+
PicoPhone.country_calling_code("FR") # 33
|
|
115
|
+
PicoPhone.country_calling_code("BR") # 55
|
|
116
|
+
PicoPhone.country_calling_code("CA") # 1 (NANP — same calling code as US)
|
|
117
|
+
PicoPhone.country_calling_code("XX") # 0 (unknown region)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Truncating too-long numbers
|
|
121
|
+
|
|
122
|
+
`truncate` removes trailing digits from a number that is too long until a valid number is found. Returns a new `PhoneNumber` on success, or `nil` when the number was already valid or no valid truncation exists. The receiver is never mutated.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
phone = PicoPhone.parse("+151027456560000", "US")
|
|
126
|
+
phone.valid? # false
|
|
127
|
+
|
|
128
|
+
truncated = phone.truncate
|
|
129
|
+
truncated.e164 # "+15102745656"
|
|
130
|
+
truncated.valid? # true
|
|
131
|
+
|
|
132
|
+
PicoPhone.parse("+15102745656").truncate # nil (already valid)
|
|
133
|
+
PicoPhone.parse("garbage").truncate # nil (cannot be truncated)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Geographic description
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
phone = PicoPhone.parse("5102745656", "US")
|
|
140
|
+
|
|
141
|
+
phone.geo_name # "California"
|
|
142
|
+
phone.geo_name("de") # description in another language, where available
|
|
143
|
+
|
|
144
|
+
phone = PicoPhone.parse("0435582008", "AU")
|
|
145
|
+
|
|
146
|
+
phone.geo_name # "Australia" (falls back to the country name when no finer description exists)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Backed by libphonenumber's own offline geocoder, not a separate re-implementation — it loads its area-code data lazily, one region/language file at a time, so looking up a number doesn't pull the whole world's geocoding data into memory (see the memory benchmark in [Benchmarks](#benchmarks)).
|
|
150
|
+
|
|
151
|
+
### Carrier lookup
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
phone = PicoPhone.parse("6001234567", "IN")
|
|
155
|
+
|
|
156
|
+
phone.carrier_name # "Reliance Jio"
|
|
157
|
+
phone.carrier_name("de") # carrier name in another language, where available
|
|
158
|
+
|
|
159
|
+
phone = PicoPhone.parse("5102745656", "US")
|
|
160
|
+
|
|
161
|
+
phone.carrier_name # "" (no carrier mapping for this prefix -- number portability makes carrier-by-prefix unreliable in many countries, including the US)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`carrier_name` returns the carrier the number was **originally allocated to**, not necessarily its current carrier — in countries that support mobile number portability, a number may have since been ported away from that carrier. This matches libphonenumber's own Java carrier mapper, which documents the same caveat verbatim ("the carrier name is the one the number was originally allocated to, however if the country supports mobile number portability the number might not belong to the returned carrier anymore").
|
|
165
|
+
|
|
166
|
+
libphonenumber has no upstream C++ port of its carrier mapper (only Java), so this is a first-party pico_phone implementation reusing the same lazy per-(country code, language) loading machinery as `geo_name` above, rather than a Ruby-level reimplementation. Unlike `geo_name`, there's no fallback to a country-level description when a prefix has no carrier data — an unmapped number just returns an empty string, matching phonelib's `carrier` semantics.
|
|
167
|
+
|
|
168
|
+
### Timezone lookup
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
phone = PicoPhone.parse("+12082123456")
|
|
172
|
+
|
|
173
|
+
phone.timezones # ["America/Boise", "America/Los_Angeles"]
|
|
174
|
+
|
|
175
|
+
phone = PicoPhone.parse("6001234567", "IN")
|
|
176
|
+
|
|
177
|
+
phone.timezones # ["Asia/Calcutta"]
|
|
178
|
+
|
|
179
|
+
phone = PicoPhone.parse("+33612345678")
|
|
180
|
+
|
|
181
|
+
phone.timezones # ["Europe/Paris"]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`timezones` returns an array of [IANA time zone identifiers](https://www.iana.org/time-zones) for the number's prefix. A number can map to more than one timezone when its prefix covers a geographic area that spans multiple zones — NANP area codes in particular span many US time zones. The method returns an empty array when no mapping exists for the number's prefix.
|
|
185
|
+
|
|
186
|
+
libphonenumber has no upstream C++ port of its timezone mapper (only Java), so this is a first-party pico_phone implementation using the same prefix-lookup machinery (`AreaCodeMap`) as `geo_name` and `carrier_name`. The timezone table is small enough (~86KB on disk) to load once at first call rather than lazily per-region — the initial load cost is negligible.
|
|
187
|
+
|
|
188
|
+
### Checking validity for a specific country on a parsed number
|
|
189
|
+
|
|
190
|
+
The module-level `valid_for_country?` accepts a raw string. The same check is also available as an instance method once a number has been parsed.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
phone = PicoPhone.parse("+15102745656")
|
|
194
|
+
|
|
195
|
+
phone.valid_for_country?("US") # true
|
|
196
|
+
phone.valid_for_country?("AU") # false
|
|
197
|
+
phone.invalid_for_country?("US") # false
|
|
198
|
+
phone.invalid_for_country?("AU") # true
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Original input and string conversion
|
|
202
|
+
|
|
203
|
+
`original` returns the input exactly as it was passed to `parse`, regardless of whether parsing succeeded. `to_s` returns the e164 format for a valid number, and falls back to the original input for an invalid one.
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
phone = PicoPhone.parse("5102745656", "US")
|
|
207
|
+
phone.original # "5102745656"
|
|
208
|
+
phone.to_s # "+15102745656"
|
|
209
|
+
|
|
210
|
+
phone = PicoPhone.parse("not a number")
|
|
211
|
+
phone.original # "not a number"
|
|
212
|
+
phone.to_s # "not a number"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Additional formatting options
|
|
216
|
+
|
|
217
|
+
`format_in_original_format` returns the number formatted in the same style it was originally entered — international if a `+` prefix was used, national otherwise.
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
PicoPhone.parse("+15102745656", "US").format_in_original_format # "+1 510-274-5656"
|
|
221
|
+
PicoPhone.parse("5102745656", "US").format_in_original_format # "(510) 274-5656"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`out_of_country_format(region)` returns the dialing string needed to reach the number from a given country. Within shared-prefix regions such as NANP no international prefix is prepended; from other regions the appropriate dialing prefix for the calling country is used.
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
phone = PicoPhone.parse("+15102745656", "US")
|
|
228
|
+
|
|
229
|
+
phone.out_of_country_format("US") # "1 (510) 274-5656"
|
|
230
|
+
phone.out_of_country_format("GB") # "00 1 510-274-5656"
|
|
231
|
+
phone.out_of_country_format("AU") # "0011 1 510-274-5656"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`mobile_dialing_format(region)` returns the most convenient representation for dialing the number from a mobile device in the given region, typically the full international format with separators.
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
phone = PicoPhone.parse("+15102745656", "US")
|
|
238
|
+
|
|
239
|
+
phone.mobile_dialing_format("US") # "+1 510-274-5656"
|
|
240
|
+
phone.mobile_dialing_format("GB") # "+1 510-274-5656"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Comparing phone numbers
|
|
244
|
+
|
|
245
|
+
`number_match` compares two phone number strings and returns how closely they match. E.164 input gives the most precise result; national-format strings without a country code may return `:nsn_match` where a full E.164 comparison would return `:exact_match`.
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
PicoPhone.number_match("+15102745656", "+15102745656") # :exact_match
|
|
249
|
+
PicoPhone.number_match("+15102745656", "5102745656") # :nsn_match (no country code in second arg)
|
|
250
|
+
PicoPhone.number_match("+15102745656", "2745656") # :short_nsn_match
|
|
251
|
+
PicoPhone.number_match("+15102745656", "+61435582008") # :no_match
|
|
252
|
+
PicoPhone.number_match("+15102745656", "garbage") # :invalid_number
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`match_type` is the instance-method equivalent. It accepts either a string or a `PhoneNumber`. When passed a `PhoneNumber`, the country code already embedded in the parsed object is used, which gives `:exact_match` where a bare national-format string would give `:nsn_match`.
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
phone = PicoPhone.parse("+15102745656")
|
|
259
|
+
|
|
260
|
+
phone.match_type("+15102745656") # :exact_match
|
|
261
|
+
phone.match_type(PicoPhone.parse("5102745656", "US")) # :exact_match (country code resolved at parse time)
|
|
262
|
+
phone.match_type("5102745656") # :nsn_match (string has no country code)
|
|
263
|
+
phone.match_type("+61435582008") # :no_match
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Possible return values are `:exact_match`, `:nsn_match`, `:short_nsn_match`, `:no_match`, and `:invalid_number`.
|
|
267
|
+
|
|
268
|
+
### Finding phone numbers in text
|
|
269
|
+
|
|
270
|
+
`find_numbers` scans an arbitrary block of text and returns every phone number found, along with its position and the raw substring that was matched. It uses libphonenumber's `PhoneNumberMatcher` internally.
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
matches = PicoPhone.find_numbers("Call me at +1 425 882-8080 or (650) 253-0000 for details.", "US")
|
|
274
|
+
|
|
275
|
+
matches.size # 2
|
|
276
|
+
matches.first.raw_string # "+1 425 882-8080"
|
|
277
|
+
matches.first.start # 11
|
|
278
|
+
matches.first.end_index # 26
|
|
279
|
+
matches.first.number # #<PicoPhone::PhoneNumber>
|
|
280
|
+
matches.first.number.e164 # "+14258828080"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Each element in the returned array is a `PicoPhone::PhoneNumberMatch` with four methods:
|
|
284
|
+
|
|
285
|
+
| Method | Returns |
|
|
286
|
+
|--------|---------|
|
|
287
|
+
| `start` | Byte offset of the match start in the searched text |
|
|
288
|
+
| `end_index` | Exclusive byte offset of the match end (`text[start...end_index]` is the match) |
|
|
289
|
+
| `raw_string` | The matched substring exactly as it appears in the text |
|
|
290
|
+
| `number` | A fully-parsed `PicoPhone::PhoneNumber` |
|
|
291
|
+
|
|
292
|
+
The `leniency` keyword argument controls how strictly a candidate is required to look like a phone number before being included. Defaults to `:valid`.
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
# :possible — accept any number-like sequence with a plausible digit count
|
|
296
|
+
PicoPhone.find_numbers(text, "US", leniency: :possible)
|
|
297
|
+
|
|
298
|
+
# :valid (default) — must be a valid number for its region
|
|
299
|
+
PicoPhone.find_numbers(text, "US", leniency: :valid)
|
|
300
|
+
|
|
301
|
+
# :strict_grouping — must also be formatted in a regionally plausible grouping
|
|
302
|
+
PicoPhone.find_numbers(text, "US", leniency: :strict_grouping)
|
|
303
|
+
|
|
304
|
+
# :exact_grouping — must match exactly how libphonenumber would format it
|
|
305
|
+
PicoPhone.find_numbers(text, "US", leniency: :exact_grouping)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
> **Note:** Some Linux distributions ship a `libphonenumber` system package compiled with
|
|
309
|
+
> `USE_ALTERNATE_FORMATS=OFF`, which disables alternate-format pattern recognition used by
|
|
310
|
+
> `find_numbers`. If the method returns fewer matches than expected on Linux, building with
|
|
311
|
+
> `PICO_PHONE_NATIVE_BUILD=1` uses the vendored library, which always enables alternate formats.
|
|
312
|
+
|
|
313
|
+
### Finding possible or valid countries for a phone number
|
|
314
|
+
|
|
315
|
+
A calling code is not always 1:1 with a country. `+1` covers the US, Canada, and around 20 Caribbean territories. `+7` covers both Russia and Kazakhstan. These methods let you find which countries a given number could belong to.
|
|
316
|
+
|
|
317
|
+
`possible_countries` uses a lenient check: for unambiguous calling codes (e.g. `+33` for France) it only requires the number to be the right length. For ambiguous calling codes it narrows the list down using full pattern validation, since length alone cannot distinguish between candidates.
|
|
318
|
+
|
|
319
|
+
`valid_countries` always applies full pattern validation and returns only the regions where the number is genuinely valid.
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
PicoPhone.possible_countries("+15102745656") # ["US"]
|
|
323
|
+
PicoPhone.possible_countries("+12423570000") # ["BS"] (Bahamas, also a NANP number)
|
|
324
|
+
PicoPhone.possible_countries("+78005553535") # ["RU", "KZ"]
|
|
325
|
+
|
|
326
|
+
PicoPhone.valid_countries("+15102745656") # ["US"]
|
|
327
|
+
PicoPhone.valid_countries("+78005553535") # ["RU", "KZ"]
|
|
328
|
+
|
|
329
|
+
# A number that is the right length for France but fails pattern validation
|
|
330
|
+
PicoPhone.possible_countries("+33000000000") # ["FR"]
|
|
331
|
+
PicoPhone.valid_countries("+33000000000") # []
|
|
332
|
+
|
|
333
|
+
# Both methods return [] for unparseable input
|
|
334
|
+
PicoPhone.possible_countries("not a number") # []
|
|
335
|
+
|
|
336
|
+
phone = PicoPhone.parse("+15102745656")
|
|
337
|
+
phone.possible_countries # ["US"]
|
|
338
|
+
phone.valid_countries # ["US"]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Number characteristics
|
|
342
|
+
|
|
343
|
+
`geographical?` returns true for fixed-line numbers tied to a geographic area code. Mobile, toll-free, and other non-geographic number types return false.
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
PicoPhone.parse("+15102745656", "US").geographical? # true (fixed-line with area code)
|
|
347
|
+
PicoPhone.parse("+61435582008", "AU").geographical? # false (AU mobile)
|
|
348
|
+
PicoPhone.parse("+18005551234", "US").geographical? # false (toll-free)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`can_be_internationally_dialled?` returns false for numbers that can only be reached from within their own country.
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
PicoPhone.parse("+15102745656", "US").can_be_internationally_dialled? # true
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Supported types for a region
|
|
358
|
+
|
|
359
|
+
`supported_types_for_region` returns the phone number types that actually exist in a given country. The set of types varies significantly between regions.
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
PicoPhone.supported_types_for_region("US")
|
|
363
|
+
# => [:fixed_line, :mobile, :toll_free, :premium_rate, :personal_number]
|
|
364
|
+
|
|
365
|
+
PicoPhone.supported_types_for_region("AU")
|
|
366
|
+
# => [:fixed_line, :mobile, :toll_free, :premium_rate, :shared_cost, :voip, :pager]
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Example numbers
|
|
370
|
+
|
|
371
|
+
`example_number` returns a valid example `PhoneNumber` instance for a region. `example_number_for_type` returns an example for a specific number type. Both return a fully parsed object, so all the usual methods are available on the result.
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
PicoPhone.example_number("AU").e164 # "+61212345678"
|
|
375
|
+
PicoPhone.example_number("AU").national # "02 1234 5678"
|
|
376
|
+
|
|
377
|
+
PicoPhone.example_number_for_type("US", :toll_free).e164 # "+18002345678"
|
|
378
|
+
PicoPhone.example_number_for_type("AU", :mobile).e164 # "+61412345678"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
> **Note:** Some Linux distributions ship a `libphonenumber` system package compiled with
|
|
382
|
+
> `USE_LITE_METADATA=ON`, which strips example number data from the library. If
|
|
383
|
+
> `example_number` or `example_number_for_type` returns `nil` on Linux, this is the cause.
|
|
384
|
+
> Build with `PICO_PHONE_NATIVE_BUILD=1` to use the vendored library, which always includes
|
|
385
|
+
> full metadata.
|
|
386
|
+
|
|
387
|
+
### Checking if a number is possible for a specific type
|
|
388
|
+
|
|
389
|
+
`possible_for_type?` checks whether a number's digit count is consistent with a given type in its region. This is a length-based check — more permissive than `type`, which classifies the number strictly.
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
phone = PicoPhone.parse("+15102745656", "US")
|
|
393
|
+
phone.possible_for_type?(:fixed_line_or_mobile) # true
|
|
394
|
+
phone.possible_for_type?(:toll_free) # true (same digit count)
|
|
395
|
+
|
|
396
|
+
phone.type # :fixed_line_or_mobile (strict classification)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Vanity numbers
|
|
400
|
+
|
|
401
|
+
`alpha_number?` identifies vanity number strings before parsing or converting them. `convert_alpha_characters` converts the alpha characters to their dialable digit equivalents.
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
PicoPhone.alpha_number?("1-800-FLOWERS") # true
|
|
405
|
+
PicoPhone.alpha_number?("+15102745656") # false
|
|
406
|
+
|
|
407
|
+
PicoPhone.convert_alpha_characters("1-800-FLOWERS") # "1-800-3569377"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Once converted, the result can be passed to `parse` as usual.
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
PicoPhone.parse(PicoPhone.convert_alpha_characters("1-800-FLOWERS")).type # :toll_free
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Supported regions
|
|
417
|
+
|
|
418
|
+
`supported_regions` returns an array of all region codes the library knows about. Useful for populating a country selector or validating a region code before passing it elsewhere.
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
PicoPhone.supported_regions # ["AC", "AD", "AE", ...]
|
|
422
|
+
PicoPhone.supported_regions.size # 245
|
|
423
|
+
PicoPhone.supported_regions.include?("US") # true
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Short numbers and emergency services
|
|
427
|
+
|
|
428
|
+
Short numbers (emergency services, short codes, etc.) cannot be parsed as regular phone numbers and require a region to be meaningful. These methods wrap libphonenumber's `ShortNumberInfo` API.
|
|
429
|
+
|
|
430
|
+
`emergency_number?` checks whether a string exactly matches an emergency service number in the given region.
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
PicoPhone.emergency_number?("911", "US") # true
|
|
434
|
+
PicoPhone.emergency_number?("999", "GB") # true
|
|
435
|
+
PicoPhone.emergency_number?("411", "US") # false
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`short_number_valid?` checks whether a string is a valid short number in the given region.
|
|
439
|
+
|
|
440
|
+
```
|
|
441
|
+
PicoPhone.short_number_valid?("911", "US") # true
|
|
442
|
+
PicoPhone.short_number_valid?("411", "US") # true
|
|
443
|
+
PicoPhone.short_number_valid?("411", "GB") # false
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`short_number_cost` returns the cost category of a short number as a symbol. Emergency numbers are always `:toll_free`. Returns `:unknown_cost` if the number cannot be parsed or has no cost data for the region.
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
PicoPhone.short_number_cost("911", "US") # :toll_free
|
|
450
|
+
PicoPhone.short_number_cost("411", "US") # :unknown_cost
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Possible return values are `:toll_free`, `:standard_rate`, `:premium_rate`, and `:unknown_cost`.
|
|
454
|
+
|
|
455
|
+
### Extensions
|
|
456
|
+
|
|
457
|
+
PicoPhone exposes libphonenumber's methods that identify and extract the extension out of a parsed phone number.
|
|
458
|
+
|
|
459
|
+
```
|
|
460
|
+
phone = PicoPhone.parse("5102745656;456", "US")
|
|
461
|
+
|
|
462
|
+
phone.has_extension? # true
|
|
463
|
+
phone.extension # 456
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
You can also format a phone number including its extension. The extension prefix can also be customized as needed. If no custom value is indicated, it will default to `;`
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
PicoPhone.default_extension_prefix = " ext. "
|
|
470
|
+
|
|
471
|
+
phone = PicoPhone.parse("5102745656;456", "US")
|
|
472
|
+
|
|
473
|
+
phone.full_national # (510) 274-5656 ext. 456
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
## Benchmarks
|
|
478
|
+
|
|
479
|
+
pico_phone wraps the actual libphonenumber C++ engine via [Rice](https://github.com/ruby-rice/rice). [phonelib](https://github.com/daddyz/phonelib) instead reimplements libphonenumber's pattern-matching in pure Ruby. That difference in architecture shows up clearly in throughput, and less clearly (and more conditionally) in memory.
|
|
480
|
+
|
|
481
|
+
The numbers below are one data point — macOS arm64, Ruby 3.4.1, pico_phone vs. phonelib 0.10.22 — not a guarantee for your platform. Run `bundle exec rake bench` yourself to reproduce them (requires `bundle install`, which pulls in `phonelib` and `benchmark-ips` as dev-only dependencies for comparison).
|
|
482
|
+
|
|
483
|
+
### Speed
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
$ bundle exec rake bench:speed
|
|
487
|
+
|
|
488
|
+
pico_phone phonelib
|
|
489
|
+
valid? 128.1k i/s 35.9k i/s (4.7x slower)
|
|
490
|
+
parse + e164 168.8k i/s 64.9k i/s (2.6x slower)
|
|
491
|
+
parse + national 109.5k i/s 47.4k i/s (3.6x slower)
|
|
492
|
+
valid? (30 numbers, multi-region) 3.0k i/s 1.0k i/s (3.0x slower)
|
|
493
|
+
carrier_name / carrier 173.5k i/s 42.9k i/s (4.1x slower)
|
|
494
|
+
timezones / timezone 98.1k i/s 2.5k i/s (39.7x slower)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Calling into compiled C++ beats matching regexes in Ruby, as expected.
|
|
498
|
+
|
|
499
|
+
### Memory
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
$ bundle exec rake bench:memory
|
|
503
|
+
|
|
504
|
+
Scenario Peak MB
|
|
505
|
+
---------------------------------------------------
|
|
506
|
+
baseline 21.4
|
|
507
|
+
pico_phone (light) 29.8
|
|
508
|
+
pico_phone (heavy) 40.7
|
|
509
|
+
pico_phone (heavy, +geo_name) 39.9
|
|
510
|
+
pico_phone (heavy, +carrier_name) 39.7
|
|
511
|
+
pico_phone (heavy, +timezones) 41.1
|
|
512
|
+
phonelib (light) 24.2
|
|
513
|
+
phonelib (heavy) 31.2
|
|
514
|
+
phonelib (heavy, +geo/carrier/timezone) 184.6
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
For validate/parse/format, memory use is comparable between the two, and pico_phone isn't the clear winner: it uses somewhat *more* than phonelib in this test (partly the cost of linking in libphonenumber's offline geocoder and carrier mapper, used below). Memory stays flat under sustained load for both (no leak).
|
|
518
|
+
|
|
519
|
+
phonelib's footprint balloons specifically when something calls `geo_name`, `carrier`, or `timezone`. Those lazily `Marshal.load` a ~4MB serialized data file — area-code-to-city, carrier, and timezone tables for every region in the world — into nested Ruby `Hash`/`Array`/`String` objects, then cache the whole thing in a class variable for the life of the process (see `phonelib/core.rb`, `@@phone_ext_data`).
|
|
520
|
+
|
|
521
|
+
pico_phone's `geo_name` (backed by libphonenumber's C++ offline geocoder, see [Geographic description](#geographic-description)) and `carrier_name` (see [Carrier lookup](#carrier-lookup)) don't have this problem: both load their prefix data lazily, one (country code, language) file at a time, and cache only what's actually queried — calling either 600k times across 30 regions above added no measurable RSS over the no-lookup baseline (both landed at or below the plain `pico_phone (heavy)` figure, within run-to-run noise). `timezones` (see [Timezone lookup](#timezone-lookup)) loads its single flat table (~86KB) once at first call and caches it for the life of the process — the one-time cost is negligible given the small table size.
|
|
522
|
+
|
|
523
|
+
## Development
|
|
524
|
+
|
|
525
|
+
After checking out the repo, run `bin/setup` to install dependencies. Running `rake spec` will run the tests. If you make any changes to the `pico_phone.cpp` file, running `rake` will compile the gem with the new changes and run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
526
|
+
|
|
527
|
+
### Cross-platform verification
|
|
528
|
+
|
|
529
|
+
The extension links against libphonenumber, which behaves differently across platforms (system library versions, linking strategies, Abseil ABI). Before merging changes to the C++ extension, verify across all supported platforms:
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
# macOS arm64 — dynamic (default developer workflow)
|
|
533
|
+
bundle exec rake
|
|
534
|
+
|
|
535
|
+
# macOS arm64 — static (the NATIVE_BUILD path used for precompiled gems)
|
|
536
|
+
PICO_PHONE_NATIVE_BUILD=1 bundle exec rake compile spec
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
For Linux, use `verify_docker.sh` (requires Docker Desktop):
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
bash verify_docker.sh dynamic # Ubuntu arm64 + x86_64, dynamic linking (~2 min each)
|
|
543
|
+
bash verify_docker.sh static # Ubuntu arm64 + x86_64, NATIVE_BUILD=1 (~8–15 min each)
|
|
544
|
+
bash verify_docker.sh musl # Alpine arm64 + x86_64, always NATIVE_BUILD=1 (~10–15 min each)
|
|
545
|
+
bash verify_docker.sh portable # Debian 12 arm64 + x86_64: the glibc release recipe, plus a smoke test of the packaged gem on bare Debian images
|
|
546
|
+
bash verify_docker.sh # all eight Linux combinations
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
The static Linux runs compile abseil, protobuf, ICU, and libphonenumber from source inside the container — that's what makes them slow. The dynamic runs use Ubuntu's packaged `libphonenumber-dev` and are much faster. Alpine has no equivalent system package, so musl is always static.
|
|
550
|
+
|
|
551
|
+
The `portable` runs are the ones that match how the glibc release gems are built: inside a Debian 12 container, so the resulting binary only needs glibc 2.34 / libstdc++ 3.4.30 (the versions `verify_docker.sh` asserts as its floor; keep them in sync with the platform line in [Installation](#installation)). They then install the packaged gem into bare `ruby:slim` images with no ICU and no compiler and exercise it (`smoke_packaged_gem.sh`), because a passing spec suite on the build machine can't tell you whether the shipped artifact loads elsewhere.
|
|
552
|
+
|
|
553
|
+
## Contributing
|
|
554
|
+
|
|
555
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/gjack/pico_phone.
|
|
556
|
+
|
|
557
|
+
## License
|
|
558
|
+
|
|
559
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
560
|
+
|
|
561
|
+
pico_phone's precompiled native gems compile against and statically embed several third-party libraries — libphonenumber and Abseil (Apache License 2.0), protobuf (BSD-3-Clause), ICU (Unicode License v3), and, on macOS, Boost (Boost Software License 1.0) — plus Rice (BSD-2-Clause-style), which is compiled directly in as a header-only library. Their licenses are reproduced in full in [`THIRD_PARTY_LICENSES.txt`](THIRD_PARTY_LICENSES.txt), which ships with the gem.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/pico_phone/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pico_phone
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.5
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Gabi Jack
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rice
|
|
@@ -77,6 +77,7 @@ extensions: []
|
|
|
77
77
|
extra_rdoc_files: []
|
|
78
78
|
files:
|
|
79
79
|
- LICENSE.txt
|
|
80
|
+
- README.md
|
|
80
81
|
- THIRD_PARTY_LICENSES.txt
|
|
81
82
|
- ext/pico_phone/carrier_data.cc
|
|
82
83
|
- ext/pico_phone/carrier_mapper.cc
|