hdo-storting-importer 0.0.8 → 0.0.9

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.
Files changed (50) hide show
  1. data/Gemfile +1 -1
  2. data/Rakefile +0 -1
  3. data/features/convert.feature +97 -79
  4. data/hdo-storting-importer.gemspec +5 -0
  5. data/lib/hdo/storting_importer/category.rb +16 -56
  6. data/lib/hdo/storting_importer/cli.rb +6 -34
  7. data/lib/hdo/storting_importer/committee.rb +13 -24
  8. data/lib/hdo/storting_importer/converter.rb +4 -9
  9. data/lib/hdo/storting_importer/district.rb +12 -24
  10. data/lib/hdo/storting_importer/fusion_table.rb +52 -0
  11. data/lib/hdo/storting_importer/has_json_schema.rb +85 -0
  12. data/lib/hdo/storting_importer/issue.rb +29 -60
  13. data/lib/hdo/storting_importer/party.rb +13 -24
  14. data/lib/hdo/storting_importer/promise.rb +60 -55
  15. data/lib/hdo/storting_importer/proposition.rb +61 -0
  16. data/lib/hdo/storting_importer/representative.rb +37 -70
  17. data/lib/hdo/storting_importer/schema/category.json +28 -0
  18. data/lib/hdo/storting_importer/schema/committee.json +21 -0
  19. data/lib/hdo/storting_importer/schema/district.json +21 -0
  20. data/lib/hdo/storting_importer/schema/issue.json +62 -0
  21. data/lib/hdo/storting_importer/schema/party.json +21 -0
  22. data/lib/hdo/storting_importer/schema/promise.json +42 -0
  23. data/lib/hdo/storting_importer/schema/proposition.json +34 -0
  24. data/lib/hdo/storting_importer/schema/representative.json +61 -0
  25. data/lib/hdo/storting_importer/schema/vote.json +64 -0
  26. data/lib/hdo/storting_importer/schema.json +5 -0
  27. data/lib/hdo/storting_importer/util.rb +13 -11
  28. data/lib/hdo/storting_importer/version.rb +1 -1
  29. data/lib/hdo/storting_importer/vote.rb +46 -143
  30. data/lib/hdo/storting_importer.rb +12 -3
  31. data/spec/fixtures/output/categories.json +1 -0
  32. data/spec/fixtures/output/committees.json +1 -0
  33. data/spec/fixtures/output/districts.json +1 -0
  34. data/spec/fixtures/output/issues.json +1 -0
  35. data/spec/fixtures/output/parties.json +1 -0
  36. data/spec/fixtures/output/representatives.json +1 -0
  37. data/spec/fixtures/output/votes.json +1 -0
  38. data/spec/hdo/storting_importer/category_spec.rb +29 -33
  39. data/spec/hdo/storting_importer/committee_spec.rb +29 -16
  40. data/spec/hdo/storting_importer/converter_spec.rb +3 -3
  41. data/spec/hdo/storting_importer/district_spec.rb +27 -18
  42. data/spec/hdo/storting_importer/issue_spec.rb +44 -27
  43. data/spec/hdo/storting_importer/party_spec.rb +25 -16
  44. data/spec/hdo/storting_importer/promise_spec.rb +54 -40
  45. data/spec/hdo/storting_importer/proposition_spec.rb +44 -0
  46. data/spec/hdo/storting_importer/representative_spec.rb +73 -26
  47. data/spec/hdo/storting_importer/vote_spec.rb +73 -75
  48. data/spec/spec_helper.rb +21 -1
  49. metadata +95 -3
  50. data/.gitmodules +0 -3
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source :rubygems
2
2
 
3
- gemspec
3
+ gemspec
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rake'
2
2
  require 'rspec/core/rake_task'
3
- require 'pry'
4
3
 
5
4
  RSpec::Core::RakeTask.new
6
5
 
@@ -107,83 +107,101 @@ Feature: Import data
107
107
  When I run `hdo-converter districts fylker.xml`
108
108
  Then the stdout should contain:
