pico_phone 0.2.0-x86_64-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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 92c709a6d082670f302a45e0eda2c20e6af69e94ff7fe635e337a39271fbef8a
4
+ data.tar.gz: 2331a5283c685db4d5dd441bfaec5baebe31edd69456839bdbe378c824abb872
5
+ SHA512:
6
+ metadata.gz: a9ce8ce3e336b6fe6c8e5f9feb23d5c41bab94e5624ad6c69f5e094856ec338172c7e05fbfac130a3abd7beff110ed6c9087b6c5da9bc0768c23e1bdd2b497da
7
+ data.tar.gz: d9b54379e85a95ff513c6412dacb3370afa31d804cbfd0c1e5123b461c3fa7df8e41f7914bbf981f308a1c14da271c5957ee974e54c2253c2304248291d710d7
@@ -0,0 +1,66 @@
1
+ require "mkmf-rice"
2
+
3
+ $CXXFLAGS << ' -std=c++17'
4
+
5
+ VENDOR_INSTALL = File.expand_path("vendor/install", __dir__)
6
+ NATIVE_BUILD = ENV["PICO_PHONE_NATIVE_BUILD"] == "1"
7
+
8
+ if NATIVE_BUILD
9
+ # Static linking against vendored libraries for native gem builds.
10
+ # All five dependencies are baked into the .so — users need nothing installed.
11
+ $INCFLAGS << " -I#{VENDOR_INSTALL}/include"
12
+
13
+ static_libs = []
14
+ static_libs << "#{VENDOR_INSTALL}/lib/libphonenumber.a"
15
+ static_libs << "#{VENDOR_INSTALL}/lib/libprotobuf.a"
16
+ static_libs += Dir["#{VENDOR_INSTALL}/lib/libabsl_*.a"].sort
17
+
18
+ if RUBY_PLATFORM.include?("darwin")
19
+ boost_prefix = `brew --prefix boost 2>/dev/null`.strip
20
+ icu_prefix = `brew --prefix icu4c@78 2>/dev/null`.strip
21
+
22
+ %w[libboost_date_time libboost_thread libboost_atomic].each do |lib|
23
+ static_libs << "#{boost_prefix}/lib/#{lib}.a"
24
+ end
25
+
26
+ %w[libicui18n libicuuc libicudata].each do |lib|
27
+ static_libs << "#{icu_prefix}/lib/#{lib}.a"
28
+ end
29
+ else
30
+ # Linux: libphonenumber was built with USE_BOOST=OFF so no Boost needed.
31
+ # Ubuntu's libicu-dev static archives are not compiled with -fPIC and cannot
32
+ # be linked into a shared object. Link ICU dynamically instead — libicu74 is
33
+ # part of the Ubuntu 24.04 base system and is present in the target environment.
34
+ $LOCAL_LIBS << " -licui18n -licuuc -licudata -lpthread -ldl"
35
+ end
36
+
37
+ $LOCAL_LIBS << " " + static_libs.join(" ")
38
+
39
+ unless find_header("phonenumbers/phonenumberutil.h")
40
+ abort "Could not find phonenumberutil.h in #{VENDOR_INSTALL}/include — run build_deps.sh first"
41
+ end
42
+ else
43
+ # Dynamic linking against system/Homebrew libraries (default developer workflow).
44
+ if RUBY_PLATFORM.include?("darwin")
45
+ %w[libphonenumber protobuf abseil].each do |pkg|
46
+ prefix = `brew --prefix #{pkg} 2>/dev/null`.strip
47
+ next if prefix.empty?
48
+ $INCFLAGS << " -I#{prefix}/include"
49
+ $LDFLAGS << " -L#{prefix}/lib"
50
+ end
51
+ end
52
+
53
+ $LOCAL_LIBS << " -lphonenumber"
54
+
55
+ unless find_header("phonenumbers/phonenumberutil.h")
56
+ abort <<~MSG
57
+
58
+ Could not find libphonenumber. Please install it before installing this gem:
59
+ macOS: brew install libphonenumber
60
+ Ubuntu: sudo apt-get install libphonenumber-dev
61
+ Fedora: sudo dnf install libphonenumber-devel
62
+ MSG
63
+ end
64
+ end
65
+
66
+ create_makefile("pico_phone/pico_phone")
@@ -0,0 +1,834 @@
1
+ #include <rice/rice.hpp>
2
+ #include <rice/stl.hpp>
3
+ #include <string.h>
4
+ #include <list>
5
+ #include <set>
6
+ #include <phonenumbers/phonenumber.pb.h>
7
+ #include <phonenumbers/phonenumberutil.h>
8
+ #include <phonenumbers/shortnumberinfo.h>
9
+
10
+ using namespace Rice;
11
+ using namespace i18n::phonenumbers;
12
+
13
+ static VALUE rb_cPhoneNumber;
14
+ static VALUE rb_mPicoPhone;
15
+
16
+ size_t phone_number_size(const void *data) { return sizeof(PhoneNumber); }
17
+
18
+ void phone_number_free(void *data) {
19
+ PhoneNumber *phone_number = static_cast<PhoneNumber *>(data);
20
+ phone_number->~PhoneNumber();
21
+ xfree(data);
22
+ }
23
+
24
+ static const rb_data_type_t phone_number_type = {
25
+ .wrap_struct_name = "phone_number",
26
+ .function =
27
+ {
28
+ .dmark = NULL,
29
+ .dfree = phone_number_free,
30
+ .dsize = phone_number_size,
31
+ },
32
+ .parent = NULL,
33
+ .data = NULL,
34
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
35
+ };
36
+
37
+ VALUE rb_phone_number_alloc(VALUE self) {
38
+ void *phone_number_data = ALLOC(PhoneNumber);
39
+ PhoneNumber *phone_number = new (phone_number_data) PhoneNumber();
40
+ phone_number = phone_number;
41
+
42
+ return TypedData_Wrap_Struct(self, &phone_number_type, phone_number);
43
+ }
44
+
45
+ VALUE pico_phone_phone_number_parse(int argc, VALUE *argv, Object self) {
46
+ return rb_class_new_instance(argc, argv, rb_cPhoneNumber);
47
+ }
48
+
49
+ void pico_phone_set_default_country(VALUE self, VALUE str_code) {
50
+ if (RB_NIL_P(str_code)) {
51
+ str_code = rb_str_new("ZZ", 2);
52
+ }
53
+
54
+ rb_ivar_set(self, rb_intern("@default_country"), str_code);
55
+ }
56
+
57
+ void pico_phone_set_default_extension_prefix(VALUE self, VALUE str_code) {
58
+ if (RB_NIL_P(str_code)) {
59
+ str_code = rb_str_new(";", 1);
60
+ }
61
+
62
+ rb_ivar_set(self, rb_intern("@default_extn_prefix"), str_code);
63
+ }
64
+
65
+ String pico_phone_get_default_country(Object self) {
66
+ return self.iv_get("@default_country");
67
+ }
68
+
69
+ Object is_phone_number_valid(Object self, String str, String cc) {
70
+ std::string phone_number = str.c_str();
71
+ std::string country = cc.c_str();
72
+
73
+ if (country.empty() || phone_number.empty()) {
74
+ return Qfalse;
75
+ }
76
+
77
+ PhoneNumber parsed_number;
78
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
79
+
80
+ auto result = phone_util.ParseAndKeepRawInput(phone_number, country, &parsed_number);
81
+
82
+ if (result != PhoneNumberUtil::NO_PARSING_ERROR) {
83
+ return Qfalse;
84
+ }
85
+
86
+ if (country == "ZZ" && phone_util.IsValidNumber(parsed_number)) {
87
+ return Qtrue;
88
+ } else if (phone_util.IsValidNumberForRegion(parsed_number, country)) {
89
+ return Qtrue;
90
+ } else {
91
+ return Qfalse;
92
+ }
93
+ }
94
+
95
+ Object is_phone_number_possible(Object self, String str, String cc) {
96
+ std::string phone_number = str.c_str();
97
+ std::string country = cc.c_str();
98
+
99
+ if (country.empty() || phone_number.empty()) {
100
+ return Qnil;
101
+ }
102
+
103
+ PhoneNumber parsed_number;
104
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
105
+
106
+ auto result = phone_util.Parse(phone_number, country, &parsed_number);
107
+
108
+ if (result == PhoneNumberUtil::NO_PARSING_ERROR && phone_util.IsPossibleNumber(parsed_number)) {
109
+ return Qtrue;
110
+ } else {
111
+ return Qfalse;
112
+ }
113
+ }
114
+
115
+ Object pico_phone_is_valid_for_default_country(Object self, String phone_number) {
116
+ String country = self.iv_get("@default_country");
117
+ return is_phone_number_valid(self, phone_number, country);
118
+ }
119
+
120
+ Object pico_phone_is_valid_for_country(Object self, String phone_number, String country) {
121
+ return is_phone_number_valid(self, phone_number, country);
122
+ }
123
+
124
+ Object pico_phone_is_possible_for_default_country(Object self, String phone_number) {
125
+ String country = self.iv_get("@default_country");
126
+ return is_phone_number_possible(self, phone_number, country);
127
+ }
128
+
129
+ Object pico_phone_is_possible_for_country(Object self, String phone_number, String country) {
130
+ return is_phone_number_possible(self, phone_number, country);
131
+ }
132
+
133
+ static Array regions_for_number(const PhoneNumber& number, bool strict) {
134
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
135
+ std::list<std::string> regions;
136
+ phone_util.GetRegionCodesForCountryCallingCode(number.country_code(), &regions);
137
+
138
+ Array result;
139
+
140
+ // For the lenient (possible) check with a single region there is nothing to
141
+ // disambiguate, so a length-only check is sufficient and preferable: it lets
142
+ // possible_countries return the region for numbers that are the right length
143
+ // but fail strict pattern validation (e.g. a digit transposition in a French
144
+ // number still belongs to France). For ambiguous calling codes (+1, +7, +44,
145
+ // …) we must use IsValidNumberForRegion to distinguish between the candidate
146
+ // regions regardless of strictness, since IsPossibleNumber has no concept of
147
+ // region and would accept every candidate region equally.
148
+ if (!strict && regions.size() == 1) {
149
+ if (phone_util.IsPossibleNumber(number)) {
150
+ const auto& region = regions.front();
151
+ result.push(Object(rb_str_new(region.c_str(), region.size())));
152
+ }
153
+ return result;
154
+ }
155
+
156
+ for (const auto& region : regions) {
157
+ if (phone_util.IsValidNumberForRegion(number, region)) {
158
+ result.push(Object(rb_str_new(region.c_str(), region.size())));
159
+ }
160
+ }
161
+ return result;
162
+ }
163
+
164
+ Array pico_phone_supported_regions(Object self) {
165
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
166
+ std::set<std::string> regions;
167
+ phone_util.GetSupportedRegions(&regions);
168
+
169
+ Array result;
170
+ for (const auto& region : regions) {
171
+ result.push(Object(rb_str_new(region.c_str(), region.size())));
172
+ }
173
+ return result;
174
+ }
175
+
176
+ String pico_phone_convert_alpha_characters(Object self, String str) {
177
+ std::string phone_str = str.c_str();
178
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
179
+ phone_util.ConvertAlphaCharactersInNumber(&phone_str);
180
+ return rb_str_new(phone_str.c_str(), phone_str.size());
181
+ }
182
+
183
+ Object pico_phone_is_alpha_number(Object self, String str) {
184
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
185
+ return phone_util.IsAlphaNumber(str.c_str()) ? Qtrue : Qfalse;
186
+ }
187
+
188
+ static const ShortNumberInfo& GetShortNumberInfo() {
189
+ static ShortNumberInfo instance;
190
+ return instance;
191
+ }
192
+
193
+ Object pico_phone_is_emergency_number(Object self, String number, String region) {
194
+ return GetShortNumberInfo().IsEmergencyNumber(number.c_str(), region.c_str()) ? Qtrue : Qfalse;
195
+ }
196
+
197
+ Object pico_phone_is_short_number_valid(Object self, String str, String region) {
198
+ std::string number_str = str.c_str();
199
+ std::string region_str = region.c_str();
200
+
201
+ PhoneNumber parsed;
202
+ if (PhoneNumberUtil::GetInstance()->Parse(number_str, region_str, &parsed) != PhoneNumberUtil::NO_PARSING_ERROR) {
203
+ return Qfalse;
204
+ }
205
+ return GetShortNumberInfo().IsValidShortNumberForRegion(parsed, region_str) ? Qtrue : Qfalse;
206
+ }
207
+
208
+ Object pico_phone_short_number_cost(Object self, String str, String region) {
209
+ std::string number_str = str.c_str();
210
+ std::string region_str = region.c_str();
211
+
212
+ PhoneNumber parsed;
213
+ if (PhoneNumberUtil::GetInstance()->Parse(number_str, region_str, &parsed) != PhoneNumberUtil::NO_PARSING_ERROR) {
214
+ return rb_id2sym(rb_intern("unknown_cost"));
215
+ }
216
+
217
+ VALUE cost;
218
+ switch (GetShortNumberInfo().GetExpectedCostForRegion(parsed, region_str)) {
219
+ case ShortNumberInfo::TOLL_FREE:
220
+ cost = rb_intern("toll_free");
221
+ break;
222
+ case ShortNumberInfo::STANDARD_RATE:
223
+ cost = rb_intern("standard_rate");
224
+ break;
225
+ case ShortNumberInfo::PREMIUM_RATE:
226
+ cost = rb_intern("premium_rate");
227
+ break;
228
+ default:
229
+ cost = rb_intern("unknown_cost");
230
+ break;
231
+ }
232
+ return rb_id2sym(cost);
233
+ }
234
+
235
+ Array pico_phone_possible_countries_for_string(Object self, String str) {
236
+ std::string phone_number_str = str.c_str();
237
+ if (phone_number_str.empty()) return Array();
238
+
239
+ PhoneNumber parsed_number;
240
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
241
+
242
+ auto parse_result = phone_util.Parse(phone_number_str, "ZZ", &parsed_number);
243
+ if (parse_result != PhoneNumberUtil::NO_PARSING_ERROR) return Array();
244
+
245
+ return regions_for_number(parsed_number, false);
246
+ }
247
+
248
+ Array pico_phone_valid_countries_for_string(Object self, String str) {
249
+ std::string phone_number_str = str.c_str();
250
+ if (phone_number_str.empty()) return Array();
251
+
252
+ PhoneNumber parsed_number;
253
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
254
+
255
+ auto parse_result = phone_util.Parse(phone_number_str, "ZZ", &parsed_number);
256
+ if (parse_result != PhoneNumberUtil::NO_PARSING_ERROR) return Array();
257
+
258
+ return regions_for_number(parsed_number, true);
259
+ }
260
+
261
+ Object parsed_number_possible_with_reason(Object self) {
262
+ if (rb_ivar_defined(self, rb_intern("@possible_with_reason"))) {
263
+ return rb_iv_get(self, "@possible_with_reason");
264
+ }
265
+
266
+ PhoneNumber *phone_number;
267
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
268
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
269
+
270
+ VALUE reason;
271
+ switch (phone_util.IsPossibleNumberWithReason(*phone_number)) {
272
+ case PhoneNumberUtil::IS_POSSIBLE:
273
+ reason = rb_intern("is_possible");
274
+ break;
275
+ case PhoneNumberUtil::IS_POSSIBLE_LOCAL_ONLY:
276
+ reason = rb_intern("is_possible_local_only");
277
+ break;
278
+ case PhoneNumberUtil::TOO_SHORT:
279
+ reason = rb_intern("too_short");
280
+ break;
281
+ case PhoneNumberUtil::TOO_LONG:
282
+ reason = rb_intern("too_long");
283
+ break;
284
+ case PhoneNumberUtil::INVALID_COUNTRY_CODE:
285
+ reason = rb_intern("invalid_country_code");
286
+ break;
287
+ case PhoneNumberUtil::INVALID_LENGTH:
288
+ reason = rb_intern("invalid_length");
289
+ break;
290
+ default:
291
+ reason = rb_intern("unknown");
292
+ break;
293
+ }
294
+ return rb_iv_set(self, "@possible_with_reason", rb_id2sym(reason));
295
+ }
296
+
297
+ Object parsed_number_geographical(Object self) {
298
+ PhoneNumber *phone_number;
299
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
300
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
301
+ return phone_util.IsNumberGeographical(*phone_number) ? Qtrue : Qfalse;
302
+ }
303
+
304
+ Object parsed_number_can_be_internationally_dialled(Object self) {
305
+ PhoneNumber *phone_number;
306
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
307
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
308
+ return phone_util.CanBeInternationallyDialled(*phone_number) ? Qtrue : Qfalse;
309
+ }
310
+
311
+ VALUE phone_number_nullify_ivars(Object self) {
312
+ rb_iv_set(self, "@input_country", Qnil);
313
+ rb_iv_set(self, "@possible", Qfalse);
314
+ rb_iv_set(self, "@valid", Qfalse);
315
+ rb_iv_set(self, "@type", Qnil);
316
+ rb_iv_set(self, "@national", Qnil);
317
+ rb_iv_set(self, "@international", Qnil);
318
+ rb_iv_set(self, "@e164", Qnil);
319
+ rb_iv_set(self, "@country_code", Qnil);
320
+ rb_iv_set(self, "@country", Qnil);
321
+ rb_iv_set(self, "@area_code", Qnil);
322
+ rb_iv_set(self, "@local_number", Qnil);
323
+ rb_iv_set(self, "@original_format", Qnil);
324
+ rb_iv_set(self, "@raw_national", Qnil);
325
+ rb_iv_set(self, "@possible_with_reason", Qnil);
326
+
327
+ return Qtrue;
328
+ }
329
+
330
+ VALUE phone_number_initialize(int argc, VALUE *argv, VALUE self) {
331
+ VALUE str;
332
+ VALUE input_country;
333
+
334
+ rb_scan_args(argc, argv, "11", &str, &input_country);
335
+ rb_iv_set(self, "@input_country", input_country);
336
+ rb_iv_set(self, "@original", str);
337
+
338
+ if (RB_NIL_P(input_country)) {
339
+ input_country = rb_iv_get(rb_mPicoPhone, "@default_country");
340
+ }
341
+
342
+ if (RB_FIXNUM_P(str)) {
343
+ str = rb_fix2str(str, 10);
344
+ } else if (!RB_TYPE_P(str, T_STRING)) {
345
+ return phone_number_nullify_ivars(self);
346
+ }
347
+
348
+ PhoneNumber *phone_number;
349
+
350
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
351
+
352
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
353
+ std::string phone_number_value(StringValuePtr(str), RSTRING_LEN(str));
354
+ std::string country(StringValuePtr(input_country), RSTRING_LEN(input_country));
355
+
356
+ PhoneNumber parsed_number;
357
+
358
+ auto result = phone_util.ParseAndKeepRawInput(phone_number_value, country, &parsed_number);
359
+
360
+ if (result != PhoneNumberUtil::NO_PARSING_ERROR) {
361
+ phone_number_nullify_ivars(self);
362
+ }
363
+
364
+ phone_number->Swap(&parsed_number);
365
+
366
+ return self;
367
+ }
368
+
369
+ Object is_parsed_phone_number_possible(Object self) {
370
+ if (rb_ivar_defined(self, rb_intern("@possible"))) {
371
+ return rb_iv_get(self, "@possible");
372
+ }
373
+
374
+ PhoneNumber *phone_number;
375
+
376
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
377
+
378
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
379
+
380
+ if (phone_util.IsPossibleNumber(*phone_number)) {
381
+ return rb_iv_set(self, "@possible", Qtrue);
382
+ } else {
383
+ return rb_iv_set(self, "@possible", Qfalse);
384
+ }
385
+ }
386
+
387
+ Object is_parsed_phone_number_impossible(Object self) {
388
+ return (bool) is_parsed_phone_number_possible(self) ? Qfalse : Qtrue;
389
+ }
390
+
391
+ Object is_parsed_phone_number_valid(Object self) {
392
+ if (rb_ivar_defined(self, rb_intern("@valid"))) {
393
+ return rb_iv_get(self, "@valid");
394
+ }
395
+
396
+ VALUE input_country = rb_iv_get(self, "@input_country");
397
+ if (RB_NIL_P(input_country)) {
398
+ input_country = rb_iv_get(rb_mPicoPhone, "@default_country");
399
+ }
400
+
401
+ PhoneNumber *phone_number;
402
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
403
+
404
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
405
+
406
+ if (!rb_str_equal(input_country, rb_str_new_literal("ZZ"))) {
407
+ std::string country(StringValuePtr(input_country), RSTRING_LEN(input_country));
408
+ if (phone_util.IsValidNumberForRegion(*phone_number, country)) {
409
+ return rb_iv_set(self, "@valid", Qtrue);
410
+ } else {
411
+ return rb_iv_set(self, "@valid", Qfalse);
412
+ }
413
+ }
414
+
415
+ if (phone_util.IsValidNumber(*phone_number)) {
416
+ return rb_iv_set(self, "@valid", Qtrue);
417
+ } else {
418
+ return rb_iv_set(self, "@valid", Qfalse);
419
+ }
420
+ }
421
+
422
+ Object is_parsed_phone_number_invalid(Object self) {
423
+ return (bool) is_parsed_phone_number_valid(self) ? Qfalse : Qtrue;
424
+ }
425
+
426
+ static VALUE phone_number_type_to_symbol(PhoneNumberUtil::PhoneNumberType type) {
427
+ switch (type) {
428
+ case PhoneNumberUtil::FIXED_LINE: return rb_id2sym(rb_intern("fixed_line"));
429
+ case PhoneNumberUtil::MOBILE: return rb_id2sym(rb_intern("mobile"));
430
+ case PhoneNumberUtil::FIXED_LINE_OR_MOBILE:return rb_id2sym(rb_intern("fixed_line_or_mobile"));
431
+ case PhoneNumberUtil::TOLL_FREE: return rb_id2sym(rb_intern("toll_free"));
432
+ case PhoneNumberUtil::PREMIUM_RATE: return rb_id2sym(rb_intern("premium_rate"));
433
+ case PhoneNumberUtil::SHARED_COST: return rb_id2sym(rb_intern("shared_cost"));
434
+ case PhoneNumberUtil::VOIP: return rb_id2sym(rb_intern("voip"));
435
+ case PhoneNumberUtil::PERSONAL_NUMBER: return rb_id2sym(rb_intern("personal_number"));
436
+ case PhoneNumberUtil::PAGER: return rb_id2sym(rb_intern("pager"));
437
+ case PhoneNumberUtil::UAN: return rb_id2sym(rb_intern("uan"));
438
+ case PhoneNumberUtil::VOICEMAIL: return rb_id2sym(rb_intern("voicemail"));
439
+ default: return rb_id2sym(rb_intern("unknown"));
440
+ }
441
+ }
442
+
443
+ static PhoneNumberUtil::PhoneNumberType symbol_to_phone_number_type(VALUE sym) {
444
+ ID id = rb_to_id(sym);
445
+ if (id == rb_intern("fixed_line")) return PhoneNumberUtil::FIXED_LINE;
446
+ if (id == rb_intern("mobile")) return PhoneNumberUtil::MOBILE;
447
+ if (id == rb_intern("fixed_line_or_mobile")) return PhoneNumberUtil::FIXED_LINE_OR_MOBILE;
448
+ if (id == rb_intern("toll_free")) return PhoneNumberUtil::TOLL_FREE;
449
+ if (id == rb_intern("premium_rate")) return PhoneNumberUtil::PREMIUM_RATE;
450
+ if (id == rb_intern("shared_cost")) return PhoneNumberUtil::SHARED_COST;
451
+ if (id == rb_intern("voip")) return PhoneNumberUtil::VOIP;
452
+ if (id == rb_intern("personal_number")) return PhoneNumberUtil::PERSONAL_NUMBER;
453
+ if (id == rb_intern("pager")) return PhoneNumberUtil::PAGER;
454
+ if (id == rb_intern("uan")) return PhoneNumberUtil::UAN;
455
+ if (id == rb_intern("voicemail")) return PhoneNumberUtil::VOICEMAIL;
456
+ return PhoneNumberUtil::UNKNOWN;
457
+ }
458
+
459
+ Object parsed_phone_type(Object self) {
460
+ if (rb_ivar_defined(self, rb_intern("@type"))) {
461
+ return rb_iv_get(self, "@type");
462
+ }
463
+
464
+ PhoneNumber *phone_number;
465
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
466
+
467
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
468
+ return rb_iv_set(self, "@type", phone_number_type_to_symbol(phone_util.GetNumberType(*phone_number)));
469
+ }
470
+
471
+ static inline String format_parsed_phone_number(Object self, PhoneNumberUtil::PhoneNumberFormat selected_format, bool full_format = false) {
472
+ PhoneNumber *phone_number;
473
+
474
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
475
+ PhoneNumber copied_proto(*phone_number);
476
+
477
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
478
+ std::string formatted_phone_number;
479
+ String extension_prefix = rb_iv_get(rb_mPicoPhone, "@default_extn_prefix");
480
+
481
+ // if the phone number has an extension
482
+ // remove it so it's not part of formatting
483
+ if (phone_number->has_extension()) {
484
+ copied_proto.clear_extension();
485
+ }
486
+
487
+ phone_util.Format(copied_proto, selected_format, &formatted_phone_number);
488
+
489
+ return (full_format && phone_number->has_extension()) ? formatted_phone_number.append(extension_prefix.c_str()).append(phone_number->extension()) : formatted_phone_number;
490
+ }
491
+
492
+ String format_parsed_number_national(Object self) {
493
+ if (rb_ivar_defined(self, rb_intern("@national"))) {
494
+ return rb_iv_get(self, "@national");
495
+ }
496
+
497
+ return rb_iv_set(self, "@national", format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::NATIONAL));
498
+ }
499
+
500
+ String format_parsed_number_full_national(Object self) {
501
+ return format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::NATIONAL, true);
502
+ }
503
+
504
+ String format_parsed_number_raw_national(Object self) {
505
+ if (rb_ivar_defined(self, rb_intern("@raw_national"))) {
506
+ return rb_iv_get(self, "@raw_national");
507
+ }
508
+
509
+ PhoneNumber *phone_number;
510
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
511
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
512
+
513
+ std::string national_number;
514
+ phone_util.GetNationalSignificantNumber(*phone_number, &national_number);
515
+
516
+ return rb_iv_set(self, "@raw_national", rb_str_new(national_number.c_str(), national_number.size()));
517
+ }
518
+
519
+ String format_parsed_international(Object self) {
520
+ if (rb_ivar_defined(self, rb_intern("@international"))) {
521
+ return rb_iv_get(self, "@international");
522
+ }
523
+ return rb_iv_set(self, "@international", format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL));
524
+ }
525
+
526
+ String format_parsed_full_international(Object self) {
527
+ return format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::INTERNATIONAL, true);
528
+ }
529
+
530
+ String format_parsed_number_raw_international(Object self) {
531
+ if (rb_ivar_defined(self, rb_intern("@raw_international"))) {
532
+ return rb_iv_get(self, "@raw_international");
533
+ }
534
+
535
+ String formatted;
536
+
537
+ if (rb_ivar_defined(self, rb_intern("@e164"))) {
538
+ formatted = rb_iv_get(self, "@e164");
539
+ } else {
540
+ formatted = format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::E164);
541
+ }
542
+
543
+ std::string formatted_raw = formatted.str().erase(0, 1);
544
+
545
+ return rb_iv_set(self, "@raw_international", (String) formatted_raw);
546
+ }
547
+
548
+ String format_parsed_number_e164(Object self) {
549
+ if (rb_ivar_defined(self, rb_intern("@e164"))) {
550
+ return rb_iv_get(self, "@e164");
551
+ }
552
+ return rb_iv_set(self, "@e164", format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::E164));
553
+ }
554
+
555
+ String format_parsed_number_full_e164(Object self) {
556
+ return format_parsed_phone_number(self, PhoneNumberUtil::PhoneNumberFormat::E164, true);
557
+ }
558
+
559
+ String format_parsed_number_in_original_format(Object self) {
560
+ if (rb_ivar_defined(self, rb_intern("@original_format"))) {
561
+ return rb_iv_get(self, "@original_format");
562
+ }
563
+
564
+ PhoneNumber *phone_number;
565
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
566
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
567
+
568
+ VALUE input_country = rb_iv_get(self, "@input_country");
569
+ if (RB_NIL_P(input_country)) {
570
+ input_country = rb_iv_get(rb_mPicoPhone, "@default_country");
571
+ }
572
+ std::string region(StringValuePtr(input_country), RSTRING_LEN(input_country));
573
+
574
+ std::string formatted;
575
+ phone_util.FormatInOriginalFormat(*phone_number, region, &formatted);
576
+ return rb_iv_set(self, "@original_format", rb_str_new(formatted.c_str(), formatted.size()));
577
+ }
578
+
579
+ String format_parsed_number_out_of_country(Object self, String calling_from) {
580
+ PhoneNumber *phone_number;
581
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
582
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
583
+
584
+ std::string formatted;
585
+ phone_util.FormatOutOfCountryCallingNumber(*phone_number, calling_from.c_str(), &formatted);
586
+ return rb_str_new(formatted.c_str(), formatted.size());
587
+ }
588
+
589
+ String format_parsed_number_mobile_dialing(Object self, String calling_from) {
590
+ PhoneNumber *phone_number;
591
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
592
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
593
+
594
+ std::string formatted;
595
+ phone_util.FormatNumberForMobileDialing(*phone_number, calling_from.c_str(), true, &formatted);
596
+ return rb_str_new(formatted.c_str(), formatted.size());
597
+ }
598
+
599
+ Object parsed_phone_number_has_extension(Object self) {
600
+ PhoneNumber *phone_number;
601
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
602
+
603
+ return phone_number->has_extension() ? Qtrue : Qfalse;
604
+ }
605
+
606
+ // Returns the extension for the parsed phone number according to the pattern in
607
+ // libphonenumber library https://github.com/google/libphonenumber/blob/424617599369e7adba8a5d1509b910d9ce2e3e44/cpp/src/phonenumbers/phonenumberutil.cc#L220
608
+ String parsed_number_extension(Object self) {
609
+ PhoneNumber *phone_number;
610
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
611
+
612
+ if (phone_number->has_extension()) {
613
+ return phone_number->extension();
614
+ } else {
615
+ return String("");
616
+ }
617
+ }
618
+
619
+ Object parsed_number_country_code(Object self) {
620
+ if (rb_ivar_defined(self, rb_intern("@country_code"))) {
621
+ return rb_iv_get(self, "@country_code");
622
+ }
623
+
624
+ PhoneNumber *phone_number;
625
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
626
+
627
+ int code = phone_number->country_code();
628
+
629
+ return rb_iv_set(self, "@country_code", INT2FIX(code));
630
+ }
631
+
632
+ String parsed_number_country(Object self) {
633
+ if (rb_ivar_defined(self, rb_intern("@country"))) {
634
+ return rb_iv_get(self, "@country");
635
+ }
636
+
637
+ VALUE input_country = rb_iv_get(self, "@input_country");
638
+
639
+ if(RB_NIL_P(input_country)) {
640
+ PhoneNumber *phone_number;
641
+ std::string country;
642
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
643
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
644
+ phone_util.GetRegionCodeForCountryCode(phone_number->country_code(), &country);
645
+
646
+ return rb_iv_set(self, "@country", rb_str_new(country.c_str(), country.size()));
647
+ } else {
648
+ return rb_iv_set(self, "@country", input_country);
649
+ }
650
+ }
651
+
652
+ String parsed_number_area_code(Object self) {
653
+ if (rb_ivar_defined(self, rb_intern("@area_code"))) {
654
+ return rb_iv_get(self, "@area_code");
655
+ }
656
+
657
+ PhoneNumber *phone_number;
658
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
659
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
660
+ std::string national_significant_number;
661
+ std::string area_code;
662
+ int area_code_size = phone_util.GetLengthOfGeographicalAreaCode(*phone_number);
663
+
664
+ if (area_code_size > 0) {
665
+ phone_util.GetNationalSignificantNumber(*phone_number, &national_significant_number);
666
+ area_code = national_significant_number.substr(0, area_code_size);
667
+ } else {
668
+ area_code = "";
669
+ }
670
+
671
+ return rb_iv_set(self, "@area_code", rb_str_new(area_code.c_str(), area_code.size()));
672
+ }
673
+
674
+ String parsed_number_local_number(Object self) {
675
+ if (rb_ivar_defined(self, rb_intern("@local_number"))) {
676
+ return rb_iv_get(self, "@local_number");
677
+ }
678
+
679
+ PhoneNumber *phone_number;
680
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
681
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
682
+
683
+ std::string national_significant_number;
684
+ phone_util.GetNationalSignificantNumber(*phone_number, &national_significant_number);
685
+
686
+ int area_code_length = phone_util.GetLengthOfGeographicalAreaCode(*phone_number);
687
+ std::string local = area_code_length > 0
688
+ ? national_significant_number.substr(area_code_length)
689
+ : national_significant_number;
690
+
691
+ return rb_iv_set(self, "@local_number", rb_str_new(local.c_str(), local.size()));
692
+ }
693
+
694
+ Object parsed_number_valid_for_country(Object self, String country) {
695
+ PhoneNumber *phone_number;
696
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
697
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
698
+ return phone_util.IsValidNumberForRegion(*phone_number, country.c_str()) ? Qtrue : Qfalse;
699
+ }
700
+
701
+ Object parsed_number_invalid_for_country(Object self, String country) {
702
+ return RTEST(parsed_number_valid_for_country(self, country)) ? Qfalse : Qtrue;
703
+ }
704
+
705
+ Object parsed_number_original(Object self) {
706
+ return rb_iv_get(self, "@original");
707
+ }
708
+
709
+ Object parsed_number_to_s(Object self) {
710
+ if (RTEST(is_parsed_phone_number_valid(self))) {
711
+ return format_parsed_number_e164(self);
712
+ }
713
+ VALUE original = rb_iv_get(self, "@original");
714
+ return RB_NIL_P(original) ? Object(rb_str_new("", 0)) : Object(original);
715
+ }
716
+
717
+ Array parsed_number_possible_countries(Object self) {
718
+ PhoneNumber *phone_number;
719
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
720
+ return regions_for_number(*phone_number, false);
721
+ }
722
+
723
+ Array parsed_number_valid_countries(Object self) {
724
+ PhoneNumber *phone_number;
725
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
726
+ return regions_for_number(*phone_number, true);
727
+ }
728
+
729
+ Array pico_phone_supported_types_for_region(Object self, String region) {
730
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
731
+ std::set<PhoneNumberUtil::PhoneNumberType> types;
732
+ phone_util.GetSupportedTypesForRegion(region.c_str(), &types);
733
+
734
+ Array result;
735
+ for (const auto& type : types) {
736
+ result.push(Object(phone_number_type_to_symbol(type)));
737
+ }
738
+ return result;
739
+ }
740
+
741
+ Object pico_phone_example_number(Object self, String region) {
742
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
743
+ PhoneNumber example;
744
+ if (!phone_util.GetExampleNumber(region.c_str(), &example)) return Qnil;
745
+
746
+ std::string formatted;
747
+ phone_util.Format(example, PhoneNumberUtil::E164, &formatted);
748
+ VALUE args[1] = { rb_str_new(formatted.c_str(), formatted.size()) };
749
+ return rb_class_new_instance(1, args, rb_cPhoneNumber);
750
+ }
751
+
752
+ Object pico_phone_example_number_for_type(Object self, String region, Object type_sym) {
753
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
754
+ PhoneNumber example;
755
+ PhoneNumberUtil::PhoneNumberType type = symbol_to_phone_number_type(type_sym);
756
+ if (!phone_util.GetExampleNumberForType(region.c_str(), type, &example)) return Qnil;
757
+
758
+ std::string formatted;
759
+ phone_util.Format(example, PhoneNumberUtil::E164, &formatted);
760
+ VALUE args[1] = { rb_str_new(formatted.c_str(), formatted.size()) };
761
+ return rb_class_new_instance(1, args, rb_cPhoneNumber);
762
+ }
763
+
764
+ Object parsed_number_possible_for_type(Object self, Object type_sym) {
765
+ PhoneNumber *phone_number;
766
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
767
+ const PhoneNumberUtil &phone_util(*PhoneNumberUtil::GetInstance());
768
+ PhoneNumberUtil::PhoneNumberType type = symbol_to_phone_number_type(type_sym);
769
+ return phone_util.IsPossibleNumberForType(*phone_number, type) ? Qtrue : Qfalse;
770
+ }
771
+
772
+ extern "C"
773
+ void Init_pico_phone() {
774
+ rb_mPicoPhone = define_module("PicoPhone")
775
+ .define_singleton_method("default_country", &pico_phone_get_default_country)
776
+ .define_singleton_method("valid?", &pico_phone_is_valid_for_default_country)
777
+ .define_singleton_method("valid_for_country?", &pico_phone_is_valid_for_country)
778
+ .define_singleton_method("possible?", &pico_phone_is_possible_for_default_country)
779
+ .define_singleton_method("possible_for_country?", &pico_phone_is_possible_for_country)
780
+ .define_singleton_method("possible_countries", &pico_phone_possible_countries_for_string)
781
+ .define_singleton_method("valid_countries", &pico_phone_valid_countries_for_string)
782
+ .define_singleton_method("supported_regions", &pico_phone_supported_regions)
783
+ .define_singleton_method("convert_alpha_characters", &pico_phone_convert_alpha_characters)
784
+ .define_singleton_method("alpha_number?", &pico_phone_is_alpha_number)
785
+ .define_singleton_method("emergency_number?", &pico_phone_is_emergency_number)
786
+ .define_singleton_method("short_number_valid?", &pico_phone_is_short_number_valid)
787
+ .define_singleton_method("short_number_cost", &pico_phone_short_number_cost)
788
+ .define_singleton_method("supported_types_for_region", &pico_phone_supported_types_for_region)
789
+ .define_singleton_method("example_number", &pico_phone_example_number)
790
+ .define_singleton_method("example_number_for_type", &pico_phone_example_number_for_type);
791
+
792
+ rb_define_module_function(rb_mPicoPhone, "default_country=", reinterpret_cast<VALUE (*)(...)>(pico_phone_set_default_country), 1);
793
+ rb_define_module_function(rb_mPicoPhone, "default_extension_prefix=", reinterpret_cast<VALUE (*)(...)>(pico_phone_set_default_extension_prefix), 1);
794
+ rb_define_singleton_method(rb_mPicoPhone, "parse", reinterpret_cast<VALUE (*)(...)>(pico_phone_phone_number_parse), -1);
795
+ rb_ivar_set(rb_mPicoPhone, rb_intern("@default_country"), rb_str_new("ZZ", 2));
796
+ rb_ivar_set(rb_mPicoPhone, rb_intern("@default_extn_prefix"), rb_str_new(";", 1));
797
+
798
+ rb_cPhoneNumber = define_class_under(rb_mPicoPhone, "PhoneNumber")
799
+ .define_method("possible?", &is_parsed_phone_number_possible)
800
+ .define_method("impossible?", &is_parsed_phone_number_impossible)
801
+ .define_method("valid?", &is_parsed_phone_number_valid)
802
+ .define_method("invalid?", &is_parsed_phone_number_invalid)
803
+ .define_method("type", &parsed_phone_type)
804
+ .define_method("national", &format_parsed_number_national)
805
+ .define_method("international", &format_parsed_international)
806
+ .define_method("e164", &format_parsed_number_e164)
807
+ .define_method("extension", &parsed_number_extension)
808
+ .define_method("has_extension?", &parsed_phone_number_has_extension)
809
+ .define_method("full_national", &format_parsed_number_full_national)
810
+ .define_method("full_international", &format_parsed_full_international)
811
+ .define_method("full_e164", &format_parsed_number_full_e164)
812
+ .define_method("format_in_original_format", &format_parsed_number_in_original_format)
813
+ .define_method("out_of_country_format", &format_parsed_number_out_of_country)
814
+ .define_method("mobile_dialing_format", &format_parsed_number_mobile_dialing)
815
+ .define_method("country_code", &parsed_number_country_code)
816
+ .define_method("country", &parsed_number_country)
817
+ .define_method("area_code", &parsed_number_area_code)
818
+ .define_method("local_number", &parsed_number_local_number)
819
+ .define_method("valid_for_country?", &parsed_number_valid_for_country)
820
+ .define_method("invalid_for_country?", &parsed_number_invalid_for_country)
821
+ .define_method("original", &parsed_number_original)
822
+ .define_method("to_s", &parsed_number_to_s)
823
+ .define_method("raw_national", &format_parsed_number_raw_national)
824
+ .define_method("raw_international", &format_parsed_number_raw_international)
825
+ .define_method("possible_countries", &parsed_number_possible_countries)
826
+ .define_method("valid_countries", &parsed_number_valid_countries)
827
+ .define_method("possible_with_reason", &parsed_number_possible_with_reason)
828
+ .define_method("geographical?", &parsed_number_geographical)
829
+ .define_method("can_be_internationally_dialled?", &parsed_number_can_be_internationally_dialled)
830
+ .define_method("possible_for_type?", &parsed_number_possible_for_type);
831
+
832
+ rb_define_alloc_func(rb_cPhoneNumber, rb_phone_number_alloc);
833
+ rb_define_method(rb_cPhoneNumber, "initialize", reinterpret_cast<VALUE (*)(...)>(phone_number_initialize), -1);
834
+ }
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Not loaded at runtime — exists only for YARD and IDE tooling.
4
+ # All methods are defined in ext/pico_phone/pico_phone.cpp via Rice.
5
+
6
+ module PicoPhone
7
+ # @!method self.parse(string, region = nil)
8
+ # Parse a phone number string into a PhoneNumber object.
9
+ # @param string [String] raw phone number input
10
+ # @param region [String, nil] ISO 3166-1 alpha-2 region hint (e.g. "US")
11
+ # @return [PhoneNumber]
12
+
13
+ # @!method self.valid?(string)
14
+ # @param string [String]
15
+ # @return [Boolean]
16
+
17
+ # @!method self.possible?(string)
18
+ # @param string [String]
19
+ # @return [Boolean]
20
+
21
+ # @!method self.valid_for_country?(string, region)
22
+ # @param string [String]
23
+ # @param region [String] ISO 3166-1 alpha-2 region code
24
+ # @return [Boolean]
25
+
26
+ # @!method self.possible_for_country?(string, region)
27
+ # @param string [String]
28
+ # @param region [String] ISO 3166-1 alpha-2 region code
29
+ # @return [Boolean]
30
+
31
+ # @!method self.possible_countries(string)
32
+ # Regions that could own this number based on length or pattern.
33
+ # @param string [String]
34
+ # @return [Array<String>] ISO 3166-1 alpha-2 region codes
35
+
36
+ # @!method self.valid_countries(string)
37
+ # Regions for which this number passes strict pattern validation.
38
+ # @param string [String]
39
+ # @return [Array<String>] ISO 3166-1 alpha-2 region codes
40
+
41
+ # @!method self.supported_types_for_region(region)
42
+ # @param region [String] ISO 3166-1 alpha-2 region code
43
+ # @return [Array<Symbol>]
44
+
45
+ # @!method self.example_number(region)
46
+ # @param region [String] ISO 3166-1 alpha-2 region code
47
+ # @return [PhoneNumber]
48
+
49
+ # @!method self.example_number_for_type(region, type)
50
+ # @param region [String] ISO 3166-1 alpha-2 region code
51
+ # @param type [Symbol] e.g. :mobile, :fixed_line, :toll_free
52
+ # @return [PhoneNumber]
53
+
54
+ # @!method self.emergency_number?(string, region)
55
+ # @param string [String]
56
+ # @param region [String] ISO 3166-1 alpha-2 region code
57
+ # @return [Boolean]
58
+
59
+ # @!method self.short_number_valid?(string, region)
60
+ # @param string [String]
61
+ # @param region [String] ISO 3166-1 alpha-2 region code
62
+ # @return [Boolean]
63
+
64
+ # @!method self.short_number_cost(string, region)
65
+ # @param string [String]
66
+ # @param region [String] ISO 3166-1 alpha-2 region code
67
+ # @return [Symbol] :toll_free, :standard_rate, :premium_rate, or :unknown_cost
68
+
69
+ # @!method self.supported_regions
70
+ # All region codes the library knows about (~245 in libphonenumber 9.x).
71
+ # @return [Array<String>] ISO 3166-1 alpha-2 region codes
72
+
73
+ # @!method self.convert_alpha_characters(string)
74
+ # Convert vanity number alpha characters to digits (e.g. "1-800-FLOWERS" → "1-800-3569377").
75
+ # @param string [String]
76
+ # @return [String]
77
+
78
+ # @!method self.alpha_number?(string)
79
+ # @param string [String]
80
+ # @return [Boolean]
81
+
82
+ class PhoneNumber
83
+ # @param string [String, nil] raw phone number input
84
+ # @param region [String, nil] ISO 3166-1 alpha-2 region hint (e.g. "US")
85
+ def initialize(string, region = nil); end
86
+
87
+ # @return [Boolean]
88
+ def valid?; end
89
+
90
+ # @return [Boolean]
91
+ def invalid?; end
92
+
93
+ # @return [Boolean]
94
+ def possible?; end
95
+
96
+ # @return [Boolean]
97
+ def impossible?; end
98
+
99
+ # Why the number is or is not possible.
100
+ # @return [Symbol] :is_possible, :is_possible_local_only, :too_short, :too_long,
101
+ # :invalid_country_code, or :invalid_length
102
+ def possible_with_reason; end
103
+
104
+ # @return [String] e.g. "(510) 274-5656"
105
+ def national; end
106
+
107
+ # @return [String] e.g. "+1 510-274-5656"
108
+ def international; end
109
+
110
+ # @return [String] e.g. "+15102745656"
111
+ def e164; end
112
+
113
+ # @return [String, nil]
114
+ def extension; end
115
+
116
+ # @return [Boolean]
117
+ def has_extension?; end
118
+
119
+ # National format with extension appended using the configured prefix.
120
+ # @return [String]
121
+ def full_national; end
122
+
123
+ # International format with extension appended using the configured prefix.
124
+ # @return [String]
125
+ def full_international; end
126
+
127
+ # E.164 format with extension appended using the configured prefix.
128
+ # @return [String]
129
+ def full_e164; end
130
+
131
+ # @return [Integer] e.g. 1 for US/CA, 61 for AU
132
+ def country_code; end
133
+
134
+ # @return [String] ISO 3166-1 alpha-2 region code (e.g. "US")
135
+ def country; end
136
+
137
+ # @return [String] geographic area code digits, or empty string if none
138
+ def area_code; end
139
+
140
+ # National significant number as plain digits, no formatting punctuation.
141
+ # @return [String]
142
+ def raw_national; end
143
+
144
+ # International number digits with calling code, no formatting punctuation.
145
+ # @return [String]
146
+ def raw_international; end
147
+
148
+ # @return [Symbol] :fixed_line, :mobile, :fixed_line_or_mobile, :toll_free,
149
+ # :premium_rate, :shared_cost, :voip, :personal_number, :pager, :uan,
150
+ # :voicemail, or :unknown
151
+ def type; end
152
+
153
+ # Subscriber number after the area code. Returns the full national number when
154
+ # there is no geographic area code (e.g. mobile numbers in AU).
155
+ # @return [String]
156
+ def local_number; end
157
+
158
+ # @param region [String] ISO 3166-1 alpha-2 region code
159
+ # @return [Boolean]
160
+ def valid_for_country?(region); end
161
+
162
+ # @param region [String] ISO 3166-1 alpha-2 region code
163
+ # @return [Boolean]
164
+ def invalid_for_country?(region); end
165
+
166
+ # The raw input string as passed to the constructor. Survives a failed parse.
167
+ # @return [String, nil]
168
+ def original; end
169
+
170
+ # E.164 for a valid number; falls back to {#original} for an invalid one.
171
+ # Returns an empty string when nil was passed as input.
172
+ # @return [String]
173
+ def to_s; end
174
+
175
+ # Format using the same style (international vs. national) as the original input.
176
+ # @return [String]
177
+ def format_in_original_format; end
178
+
179
+ # Format the number as it would be dialed from outside its home country.
180
+ # @param region [String] ISO 3166-1 alpha-2 region of the caller
181
+ # @return [String]
182
+ def out_of_country_format(region); end
183
+
184
+ # Format the number for convenient dialing on a mobile device in the given region.
185
+ # @param region [String] ISO 3166-1 alpha-2 region of the caller
186
+ # @return [String]
187
+ def mobile_dialing_format(region); end
188
+
189
+ # Regions that could own this number based on length (unambiguous codes) or
190
+ # pattern (shared calling codes like +1 or +7).
191
+ # @return [Array<String>] ISO 3166-1 alpha-2 region codes
192
+ def possible_countries; end
193
+
194
+ # Regions for which this number passes strict pattern validation.
195
+ # @return [Array<String>] ISO 3166-1 alpha-2 region codes
196
+ def valid_countries; end
197
+
198
+ # True for fixed-line numbers tied to a geographic area code.
199
+ # @return [Boolean]
200
+ def geographical?; end
201
+
202
+ # True when the number's digit count is consistent with the given type in its region.
203
+ # More permissive than {#type}: a 10-digit US number is possible for both
204
+ # :fixed_line_or_mobile and :toll_free since they share the same digit count.
205
+ # @param type [Symbol] e.g. :mobile, :toll_free, :fixed_line
206
+ # @return [Boolean]
207
+ def possible_for_type?(type); end
208
+
209
+ # False for numbers that only work within their own country.
210
+ # @return [Boolean]
211
+ def can_be_internationally_dialled?; end
212
+ end
213
+ end
Binary file
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PicoPhone
4
+ VERSION = "0.2.0"
5
+ end
data/lib/pico_phone.rb ADDED
@@ -0,0 +1,8 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+ require "pico_phone/version"
4
+ require "pico_phone/pico_phone"
5
+
6
+ module PicoPhone
7
+ class Error < StandardError; end
8
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pico_phone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: x86_64-linux
6
+ authors:
7
+ - Gabi Jack
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rice
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.2'
69
+ description: pico_phone wraps Google's libphonenumber C++ library via a Rice native
70
+ extension, providing phone number parsing, validation, and formatting for any country.
71
+ It uses the same engine as Android's dialer and delivers native C++ performance
72
+ for high-throughput server-side use.
73
+ email:
74
+ - gabi@gabijack.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ext/pico_phone/extconf.rb
80
+ - ext/pico_phone/pico_phone.cpp
81
+ - lib/pico_phone.rb
82
+ - lib/pico_phone/phone_number.rb
83
+ - lib/pico_phone/pico_phone.so
84
+ - lib/pico_phone/version.rb
85
+ homepage: https://github.com/gjack/pico_phone
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ homepage_uri: https://github.com/gjack/pico_phone
90
+ source_code_uri: https://github.com/gjack/pico_phone
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 3.0.0
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.5.22
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: A thin Ruby wrapper around Google's libphonenumber for parsing, validating,
110
+ and formatting phone numbers
111
+ test_files: []