governator 0.1.10 → 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '0786767ccd0fd5582e610a5624efa8637ad61996'
4
- data.tar.gz: 27c94c6ec91d39c2a4f7db1508fc9175b0bdef26
3
+ metadata.gz: cc2cfb3afb5a52e54736ec79856170eae33635ca
4
+ data.tar.gz: 5d707a4ee20acefff40bc18565615e9df2bf9d29
5
5
  SHA512:
6
- metadata.gz: dc01befab8bb1465b9704709ea70df929b028fdb79aed39db6c9eb6fb4879afe5ade43623ddda86de8a5f0455f8c585ab7471cd55f12aea773e1639f3f3bde9e
7
- data.tar.gz: c3ff56a12367e31f02ed04124fad148bcc2d57845e9dc677a9a610235c9daa9bd07e5dfede666446ee8015dcd9afebba0bbd5f7a1895e3f0d3ba34c4a94f7297
6
+ metadata.gz: a06d70c950212b8f5436b9d4085841c14f67f7443eab04a2f14f4c9cba7076e56709c2f9ccdba9094c932f643f7d854190d24bac4bed708a791e9be472e101f7
7
+ data.tar.gz: 71ab8dd0ac9214a82982e54c01ba2ceff210403e50c0c0f94949b59c9ba3e12d452b5fddc47acb247645d92a134098eb50c2240e65892121cf2b9542cba9bc5a
data/governator.gemspec CHANGED
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.required_ruby_version = '>= 2.3'
25
25
 
26
- spec.add_dependency 'faraday', '~> 0.11.0'
27
26
  spec.add_dependency 'twitter', '~> 6.1.0'
28
27
  spec.add_dependency 'nokogiri', '~> 1.8.0'
29
28
 
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'governator/http_client'
3
4
  require 'governator/page_scraper'
4
5
 
5
6
  class Governator
7
+ # Scrapes the biographical page for an individual Governor
6
8
  class BioPage < PageScraper
9
+ include HTTPClient
10
+
7
11
  attr_reader :uri
8
12
 
9
13
  def initialize(uri)
10
14
  @uri = uri
11
- @raw = Nokogiri::HTML(CONN.get(uri).body)
15
+ @raw = Nokogiri::HTML get_page_contents(uri)
12
16
  check_for_alt_office
13
17
  end
14
18
 
15
19
  def check_for_alt_office
16
- @alt_office_present = if raw.css('address')[2].to_s =~ /Phone|Address|Fax/
17
- true
18
- else
19
- false
20
- end
20
+ @alt_office_present =
21
+ raw.css('address')[2].to_s =~ /Phone|Address|Fax/ ? true : false
21
22
  end
22
23
 
23
24
  def alt_office_present?
@@ -29,11 +30,7 @@ class Governator
29
30
  end
30
31
 
31
32
  def party_panel
32
- @_party_panel ||= if alt_office_present?
33
- raw.css('address')[3]
34
- else
35
- raw.css('address')[2]
36
- end
33
+ @_party_panel ||= alt_office_present? ? raw.css('address')[3] : raw.css('address')[2]
37
34
  end
38
35
 
39
36
  def party_paragraph
@@ -72,7 +69,7 @@ class Governator
72
69
  end
73
70
 
74
71
  def phone
75
- @_phone ||= address_paragraph_text(4)&.delete("\t\nPhone: ").strip.sub('/', '-')
72
+ @_phone ||= address_paragraph_text(4)&.delete("\t\nPhone: ")&.strip&.sub('/', '-')
76
73
  end
77
74
 
78
75
  def fax
@@ -1,12 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'governator/http_client'
4
+
3
5
  class Governator
4
6
  class CivilServices
7
+ extend HTTPClient
8
+
5
9
  def self.json
6
- @_json ||= JSON.parse Faraday.get(
7
- 'https://raw.githubusercontent.com/CivilServiceUSA/us-governors'\
8
- '/master/us-governors/data/us-governors.json'
9
- ).body
10
+ @_json ||= JSON.parse get_page_contents(uri)
11
+ end
12
+
13
+ def self.uri
14
+ @_uri ||= 'https://raw.githubusercontent.com/CivilServiceUSA/us-governors'\
15
+ '/master/us-governors/data/us-governors.json'
10
16
  end
