piplapis-ruby 5.0.4 → 5.0.5

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.
@@ -1,67 +1,66 @@
1
- require_relative 'version'
2
-
3
- module Pipl
4
-
5
- module Default
6
-
7
- API_ENDPOINT = 'https://api.pipl.com/search/'.freeze
8
- API_KEY = 'sample_key'.freeze
9
- USER_AGENT = "piplapis/ruby/#{Pipl::VERSION}".freeze
10
-
11
- class << self
12
-
13
- def options
14
- Hash[Pipl::Configurable.keys.map{|key| [key, send(key)]}]
15
- end
16
-
17
- def api_key
18
- ENV.fetch 'PIPL_API_KEY', API_KEY
19
- end
20
-
21
- def minimum_probability
22
- ENV['PIPL_MINIMUM_PROBABILITY']
23
- end
24
-
25
- def minimum_match
26
- ENV['PIPL_MINIMUM_MATCH']
27
- end
28
-
29
- def hide_sponsored
30
- ENV['PIPL_HIDE_SPONSORED']
31
- end
32
-
33
- def live_feeds
34
- ENV['PIPL_LIVE_FEEDS']
35
- end
36
-
37
- def show_sources
38
- ENV['PIPL_SHOW_SOURCES']
39
- end
40
-
41
- def match_requirements
42
- ENV['PIPL_MATCH_REQUIREMENTS']
43
- end
44
-
45
- def source_category_requirements
46
- ENV['PIPL_SOURCE_CATEGORY_REQUIREMENTS']
47
- end
48
-
49
- def infer_persons
50
- ENV['PIPL_INFER_PERSONS']
51
- end
52
-
53
- def strict_validation
54
- ENV['PIPL_USER_STRICT_VALIDATION']
55
- end
56
-
57
- def api_endpoint
58
- ENV.fetch 'PIPL_API_ENDPOINT', API_ENDPOINT
59
- end
60
-
61
- def user_agent
62
- ENV.fetch 'PIPL_USER_AGENT', USER_AGENT
63
- end
64
-
65
- end
66
- end
67
- end
1
+ require_relative 'version'
2
+
3
+ module Pipl
4
+
5
+ module Default
6
+
7
+ API_ENDPOINT = 'https://api.pipl.com/search/'.freeze
8
+ USER_AGENT = "piplapis/ruby/#{Pipl::VERSION}".freeze
9
+
10
+ class << self
11
+
12
+ def options
13
+ Hash[Pipl::Configurable.keys.map{|key| [key, send(key)]}]
14
+ end
15
+
16
+ def api_key
17
+ ENV['PIPL_API_KEY']
18
+ end
19
+
20
+ def minimum_probability
21
+ ENV['PIPL_MINIMUM_PROBABILITY']
22
+ end
23
+
24
+ def minimum_match
25
+ ENV['PIPL_MINIMUM_MATCH']
26
+ end
27
+
28
+ def hide_sponsored
29
+ ENV['PIPL_HIDE_SPONSORED']
30
+ end
31
+
32
+ def live_feeds
33
+ ENV['PIPL_LIVE_FEEDS']
34
+ end
35
+
36
+ def show_sources
37
+ ENV['PIPL_SHOW_SOURCES']
38
+ end
39
+
40
+ def match_requirements
41
+ ENV['PIPL_MATCH_REQUIREMENTS']
42
+ end
43
+
44
+ def source_category_requirements
45
+ ENV['PIPL_SOURCE_CATEGORY_REQUIREMENTS']
46
+ end
47
+
48
+ def infer_persons
49
+ ENV['PIPL_INFER_PERSONS']
50
+ end
51
+
52
+ def strict_validation
53
+ ENV['PIPL_USER_STRICT_VALIDATION']
54
+ end
55
+
56
+ def api_endpoint
57
+ ENV.fetch 'PIPL_API_ENDPOINT', API_ENDPOINT
58
+ end
59
+
60
+ def user_agent
61
+ ENV.fetch 'PIPL_USER_AGENT', USER_AGENT
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -1,62 +1,62 @@
1
- module Pipl
2
-
3
- class AbstractMethodInvoked < StandardError;
4
- end
5
-
6
- class Client
7
-
8
- class APIError < Exception
9
- attr_reader :status_code
10
- attr_reader :qps_allotted, :qps_current, :qps_live_allotted, :qps_live_current, :qps_demo_allotted,
11
- :qps_demo_current, :quota_allotted, :quota_current, :quota_reset, :demo_usage_allotted,
12
- :demo_usage_current, :demo_usage_expiry
13
-
14
- def initialize(message, status_code, params={})
15
- super message
16
- @status_code = status_code
17
- @qps_allotted = params[:qps_allotted]
18
- @qps_current = params[:qps_current]
19
- @qps_live_allotted = params[:qps_live_allotted]
20
- @qps_live_current = params[:qps_live_current]
21
- @qps_demo_allotted = params[:qps_demo_allotted]
22
- @qps_demo_current = params[:qps_demo_current]
23
- @quota_allotted = params[:quota_allotted]
24
- @quota_current = params[:quota_current]
25
- @quota_reset = params[:quota_reset]
26
- @demo_usage_allotted = params[:demo_usage_allotted]
27
- @demo_usage_current = params[:demo_usage_current]
28
- @demo_usage_expiry = params[:demo_usage_expiry]
29
- end
30
-
31
- def is_user_error?
32
- (400..499).member?(@status_code)
33
- end
34
-
35
- def is_pipl_error?
36
- not is_user_error?
37
- end
38
-
39
- def self.deserialize(json_str, headers={})
40
- h = JSON.parse(json_str, symbolize_names: true)
41
- params = Utils::extract_rate_limits(headers)
42
- self.new(h[:error], h[:@http_status_code], params)
43
- end
44
-
45
- def self.from_http_response(resp)
46
- begin
47
- self.deserialize(resp.body, resp)
48
- rescue
49
- Pipl::Client::APIError.new resp.message, resp.code
50
- end
51
- end
52
-
53
- # Here for backward compatibility
54
- def self.from_json(json_str)
55
- self.deserialize(json_str)
56
- end
57
-
58
- end
59
-
60
- end
61
-
1
+ module Pipl
2
+
3
+ class AbstractMethodInvoked < StandardError;
4
+ end
5
+
6
+ class Client
7
+
8
+ class APIError < Exception
9
+ attr_reader :status_code
10
+ attr_reader :qps_allotted, :qps_current, :qps_live_allotted, :qps_live_current, :qps_demo_allotted,
11
+ :qps_demo_current, :quota_allotted, :quota_current, :quota_reset, :demo_usage_allotted,
12
+ :demo_usage_current, :demo_usage_expiry
13
+
14
+ def initialize(message, status_code, params={})
15
+ super message
16
+ @status_code = status_code
17
+ @qps_allotted = params[:qps_allotted]
18
+ @qps_current = params[:qps_current]
19
+ @qps_live_allotted = params[:qps_live_allotted]
20
+ @qps_live_current = params[:qps_live_current]
21
+ @qps_demo_allotted = params[:qps_demo_allotted]
22
+ @qps_demo_current = params[:qps_demo_current]
23
+ @quota_allotted = params[:quota_allotted]
24
+ @quota_current = params[:quota_current]
25
+ @quota_reset = params[:quota_reset]
26
+ @demo_usage_allotted = params[:demo_usage_allotted]
27
+ @demo_usage_current = params[:demo_usage_current]
28
+ @demo_usage_expiry = params[:demo_usage_expiry]
29
+ end
30
+
31
+ def is_user_error?
32
+ (400..499).member?(@status_code)
33
+ end
34
+
35
+ def is_pipl_error?
36
+ not is_user_error?
37
+ end
38
+
39
+ def self.deserialize(json_str, headers={})
40
+ h = JSON.parse(json_str, symbolize_names: true)
41
+ params = Utils::extract_rate_limits(headers)
42
+ self.new(h[:error], h[:@http_status_code], params)
43
+ end
44
+
45
+ def self.from_http_response(resp)
46
+ begin
47
+ self.deserialize(resp.body, resp)
48
+ rescue
49
+ Pipl::Client::APIError.new resp.message, resp.code
50
+ end
51
+ end
52
+
53
+ # Here for backward compatibility
54
+ def self.from_json(json_str)
55
+ self.deserialize(json_str)
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
62
  end
