pico_phone 0.3.2 → 0.5.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.
@@ -0,0 +1,127 @@
1
+ // First-party pico_phone file. See carrier_mapper.h.
2
+
3
+ #include "carrier_mapper.h"
4
+
5
+ #include <algorithm>
6
+ #include <cstring>
7
+ #include <string>
8
+
9
+ #include "phonenumbers/geocoding/area_code_map.h"
10
+ #include "phonenumbers/geocoding/carrier_data.h"
11
+ #include "phonenumbers/geocoding/mapping_file_provider.h"
12
+ #include "phonenumbers/phonenumber.pb.h"
13
+
14
+ #include "absl/synchronization/mutex.h"
15
+
16
+ namespace i18n {
17
+ namespace phonenumbers {
18
+
19
+ using std::string;
20
+
21
+ namespace {
22
+
23
+ // Returns true if s1 comes strictly before s2 in lexicographic order.
24
+ bool IsLowerThan(const char* s1, const char* s2) {
25
+ return strcmp(s1, s2) < 0;
26
+ }
27
+
28
+ } // namespace
29
+
30
+ PhoneNumberCarrierMapper::PhoneNumberCarrierMapper() {
31
+ provider_.reset(new MappingFileProvider(get_carrier_country_calling_codes(),
32
+ get_carrier_country_calling_codes_size(),
33
+ get_carrier_country_languages));
34
+ prefix_language_code_pairs_ = get_carrier_prefix_language_code_pairs();
35
+ prefix_language_code_pairs_size_ = get_carrier_prefix_language_code_pairs_size();
36
+ get_prefix_descriptions_ = get_carrier_prefix_descriptions;
37
+ }
38
+
39
+ PhoneNumberCarrierMapper::~PhoneNumberCarrierMapper() {
40
+ // Pointer-taking constructor, not the reference-taking one: this file
41
+ // compiles against two different Abseil versions depending on build path
42
+ // (vendored 20260526.0 for the static/native build, whatever Debian/Ubuntu
43
+ // packages for the dynamic/apt-linked build), and only the older
44
+ // pointer-taking overload exists in both. The vendored geocoder itself hit
45
+ // the same tension in the other direction (see build_deps.sh's
46
+ // absl::MutexLock patch) -- there it's a -Werror'd deprecation inside
47
+ // libphonenumber's own CMake build; here it's just a harmless warning
48
+ // since pico_phone's own extension build doesn't set -Werror.
49
+ absl::MutexLock l(&mu_);
50
+ for (AreaCodeMaps::const_iterator it = available_maps_.begin();
51
+ it != available_maps_.end(); ++it) {
52
+ delete it->second;
53
+ }
54
+ }
55
+
56
+ const AreaCodeMap* PhoneNumberCarrierMapper::GetPhonePrefixDescriptions(
57
+ int prefix, const string& language, const string& script,
58
+ const string& region) const {
59
+ string filename;
60
+ provider_->GetFileName(prefix, language, script, region, &filename);
61
+ if (filename.empty()) {
62
+ return NULL;
63
+ }
64
+ AreaCodeMaps::const_iterator it = available_maps_.find(filename);
65
+ if (it == available_maps_.end()) {
66
+ return LoadAreaCodeMapFromFile(filename);
67
+ }
68
+ return it->second;
69
+ }
70
+
71
+ const AreaCodeMap* PhoneNumberCarrierMapper::LoadAreaCodeMapFromFile(
72
+ const string& filename) const {
73
+ const char** const prefix_language_code_pairs_end =
74
+ prefix_language_code_pairs_ + prefix_language_code_pairs_size_;
75
+ const char** const prefix_language_code_pair =
76
+ std::lower_bound(prefix_language_code_pairs_,
77
+ prefix_language_code_pairs_end,
78
+ filename.c_str(), IsLowerThan);
79
+ if (prefix_language_code_pair != prefix_language_code_pairs_end &&
80
+ filename.compare(*prefix_language_code_pair) == 0) {
81
+ AreaCodeMap* const m = new AreaCodeMap();
82
+ m->ReadAreaCodeMap(get_prefix_descriptions_(
83
+ prefix_language_code_pair - prefix_language_code_pairs_));
84
+ return available_maps_.insert(AreaCodeMaps::value_type(filename, m))
85
+ .first->second;
86
+ }
87
+ return NULL;
88
+ }
89
+
90
+ const char* PhoneNumberCarrierMapper::GetAreaDescription(
91
+ const PhoneNumber& number, const string& lang, const string& script,
92
+ const string& region) const {
93
+ const int country_calling_code = number.country_code();
94
+ // NANPA area is not split in C++ code (matches PhoneNumberOfflineGeocoder).
95
+ const int phone_prefix = country_calling_code;
96
+ absl::MutexLock l(&mu_);
97
+ const AreaCodeMap* const descriptions = GetPhonePrefixDescriptions(
98
+ phone_prefix, lang, script, region);
99
+ const char* description = descriptions ? descriptions->Lookup(number) : NULL;
100
+ // When a carrier name is not available in the requested language, fall
101
+ // back to English.
102
+ if ((!description || *description == '\0') && MayFallBackToEnglish(lang)) {
103
+ const AreaCodeMap* default_descriptions = GetPhonePrefixDescriptions(
104
+ phone_prefix, "en", "", "");
105
+ if (!default_descriptions) {
106
+ return "";
107
+ }
108
+ description = default_descriptions->Lookup(number);
109
+ }
110
+ return description ? description : "";
111
+ }
112
+
113
+ // Don't fall back to English if the requested language is among the following:
114
+ // - Chinese
115
+ // - Japanese
116
+ // - Korean
117
+ bool PhoneNumberCarrierMapper::MayFallBackToEnglish(const string& lang) const {
118
+ return lang.compare("zh") && lang.compare("ja") && lang.compare("ko");
119
+ }
120
+
121
+ string PhoneNumberCarrierMapper::GetNameForNumber(
122
+ const PhoneNumber& number, const string& lang) const {
123
+ return GetAreaDescription(number, lang, "", "");
124
+ }
125
+
126
+ } // namespace phonenumbers
127
+ } // namespace i18n
@@ -0,0 +1,88 @@
1
+ // First-party pico_phone file. A trimmed mirror of libphonenumber's
2
+ // PhoneNumberOfflineGeocoder (phonenumbers/geocoding/
3
+ // phonenumber_offline_geocoder.h in the vendored source), reusing the same
4
+ // AreaCodeMap/MappingFileProvider machinery -- lazy per (country calling
5
+ // code, language) file loading, cached for the life of the process -- but
6
+ // backed by carrier_data.cc instead of geocoding_data.cc, and with the
7
+ // ICU-based country-name fallback removed: carrier data has no natural
8
+ // "fall back to the country name" concept (unlike geocoding), so an
9
+ // unmapped prefix returns an empty string, matching phonelib's carrier
10
+ // semantics. This also means, unlike the geocoder, this class has no ICU
11
+ // dependency at all.
12
+
13
+ #ifndef PICO_PHONE_CARRIER_MAPPER_H_
14
+ #define PICO_PHONE_CARRIER_MAPPER_H_
15
+
16
+ #include <map>
17
+ #include <string>
18
+
19
+ #include "absl/synchronization/mutex.h"
20
+ #include "phonenumbers/base/memory/scoped_ptr.h"
21
+
22
+ namespace i18n {
23
+ namespace phonenumbers {
24
+
25
+ class AreaCodeMap;
26
+ class MappingFileProvider;
27
+ class PhoneNumber;
28
+ struct CountryLanguages;
29
+ struct PrefixDescriptions;
30
+
31
+ class PhoneNumberCarrierMapper {
32
+ private:
33
+ typedef std::map<std::string, const AreaCodeMap*> AreaCodeMaps;
34
+
35
+ public:
36
+ PhoneNumberCarrierMapper();
37
+
38
+ PhoneNumberCarrierMapper(const PhoneNumberCarrierMapper&) = delete;
39
+ PhoneNumberCarrierMapper& operator=(const PhoneNumberCarrierMapper&) = delete;
40
+
41
+ ~PhoneNumberCarrierMapper();
42
+
43
+ // Returns the carrier name for the given phone number in the given
44
+ // language, or an empty string if no carrier mapping is available for
45
+ // this prefix. lang is a two or three-letter lowercase ISO 639 language
46
+ // code, as with PhoneNumberOfflineGeocoder.
47
+ //
48
+ // The carrier name is the one the number was originally allocated to,
49
+ // however if the country supports mobile number portability the number
50
+ // might not belong to the returned carrier anymore (same caveat as
51
+ // upstream's Java-only PhoneNumberToCarrierMapper.getNameForNumber,
52
+ // verbatim -- see java/carrier/src/.../PhoneNumberToCarrierMapper.java
53
+ // in the vendored source tree).
54
+ std::string GetNameForNumber(const PhoneNumber& number,
55
+ const std::string& lang) const;
56
+
57
+ private:
58
+ typedef const PrefixDescriptions* (*prefix_descriptions_getter)(int index);
59
+
60
+ const AreaCodeMap* LoadAreaCodeMapFromFile(
61
+ const std::string& filename) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
62
+
63
+ const AreaCodeMap* GetPhonePrefixDescriptions(
64
+ int prefix, const std::string& language, const std::string& script,
65
+ const std::string& region) const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_);
66
+
67
+ const char* GetAreaDescription(const PhoneNumber& number,
68
+ const std::string& lang,
69
+ const std::string& script,
70
+ const std::string& region) const
71
+ ABSL_LOCKS_EXCLUDED(mu_);
72
+
73
+ bool MayFallBackToEnglish(const std::string& lang) const;
74
+
75
+ scoped_ptr<const MappingFileProvider> provider_;
76
+
77
+ const char** prefix_language_code_pairs_;
78
+ int prefix_language_code_pairs_size_;
79
+ prefix_descriptions_getter get_prefix_descriptions_;
80
+
81
+ mutable absl::Mutex mu_;
82
+ mutable AreaCodeMaps available_maps_ ABSL_GUARDED_BY(mu_);
83
+ };
84
+
85
+ } // namespace phonenumbers
86
+ } // namespace i18n
87
+
88
+ #endif // PICO_PHONE_CARRIER_MAPPER_H_
@@ -2,6 +2,14 @@ require "mkmf-rice"
2
2
 