11
17
 
12
18
  attr_reader :governor
@@ -39,7 +45,7 @@ class Governator
39
45
 
40
46
  def record
41
47
  @_record ||= self.class.json.detect do |record|
42
- record['last_name'] == governor.last && record['state_name'] == governor.state_name
48
+ record['last_name'] == governor.last_name && record['state_name'] == governor.state_name
43
49
  end
44
50
  end
45
51
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Governator
4
+ # The configuration object to set Twitter preferences and keys
5
+ class Config
6
+ class << self
7
+ attr_reader :use_twitter
8
+
9
+ def use_twitter=(boolean)
10
+ raise ArgumentError, 'value must be Boolean value' unless [true, false].include? boolean
11
+ @use_twitter = boolean
12
+ require 'twitter' if use_twitter?
13
+ end
14
+
15
+ def use_twitter?
16
+ @use_twitter || false
17
+ end
18
+
19
+ def twitter(&block)
20
+ Governator::TwitterClient.config(&block)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Governator
4
+ # Instance methods included in the Governator class
5
+ class Governor
6
+ attr_reader :panel, :state_name, :official_full, :parsed_name,
7
+ :url, :party, :office_locations, :twitter
8
+
9
+ private :panel
10
+
11
+ def self.create(panel)
12
+ new(panel).tap do |g|
13
+ g.send :build
14
+ g.send :save
15
+ end
16
+ end
17
+
18
+ # Initializes a new Governator instance
19
+ #
20
+ # @param panel [Governator::Panel] the Panel scraper object to pull the Governor's data from
21
+ def initialize(panel)
22
+ @panel = panel
23
+ end
24
+
25
+ def to_h
26
+ syms = %i[photo_url state_name official_full url party office_locations]
27
+ syms.each_with_object({}) do |sym, hsh|
28
+ hsh[sym] = send(sym)
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ "#<Governator #{official_full}>"
34
+ end
35
+
36
+ def secondary_office
37
+ @_secondary_office ||= office(prefix: :alt_)
38
+ end
39
+
40
+ def primary_office
41
+ @_primary_office ||= office
42
+ end
43
+
44
+ def photo_url
45
+ civil_services.photo_url || Governator::BASE_URI + panel.image
46
+ end
47
+
48
+ def facebook
49
+ civil_services.facebook
50
+ end
51
+
52
+ def contact_form
53
+ civil_services.contact_form
54
+ end
55
+
56
+ def first_name
57
+ parsed_name.first_name
58
+ end
59
+
60
+ def middle_name
61
+ parsed_name.middle_name
62
+ end
63
+
64
+ def nickname
65
+ parsed_name.nickname
66
+ end
67
+
68
+ def last_name
69
+ parsed_name.last_name
70
+ end
71
+
72
+ def suffix
73
+ parsed_name.suffix
74
+ end
75
+
76
+ private
77
+
78
+ def office(prefix: nil)
79
+ syms = %i[address city state zip phone fax office_type]
80
+ syms.each_with_object(Governator::Office.new) do |sym, office|
81
+ office[sym] = bio_page.send("#{prefix}#{sym}")
82
+ end
83
+ end
84
+
85
+ def build
86
+ @state_name = panel.state
87
+ @official_full = panel.governor_name
88
+ @url = bio_page.website
89
+ @party = bio_page.party
90
+
91
+ @parsed_name = Governator::NameParser.new(official_full).parse
92
+ build_office_locations
93
+ fetch_twitter_handle
94
+ self
95
+ end
96
+
97
+ def bio_page
98
+ @_bio_page ||= Governator::BioPage.new(panel.bio_page)
99
+ end
100
+
101
+ def civil_services
102
+ @_civil_services ||= Governator::CivilServices.new(self)
103
+ end
104
+
105
+ def fetch_twitter_handle
106
+ check_twitter_client_for_handle if Governator::Config.use_twitter?
107
+ @twitter = civil_services.twitter if twitter.nil?
108
+ end
109
+
110
+ def check_twitter_client_for_handle
111
+ twitter_governor = Governator::TwitterClient.governors.detect do |tg|
112
+ tg[:name].match(last_name) && (
113
+ tg[:location].match(state_name) || tg[:description].match(state_name)
114
+ )
115
+ end
116
+
117
+ @twitter = twitter_governor[:screen_name] if twitter_governor
118
+ end
119
+
120
+ def build_office_locations
121
+ @office_locations = [primary_office]
122
+ @office_locations << secondary_office if bio_page.alt_office_present?
123
+ end
124
+
125
+ def save
126
+ Governator.governors << self
127
+ self
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+
5
+ class Governator
6
+ # Module to mixin HTTP web content retrieval
7
+ module HTTPClient
8
+ module_function
9
+
10
+ def get_page_contents(path)
11
+ path = URI.parse(path)
12
+ uri = path.relative? ? "#{base_uri}#{path}" : path
13
+ open(uri, &:read)
14
+ end
15
+
16
+ def base_uri
17
+ @base_uri ||= 'https://www.nga.org'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Governator
4
+ # Data structure for a governor's parsed name
5
+ Name = Struct.new(:full_name, :first_name, :middle_name, :nickname, :last_name, :suffix) do
6
+ def to_s
7
+ full_name
8
+ end
9
+ end
10
+ end
@@ -1,61 +1,113 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Governator
4
+ # Parses a name into it's component parts
5
+ #
6
+ # === Example
7
+ # name = "Firstname Two Middlenames 'Nick Name' Lastname, Suffix."
8
+ # name_parser = NameParser.new(name)
9
+ # parsed_name = name_parser.parse
10
+ # parsed_name.first_name # => "Firstname"
11
+ # parsed_name.middle_name # => "Two Middlenames"
12
+ # parsed_name.nickname # => "Nick Name"
13
+ # parsed_name.last_name # => "Lastname"
14
+ # parsed_name.suffix # => "Suffix"
4
15
  class NameParser
