hdo-storting-importer 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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