3
3
  $CXXFLAGS << ' -std=c++17'
4
4
 
5
+ # carrier_mapper.cc needs AreaCodeMap/MappingFileProvider/the
6
+ # CountryLanguages+PrefixDescriptions structs, none of which libphonenumber
7
+ # installs publicly (only phonenumber_offline_geocoder.h is installed) in
8
+ # either build path below. Headers vendored verbatim under
9
+ # vendor_headers/phonenumbers/geocoding/ -- see that directory's own
10
+ # comments. Needed unconditionally, regardless of NATIVE_BUILD.
11
+ $INCFLAGS << " -I#{File.expand_path("vendor_headers", __dir__)}"
12
+
5
13
  VENDOR_INSTALL = File.expand_path("vendor/install", __dir__)
6
14
  NATIVE_BUILD = ENV["PICO_PHONE_NATIVE_BUILD"] == "1"
7
15
 
@@ -11,14 +19,30 @@ if NATIVE_BUILD
11
19
  $INCFLAGS << " -I#{VENDOR_INSTALL}/include"
12
20
 
13
21
  static_libs = []
22
+ # geocoding depends on symbols from phonenumber, so it must precede it in
23
+ # the static link order.
24
+ static_libs << "#{VENDOR_INSTALL}/lib/libgeocoding.a"
14
25
  static_libs << "#{VENDOR_INSTALL}/lib/libphonenumber.a"