5
- attr_reader :official_full, :first, :last, :middle, :nickname, :suffix
6
-
7
- def initialize(official_full)
8
- @official_full = official_full
16
+ # Initialize a new NameParser object
17
+ #
18
+ # @param original [String] the original name to parse
19
+ def initialize(original)
20
+ @original = original
9
21
  end
10
22
 
23
+ # Parse the name into its component parts
24
+ #
25
+ # @api public
26
+ #
27
+ # @return [Governator::Name]
11
28
  def parse
12
29
  split_name
13
- [first, last, middle, nickname, suffix]
30
+ Governator::Name.new(original, first, middle, nickname, last, suffix)
14
31
  end
15
32
 
16
- def name_array
17
- @_name_array ||= official_full.split(' ')
33
+ private
34
+
35
+ attr_reader :original, :first, :last, :middle, :nickname, :suffix
36
+
37
+ # @api private
38
+ def first_middle_last
39
+ @_first_middle_last ||= parsing_copy.split(' ')
18
40
  end
19
41
 
42
+ # @api private
20
43
  def split_name
21
44
  detect_nickname
22
45
  detect_suffix
23
- if name_array.length == 2
46
+ if two_part_name?
24
47
  set_first_and_last
25
- elsif name_array[0].include?('.') && name_array[1].include?('.')
48
+ elsif initialed_first_name?
26
49
  set_name_with_initialed_first
27
- elsif name_array.length >= 3
50
+ elsif three_or_more_part_name?
28
51
  set_first_last_and_middle
29
52
  end
30
53
  end
31
54
 
55
+ # @api private
56
+ def three_or_more_part_name?
57
+ first_middle_last.length >= 3
58
+ end
59
+
60
+ # @api private
61
+ def initialed_first_name?
62
+ first_middle_last[0].include?('.') && first_middle_last[1].include?('.')
63
+ end
64
+
65
+ # @api private
66
+ def two_part_name?
67
+ first_middle_last.length == 2
68
+ end
69
+
70
+ # @api private
32
71
  def set_first_and_last
33
- @first = name_array.first
34
- @last = name_array.last
72
+ @first = first_middle_last.first
73
+ @last = first_middle_last.last
35
74
  end
36
75
 
76
+ # @api private
37
77
  def set_name_with_initialed_first
38
- @first = "#{name_array.shift} #{name_array.shift}"
39
- @last = name_array.pop
40
- @middle = name_array.pop
78
+ @first = "#{first_middle_last.first} #{first_middle_last[1]}"
79
+ @last = first_middle_last.last
80
+ @middle = first_middle_last[2..-2].join(' ')
41
81
  end