109
109
  """
110
- <?xml version="1.0" encoding="UTF-8"?>
111
- <districts>
112
- <district>
113
- <externalId>AA</externalId>
114
- <name>Aust-Agder</name>
115
- </district>
116
- <district>
117
- <externalId>VA</externalId>
118
- <name>Vest-Agder</name>
119
- </district>
120
- <district>
121
- <externalId>Ak</externalId>
122
- <name>Akershus</name>
123
- </district>
124
- <district>
125
- <externalId>Bu</externalId>
126
- <name>Buskerud</name>
127
- </district>
128
- <district>
129
- <externalId>Fi</externalId>
130
- <name>Finnmark</name>
131
- </district>
132
- <district>
133
- <externalId>He</externalId>
134
- <name>Hedmark</name>
135
- </district>
136
- <district>
137
- <externalId>Ho</externalId>
138
- <name>Hordaland</name>
139
- </district>
140
- <district>
141
- <externalId>MR</externalId>
142
- <name>Møre og Romsdal</name>
143
- </district>
144
- <district>
145
- <externalId>No</externalId>
146
- <name>Nordland</name>
147
- </district>
148
- <district>
149
- <externalId>Op</externalId>
150
- <name>Oppland</name>
151
- </district>
152
- <district>
153
- <externalId>Os</externalId>
154
- <name>Oslo</name>
155
- </district>
156
- <district>
157
- <externalId>Ro</externalId>
158
- <name>Rogaland</name>
159
- </district>
160
- <district>
161
- <externalId>SF</externalId>
162
- <name>Sogn og Fjordane</name>
163
- </district>
164
- <district>
165
- <externalId>Te</externalId>
166
- <name>Telemark</name>
167
- </district>
168
- <district>
169
- <externalId>Tr</externalId>
170
- <name>Troms</name>
171
- </district>
172
- <district>
173
- <externalId>NT</externalId>
174
- <name>Nord-Trøndelag</name>
175
- </district>
176
- <district>
177
- <externalId>ST</externalId>
178
- <name>Sør-Trøndelag</name>
179
- </district>
180
- <district>
181
- <externalId>Ve</externalId>
182
- <name>Vestfold</name>
183
- </district>
184
- <district>
185
- <externalId>Øs</externalId>
186
- <name>Østfold</name>
187
- </district>
188
- </districts>
110
+ [
111
+ {
112
+ "kind": "hdo#district",
113
+ "externalId": "AA",
114
+ "name": "Aust-Agder"
115
+ },
116
+ {
117
+ "kind": "hdo#district",
118
+ "externalId": "VA",
119
+ "name": "Vest-Agder"
120
+ },
121
+ {
122
+ "kind": "hdo#district",
123
+ "externalId": "Ak",
124
+ "name": "Akershus"
125
+ },
126
+ {
127
+ "kind": "hdo#district",
128
+ "externalId": "Bu",
129
+ "name": "Buskerud"
130
+ },
131
+ {
132
+ "kind": "hdo#district",
133
+ "externalId": "Fi",
134
+ "name": "Finnmark"
135
+ },
136
+ {
137
+ "kind": "hdo#district",
138
+ "externalId": "He",
139
+ "name": "Hedmark"
140
+ },
141
+ {
142
+ "kind": "hdo#district",
143
+ "externalId": "Ho",
144
+ "name": "Hordaland"
145
+ },
146
+ {
147
+ "kind": "hdo#district",
148
+ "externalId": "MR",
149
+ "name": "Møre og Romsdal"
150
+ },
151
+ {
152
+ "kind": "hdo#district",
153
+ "externalId": "No",
154
+ "name": "Nordland"
155
+ },
156
+ {
157
+ "kind": "hdo#district",
158
+ "externalId": "Op",
159
+ "name": "Oppland"
160
+ },
161
+ {
162
+ "kind": "hdo#district",
163
+ "externalId": "Os",
164
+ "name": "Oslo"
165
+ },
166
+ {
167
+ "kind": "hdo#district",
168
+ "externalId": "Ro",
169
+ "name": "Rogaland"
170
+ },
171
+ {
172
+ "kind": "hdo#district",
173
+ "externalId": "SF",
174
+ "name": "Sogn og Fjordane"
175
+ },
176
+ {
177
+ "kind": "hdo#district",
178
+ "externalId": "Te",
179
+ "name": "Telemark"
180
+ },
181
+ {
182
+ "kind": "hdo#district",
183
+ "externalId": "Tr",
184
+ "name": "Troms"
185
+ },
186
+ {
187
+ "kind": "hdo#district",
188
+ "externalId": "NT",
189
+ "name": "Nord-Trøndelag"
190
+ },
191
+ {
192
+ "kind": "hdo#district",
193
+ "externalId": "ST",
194
+ "name": "Sør-Trøndelag"
195
+ },
196
+ {
197
+ "kind": "hdo#district",
198
+ "externalId": "Ve",
199
+ "name": "Vestfold"
200
+ },
201
+ {
202
+ "kind": "hdo#district",
203
+ "externalId": "Øs",
204
+ "name": "Østfold"
205
+ }
206
+ ]
189
207
  """
