spout 0.8.0.beta14 → 0.8.0.beta15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +49 -4
- data/lib/spout/commands/exporter.rb +1 -0
- data/lib/spout/commands/images.rb +6 -162
- data/lib/spout/commands/importer.rb +2 -1
- data/lib/spout/commands/project_generator.rb +4 -0
- data/lib/spout/helpers/array_statistics.rb +0 -2
- data/lib/spout/helpers/iterators.rb +17 -0
- data/lib/spout/helpers/json_loader.rb +2 -0
- data/lib/spout/models/dictionary.rb +59 -0
- data/lib/spout/models/domain.rb +45 -0
- data/lib/spout/models/form.rb +42 -0
- data/lib/spout/models/option.rb +14 -0
- data/lib/spout/models/variable.rb +52 -0
- data/lib/spout/templates/Rakefile +1 -2
- data/lib/spout/templates/test/dictionary_test.rb +24 -0
- data/lib/spout/tests.rb +2 -0
- data/lib/spout/version.rb +1 -1
- data/lib/spout.rb +3 -3
- metadata +8 -4
- data/lib/spout/application.rb +0 -13
- data/lib/spout/commands/test_runner.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0ad7fac5482c072ad581a633a31afe12086a659
|
4
|
+
data.tar.gz: 20de4cb06ae7b431d5e96aa3d82308088986ee43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b12a3c4217018bf0cd8f55344a3d0aace8693a538555ed1d234487872631841a4c4526cabd73f44b438368729cda39ed176086ca3bfebbd1c8769d38c7c9cb1
|
7
|
+
data.tar.gz: 48936868e7b7732ebd84eb2d7c71ff4724889a4395afa9e8adbbaca3ce270a3d95ec982342dde2b3a1a5258d79b9628b44f9b82c4ac0bd0fc04d1e7644458c44
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,9 @@
|
|
5
5
|
- `include Spout::Tests::FormExistenceValidation`
|
6
6
|
- `include Spout::Tests::FormNameUniqueness`
|
7
7
|
- `include Spout::Tests::FormNameMatch`
|
8
|
+
- Test iterators have been added to provide access to `@variables`, `@forms`, and `@domains` to build custom tests in `dictionary_test.rb`
|
9
|
+
- Add the line `include Spout::Helpers::Iterators` to `dictionary_test.rb` to access the iterators
|
10
|
+
- See [README.md](https://github.com/sleepepi/spout/blob/master/README.md) for examples
|
8
11
|
- Added `spout graphs` command that generates JSON charts and tables of each variable in a dataset
|
9
12
|
- This command requires a .spout.yml file to be specified to identify the following variables:
|
10
13
|
- `visit`: This variable is used to separate subject encounters in a histogram
|
@@ -15,6 +18,12 @@
|
|
15
18
|
- Added `spout outliers` command that returns a list of integer or numeric variables that contain major and minor outliers
|
16
19
|
- Removed the deprecated `spout hybrid` command
|
17
20
|
- Spout tests are now run using minitest in favor of test unit
|
21
|
+
- Spout dictionary can now be loaded using the following command in irb:
|
22
|
+
- `require 'spout'; dictionary = Spout::Models::Dictionary.new(Dir.pwd)`
|
23
|
+
- `dictionary.load_all!`
|
24
|
+
- `dictionary.variables.count`
|
25
|
+
- `dictionary.domains.count`
|
26
|
+
- `dictionary.forms.count`
|
18
27
|
- **Gem Changes**
|
19
28
|
- Updated to colorize 0.7.2
|
20
29
|
- Updated to minitest
|
data/README.md
CHANGED
@@ -101,19 +101,20 @@ If not, you can add the following to your `test` directory to include all Spout
|
|
101
101
|
|
102
102
|
`test/dictionary_test.rb`
|
103
103
|
|
104
|
-
```
|
104
|
+
```ruby
|
105
105
|
require 'spout/tests'
|
106
106
|
|
107
107
|
class DictionaryTest < Minitest::Test
|
108
|
+
# This line includes all default Spout Dictionary tests
|
108
109
|
include Spout::Tests
|
109
110
|
end
|
110
111
|
```
|
111
112
|
|
112
|
-
```
|
113
|
+
```ruby
|
113
114
|
require 'spout/tests'
|
114
115
|
|
115
116
|
class DictionaryTest < Minitest::Test
|
116
|
-
#
|
117
|
+
# You can include only certain Spout tests by including them individually
|
117
118
|
include Spout::Tests::JsonValidation
|
118
119
|
include Spout::Tests::VariableTypeValidation
|
119
120
|
include Spout::Tests::VariableNameUniqueness
|
@@ -128,10 +129,54 @@ end
|
|
128
129
|
|
129
130
|
Then run either `spout test` or `bundle exec rake` to run your tests.
|
130
131
|
|
132
|
+
You can also use Spout iterators to create custom tests for variables, forms, and domains in your data dictionary.
|
133
|
+
|
134
|
+
**Example Custom Test 1:** Test that `integer` and `numeric` variables have a valid unit type
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
class DictionaryTest < Minitest::Test
|
138
|
+
# This line includes all default Spout Dictionary tests
|
139
|
+
include Spout::Tests
|
140
|
+
|
141
|
+
# This line provides access to @variables, @forms, and @domains iterators
|
142
|
+
# iterators that can be used to write custom tests
|
143
|
+
include Spout::Helpers::Iterators
|
144
|
+
|
145
|
+
VALID_UNITS = ['minutes', 'hours']
|
146
|
+
|
147
|
+
@variables.select{|v| v.type == 'numeric' or v.type == 'integer'}.each do |variable|
|
148
|
+
define_method("test_valid_units: "+variable.path) do
|
149
|
+
puts variable.class
|
150
|
+
# => Spout::Models::Variable
|
151
|
+
assert VALID_UNITS.include?(variable.units)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
**Example Custom Test 2:** Tests that variables have at least 2 or more labels.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class DictionaryTest < Minitest::Test
|
161
|
+
# This line includes all default Spout Dictionary tests
|
162
|
+
include Spout::Tests
|
163
|
+
|
164
|
+
# This line provides access to @variables, @forms, and @domains
|
165
|
+
# iterators that can be used to write custom tests
|
166
|
+
include Spout::Helpers::Iterators
|
167
|
+
|
168
|
+
@variables.select{|v| ['numeric','integer'].include?(v.type)}.each do |variable|
|
169
|
+
define_method("test_at_least_two_labels: "+variable.path) do
|
170
|
+
assert_operator 2, :<=, variable.labels.size
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
131
176
|
|
132
177
|
### Test your data dictionary coverage of your dataset
|
133
178
|
|
134
|
-
Spout lets you generate a nice visual coverage report that displays how well the data dictionary covers your dataset. Place your dataset csvs into `./csvs
|
179
|
+
Spout lets you generate a nice visual coverage report that displays how well the data dictionary covers your dataset. Place your dataset csvs into `./csvs/<version>/` and then run the following Spout command:
|
135
180
|
|
136
181
|
```
|
137
182
|
spout coverage
|
@@ -15,7 +15,8 @@ module Spout
|
|
15
15
|
@variable_files = Dir.glob('variables/**/*.json')
|
16
16
|
@standard_version = standard_version
|
17
17
|
@pretend = (argv.delete('--pretend') != nil)
|
18
|
-
|
18
|
+
@sizes = sizes
|
19
|
+
@types = types
|
19
20
|
|
20
21
|
@valid_ids = variable_ids
|
21
22
|
|
@@ -47,13 +48,12 @@ module Spout
|
|
47
48
|
FileUtils.mkpath( options_folder )
|
48
49
|
tmp_options_file = File.join( options_folder, 'options.json' )
|
49
50
|
|
50
|
-
sizes = []
|
51
|
-
|
52
51
|
variable_files_count = @variable_files.count
|
53
52
|
@variable_files.each_with_index do |variable_file, file_index|
|
54
53
|
json = JSON.parse(File.read(variable_file)) rescue json = nil
|
55
54
|
next unless json
|
56
55
|
next unless @valid_ids.include?(json["id"].to_s.downcase) or @valid_ids.size == 0
|
56
|
+
next unless @types.include?(json["type"]) or @types.size == 0
|
57
57
|
next unless ["numeric", "integer", "choices"].include?(json["type"])
|
58
58
|
variable_name = json['id'].to_s.downcase
|
59
59
|
next unless Spout::Models::Subject.method_defined?(variable_name)
|
@@ -96,170 +96,14 @@ module Spout
|
|
96
96
|
}
|
97
97
|
eos
|
98
98
|
end
|
99
|
-
run_phantom_js("#{json['id']}-lg.png", 600, tmp_options_file) if sizes.size == 0 or sizes.include?('lg')
|
100
|
-
run_phantom_js("#{json['id']}.png", 75, tmp_options_file) if sizes.size == 0 or sizes.include?('sm')
|
99
|
+
run_phantom_js("#{json['id']}-lg.png", 600, tmp_options_file) if @sizes.size == 0 or @sizes.include?('lg')
|
100
|
+
run_phantom_js("#{json['id']}.png", 75, tmp_options_file) if @sizes.size == 0 or @sizes.include?('sm')
|
101
101
|
end
|
102
102
|
|
103
103
|
end
|
104
|
-
File.delete(tmp_options_file) if File.
|
104
|
+
File.delete(tmp_options_file) if File.exist?(tmp_options_file)
|
105
105
|
end
|
106
106
|
|
107
|
-
|
108
|
-
# def initialize(types, variable_ids, sizes, standard_version)
|
109
|
-
# @standard_version = standard_version
|
110
|
-
# total_index_count = Dir.glob("variables/**/*.json").count
|
111
|
-
|
112
|
-
# last_completed = 0
|
113
|
-
|
114
|
-
# options_folder = "images/#{@standard_version}"
|
115
|
-
# FileUtils.mkpath( options_folder )
|
116
|
-
# tmp_options_file = File.join( options_folder, 'options.json' )
|
117
|
-
|
118
|
-
# Dir.glob("csvs/#{standard_version}/*.csv").each do |csv_file|
|
119
|
-
# puts "Working on: #{csv_file}"
|
120
|
-
# t = Time.now
|
121
|
-
# csv_table = CSV.table(csv_file, encoding: 'iso-8859-1').by_col!
|
122
|
-
# puts "Loaded #{csv_file} in #{Time.now - t} seconds."
|
123
|
-
|
124
|
-
# total_header_count = csv_table.headers.count
|
125
|
-
# csv_table.headers.each_with_index do |header, index|
|
126
|
-
# puts "Column #{ index + 1 } of #{ total_header_count } for #{header} in #{csv_file}"
|
127
|
-
# if variable_file = Dir.glob("variables/**/#{header.downcase}.json", File::FNM_CASEFOLD).first
|
128
|
-
# json = JSON.parse(File.read(variable_file)) rescue json = nil
|
129
|
-
# next unless json
|
130
|
-
# next unless ["choices", "numeric", "integer"].include?(json["type"])
|
131
|
-
# next unless types.size == 0 or types.include?(json['type'])
|
132
|
-
# next unless variable_ids.size == 0 or variable_ids.include?(json['id'].to_s.downcase)
|
133
|
-
|
134
|
-
# basename = File.basename(variable_file).gsub(/\.json$/, '').downcase
|
135
|
-
# col_data = csv_table[header]
|
136
|
-
|
137
|
-
# case json["type"] when "choices"
|
138
|
-
# domain_file = Dir.glob("domains/**/#{json['domain']}.json").first
|
139
|
-
# domain_json = JSON.parse(File.read(domain_file)) rescue domain_json = nil
|
140
|
-
# next unless domain_json
|
141
|
-
|
142
|
-
# create_pie_chart_options_file(col_data, tmp_options_file, domain_json)
|
143
|
-
# when 'numeric', 'integer'
|
144
|
-
# create_line_chart_options_file(col_data, tmp_options_file, json["units"])
|
145
|
-
# else
|
146
|
-
# next
|
147
|
-
# end
|
148
|
-
|
149
|
-
# run_phantom_js("#{basename}-lg.png", 600, tmp_options_file) if sizes.size == 0 or sizes.include?('lg')
|
150
|
-
# run_phantom_js("#{basename}.png", 75, tmp_options_file) if sizes.size == 0 or sizes.include?('sm')
|
151
|
-
# end
|
152
|
-
# end
|
153
|
-
# end
|
154
|
-
# File.delete(tmp_options_file) if File.exists?(tmp_options_file)
|
155
|
-
# end
|
156
|
-
|
157
|
-
# def graph_values(col_data)
|
158
|
-
# categories = []
|
159
|
-
|
160
|
-
# col_data = col_data.select{|v| !['', 'null'].include?(v.to_s.strip.downcase)}.collect(&:to_f)
|
161
|
-
|
162
|
-
# all_integers = false
|
163
|
-
# all_integers = (col_data.count{|i| i.denominator != 1} == 0)
|
164
|
-
|
165
|
-
# minimum = col_data.min || 0
|
166
|
-
# maximum = col_data.max || 100
|
167
|
-
|
168
|
-
# default_max_buckets = 30
|
169
|
-
# max_buckets = all_integers ? [maximum - minimum + 1, default_max_buckets].min : default_max_buckets
|
170
|
-
# bucket_size = (maximum - minimum + 1).to_f / max_buckets
|
171
|
-
|
172
|
-
# (0..(max_buckets-1)).each do |bucket|
|
173
|
-
# val_min = (bucket_size * bucket) + minimum
|
174
|
-
# val_max = bucket_size * (bucket + 1) + minimum
|
175
|
-
# # Greater or equal to val_min, less than val_max
|
176
|
-
# # categories << "'#{val_min} to #{val_max}'"
|
177
|
-
# categories << "#{all_integers || (maximum - minimum) > (default_max_buckets / 2) ? val_min.round : "%0.02f" % val_min}"
|
178
|
-
# end
|
179
|
-
|
180
|
-
# new_values = []
|
181
|
-
# (0..max_buckets-1).each do |bucket|
|
182
|
-
# val_min = (bucket_size * bucket) + minimum
|
183
|
-
# val_max = bucket_size * (bucket + 1) + minimum
|
184
|
-
# # Greater or equal to val_min, less than val_max
|
185
|
-
# new_values << col_data.count{|i| i >= val_min and i < val_max}
|
186
|
-
# end
|
187
|
-
|
188
|
-
# values = []
|
189
|
-
|
190
|
-
# values << { name: '', data: new_values, showInLegend: false }
|
191
|
-
|
192
|
-
# [ values, categories ]
|
193
|
-
# end
|
194
|
-
|
195
|
-
|
196
|
-
# def create_pie_chart_options_file(values, options_file, domain_json)
|
197
|
-
|
198
|
-
# values.select!{|v| !['', 'null'].include?(v.to_s.strip.downcase) }
|
199
|
-
# counts = values.group_by{|a| a}.collect{|k,v| [(domain_json.select{|h| h['value'] == k.to_s}.first['display_name'] rescue (k.to_s == '' ? 'NULL' : k)), v.count]}
|
200
|
-
|
201
|
-
# total_count = counts.collect(&:last).inject(&:+)
|
202
|
-
|
203
|
-
# data = counts.collect{|value, count| [value, (count * 100.0 / total_count)]}
|
204
|
-
|
205
|
-
# File.open(options_file, "w") do |outfile|
|
206
|
-
# outfile.puts <<-eos
|
207
|
-
# {
|
208
|
-
# "title": {
|
209
|
-
# "text": ""
|
210
|
-
# },
|
211
|
-
|
212
|
-
# "credits": {
|
213
|
-
# "enabled": false,
|
214
|
-
# },
|
215
|
-
# "series": [{
|
216
|
-
# "type": "pie",
|
217
|
-
# "name": "",
|
218
|
-
# "data": #{data.to_json}
|
219
|
-
# }]
|
220
|
-
# }
|
221
|
-
# eos
|
222
|
-
# end
|
223
|
-
# end
|
224
|
-
|
225
|
-
|
226
|
-
# def create_line_chart_options_file(values, options_file, units)
|
227
|
-
# ( series, categories ) = graph_values(values)
|
228
|
-
|
229
|
-
# File.open(options_file, "w") do |outfile|
|
230
|
-
# outfile.puts <<-eos
|
231
|
-
# {
|
232
|
-
# "chart": {
|
233
|
-
# "type": "areaspline"
|
234
|
-
# },
|
235
|
-
# "title": {
|
236
|
-
# "text": ""
|
237
|
-
# },
|
238
|
-
# "credits": {
|
239
|
-
# "enabled": false,
|
240
|
-
# },
|
241
|
-
# "xAxis": {
|
242
|
-
# "categories": #{categories.to_json},
|
243
|
-
# "labels": {
|
244
|
-
# "step": #{(categories.size.to_f / 12).ceil}
|
245
|
-
# },
|
246
|
-
# "title": {
|
247
|
-
# "text": #{units.to_json}
|
248
|
-
# }
|
249
|
-
# },
|
250
|
-
# "yAxis": {
|
251
|
-
# "maxPadding": 0,
|
252
|
-
# "minPadding": 0,
|
253
|
-
# "title": {
|
254
|
-
# "text": "Count"
|
255
|
-
# }
|
256
|
-
# },
|
257
|
-
# "series": #{series.to_json}
|
258
|
-
# }
|
259
|
-
# eos
|
260
|
-
# end
|
261
|
-
# end
|
262
|
-
|
263
107
|
def run_phantom_js(png_name, width, tmp_options_file)
|
264
108
|
graph_path = File.join(Dir.pwd, 'images', @standard_version, png_name)
|
265
109
|
directory = File.join( File.dirname(__FILE__), '..', 'support', 'javascripts' )
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'csv'
|
2
2
|
require 'json'
|
3
3
|
require 'fileutils'
|
4
|
+
require 'colorize'
|
4
5
|
|
5
6
|
module Spout
|
6
7
|
module Commands
|
@@ -10,7 +11,7 @@ module Spout
|
|
10
11
|
|
11
12
|
@csv_file = argv[1].to_s
|
12
13
|
|
13
|
-
unless File.
|
14
|
+
unless File.exist?(@csv_file)
|
14
15
|
puts csv_usage
|
15
16
|
return self
|
16
17
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
1
3
|
TEMPLATES_DIRECTORY = File.expand_path('../../templates', __FILE__)
|
2
4
|
|
3
5
|
module Spout
|
@@ -35,6 +37,8 @@ EOT
|
|
35
37
|
copy_file 'keep', 'domains/.keep'
|
36
38
|
directory 'variables'
|
37
39
|
copy_file 'keep', 'variables/.keep'
|
40
|
+
directory 'forms'
|
41
|
+
copy_file 'keep', 'forms/.keep'
|
38
42
|
directory 'test'
|
39
43
|
copy_file 'test/dictionary_test.rb'
|
40
44
|
copy_file 'test/test_helper.rb'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spout/models/dictionary'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Helpers
|
5
|
+
module Iterators
|
6
|
+
|
7
|
+
def self.included(c)
|
8
|
+
class << c; attr_accessor :dictionary, :variables, :domains, :forms; end
|
9
|
+
c.instance_variable_set(:@dictionary, Spout::Models::Dictionary.new(Dir.pwd).load_all!)
|
10
|
+
c.instance_variable_set(:@variables, c.instance_variable_get(:@dictionary).variables)
|
11
|
+
c.instance_variable_set(:@domains, c.instance_variable_get(:@dictionary).domains)
|
12
|
+
c.instance_variable_set(:@forms, c.instance_variable_get(:@dictionary).forms)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spout/models/variable'
|
2
|
+
require 'spout/models/domain'
|
3
|
+
require 'spout/models/form'
|
4
|
+
|
5
|
+
module Spout
|
6
|
+
module Models
|
7
|
+
class Dictionary
|
8
|
+
attr_accessor :variables, :domains, :forms
|
9
|
+
attr_accessor :app_path
|
10
|
+
|
11
|
+
attr_reader :variable_files, :domain_files, :form_files
|
12
|
+
|
13
|
+
def initialize(app_path)
|
14
|
+
@app_path = app_path
|
15
|
+
|
16
|
+
@variable_files = json_files('variables')
|
17
|
+
@domain_files = json_files('domains')
|
18
|
+
@form_files = json_files('forms')
|
19
|
+
|
20
|
+
@variables = []
|
21
|
+
@domains = []
|
22
|
+
@forms = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_all!
|
26
|
+
load_variables!
|
27
|
+
load_domains!
|
28
|
+
load_forms!
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_variables!
|
33
|
+
load_type!('Variable')
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_domains!
|
37
|
+
load_type!('Domain')
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_forms!
|
41
|
+
load_type!('Form')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def json_files(type)
|
47
|
+
Dir.glob(File.join(@app_path, type, "**", "*.json"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_type!(method)
|
51
|
+
results = instance_variable_get("@#{method.downcase}_files").collect do |file|
|
52
|
+
Object.const_get("Spout::Models::#{method}").new(file, @app_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
instance_variable_set("@#{method.downcase}s", results)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'spout/models/option'
|
4
|
+
|
5
|
+
module Spout
|
6
|
+
module Models
|
7
|
+
|
8
|
+
class Domain
|
9
|
+
attr_accessor :id, :folder, :options
|
10
|
+
attr_reader :errors
|
11
|
+
|
12
|
+
def initialize(file_name, dictionary_root)
|
13
|
+
@errors = []
|
14
|
+
@id = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
15
|
+
|
16
|
+
@folder = file_name.to_s.gsub(/^#{dictionary_root}\/domains\/|#{@id}\.json$/, '')
|
17
|
+
@options = []
|
18
|
+
|
19
|
+
json = begin
|
20
|
+
if File.exist?(file_name)
|
21
|
+
JSON.parse(File.read(file_name))
|
22
|
+
else
|
23
|
+
@errors << "No corresponding #{@id}.json file found."
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
rescue => e
|
27
|
+
@errors << "Parsing error found in #{@id}.json: #{e.message}" if file_name != nil
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
if json and json.kind_of? Array
|
32
|
+
@id = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
33
|
+
@options = (json || []).collect do |option|
|
34
|
+
Spout::Models::Option.new(option)
|
35
|
+
end
|
36
|
+
elsif json
|
37
|
+
@errors << "Domain must be a valid array in the following format: [\n {\n \"value\": \"1\",\n \"display_name\": \"First Choice\",\n \"description\": \"First Description\"\n },\n {\n \"value\": \"2\",\n \"display_name\": \"Second Choice\",\n \"description\": \"Second Description\"\n }\n]"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# {
|
2
|
+
# "id": "intake_questionnaire",
|
3
|
+
# "display_name": "Intake Questionnaire at Baseline Visit",
|
4
|
+
# "code_book": "Baseline-Visit-Intake-Questionnaire.pdf"
|
5
|
+
# }
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module Spout
|
10
|
+
module Models
|
11
|
+
class Form
|
12
|
+
attr_accessor :id, :display_name, :code_book
|
13
|
+
attr_accessor :errors
|
14
|
+
|
15
|
+
def initialize(file_name, dictionary_root)
|
16
|
+
@errors = []
|
17
|
+
@id = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
18
|
+
@folder = file_name.to_s.gsub(/^#{dictionary_root}\/forms\/|#{@id}\.json$/, '')
|
19
|
+
|
20
|
+
json = begin
|
21
|
+
JSON.parse(File.read(file_name))
|
22
|
+
rescue => e
|
23
|
+
form_name = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
24
|
+
@errors << "Error Parsing #{form_name}.json: #{e.message}"
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
if json and json.kind_of? Hash
|
29
|
+
%w( display_name code_book ).each do |method|
|
30
|
+
instance_variable_set("@#{method}", json[method])
|
31
|
+
end
|
32
|
+
|
33
|
+
@errors << "'id': #{json['id'].inspect} does not match filename #{@id.inspect}" if @id != json['id']
|
34
|
+
elsif json
|
35
|
+
@errors << "Form must be a valid hash in the following format: {\n\"id\": \"FORM_ID\",\n \"display_name\": \"FORM DISPLAY NAME\",\n \"code_book\": \"FORMPDF.pdf\"\n}"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Spout
|
2
|
+
module Models
|
3
|
+
class Option
|
4
|
+
attr_accessor :display_name, :value, :description
|
5
|
+
|
6
|
+
def initialize(option_hash)
|
7
|
+
%w( display_name value description ).each do |method|
|
8
|
+
instance_variable_set("@#{method}", (option_hash.kind_of?(Hash) ? option_hash : {})[method])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Spout
|
4
|
+
module Models
|
5
|
+
class Variable
|
6
|
+
# VARIABLE_TYPES = ['choices', 'numeric', 'integer']
|
7
|
+
|
8
|
+
attr_accessor :id, :folder, :display_name, :description, :type, :units, :labels, :commonly_used
|
9
|
+
attr_accessor :domain_name, :form_names
|
10
|
+
attr_accessor :domain, :forms
|
11
|
+
attr_reader :errors
|
12
|
+
|
13
|
+
def initialize(file_name, dictionary_root)
|
14
|
+
@errors = []
|
15
|
+
@id = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
16
|
+
@folder = file_name.to_s.gsub(/^#{dictionary_root}\/variables\/|#{@id}\.json$/, '')
|
17
|
+
|
18
|
+
|
19
|
+
json = begin
|
20
|
+
JSON.parse(File.read(file_name))
|
21
|
+
rescue => e
|
22
|
+
error = e.message
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
if json and json.kind_of? Hash
|
27
|
+
|
28
|
+
%w( display_name description type units commonly_used ).each do |method|
|
29
|
+
instance_variable_set("@#{method}", json[method])
|
30
|
+
end
|
31
|
+
|
32
|
+
@errors << "'id': #{json['id'].inspect} does not match filename #{@id.inspect}" if @id != json['id']
|
33
|
+
|
34
|
+
@domain_name = json['domain'] # Spout::Models::Domain.new(json['domain'], dictionary_root)
|
35
|
+
@labels = (json['labels'] || [])
|
36
|
+
@form_names = (json['forms'] || []).collect do |form_name|
|
37
|
+
form_name
|
38
|
+
end
|
39
|
+
elsif json
|
40
|
+
@errors << "Variable must be a valid hash in the following format: {\n\"id\": \"VARIABLE_ID\",\n \"display_name\": \"VARIABLE DISPLAY NAME\",\n \"description\": \"VARIABLE DESCRIPTION\"\n}"
|
41
|
+
end
|
42
|
+
|
43
|
+
@errors = (@errors + [error]).compact
|
44
|
+
end
|
45
|
+
|
46
|
+
def path
|
47
|
+
File.join(@folder, "#{@id}.json")
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,2 +1 @@
|
|
1
|
-
require 'spout'
|
2
|
-
Spout::Application.new.load_tasks
|
1
|
+
require 'spout/tasks'
|
@@ -1,8 +1,32 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class DictionaryTest < Minitest::Test
|
4
|
+
# This line includes all default Spout Dictionary tests
|
4
5
|
include Spout::Tests
|
5
6
|
|
7
|
+
# This line provides access to @variables, @forms, and @domains iterators
|
8
|
+
# iterators that can be used to write custom tests
|
9
|
+
include Spout::Helpers::Iterators
|
10
|
+
|
11
|
+
# Example 1: Create custom tests to show that `integer` and `numeric` variables have a valid unit type
|
12
|
+
# VALID_UNITS = ['minutes', 'hours'] # Add your own valid units to this array
|
13
|
+
# @variables.select{|v| v.type == 'numeric' or v.type == 'integer'}.each do |variable|
|
14
|
+
# define_method("test_units: "+variable.path) do
|
15
|
+
# message = "\"#{variable.units}\"".colorize( :red ) + " invalid units.\n" +
|
16
|
+
# " Valid types: " +
|
17
|
+
# VALID_UNITS.sort.collect{|u| u.inspect.colorize( :white )}.join(', ')
|
18
|
+
# assert VALID_UNITS.include?(variable.units), message
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
22
|
+
# Example 2: Create custom tests to show that variables have 2 or more labels.
|
23
|
+
# @variables.select{|v| ['numeric','integer'].include?(v.type)}.each do |variable|
|
24
|
+
# define_method("test_at_least_two_labels: "+variable.path) do
|
25
|
+
# assert_operator 2, :<=, variable.labels.size
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
|
29
|
+
# Example 3: Create regular Ruby tests
|
6
30
|
# You may add additional tests here
|
7
31
|
# def test_truth
|
8
32
|
# assert true
|
data/lib/spout/tests.rb
CHANGED
data/lib/spout/version.rb
CHANGED
data/lib/spout.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "spout/version"
|
2
|
-
|
3
|
-
require 'spout/
|
2
|
+
|
3
|
+
require 'spout/models/dictionary'
|
4
4
|
|
5
5
|
Spout::COMMANDS = {
|
6
6
|
'n' => :new_project,
|
@@ -108,7 +108,7 @@ EOT
|
|
108
108
|
private
|
109
109
|
|
110
110
|
def self.flag_values(flags, param)
|
111
|
-
flags.select{|f| f
|
111
|
+
flags.select{|f| f =~ /^--#{param}-/}.collect{|f| f[(param.size + 3)..-1]}
|
112
112
|
end
|
113
113
|
|
114
114
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.0.
|
4
|
+
version: 0.8.0.beta15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Remo Mueller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -109,7 +109,6 @@ files:
|
|
109
109
|
- Rakefile
|
110
110
|
- bin/spout
|
111
111
|
- lib/spout.rb
|
112
|
-
- lib/spout/application.rb
|
113
112
|
- lib/spout/commands/coverage.rb
|
114
113
|
- lib/spout/commands/exporter.rb
|
115
114
|
- lib/spout/commands/graphs.rb
|
@@ -117,16 +116,21 @@ files:
|
|
117
116
|
- lib/spout/commands/importer.rb
|
118
117
|
- lib/spout/commands/outliers.rb
|
119
118
|
- lib/spout/commands/project_generator.rb
|
120
|
-
- lib/spout/commands/test_runner.rb
|
121
119
|
- lib/spout/helpers/array_statistics.rb
|
122
120
|
- lib/spout/helpers/chart_types.rb
|
121
|
+
- lib/spout/helpers/iterators.rb
|
123
122
|
- lib/spout/helpers/json_loader.rb
|
124
123
|
- lib/spout/helpers/number_helper.rb
|
125
124
|
- lib/spout/helpers/subject_loader.rb
|
126
125
|
- lib/spout/helpers/table_formatting.rb
|
127
126
|
- lib/spout/models/coverage_result.rb
|
127
|
+
- lib/spout/models/dictionary.rb
|
128
|
+
- lib/spout/models/domain.rb
|
129
|
+
- lib/spout/models/form.rb
|
130
|
+
- lib/spout/models/option.rb
|
128
131
|
- lib/spout/models/outlier_result.rb
|
129
132
|
- lib/spout/models/subject.rb
|
133
|
+
- lib/spout/models/variable.rb
|
130
134
|
- lib/spout/support/javascripts/data.js
|
131
135
|
- lib/spout/support/javascripts/highcharts-convert.js
|
132
136
|
- lib/spout/support/javascripts/highcharts-more.js
|
data/lib/spout/application.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
# module Spout
|
2
|
-
# module Commands
|
3
|
-
# class TestRunner
|
4
|
-
# def initialize(argv)
|
5
|
-
# verbose = (argv.delete('--verbose') != nil)
|
6
|
-
|
7
|
-
# puts "Loaded Suite test"
|
8
|
-
|
9
|
-
# files_loaded = []
|
10
|
-
|
11
|
-
|
12
|
-
# Dir.chdir("test") do
|
13
|
-
# $:.unshift(Dir.pwd)
|
14
|
-
# Dir.glob(File.join("**", "*_test.rb")).each do |test_file|
|
15
|
-
# files_loaded << test_file
|
16
|
-
# require 'spout/tests'
|
17
|
-
# Spout::Tests.class_eval File.read(test_file)
|
18
|
-
# # load test_file #.gsub(/\.rb$/, '')
|
19
|
-
# end
|
20
|
-
# end
|
21
|
-
|
22
|
-
# puts files_loaded.inspect
|
23
|
-
# puts Spout::Tests.constants
|
24
|
-
|
25
|
-
# Spout::Tests.constants.select{|c| Spout::Tests.const_get(c).is_a? Class}.each do |klass|
|
26
|
-
# puts "KLASS: #{klass}"
|
27
|
-
# my_instance = Spout::Tests.const_get(klass).new
|
28
|
-
# puts my_instance.methods.select{|m| m.to_s =~ /^test\_/}.inspect
|
29
|
-
# end
|
30
|
-
# # puts Spout::Tests::JsonValidation.methods
|
31
|
-
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
# end
|