pico_phone 0.1.0

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: 9666e8d2353fc78b1515c738358a13cff067bb282726cba801ba264cd9814261
4
+ data.tar.gz: 34b97cb283f62a0ac4db956812bb5dc5c25051a9eaa6618aafb9980e12876daf
5
+ SHA512:
6
+ metadata.gz: 0c0cdcd99d53e45392cb9e3440f000a95103b55233dd73ccdc3097f6011ed3b057cfcc0e51aa9ffaaf33a4f661189844cc2d83eae2de57c6600d124f789b6647
7
+ data.tar.gz: 3075b1f9eafbdf6e2facc2ac5d869a6f8b00b6058ff1995624280b773a214627e8d759959c4ce61583ad2ccaecf80922858158220bbdef46c70b1c4a7b4da78b
@@ -0,0 +1,30 @@
1
+ require "mkmf-rice"
2
+
3
+ $CXXFLAGS << ' -std=c++17'
4
+
5
+ # On macOS, libphonenumber's headers pull in protobuf and abseil transitively.
6
+ # Each is its own Homebrew formula with its own opt prefix, so we add all three.
7
+ # On Linux with libphonenumber-dev, all headers land in /usr/include and no
8
+ # extra paths are needed.
9
+ if RUBY_PLATFORM.include?('darwin')
10
+ %w[libphonenumber protobuf abseil].each do |pkg|
11
+ prefix = `brew --prefix #{pkg} 2>/dev/null`.strip
12
+ next if prefix.empty?
13
+ $INCFLAGS << " -I#{prefix}/include"
14
+ $LDFLAGS << " -L#{prefix}/lib"
15
+ end
16
+ end
17
+
18
+ $LOCAL_LIBS << ' -lphonenumber'
19
+
20
+ unless find_header('phonenumbers/phonenumberutil.h')
21
+ abort <<~MSG
22
+
23
+ Could not find libphonenumber. Please install it before installing this gem:
24
+ macOS: brew install libphonenumber
25
+ Ubuntu: sudo apt-get install libphonenumber-dev
26
+ Fedora: sudo dnf install libphonenumber-devel
27
+ MSG
28
+ end
29
+
30
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PicoPhone
4
+ VERSION = "0.1.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,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pico_phone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gabi Jack
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2026-07-03 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rice
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake-compiler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.2'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.2'
68
+ description: pico_phone wraps Google's libphonenumber C++ library via a Rice native
69
+ extension, providing phone number parsing, validation, and formatting for any country.
70
+ It uses the same engine as Android's dialer and delivers native C++ performance
71
+ for high-throughput server-side use.
72
+ email:
73
+ - gabi@gabijack.com
74
+ executables: []
75
+ extensions:
76
+ - ext/pico_phone/extconf.rb
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/version.rb
84
+ homepage: https://github.com/gjack/pico_phone
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ homepage_uri: https://github.com/gjack/pico_phone
89
+ source_code_uri: https://github.com/gjack/pico_phone
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 3.0.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.6.2
105
+ specification_version: 4
106
+ summary: A thin Ruby wrapper around Google's libphonenumber for parsing, validating,
107
+ and formatting phone numbers
108
+ test_files: []