15
26
  static_libs << "#{VENDOR_INSTALL}/lib/libprotobuf.a"
27
+ # protobuf's own transitive static deps (UTF-8 validation, upb). Whole-archive
28
+ # linking (below) force-includes every object in libprotobuf.a, including
29
+ # members that reference these, so they must be present even though nothing
30
+ # in our own code calls them directly. libutf8_range and libutf8_validity are
31
+ # two CMake target names for the exact same compiled object (utf8_range.c.o)
32
+ # in this protobuf version — link only one, or whole-archive pulls in both
33
+ # copies and the linker sees duplicate symbols.
34
+ %w[libutf8_range libupb].each do |lib|
35
+ path = "#{VENDOR_INSTALL}/lib/#{lib}.a"
36
+ static_libs << path if File.exist?(path)
37
+ end
16
38
  static_libs += Dir["#{VENDOR_INSTALL}/lib/libabsl_*.a"].sort
17
39
 
18
40
  if RUBY_PLATFORM.include?("darwin")
19
41
  boost_prefix = `brew --prefix boost 2>/dev/null`.strip
20
42
  icu_prefix = `brew --prefix icu4c@78 2>/dev/null`.strip
21
43
 
44
+ $INCFLAGS << " -I#{icu_prefix}/include"
45
+
22
46
  %w[libboost_date_time libboost_thread libboost_atomic].each do |lib|
23
47
  static_libs << "#{boost_prefix}/lib/#{lib}.a"
24
48
  end
@@ -31,18 +55,37 @@ if NATIVE_BUILD
31
55
  # Ubuntu's libicu-dev static archives are not compiled with -fPIC and cannot
32
56
  # be linked into a shared object. Link ICU dynamically instead — libicu74 is
33
57
  # part of the Ubuntu 24.04 base system and is present in the target environment.
58
+ # ICU headers (for the geocoder) come from the system libicu-dev package.
34
59
  $LOCAL_LIBS << " -licui18n -licuuc -licudata -lpthread -ldl"