@@ -22,4 +22,9 @@ Gem::Specification.new do |gem|
22
22
  gem.add_runtime_dependency "rest-client"
23
23
  gem.add_runtime_dependency "unicode_utils"
24
24
  gem.add_runtime_dependency "multi_json"
25
+ gem.add_runtime_dependency "yajl-ruby"
26
+ gem.add_runtime_dependency "jschematic", ">= 0.1.0"
27
+
28
+ gem.add_development_dependency 'pry'
29
+ gem.add_development_dependency 'rspec'
25
30
  end
@@ -3,25 +3,12 @@ module Hdo
3
3
  class Category
4
4
  include IvarEquality
5
5
  include Inspectable
6
+ include HasJsonSchema
6
7
 
7
8
  attr_reader :external_id, :name
8
9
  attr_accessor :children
9
10
 
10
- def self.type_name
11
- 'category'
12
- end
13
-
14
- def self.description
15
- 'a parliamentary category, used to categorize issues and promises'
16
- end
17
-
18
- def self.fields
19
- [
20
- EXTERNAL_ID_FIELD,
21
- Field.new(:name, true, :string, 'The name of the category.'),
22
- Field.new(:subcategories, false, 'list<category>', 'A list of subcategories.'),
23
- ]
24
- end
11
+ schema_path StortingImporter.lib.join("hdo/storting_importer/schema/category.json").to_s
25
12
 
26
13
  def self.example
27
14
  cat = new("5", "Employment")
@@ -30,8 +17,8 @@ module Hdo
30
17
  cat
31
18
  end
32
19
 
33
- def self.xml_example(builder = Util.builder)
34
- example.to_hdo_xml(builder)
20
+ def self.json_example
21
+ Util.json_pretty example
35
22
  end
36
23
 
37
24
  #
@@ -61,32 +48,11 @@ module Hdo
61
48
  cat
62
49
  end
63
50
 
64
- #
65
- # Deserialize from a HDO XML document (<categories><category>...</category></categories>)
66
- #
67
- # @param [Nokogiri::XML::Element]
68
- # @return [Array<Category>]
69
- #
70
-
71
-
72
- def self.from_hdo_doc(doc)
73
- doc.css("categories > category").map { |node| from_hdo_node(node) }
74
- end
75
-
76
- #
77
- # Deserialize from a HDO XML node
78
- #
79
- # @return [Category]
80
- #
81
-
82
- def self.from_hdo_node(node)
83
- external_id = node.css("externalId").first.text
84
- name = node.css("name").first.text
51
+ def self.from_hash(data)
52
+ obj = new data['externalId'], data['name']
53
+ obj.children = Array(data['subCategories']).map { |e| from_hash(e) }
85
54
 
86
- cat = new external_id, name
87
- cat.children = node.css("subcategories category").map { |e| from_hdo_node(e) }
88
-
89
- cat
55
+ obj
90
56
  end
91
57
 
92
58
  def initialize(external_id, name)
@@ -99,23 +65,17 @@ module Hdo
99
65
  short_inspect_string :include => [:external_id, :name]
100
66
  end
101
67
 
102
- #
103
- # Serialize as HDO XML
104
- #
68
+ def to_hash
69
+ h = {
70
+ :kind => self.class.kind,
71
+ :externalId => @external_id,
72
+ :name => @name
73
+ }
105
74
 
