spout 0.8.0.beta14 → 0.8.0.beta15
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 +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
|