35
60
  end
36
61
 
37
- $LOCAL_LIBS << " " + static_libs.join(" ")
62
+ # Adding the geocoder introduces a circular reference among several small
63
+ # Abseil static archives (e.g. synchronization <-> log_internal). A plain
64
+ # left-to-right static link only pulls in archive members that satisfy an
65
+ # already-outstanding undefined symbol, which for a -bundle target isn't
66
+ # even validated until dlopen time — so a missing member surfaces as a
67
+ # runtime "symbol not found in flat namespace" error, not a link failure.
68
+ # Force-load every member of our own vendored archives so nothing gets
69
+ # silently dropped.
70
+ if RUBY_PLATFORM.include?("darwin")
71
+ $LOCAL_LIBS << " -Wl,-all_load " + static_libs.join(" ")
72
+ else
73
+ $LOCAL_LIBS << " -Wl,--whole-archive " + static_libs.join(" ") + " -Wl,--no-whole-archive"
74
+ end
38
75
 
39
76
  unless find_header("phonenumbers/phonenumberutil.h")
40
77
  abort "Could not find phonenumberutil.h in #{VENDOR_INSTALL}/include — run build_deps.sh first"
41
78
  end
79
+
80
+ unless find_header("phonenumbers/geocoding/phonenumber_offline_geocoder.h")
81
+ abort "Could not find the geocoding header in #{VENDOR_INSTALL}/include — run build_deps.sh first"
82
+ end
42
83
  else
43
84
  # Dynamic linking against system/Homebrew libraries (default developer workflow).
44
85
  if RUBY_PLATFORM.include?("darwin")
45
- %w[libphonenumber protobuf abseil].each do |pkg|
86
+ # icu4c@78 is already a transitive dependency of Homebrew's libphonenumber
87
+ # formula (which itself ships geocoding), so no extra install step needed.
88
+ %w[libphonenumber protobuf abseil icu4c@78].each do |pkg|
46
89
  prefix = `brew --prefix #{pkg} 2>/dev/null`.strip
47
90
  next if prefix.empty?
48
91
  $INCFLAGS << " -I#{prefix}/include"
@@ -50,17 +93,26 @@ else
50
93
  end
51
94
  end
52
95
 
53
- $LOCAL_LIBS << " -lphonenumber"
96
+ $LOCAL_LIBS << " -lphonenumber -lgeocoding"
54
97
 
55
98
  unless find_header("phonenumbers/phonenumberutil.h")
56
99
  abort <<~MSG
57
100
 
58
101
  Could not find libphonenumber. Please install it before installing this gem:
59
102
  macOS: brew install libphonenumber
60
- Ubuntu: sudo apt-get install libphonenumber-dev
103
+ Ubuntu: sudo apt-get install libphonenumber-dev libicu-dev
61
104
  Fedora: sudo dnf install libphonenumber-devel
62
105
  MSG
63
106
  end
107
+
108
+ unless find_header("phonenumbers/geocoding/phonenumber_offline_geocoder.h")
109
+ abort <<~MSG
110
+
111
+ Found libphonenumber but not its geocoding headers/library. On Debian/Ubuntu
112
+ this also needs ICU development headers:
113
+ sudo apt-get install libphonenumber-dev libicu-dev
114
+ MSG
115
+ end
64
116
  end
65
117
 
66
118
  create_makefile("pico_phone/pico_phone")
@@ -3,10 +3,20 @@
3
3
  #include <string.h>
4
4
  #include <list>
5
5
  #include <set>
6
+ #include <vector>
6
7
  #include <phonenumbers/phonenumber.pb.h>
7
8
  #include <phonenumbers/phonenumberutil.h>
8
9
  #include <phonenumbers/shortnumberinfo.h>
9
10
 
11
+ // Ruby's regex engine (onigmo.h, pulled in via rice.hpp above) #defines UChar
12
+ // as unsigned char; ICU (pulled in transitively by the geocoding header)
13
+ // typedefs UChar as char16_t. We don't use Ruby's macro, so drop it before
14
+ // any ICU header is reachable.
15
+ #undef UChar
16
+ #include <phonenumbers/geocoding/phonenumber_offline_geocoder.h>
17
+ #include "carrier_mapper.h"
18
+ #include "timezone_mapper.h"
19
+
10
20
  using namespace Rice;
11
21
  using namespace i18n::phonenumbers;
12
22
 
@@ -190,6 +200,77 @@ static const ShortNumberInfo& GetShortNumberInfo() {
190
200
  return instance;
191
201
  }
192
202
 
