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 +7 -0
- data/ext/pico_phone/extconf.rb +30 -0
- data/ext/pico_phone/pico_phone.cpp +834 -0
- data/lib/pico_phone/phone_number.rb +213 -0
- data/lib/pico_phone/version.rb +5 -0
- data/lib/pico_phone.rb +8 -0
- metadata +108 -0
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(), ®ions);
|
|
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(®ions);
|
|
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
|
data/lib/pico_phone.rb
ADDED
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: []
|