42
82
 
83
+ # @api private
43
84
  def set_first_last_and_middle
44
- @first = name_array.shift
45
- @last = name_array.pop
46
- @middle = name_array.join(' ')
85
+ @first = first_middle_last.first
86
+ @last = first_middle_last.last
87
+ @middle = first_middle_last[1..-2].join(' ')
47
88
  end
48
89
 
90
+ # @api private
49
91
  def detect_nickname
50
- @nickname = name_array.detect { |name| name.include?('"') }
51
- name_array.reject! { |name| name.include?('"') }
92
+ match_data = parsing_copy.match(/\s["'][\w\s]+["']/)
93
+ return unless match_data
94
+ @nickname = match_data.to_s if match_data
95
+ parsing_copy.sub!(nickname, '')
96
+ nickname.sub!(' ', '')
52
97
  end
53
98
 
99
+ # @api private
54
100
  def detect_suffix
55
- @suffix = if name_array[-2].include?(',')
56
- name_array[-2].delete(',')
57
- name_array.pop
58
- end
101
+ match_data = parsing_copy.match(/,\s\w+\.?/)
102
+ return unless match_data
103
+ @suffix = match_data.to_s if match_data
104
+ parsing_copy.sub!(suffix, '')
105
+ suffix.delete!(', ')
106
+ end
107
+
108
+ # @api private
109
+ def parsing_copy
110
+ @_parsing_copy ||= original.dup
59
111
  end
60
112
  end
61
113
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Governator
4
- Office = Struct.new *%i[address city state zip phone fax office_type] do
4
+ Office = Struct.new(:address, :city, :state, :zip, :phone, :fax, :office_type) do
5
5
  def to_s
6
6
  [address, city, state, zip, phone, fax].compact.join("\n")
7
7
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Governator
4
+ # Wrapper for the Twitter client with convenience methods
4
5
  class TwitterClient
5
6
  class << self
6
7
  attr_reader :client
7
8
 
8
9
  def config(&block)
9
10
  @client = Twitter::REST::Client.new(&block)
11
+ rescue NameError
12
+ raise RuntimeError, 'set `use_twitter` configuration option to true if you '\
13
+ ' wish to configure and use a Twitter client'
10
14
  end
11
15
 
12
16
  def governors
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Governator
4
- VERSION = '0.1.10'
4
+ VERSION = '0.1.11'
5
5
  end
data/lib/governator.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday'
4
3
  require 'nokogiri'
5
- require 'twitter'
4
+ require 'open-uri'
6
5
 
7
6
  require 'governator/bio_page'
8
7
  require 'governator/civil_services'
8
+ require 'governator/config'
9
+ require 'governator/governor'
10
+ require 'governator/http_client'
11
+ require 'governator/name'
9
12
  require 'governator/name_parser'
10
13
  require 'governator/office'
11
14
  require 'governator/panel'
@@ -15,16 +18,13 @@ require 'governator/version'
15
18
  # Governator.scrape!
16
19
  # governors = Governor.governors
17
20
  class Governator
18
- BASE_URI = 'https://www.nga.org'
19
- CONN = Faraday.new(url: BASE_URI)
20
-
21
- class << self # <-- Begin class methods
22
- attr_reader :use_twitter
21
+ class << self
22
+ include HTTPClient
23
23
 
24
24
  def scrape!
25
25
  governors.clear
26
26
  panels.each do |panel|
27
- governor = create(panel)
27
+ governor = Governator::Governor.create(panel)
28
28
  puts "Scraped #{governor.official_full} of #{governor.state_name}"
29
29
  end
30
30
 
@@ -36,134 +36,28 @@ class Governator
36
36
  @_governors ||= []
37
37
  end
38
38
 
39
- def to_json
39
+ def serialize
40
40
  governors.map(&:to_h)
41
41
  end
42
- alias serialize to_json
43
42
 
44
43
  def config
45
- yield self
46
- end
47
-
48
- def twitter(&block)
49
- TwitterClient.config(&block)
44
+ yield Governator::Config
50
45
  end
51
46
 
52
47
  def twitter_client
53
- TwitterClient.client
48
+ Governator::TwitterClient.client
54
49
  end
55
50
 
56
- def use_twitter=(boolean)
57
- raise ArgumentError, 'value must be Boolean value' unless [true, false].include? boolean
58
- @use_twitter = boolean
59
- end
60
-
61
- private # private class methods
51
+ private
62
52
 
63
53
  def index_page
64
- @_index_page ||= Nokogiri::HTML(CONN.get('/cms/governors/bios').body)
54
+ @_index_page ||= Nokogiri::HTML get_page_contents('/cms/governors/bios')
65
55
  end
66
56
 
67
57
  def panels
68
58
  @_panels ||= index_page.css('.panel.panel-default.governors').map do |panel|
69
- Panel.new(panel)
70
- end
71
- end
72
-
73
- def create(panel)
74
- new(panel).tap do |g|
75
- g.send :build
76
- g.send :save
77
- end
78
- end
79
- end # <-- End class methods
80
-
81
- attr_reader :panel, :state_name, :bio_page, :official_full, :first, :last,
82
- :middle, :nickname, :suffix, :url, :party, :office_locations,
83
- :twitter
84
-
85
- def initialize(panel)
86
- @panel = panel
87
- end
88
-
89
- def to_h
90
- syms = %i[photo_url state_name official_full url party office_locations]
91
- syms.each_with_object({}) do |sym, hsh|
92
- hsh[sym] = send(sym)
93
- end
94
- end
95
-
96
- def inspect
97
- "#<Governator #{official_full}>"
98
- end
99
-
100
- def secondary_office
101
- @_secondary_office ||= office(prefix: :alt_)
102
- end
103
-
104
- def primary_office
105
- @_primary_office ||= office
106
- end
107
-
108
- def office(prefix: nil)
109
- syms = %i[address city state zip phone fax office_type]
110
- syms.each_with_object(Office.new) do |sym, office|
111
- office[sym] = bio_page.send("#{prefix}#{sym}")
112
- end
113
- end
114
-
115
- def photo_url
116
- civil_services.photo_url || BASE_URI + panel.image
117
- end
118
-
119
- def facebook
120
- civil_services.facebook
121
- end
122
-
123
- def contact_form
124
- civil_services.contact_form
125
- end
126
-
127
- private
128
-
129
- def build
130
- @bio_page = BioPage.new(panel.bio_page)
131
- @state_name = panel.state
132
- @official_full = panel.governor_name
133
- @url = bio_page.website
134
- @party = bio_page.party
135
-
136
- @first, @last, @middle, @nickname, @suffix = NameParser.new(official_full).parse
137
- build_office_locations
138
- fetch_twitter_handle
139
- self
140
- end
141
-
142
- def civil_services
143
- @_civil_services ||= CivilServices.new(self)
144
- end
145
-
146
- def fetch_twitter_handle
147
- if self.class.use_twitter == true
148
- twitter_governor = TwitterClient.governors.detect do |tg|
149
- tg[:name].match(last) && (
150
- tg[:location].match(state_name) || tg[:description].match(state_name)
151
- )
59
+ Governator::Panel.new(panel)
152
60
  end
153
-
154
- @twitter = twitter_governor[:screen_name] if twitter_governor
155
61
  end
156
-
157
- @twitter = civil_services.twitter if twitter.nil?
158
- end
159
-
160
- def build_office_locations
161
- @office_locations = [primary_office]
162
- @office_locations << secondary_office if bio_page.alt_office_present?
163
- end
164
-
165
- def save
166
- self.class.governors << self
167
- self
168
62
  end
169
63
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: governator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - M. Simon Borg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-24 00:00:00.000000000 Z
11
+ date: 2017-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: faraday
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.11.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.11.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: twitter
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +79,10 @@ files:
93
79
  - lib/governator.rb
94
80
  - lib/governator/bio_page.rb
95
81
  - lib/governator/civil_services.rb
82
+ - lib/governator/config.rb
83
+ - lib/governator/governor.rb
84
+ - lib/governator/http_client.rb
85
+ - lib/governator/name.rb
96
86
  - lib/governator/name_parser.rb
97
87
  - lib/governator/office.rb
98
88
  - lib/governator/page_scraper.rb