203
+ // PhoneNumberOfflineGeocoder lazily loads its area-code-to-description data
204
+ // one (country calling code, language) file at a time on first lookup, and
205
+ // caches each loaded file for the life of this instance -- unlike phonelib's
206
+ // extended data, nothing gets pulled into memory until it's actually queried.
207
+ static const PhoneNumberOfflineGeocoder& GetGeocoder() {
208
+ static PhoneNumberOfflineGeocoder instance;
209
+ return instance;
210
+ }
211
+
212
+ VALUE parsed_number_geo_name(int argc, VALUE *argv, VALUE self) {
213
+ VALUE language;
214
+ rb_scan_args(argc, argv, "01", &language);
215
+
216
+ std::string language_code = RB_NIL_P(language)
217
+ ? "en"
218
+ : std::string(StringValuePtr(language), RSTRING_LEN(language));
219
+
220
+ PhoneNumber *phone_number;
221
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
222
+
223
+ Locale locale(language_code.c_str());
224
+ std::string description = GetGeocoder().GetDescriptionForNumber(*phone_number, locale);
225
+
226
+ return rb_str_new(description.c_str(), description.size());
227
+ }
228
+
229
+ // PhoneNumberCarrierMapper mirrors PhoneNumberOfflineGeocoder's lazy
230
+ // per-(prefix, language) file loading and caching -- see carrier_mapper.h.
231
+ static const PhoneNumberCarrierMapper& GetCarrierMapper() {
232
+ static PhoneNumberCarrierMapper instance;
233
+ return instance;
234
+ }
235
+
236
+ VALUE parsed_number_carrier_name(int argc, VALUE *argv, VALUE self) {
237
+ VALUE language;
238
+ rb_scan_args(argc, argv, "01", &language);
239
+
240
+ std::string language_code = RB_NIL_P(language)
241
+ ? "en"
242
+ : std::string(StringValuePtr(language), RSTRING_LEN(language));
243
+
244
+ PhoneNumber *phone_number;
245
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
246
+
247
+ std::string name = GetCarrierMapper().GetNameForNumber(*phone_number, language_code);
248
+
249
+ return rb_str_new(name.c_str(), name.size());
250
+ }
251
+
252
+ // PhoneNumberTimeZonesMapper eagerly loads its single flat prefix table
253
+ // once at construction (unlike the geocoder/carrier mappers, there's no
254
+ // per-language dimension or lazy per-file loading to do here) -- see
255
+ // timezone_mapper.h.
256
+ static const PhoneNumberTimeZonesMapper& GetTimeZonesMapper() {
257
+ static PhoneNumberTimeZonesMapper instance;
258
+ return instance;
259
+ }
260
+
261
+ Array parsed_number_timezones(Object self) {
262
+ PhoneNumber *phone_number;
263
+ TypedData_Get_Struct(self, PhoneNumber, &phone_number_type, phone_number);
264
+
265
+ std::vector<std::string> zones = GetTimeZonesMapper().GetTimeZonesForNumber(*phone_number);
266
+
267
+ Array result;
268
+ for (const auto& zone : zones) {
269
+ result.push(Object(rb_str_new(zone.c_str(), zone.size())));
270
+ }
271
+ return result;
272
+ }
273
+
193
274
  Object pico_phone_is_emergency_number(Object self, String number, String region) {
194
275
  return GetShortNumberInfo().IsEmergencyNumber(number.c_str(), region.c_str()) ? Qtrue : Qfalse;
195
276
  }
@@ -827,8 +908,11 @@ void Init_pico_phone() {
827
908
  .define_method("possible_with_reason", &parsed_number_possible_with_reason)
828
909
  .define_method("geographical?", &parsed_number_geographical)
829
910
  .define_method("can_be_internationally_dialled?", &parsed_number_can_be_internationally_dialled)
830
- .define_method("possible_for_type?", &parsed_number_possible_for_type);
911
+ .define_method("possible_for_type?", &parsed_number_possible_for_type)
912
+ .define_method("timezones", &parsed_number_timezones);
831
913
 
832
914
  rb_define_alloc_func(rb_cPhoneNumber, rb_phone_number_alloc);
833
915
  rb_define_method(rb_cPhoneNumber, "initialize", reinterpret_cast<VALUE (*)(...)>(phone_number_initialize), -1);
916
+ rb_define_method(rb_cPhoneNumber, "geo_name", reinterpret_cast<VALUE (*)(...)>(parsed_number_geo_name), -1);
917
+ rb_define_method(rb_cPhoneNumber, "carrier_name", reinterpret_cast<VALUE (*)(...)>(parsed_number_carrier_name), -1);
834
918
  }