106
- def to_hdo_xml(builder = Util.builder)
107
- builder.category do |cat|
108
- cat.externalId external_id
109
- cat.name name
75
+ h[:subCategories] = @children.map { |e| e.to_hash } if @children.any?
110
76
 
111
- if children.any?
112
- cat.subcategories do |sub|
113
- children.each { |child| child.to_hdo_xml(sub) }
114
- end
115
- end
116
- end
77
+ h
117
78
  end
118
-
119
79
  end
120
80
  end
121
81
  end
@@ -44,21 +44,14 @@ module Hdo
44
44
  klass.from_storting_doc(doc)
45
45
  end.flatten
46
46
 
47
- str = Util.builder do |xml|
48
- xml.instruct!
49
- xml.__send__(plural) do |builder|
50
- objs.each { |e| e.to_hdo_xml(builder) }
51
- end
52
- end
53
-
54
- str
47
+ Util.json_pretty objs
55
48
  end
56
49
 
57
50
  def parse(args)
58
51
  options = {}
59
52
 
60
53
  parser = OptionParser.new do |opt|
61
- types = TYPE_TO_CLASS.keys + [:dld_issues, :dld_votes, :promises]
54
+ types = TYPE_TO_CLASS.keys + [:dld_issues, :dld_votes, :promises, :any]
62
55
  opt.banner = "Usage: #{$0} <#{types.join '|'}> <file(s)>"
63
56
  opt.on("--help", "You're looking at it.") { puts opt; exit; }
64
57
  end
@@ -76,31 +69,15 @@ module Hdo
76
69
  end
77
70
 
78
71
  def read_dld_issues
79
- doc = Nokogiri::XML.parse(File.read(File.join(StortingImporter.root, 'data/dld-issues.xml')))
80
- issues = Issue.from_hdo_doc doc
81
-
82
- Util.builder do |xml|
83
- xml.instruct!
84
- xml.issues do |issues_builder|
85
- issues.each { |i| i.to_hdo_xml(issues_builder) }
86
- end
87
- end
72
+ Util.json_pretty Issue.from_json(StortingImporter.root.join('data/dld-issues.json').read)
88
73
  end
89
74
 
90
75
  def read_dld_votes
91
- doc = Nokogiri::XML.parse(File.read(File.join(StortingImporter.root, 'folketingparser/data/votering-2011-04-04-dld-hdo.xml')))
92
- votes = Vote.from_hdo_doc doc
93
-
94
- Util.builder do |xml|
95
- xml.instruct!
96
- xml.votes do |votes_builder|
97
- votes.each { |v| v.to_hdo_xml(votes_builder) }
98
- end
99
- end
76
+ Util.json_pretty Vote.from_json StortingImporter.root.join('data/dld-votes.json').read
100
77
  end
101
78
 
102
79
  def read_promises
103
- csvs = @files.any? ? @files : Dir[File.join(StortingImporter.root, 'data/promises-*.csv')].sort_by { |e| File.basename(e) }
80
+ csvs = @files.any? ? @files : Dir[StortingImporter.root.join('data/promises-*.csv').to_s].sort_by { |e| File.basename(e) }
104
81
  content = ''
105
82
  csvs.each do |csv|
106
83
  if csv =~ /^http/
@@ -110,12 +87,7 @@ module Hdo
110
87
  end
111
88
  end
112
89
 
113
- Util.builder do |xml|
114
- xml.instruct!
115
- xml.promises do |promises|
116
- Promise.from_csv(content).each { |e| e.to_hdo_xml(promises) }
117
- end
118
- end
90
+ Util.json_pretty Promise.from_csv(content)
119
91
  end
120
92
 
121
93
  end
@@ -1,29 +1,20 @@
1
1
  module Hdo
2
2
  module StortingImporter
3
3
  class Committee
4
+ include HasJsonSchema
4
5
  include IvarEquality
5
6
 
6
7
  attr_reader :external_id, :name
7
8
  alias_method :short_inspect, :inspect
8
9
 
9
- def self.type_name
10
- 'committee'
11
- end
12
-
13
- def self.description
14
- 'a parliamentary committe'
15
- end
10
+ schema_path StortingImporter.lib.join("hdo/storting_importer/schema/committee.json").to_s
16
11
 