@@ -1,732 +1,732 @@
1
- require 'date'
2
- require 'set'
3
- require_relative 'consts'
4
- require_relative 'utils'
5
-
6
- module Pipl
7
-
8
- class Field
9
-
10
- include Pipl::Utils
11
-
12
- attr_accessor :valid_since, :last_seen, :inferred, :current
13
-
14
- def initialize(params={})
15
- @valid_since = params[:valid_since]
16
- @last_seen = params[:last_seen]
17
- @inferred = params[:inferred]
18
- @current = params[:current]
19
- end
20
-
21
- def self.from_hash(h)
22
- params = base_params_from_hash h
23
- extra_metadata.each { |p| params[p] = h["@#{p}".to_sym] }
24
- params = h.merge params
25
- self.new(params)
26
- end
27
-
28
- def self.base_params_from_hash(h)
29
- params = {
30
- inferred: h[:@inferred],
31
- current: h[:@current],
32
- type: h[:@type],
33
- display: h[:display]
34
- }
35
- params[:valid_since] = Date.strptime(h[:@valid_since], Pipl::DATE_FORMAT) if h.key? :@valid_since
36
- params[:last_seen] = Date.strptime(h[:@last_seen], Pipl::DATE_FORMAT) if h.key? :@last_seen
37
- params[:date_range] = Pipl::DateRange.from_hash(h[:date_range]) if h.key? :date_range
38
- params
39
- end
40
-
41
- def self.extra_metadata
42
- []
43
- end
44
-
45
- def to_hash
46
-
47
- end
48
-
49
- def is_searchable?
50
- true
51
- end
52
-
53
- end
54
-
55
-
56
- class Name < Field
57
- # @!attribute first
58
- # @return [String] First name
59
- # @!attribute middle
60
- # @return [String] Middle name or initial
61
- # @!attribute last
62
- # @return [String] Last name
63
- # @!attribute prefix
64
- # @return [String] Name prefix
65
- # @!attribute suffix
66
- # @return [String] Name suffix
67
- # @!attribute type
68
- # @return [String] Type of association of this name to a person. One of `present`, `maiden`, `former` or `alias`.
69
-
70
- attr_accessor :first, :middle, :last, :prefix, :suffix, :type, :raw, :display
71
-
72
- def initialize(params={})
73
- super params
74
- @first = params[:first]
75
- @middle = params[:middle]
76
- @last = params[:last]
77
- @prefix = params[:prefix]
78
- @suffix = params[:suffix]
79
- @type = params[:type]
80
- @raw = params[:raw]
81
- @display = params[:display]
82
- end
83
-
84
- def to_hash
85
- {first: @first, middle: @middle, last: @last, prefix: @prefix, suffix: @suffix, raw: @raw}
86
- .reject { |_, value| value.nil? }
87
- end
88
-
89
- def is_searchable?
90
- first = Pipl::Utils.alpha_chars(@first || '')
91
- last = Pipl::Utils.alpha_chars(@last || '')
92
- raw = Pipl::Utils.alpha_chars(@raw || '')
93
- (first.length > 1 and last.length > 1) or raw.length > 3
94
- end
95
-
96
- def to_s
97
- return @display if @display
98
-
99
- vals = [@prefix, @first, @middle, @last, @suffix]
100
- s = vals.any? ? vals.select { |v| v }.map { |v| v.capitalize }.join(' ') : nil
101
- s ? Pipl::Utils.to_utf8(s) : ''
102
- end
103
-
104
- end
105
-
106
-
107
- class Address < Field
108
- # @!attribute country
109
- # @return [String] 2 letters country code
110
- # @!attribute state
111
- # @return [String] 2 letters state code
112
- # @!attribute city
113
- # @return [String] City
114
- # @!attribute street
115
- # @return [String] Street
116
- # @!attribute house
117
- # @return [String] House
118
- # @!attribute apartment
119
- # @return [String] Apartment
120
- # @!attribute zip_code
121
- # @return [String] Zip Code
122
- # @!attribute po_box
123
- # @return [String] Post Office box number
124
- # @!attribute type
125
- # @return [String] Type of association of this address to a person. One of `home`, `work` or `old`.
126
- # @!attribute raw
127
- # @return [String] Unparsed address.
128
- # @!attribute display
129
- # @return [String] well formatted representation of this address for display purposes.
130
-
131
- attr_accessor :country, :state, :city, :street, :house, :apartment, :zip_code, :po_box, :type, :raw, :display
132
-
133
- def initialize(params={})
134
- super params
135
- @country = params[:country]
136
- @state = params[:state]
137
- @city = params[:city]
138
- @street = params[:street]
139
- @house = params[:house]
140
- @apartment = params[:apartment]
141
- @zip_code = params[:zip_code]
142
- @po_box = params[:po_box]
143
- @type = params[:type]
144
- @raw = params[:raw]
145
- @display = params[:display]
146
- end
147
-
148
- def is_valid_country?
149
- @country and Pipl::COUNTRIES.key? @country.upcase.to_sym
150
- end
151
-
152
- def is_valid_state?
153
- is_valid_country? and Pipl::STATES.key?(@country.upcase.to_sym) and
154
- @state and Pipl::STATES[@country.upcase.to_sym].key?(@state.upcase.to_sym)
155
- end
156
-
157
- def to_hash
158
- {country: @country, state: @state, city: @city, street: @street, house: @house, apartment: @apartment,
159
- zip_code: @zip_code, po_box: @po_box, raw: @raw}
160
- .reject { |_, value| value.nil? }
161
- end
162
-
163
- def is_searchable?
164
- [@raw, @country, @state, @city].any? {|x| not x.to_s.empty?}
165
- end
166
-
167
- def is_sole_searchable?
168
- not @raw.to_s.empty? or [@city, @street, @house].all? {|x| not x.to_s.empty?}
169
- end
170
-
171
- def country_full
172
- Pipl::COUNTRIES[@country.upcase.to_sym] if @country
173
- end
174
-
175
- def state_full
176
- Pipl::STATES[@country.upcase.to_sym][@state.upcase.to_sym] if is_valid_state?
177
- end
178
-
179
- def to_s
180
- return @display if @display
181
-
182
- country = @state ? @country : country_full
183
- state = @city ? @state : state_full
184
- vals = [@street, @city, state, country]
185
- s = vals.any? ? vals.select { |v| v }.join(', ') : ''
186
-
187
- if @street and (@house or @apartment)
188
- prefix = [@house, @apartment].select { |v| v and not v.empty? }.join('-')
189
- s = prefix + ' ' + (s || '')
190
- end
191
-
192
- if @po_box and @street.nil?
193
- s = "P.O. Box #{@po_box} " + (s || '')
194
- end
195
-
196
- s ? Pipl::Utils.to_utf8(s) : ''
197
- end
198
-
199
- end
200
-
201
-
202
- class Phone < Field
203
- # @!attribute country_code
204
- # @return [Fixnum] International country calling code
205
- # @!attribute number
206
- # @return [Fixnum] Actual phone number
207
- # @!attribute extension
208
- # @return [String] Office extension
209
- # @!attribute type
210
- # @return [String] Type of association of this phone to a person.
211
- # Possible values are:
212
- # mobile
213
- # home_phone
214
- # home_fax
215
- # work_phone
216
- # work_fax
217
- # pager
218
- # @!attribute raw
219
- # @return [String] Unparsed phone number
220
- # @!attribute display
221
- # @return [String] Well formatted representation of this phone number for display purposes.
222
- # @!attribute display_international
223
- # @return [String] Well formatted international representation of this phone number for display purposes.
224
-
225
- attr_accessor :country_code, :number, :extension, :type, :raw, :display, :display_international
226
-
227
- def initialize(params={})
228
- super params
229
- @country_code = params[:country_code]
230
- @number = params[:number]
231
- @extension = params[:extension]
232
- @type = params[:type]
233
- @raw = params[:raw]
234
- @display = params[:display]
235
- @display_international = params[:display_international]
236
- end
237
-
238
- def self.extra_metadata
239
- [:display_international]
240
- end
241
-
242
- def to_hash
243
- {country_code: @country_code, number: @number, extension: @extension, raw: @raw}
244
- .reject { |_, value| value.nil? }
245
- end
246
-
247
- def is_searchable?
248
- (@raw and not @raw.empty?) or not @number.nil?
249
- end
250
-
251
- end
252
-
253
-
254
- class Email < Field
255
-
256
- RE_EMAIL = Regexp.new('^[a-zA-Z0-9\'._%\-+]+@[a-zA-Z0-9._%\-]+\.[a-zA-Z]{2,24}$')
257
-
258
- # @!attribute address
259
- # @return [String] Plain email address
260
- # @!attribute address_md5
261
- # @return [String] MD5 hash of the email address
262
- # @!attribute type
263
- # @return [String] Type of email association to a person. One of `personal` or `work`.
264
- # @!attribute disposable
265
- # @return [Boolean] Indicating if this email comes from a disposable email provider.
266
- # @!attribute email_provider
267
- # @return [Boolean] Indicating if this email comes from a well known email provider like gmail or yahoo.
268
-
269
- attr_accessor :address, :address_md5, :type, :disposable, :email_provider
270
-
271
- def initialize(params={})
272
- super params
273
- @address = params[:address]
274
- @address_md5 = params[:address_md5]
275
- @type = params[:type]
276
- @disposable = params[:disposable]
277
- @email_provider = params[:email_provider]
278
- end
279
-
280
- def self.extra_metadata
281
- [:disposable, :email_provider]
282
- end
283
-
284
- def is_valid_email?
285
- not RE_EMAIL.match(@address).nil?
286
- end
287
-
288
- def is_searchable?
289
- is_valid_email? or (not @address_md5.nil? and @address_md5.length == 32)
290
- end
291
-
292
- def to_hash
293
- {address: @address, address_md5: @address_md5}.reject { |_, value| value.nil? }
294
- end
295
-
296
- def username
297
- @address.split('@')[0] if is_valid_email?
298
- end
299
-
300
- def domain
301
- @address.split('@')[1] if is_valid_email?
302
- end
303
-
304
- end
305
-
306
-
307
- class Job < Field
308
-
309
- attr_accessor :title, :organization, :industry, :date_range, :display
310
-
311
- def initialize(params={})
312
- super params
313
- @title = params[:title]
314
- @organization = params[:organization]
315
- @industry = params[:industry]
316
- @date_range = params[:date_range]
317
- @display = params[:display]
318
- end
319
-
320
- def to_hash
321
- {title: @title, organization: @organization, industry: @industry,
322
- date_range: @date_range ? @date_range.to_hash : nil}
323
- .reject { |_, value| value.nil? }
324
- end
325
-
326
- def to_s
327
- return @display if @display
328
-
329
- if @title and @organization
330
- s = @title + ' at ' + @organization
331
- else
332
- s = @title || @organization
333
- end
334
-
335
- if s and @industry
336
- if @date_range
337
- range = @date_range.years_range
338
- s += ' (%s, %d-%d)' % [@industry, range[0], range[1]]
339
- else
340
- s += ' (%s)' % [@industry]
341
- end
342
- else
343
- s = ((s || '') + ' ' + (@industry || '')).strip
344
- if s and @date_range
345
- range = @date_range.years_range
346
- s += ' (%d-%d)' % [range[0], range[1]]
347
- end
348
- end
349
-
350
- s ? Pipl::Utils.to_utf8(s) : ''
351
- end
352
-
353
- end
354
-
355
-
356
- class Education < Field
357
-
358
- attr_accessor :degree, :school, :date_range, :display
359
-
360
- def initialize(params={})
361
- super params
362
- @degree = params[:degree]
363
- @school = params[:school]
364
- @date_range = params[:date_range]
365
- @display = params[:display]
366
- end
367
-
368
- def to_hash
369
- {degree: @degree, school: @school, date_range: @date_range ? @date_range.to_hash : nil}
370
- .reject { |_, value| value.nil? }
371
- end
372
-
373
- def to_s
374
- return @display if @display
375
-
376
- if @degree and @school
377
- s = @degree + ' from ' + @school
378
- else
379
- s = @degree || @school
380
- end
381
-
382
- if s and @date_range
383
- range = @date_range.years_range
384
- s += ' (%d-%d)' % [range[0], range[1]]
385
- end
386
-
387
- s ? Pipl::Utils.to_utf8(s) : ''
388
- end
389
-
390
- end
391
-
392
-
393
- class Image < Field
394
-
395
- attr_accessor :url, :thumbnail_token
396
-
397
- def initialize(params={})
398
- super params
399
- @url = params[:url]
400
- @thumbnail_token = params[:thumbnail_token]
401
- end
402
-
403
- def thumbnail_url(params={})
404
- return unless @thumbnail_token
405
-
406
- opts = {width: 100, height: 100, favicon: true, zoom_face: true, use_https: false}.merge(params)
407
- schema = opts.delete(:use_https) ? 'https': 'http'
408
- tokens = @thumbnail_token.gsub(/&dsid=.*/,'')
409
- tokens += ',' + opts.delete(:fallback).thumbnail_token.gsub(/&dsid=.*/,'') if opts[:fallback]
410
- query_params = ["tokens=#{tokens}"] + opts.map { |k, v| "#{k}=#{v}" unless v.nil? }
411
- "#{schema}://thumb.pipl.com/image?#{query_params.compact.join('&')}"
412
- end
413
-
414
- end
415
-
416
-
417
- class Username < Field
418
-
419
- attr_accessor :content
420
-
421
- def initialize(params={})
422
- super params
423
- @content = params[:content]
424
- end
425
-
426
- def to_hash
427
- {content: @content} if @content
428
- end
429
-
430
- def is_searchable?
431
- !@content.nil? and Pipl::Utils.alnum_chars(@content).length > 3
432
- end
433
-
434
- end
435
-
436
-
437
- class UserID < Username
438
-
439
- def is_searchable?
440
- not /.+@.+/.match(@content).nil?
441
- end
442
-
443
- end
444
-
445
-
446
- class DOB < Field
447
-
448
- attr_accessor :date_range, :display
449
-
450
- def initialize(params={})
451
- super params
452
- @date_range = params[:date_range]
453
- @display = params[:display]
454
- end
455
-
456
- def self.from_birth_year(birth_year)
457
- raise ArgumentError.new('birth_year must be positive') unless birth_year > 0
458
- self.new({date_range: DateRange.from_years_range(birth_year, birth_year)})
459
- end
460
-
461
- def self.from_birth_date(birth_date)
462
- raise ArgumentError.new('birth_date can\'t be in the future') unless birth_date <= Date.today
463
- self.new({date_range: DateRange.new(birth_date, birth_date)})
464
- end
465
-
466
- def self.from_age(age)
467
- self.from_age_range(age, age)
468
- end
469
-
470
- def self.from_age_range(start_age, end_age)
471
- raise ArgumentError.new('start_age and end_age can\'t be negative') if start_age < 0 or end_age < 0
472
-
473
- if start_age > end_age
474
- start_age, end_age = end_age, start_age
475
- end
476
-
477
- today = Date.today
478
- start_date = today << end_age * 12
479
- start_date = start_date - 1
480
- end_date = today << start_age * 12
481
- self.new({date_range: Pipl::DateRange.new(start_date, end_date)})
482
- end
483
-
484
- def to_s
485
- @display or Pipl::Utils.to_utf8(age.to_s)
486
- end
487
-
488
- def age
489
- unless @date_range.nil?
490
- dob = @date_range.middle
491
- today = Date.today
492
- diff = today.year - dob.year
493
- diff = diff - 1 if dob.month > today.month or (dob.month >= today.month and dob.day > today.day)
494
- diff
495
- end
496
- end
497
-
498
- def age_range
499
- if @date_range
500
- return [self.age, self.age] unless @date_range.start and @date_range.end
501
- start_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.start, @date_range.start)}).age
502
- end_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.end, @date_range.end)}).age
503
- return end_age, start_age
504
- else
505
- return nil, nil
506
- end
507
- end
508
-
509
- def to_hash
510
- {date_range: @date_range.to_hash} if @date_range
511
- end
512
-
513
- def is_searchable?
514
- not @date_range.nil?
515
- end
516
-
517
- end
518
-
519
-
520
- class Url < Field
521
- # @!attribute url
522
- # @return [String] Actual Url
523
- # @!attribute category
524
- # @return [String] Category of the domain
525
- # Possible values are:
526
- # background_reports
527
- # contact_details
528
- # email_address
529
- # media
530
- # personal_profiles
531
- # professional_and_business
532
- # public_records
533
- # publications
534
- # school_and_classmates
535
- # web_pages
536
- # @!attribute domain
537
- # @return [String] Canonical domain of the url
538
- # @!attribute name
539
- # @return [String] Name of the website hosting the url
540
- # @!attribute sponsored
541
- # @return [Boolean] Indicate if this url comes from a sponsored data source
542
- # @!attribute sponsored
543
- # @return [String] Unique identifier of this url
544
-
545
- attr_accessor :url, :category, :domain, :name, :sponsored, :source_id
546
-
547
- def initialize(params={})
548
- super params
549
- @url = params[:url]
550
- @category = params[:category]
551
- @domain = params[:domain]
552
- @name = params[:name]
553
- @sponsored = params[:sponsored]
554
- @source_id = params[:source_id]
555
- end
556
-
557
- def self.extra_metadata
558
- [:category, :domain, :name, :sponsored, :source_id]
559
- end
560
-
561
- def is_searchable?
562
- not @url.to_s.empty?
563
- end
564
-
565
- end
566
-
567
-
568
- class Gender < Field
569
-
570
- attr_accessor :content
571
-
572
- def initialize(params={})
573
- super params
574
- @content = params[:content]
575
- end
576
-
577
- def to_s
578
- Pipl::Utils.titleize @content if @content
579
- end
580
-
581
- def to_hash
582
- {content: @content} if @content
583
- end
584
-
585
- end
586
-
587
-
588
- class Ethnicity < Field
589
-
590
- # @!attribute content
591
- # @return [String] Ethnicity name based on the U.S Census Bureau.
592
- # Possible values are:
593
- # white
594
- # black
595
- # american_indian
596
- # alaska_native
597
- # asian_indian
598
- # chinese
599
- # filipino
600
- # other_asian
601
- # japanese
602
- # korean
603
- # vietnamese
604
- # native_hawaiian
605
- # guamanian
606
- # chamorro
607
- # samoan
608
- # other_pacific_islander
609
- # other
610
-
611
- attr_accessor :content
612
-
613
- def initialize(params={})
614
- super params
615
- @content = params[:content]
616
- end
617
-
618
- def to_s
619
- Pipl::Utils.titleize @content.gsub(/_/, ' ') if @content
620
- end
621
-
622
- end
623
-
624
-
625
- class Language < Field
626
-
627
- attr_accessor :language, :region, :display
628
-
629
- def initialize(params={})
630
- super params
631
- @language = params[:language]
632
- @region = params[:region]
633
- @display = params[:display]
634
- end
635
-
636
- def to_s
637
- return @display if @display
638
- return "#{@language}_#{@region}" if @language and @region
639
- return @language if @language and not @language.empty?
640
- @region
641
- end
642
-
643
- end
644
-
645
-
646
- class OriginCountry < Field
647
-
648
- attr_accessor :country
649
-
650
- def initialize(params={})
651
- super params
652
- @country = params[:country]
653
- end
654
-
655
- def to_s
656
- Pipl::COUNTRIES[@country.upcase.to_sym] if @country
657
- end
658
-
659
- end
660
-
661
-
662
- class Tag < Field
663
-
664
- attr_accessor :content, :classification
665
-
666
- def initialize(params={})
667
- super params
668
- @content = params[:content]
669
- @classification = params[:classification]
670
- end
671
-
672
- def self.extra_metadata
673
- [:classification]
674
- end
675
-
676
- def to_s
677
- @content
678
- end
679
-
680
- end
681
-
682
-
683
- class DateRange
684
-
685
- attr_reader :start, :end
686
-
687
- def initialize(start, end_)
688
- @start = start
689
- @end = end_
690
- if @start and @end and @start > @end
691
- @start, @end = @end, @start
692
- end
693
- end
694
-
695
- # def ==(other)
696
- # other.instance_of?(self.class) and inspect == other.inspect
697
- # end
698
- #
699
- # alias_method :eql?, :==
700
-
701
- def is_exact?
702
- @start and @end and @start == @end
703
- end
704
-
705
- def middle
706
- @start and @end ? @start + ((@end - @start) / 2) : @start or @end
707
- end
708
-
709
- def years_range
710
- [@start.year, @end.year] if @start and @end
711
- end
712
-
713
- def self.from_years_range(start_year, end_year)
714
- self.new(Date.new(start_year, 1, 1), Date.new(end_year, 12, 31))
715
- end
716
-
717
- def self.from_hash(h)
718
- start_, end_ = h[:start], h[:end]
719
- initializing_start = start_ ? Date.strptime(start_, Pipl::DATE_FORMAT) : nil
720
- initializing_end = end_ ? Date.strptime(end_, Pipl::DATE_FORMAT) : nil
721
- self.new(initializing_start, initializing_end)
722
- end
723
-
724
- def to_hash
725
- h = {}
726
- h[:start] = @start.strftime(Pipl::DATE_FORMAT) if @start
727
- h[:end] = @end.strftime(Pipl::DATE_FORMAT) if @end
728
- h
729
- end
730
- end
731
-
732
- end
1
+ require 'date'
2
+ require 'set'
3
+ require_relative 'consts'
4
+ require_relative 'utils'
5
+
6
+ module Pipl
7
+
8
+ class Field
9
+
10
+ include Pipl::Utils
11
+
12
+ attr_accessor :valid_since, :last_seen, :inferred, :current
13
+
14
+ def initialize(params={})
15
+ @valid_since = params[:valid_since]
16
+ @last_seen = params[:last_seen]
17
+ @inferred = params[:inferred]
18
+ @current = params[:current]
19
+ end
20
+
21
+ def self.from_hash(h)
22
+ params = base_params_from_hash h
23
+ extra_metadata.each { |p| params[p] = h["@#{p}".to_sym] }
24
+ params = h.merge params
25
+ self.new(params)
26
+ end
27
+
28
+ def self.base_params_from_hash(h)
29
+ params = {
30
+ inferred: h[:@inferred],
31
+ current: h[:@current],
32
+ type: h[:@type],
33
+ display: h[:display]
34
+ }
35
+ params[:valid_since] = Date.strptime(h[:@valid_since], Pipl::DATE_FORMAT) if h.key? :@valid_since
36
+ params[:last_seen] = Date.strptime(h[:@last_seen], Pipl::DATE_FORMAT) if h.key? :@last_seen
37
+ params[:date_range] = Pipl::DateRange.from_hash(h[:date_range]) if h.key? :date_range
38
+ params
39
+ end
40
+
41
+ def self.extra_metadata
42
+ []
43
+ end
44
+
45
+ def to_hash
46
+
47
+ end
48
+
49
+ def is_searchable?
50
+ true
51
+ end
52
+
53
+ end
54
+
55
+
56
+ class Name < Field
57
+ # @!attribute first
58
+ # @return [String] First name
59
+ # @!attribute middle
60
+ # @return [String] Middle name or initial
61
+ # @!attribute last
62
+ # @return [String] Last name
63
+ # @!attribute prefix
64
+ # @return [String] Name prefix
65
+ # @!attribute suffix
66
+ # @return [String] Name suffix
67
+ # @!attribute type
68
+ # @return [String] Type of association of this name to a person. One of `present`, `maiden`, `former` or `alias`.
69
+
70
+ attr_accessor :first, :middle, :last, :prefix, :suffix, :type, :raw, :display
71
+
72
+ def initialize(params={})
73
+ super params
74
+ @first = params[:first]
75
+ @middle = params[:middle]
76
+ @last = params[:last]
77
+ @prefix = params[:prefix]
78
+ @suffix = params[:suffix]
79
+ @type = params[:type]
80
+ @raw = params[:raw]
81
+ @display = params[:display]
82
+ end
83
+
84
+ def to_hash
85
+ {first: @first, middle: @middle, last: @last, prefix: @prefix, suffix: @suffix, raw: @raw}
86
+ .reject { |_, value| value.nil? }
87
+ end
88
+
89
+ def is_searchable?
90
+ first = Pipl::Utils.alpha_chars(@first || '')
91
+ last = Pipl::Utils.alpha_chars(@last || '')
92
+ raw = Pipl::Utils.alpha_chars(@raw || '')
93
+ (first.length > 1 and last.length > 1) or raw.length > 3
94
+ end
95
+
96
+ def to_s
97
+ return @display if @display
98
+
99
+ vals = [@prefix, @first, @middle, @last, @suffix]
100
+ s = vals.any? ? vals.select { |v| v }.map { |v| v.capitalize }.join(' ') : nil
101
+ s ? Pipl::Utils.to_utf8(s) : ''
102
+ end
103
+
104
+ end
105
+
106
+
107
+ class Address < Field
108
+ # @!attribute country
109
+ # @return [String] 2 letters country code
110
+ # @!attribute state
111
+ # @return [String] 2 letters state code
112
+ # @!attribute city
113
+ # @return [String] City
114
+ # @!attribute street
115
+ # @return [String] Street
116
+ # @!attribute house
117
+ # @return [String] House
118
+ # @!attribute apartment
119
+ # @return [String] Apartment
120
+ # @!attribute zip_code
121
+ # @return [String] Zip Code
122
+ # @!attribute po_box
123
+ # @return [String] Post Office box number
124
+ # @!attribute type
125
+ # @return [String] Type of association of this address to a person. One of `home`, `work` or `old`.
126
+ # @!attribute raw
127
+ # @return [String] Unparsed address.
128
+ # @!attribute display
129
+ # @return [String] well formatted representation of this address for display purposes.
130
+
131
+ attr_accessor :country, :state, :city, :street, :house, :apartment, :zip_code, :po_box, :type, :raw, :display
132
+
133
+ def initialize(params={})
134
+ super params
135
+ @country = params[:country]
136
+ @state = params[:state]
137
+ @city = params[:city]
138
+ @street = params[:street]
139
+ @house = params[:house]
140
+ @apartment = params[:apartment]
141
+ @zip_code = params[:zip_code]
142
+ @po_box = params[:po_box]
143
+ @type = params[:type]
144
+ @raw = params[:raw]
145
+ @display = params[:display]
146
+ end
147
+
148
+ def is_valid_country?
149
+ @country and Pipl::COUNTRIES.key? @country.upcase.to_sym
150
+ end
151
+
152
+ def is_valid_state?
153
+ is_valid_country? and Pipl::STATES.key?(@country.upcase.to_sym) and
154
+ @state and Pipl::STATES[@country.upcase.to_sym].key?(@state.upcase.to_sym)
155
+ end
156
+
157
+ def to_hash
158
+ {country: @country, state: @state, city: @city, street: @street, house: @house, apartment: @apartment,
159
+ zip_code: @zip_code, po_box: @po_box, raw: @raw}
160
+ .reject { |_, value| value.nil? }
161
+ end
162
+
163
+ def is_searchable?
164
+ [@raw, @country, @state, @city].any? {|x| not x.to_s.empty?}
165
+ end
166
+
167
+ def is_sole_searchable?
168
+ not @raw.to_s.empty? or [@city, @street, @house].all? {|x| not x.to_s.empty?}
169
+ end
170
+
171
+ def country_full
172
+ Pipl::COUNTRIES[@country.upcase.to_sym] if @country
173
+ end
174
+
175
+ def state_full
176
+ Pipl::STATES[@country.upcase.to_sym][@state.upcase.to_sym] if is_valid_state?
177
+ end
178
+
179
+ def to_s
180
+ return @display if @display
181
+
182
+ country = @state ? @country : country_full
183
+ state = @city ? @state : state_full
184
+ vals = [@street, @city, state, country]
185
+ s = vals.any? ? vals.select { |v| v }.join(', ') : ''
186
+
187
+ if @street and (@house or @apartment)
188
+ prefix = [@house, @apartment].select { |v| v and not v.empty? }.join('-')
189
+ s = prefix + ' ' + (s || '')
190
+ end
191
+
192
+ if @po_box and @street.nil?
193
+ s = "P.O. Box #{@po_box} " + (s || '')
194
+ end
195
+
196
+ s ? Pipl::Utils.to_utf8(s) : ''
197
+ end
198
+
199
+ end
200
+
201
+
202
+ class Phone < Field
203
+ # @!attribute country_code
204
+ # @return [Fixnum] International country calling code
205
+ # @!attribute number
206
+ # @return [Fixnum] Actual phone number
207
+ # @!attribute extension
208
+ # @return [String] Office extension
209
+ # @!attribute type
210
+ # @return [String] Type of association of this phone to a person.
211
+ # Possible values are:
212
+ # mobile
213
+ # home_phone
214
+ # home_fax
215
+ # work_phone
216
+ # work_fax
217
+ # pager
218
+ # @!attribute raw
219
+ # @return [String] Unparsed phone number
220
+ # @!attribute display
221
+ # @return [String] Well formatted representation of this phone number for display purposes.
222
+ # @!attribute display_international
223
+ # @return [String] Well formatted international representation of this phone number for display purposes.
224
+
225
+ attr_accessor :country_code, :number, :extension, :type, :raw, :display, :display_international
226
+
227
+ def initialize(params={})
228
+ super params
229
+ @country_code = params[:country_code]
230
+ @number = params[:number]
231
+ @extension = params[:extension]
232
+ @type = params[:type]
233
+ @raw = params[:raw]
234
+ @display = params[:display]
235
+ @display_international = params[:display_international]
236
+ end
237
+
238
+ def self.extra_metadata
239
+ [:display_international]
240
+ end
241
+
242
+ def to_hash
243
+ {country_code: @country_code, number: @number, extension: @extension, raw: @raw}
244
+ .reject { |_, value| value.nil? }
245
+ end
246
+
247
+ def is_searchable?
248
+ (@raw and not @raw.empty?) or not @number.nil?
249
+ end
250
+
251
+ end
252
+
253
+
254
+ class Email < Field
255
+
256
+ RE_EMAIL = Regexp.new('^[a-zA-Z0-9\'._%\-+]+@[a-zA-Z0-9._%\-]+\.[a-zA-Z]{2,24}$')
257
+
258
+ # @!attribute address
259
+ # @return [String] Plain email address
260
+ # @!attribute address_md5
261
+ # @return [String] MD5 hash of the email address
262
+ # @!attribute type
263
+ # @return [String] Type of email association to a person. One of `personal` or `work`.
264
+ # @!attribute disposable
265
+ # @return [Boolean] Indicating if this email comes from a disposable email provider.
266
+ # @!attribute email_provider
267
+ # @return [Boolean] Indicating if this email comes from a well known email provider like gmail or yahoo.
268
+
269
+ attr_accessor :address, :address_md5, :type, :disposable, :email_provider
270
+
271
+ def initialize(params={})
272
+ super params
273
+ @address = params[:address]
274
+ @address_md5 = params[:address_md5]
275
+ @type = params[:type]
276
+ @disposable = params[:disposable]
277
+ @email_provider = params[:email_provider]
278
+ end
279
+
280
+ def self.extra_metadata
281
+ [:disposable, :email_provider]
282
+ end
283
+
284
+ def is_valid_email?
285
+ not RE_EMAIL.match(@address).nil?
286
+ end
287
+
288
+ def is_searchable?
289
+ is_valid_email? or (not @address_md5.nil? and @address_md5.length == 32)
290
+ end
291
+
292
+ def to_hash
293
+ {address: @address, address_md5: @address_md5}.reject { |_, value| value.nil? }
294
+ end
295
+
296
+ def username
297
+ @address.split('@')[0] if is_valid_email?
298
+ end
299
+
300
+ def domain
301
+ @address.split('@')[1] if is_valid_email?
302
+ end
303
+
304
+ end
305
+
306
+
307
+ class Job < Field
308
+
309
+ attr_accessor :title, :organization, :industry, :date_range, :display
310
+
311
+ def initialize(params={})
312
+ super params
313
+ @title = params[:title]
314
+ @organization = params[:organization]
315
+ @industry = params[:industry]
316
+ @date_range = params[:date_range]
317
+ @display = params[:display]
318
+ end
319
+
320
+ def to_hash
321
+ {title: @title, organization: @organization, industry: @industry,
322
+ date_range: @date_range ? @date_range.to_hash : nil}
323
+ .reject { |_, value| value.nil? }
324
+ end
325
+
326
+ def to_s
327
+ return @display if @display
328
+
329
+ if @title and @organization
330
+ s = @title + ' at ' + @organization
331
+ else
332
+ s = @title || @organization
333
+ end
334
+
335
+ if s and @industry
336
+ if @date_range
337
+ range = @date_range.years_range
338
+ s += ' (%s, %d-%d)' % [@industry, range[0], range[1]]
339
+ else
340
+ s += ' (%s)' % [@industry]
341
+ end
342
+ else
343
+ s = ((s || '') + ' ' + (@industry || '')).strip
344
+ if s and @date_range
345
+ range = @date_range.years_range
346
+ s += ' (%d-%d)' % [range[0], range[1]]
347
+ end
348
+ end
349
+
350
+ s ? Pipl::Utils.to_utf8(s) : ''
351
+ end
352
+
353
+ end
354
+
355
+
356
+ class Education < Field
357
+
358
+ attr_accessor :degree, :school, :date_range, :display
359
+
360
+ def initialize(params={})
361
+ super params
362
+ @degree = params[:degree]
363
+ @school = params[:school]
364
+ @date_range = params[:date_range]
365
+ @display = params[:display]
366
+ end
367
+
368
+ def to_hash
369
+ {degree: @degree, school: @school, date_range: @date_range ? @date_range.to_hash : nil}
370
+ .reject { |_, value| value.nil? }
371
+ end
372
+
373
+ def to_s
374
+ return @display if @display
375
+
376
+ if @degree and @school
377
+ s = @degree + ' from ' + @school
378
+ else
379
+ s = @degree || @school
380
+ end
381
+
382
+ if s and @date_range
383
+ range = @date_range.years_range
384
+ s += ' (%d-%d)' % [range[0], range[1]]
385
+ end
386
+
387
+ s ? Pipl::Utils.to_utf8(s) : ''
388
+ end
389
+
390
+ end
391
+
392
+
393
+ class Image < Field
394
+
395
+ attr_accessor :url, :thumbnail_token
396
+
397
+ def initialize(params={})
398
+ super params
399
+ @url = params[:url]
400
+ @thumbnail_token = params[:thumbnail_token]
401
+ end
402
+
403
+ def thumbnail_url(params={})
404
+ return unless @thumbnail_token
405
+
406
+ opts = {width: 100, height: 100, favicon: true, zoom_face: true, use_https: false}.merge(params)
407
+ schema = opts.delete(:use_https) ? 'https': 'http'
408
+ tokens = @thumbnail_token.gsub(/&dsid=.*/,'')
409
+ tokens += ',' + opts.delete(:fallback).thumbnail_token.gsub(/&dsid=.*/,'') if opts[:fallback]
410
+ query_params = ["tokens=#{tokens}"] + opts.map { |k, v| "#{k}=#{v}" unless v.nil? }
411
+ "#{schema}://thumb.pipl.com/image?#{query_params.compact.join('&')}"
412
+ end
413
+
414
+ end
415
+
416
+
417
+ class Username < Field
418
+
419
+ attr_accessor :content
420
+
421
+ def initialize(params={})
422
+ super params
423
+ @content = params[:content]
424
+ end
425
+
426
+ def to_hash
427
+ {content: @content} if @content
428
+ end
429
+
430
+ def is_searchable?
431
+ !@content.nil? and Pipl::Utils.alnum_chars(@content).length > 2
432
+ end
433
+
434
+ end
435
+
436
+
437
+ class UserID < Username
438
+
439
+ def is_searchable?
440
+ not /.+@.+/.match(@content).nil?
441
+ end
442
+
443
+ end
444
+
445
+
446
+ class DOB < Field
447
+
448
+ attr_accessor :date_range, :display
449
+
450
+ def initialize(params={})
451
+ super params
452
+ @date_range = params[:date_range]
453
+ @display = params[:display]
454
+ end
455
+
456
+ def self.from_birth_year(birth_year)
457
+ raise ArgumentError.new('birth_year must be positive') unless birth_year > 0
458
+ self.new({date_range: DateRange.from_years_range(birth_year, birth_year)})
459
+ end
460
+
461
+ def self.from_birth_date(birth_date)
462
+ raise ArgumentError.new('birth_date can\'t be in the future') unless birth_date <= Date.today
463
+ self.new({date_range: DateRange.new(birth_date, birth_date)})
464
+ end
465
+
466
+ def self.from_age(age)
467
+ self.from_age_range(age, age)
468
+ end
469
+
470
+ def self.from_age_range(start_age, end_age)
471
+ raise ArgumentError.new('start_age and end_age can\'t be negative') if start_age < 0 or end_age < 0
472
+
473
+ if start_age > end_age
474
+ start_age, end_age = end_age, start_age
475
+ end
476
+
477
+ today = Date.today
478
+ start_date = today << end_age * 12
479
+ start_date = start_date - 1
480
+ end_date = today << start_age * 12
481
+ self.new({date_range: Pipl::DateRange.new(start_date, end_date)})
482
+ end
483
+
484
+ def to_s
485
+ @display or Pipl::Utils.to_utf8(age.to_s)
486
+ end
487
+
488
+ def age
489
+ unless @date_range.nil?
490
+ dob = @date_range.middle
491
+ today = Date.today
492
+ diff = today.year - dob.year
493
+ diff = diff - 1 if dob.month > today.month or (dob.month >= today.month and dob.day > today.day)
494
+ diff
495
+ end
496
+ end
497
+
498
+ def age_range
499
+ if @date_range
500
+ return [self.age, self.age] unless @date_range.start and @date_range.end
501
+ start_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.start, @date_range.start)}).age
502
+ end_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.end, @date_range.end)}).age
503
+ return end_age, start_age
504
+ else
505
+ return nil, nil
506
+ end
507
+ end
508
+
509
+ def to_hash
510
+ {date_range: @date_range.to_hash} if @date_range
511
+ end
512
+
513
+ def is_searchable?
514
+ not @date_range.nil?
515
+ end
516
+
517
+ end
518
+
519
+
520
+ class Url < Field
521
+ # @!attribute url
522
+ # @return [String] Actual Url
523
+ # @!attribute category
524
+ # @return [String] Category of the domain
525
+ # Possible values are:
526
+ # background_reports
527
+ # contact_details
528
+ # email_address
529
+ # media
530
+ # personal_profiles
531
+ # professional_and_business
532
+ # public_records
533
+ # publications
534
+ # school_and_classmates
535
+ # web_pages
536
+ # @!attribute domain
537
+ # @return [String] Canonical domain of the url
538
+ # @!attribute name
539
+ # @return [String] Name of the website hosting the url
540
+ # @!attribute sponsored
541
+ # @return [Boolean] Indicate if this url comes from a sponsored data source
542
+ # @!attribute sponsored
543
+ # @return [String] Unique identifier of this url
544
+
545
+ attr_accessor :url, :category, :domain, :name, :sponsored, :source_id
546
+
547
+ def initialize(params={})
548
+ super params
549
+ @url = params[:url]
550
+ @category = params[:category]
551
+ @domain = params[:domain]
552
+ @name = params[:name]
553
+ @sponsored = params[:sponsored]
554
+ @source_id = params[:source_id]
555
+ end
556
+
557
+ def self.extra_metadata
558
+ [:category, :domain, :name, :sponsored, :source_id]
559
+ end
560
+
561
+ def is_searchable?
562
+ not @url.to_s.empty?
563
+ end
564
+
565
+ end
566
+
567
+
568
+ class Gender < Field
569
+
570
+ attr_accessor :content
571
+
572
+ def initialize(params={})
573
+ super params
574
+ @content = params[:content]
575
+ end
576
+
577
+ def to_s
578
+ Pipl::Utils.titleize @content if @content
579
+ end
580
+
581
+ def to_hash
582
+ {content: @content} if @content
583
+ end
584
+
585
+ end
586
+
587
+
588
+ class Ethnicity < Field
589
+
590
+ # @!attribute content
591
+ # @return [String] Ethnicity name based on the U.S Census Bureau.
592
+ # Possible values are:
593
+ # white
594
+ # black
595
+ # american_indian
596
+ # alaska_native
597
+ # asian_indian
598
+ # chinese
599
+ # filipino
600
+ # other_asian
601
+ # japanese
602
+ # korean
603
+ # vietnamese
604
+ # native_hawaiian
605
+ # guamanian
606
+ # chamorro
607
+ # samoan
608
+ # other_pacific_islander
609
+ # other
610
+
611
+ attr_accessor :content
612
+
613
+ def initialize(params={})
614
+ super params
615
+ @content = params[:content]
616
+ end
617
+
618
+ def to_s
619
+ Pipl::Utils.titleize @content.gsub(/_/, ' ') if @content
620
+ end
621
+
622
+ end
623
+
624
+
625
+ class Language < Field
626
+
627
+ attr_accessor :language, :region, :display
628
+
629
+ def initialize(params={})
630
+ super params
631
+ @language = params[:language]
632
+ @region = params[:region]
633
+ @display = params[:display]
634
+ end
635
+
636
+ def to_s
637
+ return @display if @display
638
+ return "#{@language}_#{@region}" if @language and @region
639
+ return @language if @language and not @language.empty?
640
+ @region
641
+ end
642
+
643
+ end
644
+
645
+
646
+ class OriginCountry < Field
647
+
648
+ attr_accessor :country
649
+
650
+ def initialize(params={})
651
+ super params
652
+ @country = params[:country]
653
+ end
654
+
655
+ def to_s
656
+ Pipl::COUNTRIES[@country.upcase.to_sym] if @country
657
+ end
658
+
659
+ end
660
+
661
+
662
+ class Tag < Field
663
+
664
+ attr_accessor :content, :classification
665
+
666
+ def initialize(params={})
667
+ super params
668
+ @content = params[:content]
669
+ @classification = params[:classification]
670
+ end
671
+
672
+ def self.extra_metadata
673
+ [:classification]
674
+ end
675
+
676
+ def to_s
677
+ @content
678
+ end
679
+
680
+ end
681
+
682
+
683
+ class DateRange
684
+
685
+ attr_reader :start, :end
686
+
687
+ def initialize(start, end_)
688
+ @start = start
689
+ @end = end_
690
+ if @start and @end and @start > @end
691
+ @start, @end = @end, @start
692
+ end
693
+ end
694
+
695
+ # def ==(other)
696
+ # other.instance_of?(self.class) and inspect == other.inspect
697
+ # end
698
+ #
699
+ # alias_method :eql?, :==
700
+
701
+ def is_exact?
702
+ @start and @end and @start == @end
703
+ end
704
+
705
+ def middle
706
+ @start and @end ? @start + ((@end - @start) / 2) : @start or @end
707
+ end
708
+
709
+ def years_range
710
+ [@start.year, @end.year] if @start and @end
711
+ end
712
+
713
+ def self.from_years_range(start_year, end_year)
714
+ self.new(Date.new(start_year, 1, 1), Date.new(end_year, 12, 31))
715
+ end
716
+
717
+ def self.from_hash(h)
718
+ start_, end_ = h[:start], h[:end]
719
+ initializing_start = start_ ? Date.strptime(start_, Pipl::DATE_FORMAT) : nil
720
+ initializing_end = end_ ? Date.strptime(end_, Pipl::DATE_FORMAT) : nil
721
+ self.new(initializing_start, initializing_end)
722
+ end
723
+
724
+ def to_hash
725
+ h = {}
726
+ h[:start] = @start.strftime(Pipl::DATE_FORMAT) if @start
727
+ h[:end] = @end.strftime(Pipl::DATE_FORMAT) if @end
728
+ h
729
+ end
730
+ end
731
+
732
+ end