governator 0.1.10 → 0.1.11

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 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