17
12
  def self.example
18
13
  new "ARBSOS", "Arbeids- og sosialkomiteen"
19
14
  end
20
15
 
21
- def self.xml_example(builder = Util.builder)
22
- example.to_hdo_xml(builder)
23
- end
24
-
25
- def self.fields
26
- [EXTERNAL_ID_FIELD, Field.new(:name, true, :string, 'The name of the committee.')]
16
+ def self.json_example
17
+ Util.json_pretty example
27
18
  end
28
19
 
29
20
  def self.from_storting_doc(doc)
@@ -36,12 +27,8 @@ module Hdo
36
27
  new node.css("id").first.text, node.css("navn").first.text
37
28
  end
38
29
 
39
- def self.from_hdo_doc(doc)
40
- doc.css("committees > committee").map { |e| from_hdo_node e }
41
- end
42
-
43
- def self.from_hdo_node(node)
44
- new node.css("externalId").first.text, node.css("name").first.text
30
+ def self.from_hash(hash)
31
+ new hash['externalId'], hash['name']
45
32
  end
46
33
 
47
34
  def initialize(external_id, name)
@@ -49,12 +36,14 @@ module Hdo
49
36
  @name = name
50
37
  end
51
38
 
52
- def to_hdo_xml(builder = Util.builder)
53
- builder.committee do |com|
54
- com.externalId external_id
55
- com.name name
56
- end
39
+ def to_hash
40
+ {
41
+ :kind => self.class.kind,
42
+ :externalId => @external_id,
43
+ :name => @name
44
+ }
57
45
  end
46
+
58
47
  end
59
48
  end
60
49
  end
@@ -7,15 +7,10 @@ module Hdo
7
7
  @cache = {}
8
8
  end
9
9
 
10
- def xml_for(name)
11
- Util.builder do |xml|
12
- xml.instruct!
13
- xml.__send__(name) do |builder|
14
- data_for(name).each do |obj|
15
- obj.to_hdo_xml(builder)
16
- end
17
- end
18
- end
10
+ def json_for(name, opts = nil)
11
+ obj = data_for(name)
12
+
13
+ Yajl::Encoder.encode(obj, opts && opts[:pretty])
19
14
  end
20
15
 
21
16
  private
@@ -1,29 +1,20 @@
1
1
  module Hdo
2
2
  module StortingImporter
3
3
  class District
4
+ include HasJsonSchema
4
5
  include IvarEquality
5
6
 
6
7
  attr_reader :external_id, :name
7
8
  alias_method :short_inspect, :inspect
8
9
 
9
- def self.type_name
10
- 'district'
11
- end
12
-
13
- def self.description
14
- 'an electoral district'
15
- end
10
+ schema_path StortingImporter.lib.join("hdo/storting_importer/schema/district.json").to_s
16
11
 
17
12
  def self.example
18
13
  new("Db", "Duckburg")
19
14
  end
20
15
 
21
- def self.xml_example(builder = Util.builder)
22
- example.to_hdo_xml(builder)
23
- end
24
-
25
- def self.fields
26
- [EXTERNAL_ID_FIELD, Field.new(:name, true, :string, 'The name of the electoral district.')]
16
+ def self.json_example
17
+ Util.json_pretty example
27
18
  end
28
19
 
29
20
  def self.from_storting_doc(doc)
@@ -36,12 +27,8 @@ module Hdo
36
27
  new node.css("id").first.text, node.css("navn").first.text
37
28
  end
38
29
 
39
- def self.from_hdo_doc(doc)
40
- doc.css("districts > district").map { |e| from_hdo_node(e) }
41
- end
42
-
43
- def self.from_hdo_node(node)
44
- new node.css("externalId").first.text, node.css("name").first.text
30
+ def self.from_hash(hash)
31
+ new hash.fetch('externalId'), hash.fetch('name')
45
32
  end
46
33
 
47
34
  def initialize(external_id, name)
@@ -49,11 +36,12 @@ module Hdo
49
36
  @name = name
50
37
  end
51
38
 
52
- def to_hdo_xml(builder = Util.builder)
53
- builder.district do |d|
54
- d.externalId external_id
55
- d.name name
56
- end
39
+ def to_hash
40
+ {
41
+ :kind => self.class.kind,
42
+ :externalId => @external_id,
43
+ :name => @name
44
+ }
57
45
  end
58
46
 
59
47
  end
@@ -0,0 +1,52 @@
1
+ module Hdo
2
+ module StortingImporter
3
+ #
4
+ # Simple query public Fusion Tables (onle need API key for auth).
5
+ # If we need to write to the table programatically, see https://gist.github.com/3265043.
6
+ #
7
+
8
+ class FusionTable
9
+ def initialize(api_key)
10
+ @api_key = api_key
11
+ end
12
+
13
+ def query(sql, opts = {})
14
+ resp = fix_request_failure do
15
+ RestClient.get("https://www.googleapis.com/fusiontables/v1/query", :params => {:sql => sql, :key => @api_key})
16
+ end
17
+
18
+ data = MultiJson.decode(resp)
19
+
20
+ if opts[:rows]
21
+ data.fetch('rows')
22
+ else
23
+ cols = data.fetch('columns')
24
+ rows = data.fetch('rows')
25
+
26
+ rows.map do |row|
27
+ res = {}
28
+ cols.each_with_index { |col, idx| res[col] = row[idx] }
29
+
30
+ res
31
+ end
32
+ end
33
+ end
34
+
35
+ def columns_for(table_id)
36
+ resp = fix_request_failure do
37
+ RestClient.get("https://www.googleapis.com/fusiontables/v1/tables/#{table_id}/columns", :params => {:key => @api_key})
38
+ end
39
+
40
+ data = MultiJson.decode(resp)
41
+ data['items']
42
+ end
43
+
44
+ def fix_request_failure(&blk)
45
+ yield
46
+ rescue RestClient::RequestFailed => ex
47
+ raise unless ex.respond_to?(:http_body)
48
+ raise "#{ex.message}: #{ex.http_body}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,85 @@
1
+ module Hdo
2
+ module StortingImporter
3
+ class ValidationError < StandardError
4
+ end
5
+
6
+ #
7
+ # Includer must define these methods:
8
+ #
9
+ # .schema_path
10
+ # #from_hash(hash)
11
+ # #to_hash
12
+ #
13
+
14
+ module HasJsonSchema
15
+ def self.schemas
16
+ @schemas ||= []
17
+ end
18
+
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+ end
22
+
23
+ def to_json(*args)
24
+ to_hash.to_json(*args)
25
+ end
26
+
27
+ def as_json(*args)
28
+ to_hash
29
+ end
30
+
31
+ module ClassMethods
32
+ attr_reader :schema
33
+
34
+ def schema_path(path)
35
+ @schema = MultiJson.decode(open(path).read)
36
+ HasJsonSchema.schemas << @schema
37
+ end
38
+
39
+ def schema
40
+ @schema or raise "schema must be set with #{self}.schema_path"
41
+ end
42
+
43
+ def kind
44
+ @kind ||= schema['properties']['kind']['default']
45
+ end
46
+
47
+ def description
48
+ @description ||= schema['description']
49
+ end
50
+
51
+ def properties
52
+ @properties ||= (
53
+ schema['properties'].map do |name, data|
54
+ Field.new(name, !!data['required'], data['type'], data['description'] || 'unknown')
55
+ end
56
+ )
57
+ end
58
+
59
+ # TODO: remove #fields usage
60
+ alias_method :fields, :properties
61
+
62
+ def from_json(str)
63
+ data = MultiJson.decode(str)
64
+
65
+ case data
66
+ when Array
67
+ data.map { |e| from_hash validate!(e) }
68
+ when Hash
69
+ from_hash validate!(data)
70
+ else
71
+ raise TypeError, "expected Array or Hash, got #{data.inspect}:#{data.class}"
72
+ end
73
+ end
74
+
75
+ def validate!(e)
76
+ Jschematic.validate!(e, schema, :context => HasJsonSchema.schemas, :debug => true)
77
+ e
78
+ rescue Jschematic::ValidationError => ex
79
+ raise ValidationError, "#{ex.message}: #{e.inspect}"
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end