spout 0.10.2 → 0.11.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +3 -30
- data/lib/spout/commands/coverage.rb +2 -1
- data/lib/spout/commands/deploy.rb +82 -77
- data/lib/spout/commands/exporter.rb +2 -3
- data/lib/spout/commands/graphs.rb +68 -67
- data/lib/spout/commands/help.rb +155 -0
- data/lib/spout/helpers/array_statistics.rb +36 -30
- data/lib/spout/helpers/chart_types.rb +2 -2
- data/lib/spout/helpers/config_reader.rb +5 -5
- data/lib/spout/helpers/json_request.rb +1 -2
- data/lib/spout/helpers/json_request_generic.rb +87 -0
- data/lib/spout/helpers/quietly.rb +2 -4
- data/lib/spout/helpers/semantic.rb +7 -11
- data/lib/spout/helpers/send_file.rb +23 -25
- data/lib/spout/helpers/subject_loader.rb +41 -32
- data/lib/spout/helpers/table_formatting.rb +7 -6
- data/lib/spout/models/bucket.rb +5 -4
- data/lib/spout/models/coverage_result.rb +1 -1
- data/lib/spout/models/dictionary.rb +3 -1
- data/lib/spout/models/domain.rb +7 -6
- data/lib/spout/models/empty.rb +17 -0
- data/lib/spout/models/form.rb +8 -5
- data/lib/spout/models/graphables/default.rb +41 -18
- data/lib/spout/models/graphables/histogram.rb +6 -7
- data/lib/spout/models/graphables.rb +3 -5
- data/lib/spout/models/option.rb +6 -2
- data/lib/spout/models/outlier_result.rb +3 -3
- data/lib/spout/models/record.rb +21 -3
- data/lib/spout/models/subject.rb +4 -7
- data/lib/spout/models/tables/choices_vs_choices.rb +29 -17
- data/lib/spout/models/tables/choices_vs_numeric.rb +19 -12
- data/lib/spout/models/tables/default.rb +19 -32
- data/lib/spout/models/tables/numeric_vs_choices.rb +9 -13
- data/lib/spout/models/tables/numeric_vs_numeric.rb +9 -11
- data/lib/spout/models/tables.rb +4 -6
- data/lib/spout/models/variable.rb +51 -13
- data/lib/spout/tasks/engine.rake +1 -1
- data/lib/spout/templates/ruby-version +1 -1
- data/lib/spout/templates/travis.yml +1 -1
- data/lib/spout/tests/domain_format.rb +2 -2
- data/lib/spout/tests/domain_name_format.rb +15 -0
- data/lib/spout/tests/form_name_format.rb +14 -0
- data/lib/spout/tests/variable_name_format.rb +14 -0
- data/lib/spout/tests.rb +18 -13
- data/lib/spout/version.rb +3 -3
- data/lib/spout/views/index.html.erb +2 -2
- data/lib/spout/views/outliers.html.erb +1 -1
- data/lib/spout.rb +13 -58
- data/spout.gemspec +14 -15
- metadata +25 -25
- data/lib/spout/commands/images.rb +0 -199
- data/lib/spout/support/javascripts/data.js +0 -17
- data/lib/spout/support/javascripts/highcharts-convert.js +0 -583
- data/lib/spout/support/javascripts/highcharts-more.js +0 -50
- data/lib/spout/support/javascripts/highstock.js +0 -353
- data/lib/spout/support/javascripts/jquery.1.9.1.min.js +0 -5
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
module Spout
|
7
|
+
module Helpers
|
8
|
+
class JsonRequestGeneric
|
9
|
+
class << self
|
10
|
+
def get(url, *args)
|
11
|
+
new(url, *args).get
|
12
|
+
end
|
13
|
+
|
14
|
+
def post(url, *args)
|
15
|
+
new(url, *args).post
|
16
|
+
end
|
17
|
+
|
18
|
+
def patch(url, *args)
|
19
|
+
new(url, *args).patch
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :url
|
24
|
+
|
25
|
+
def initialize(url, args = {})
|
26
|
+
@params = nested_hash_to_params(args)
|
27
|
+
@url = URI.parse(url)
|
28
|
+
|
29
|
+
@http = Net::HTTP.new(@url.host, @url.port)
|
30
|
+
if @url.scheme == 'https'
|
31
|
+
@http.use_ssl = true
|
32
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
33
|
+
end
|
34
|
+
rescue => e
|
35
|
+
puts "Error sending JsonRequestGeneric: #{e}".colorize(:red)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get
|
39
|
+
full_path = @url.path
|
40
|
+
query = ([@url.query] + @params).flatten.compact.join('&')
|
41
|
+
full_path += "?#{query}" if query.to_s != ''
|
42
|
+
response = @http.start do |http|
|
43
|
+
http.get(full_path)
|
44
|
+
end
|
45
|
+
[JSON.parse(response.body), response]
|
46
|
+
rescue => e
|
47
|
+
puts "GET Error: #{e}".colorize(:red)
|
48
|
+
end
|
49
|
+
|
50
|
+
def post
|
51
|
+
response = @http.start do |http|
|
52
|
+
http.post(@url.path, @params.flatten.compact.join('&'))
|
53
|
+
end
|
54
|
+
[JSON.parse(response.body), response]
|
55
|
+
rescue => e
|
56
|
+
puts "POST ERROR: #{e}".colorize(:red)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def patch
|
61
|
+
@params << '_method=patch'
|
62
|
+
post
|
63
|
+
end
|
64
|
+
|
65
|
+
def nested_hash_to_params(args)
|
66
|
+
args.collect do |key, value|
|
67
|
+
key_value_to_string(key, value, nil)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def key_value_to_string(key, value, scope = nil)
|
72
|
+
current_scope = (scope ? "#{scope}[#{key}]" : key)
|
73
|
+
if value.is_a? Hash
|
74
|
+
value.collect do |k,v|
|
75
|
+
key_value_to_string(k, v, current_scope)
|
76
|
+
end.join('&')
|
77
|
+
elsif value.is_a? Array
|
78
|
+
value.collect do |v|
|
79
|
+
key_value_to_string('', v, current_scope)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
"#{current_scope}=#{CGI.escape(value.to_s)}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
|
-
|
2
1
|
module Spout
|
3
2
|
module Helpers
|
3
|
+
# Silences output for tests
|
4
4
|
module Quietly
|
5
|
-
|
6
5
|
# From Rails: http://apidock.com/rails/v3.2.13/Kernel/silence_stream
|
7
6
|
def silence_stream(stream)
|
8
7
|
old_stream = stream.dup
|
9
|
-
stream.reopen(RbConfig::CONFIG['host_os']
|
8
|
+
stream.reopen(/mswin|mingw/ =~ RbConfig::CONFIG['host_os'] ? 'NUL:' : '/dev/null')
|
10
9
|
stream.sync = true
|
11
10
|
yield
|
12
11
|
ensure
|
@@ -21,7 +20,6 @@ module Spout
|
|
21
20
|
end
|
22
21
|
end
|
23
22
|
end
|
24
|
-
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
@@ -1,9 +1,7 @@
|
|
1
|
-
# def dataset_folders
|
2
|
-
# Dir.entries('csvs').select{|e| File.directory? File.join('csvs', e) }.reject{|e| [".",".."].include?(e)}.sort
|
3
|
-
# end
|
4
1
|
module Spout
|
5
2
|
module Helpers
|
6
|
-
|
3
|
+
# Helps to sort semantically versioned numbers to match versions that are
|
4
|
+
# close to each other.
|
7
5
|
class Version
|
8
6
|
attr_accessor :string
|
9
7
|
attr_reader :major, :minor, :tiny, :build
|
@@ -26,7 +24,7 @@ module Spout
|
|
26
24
|
end
|
27
25
|
|
28
26
|
def build_number
|
29
|
-
(@build
|
27
|
+
(@build.nil? ? 1 : 0)
|
30
28
|
end
|
31
29
|
|
32
30
|
def rank
|
@@ -34,21 +32,21 @@ module Spout
|
|
34
32
|
end
|
35
33
|
end
|
36
34
|
|
35
|
+
# Finds compatible versions
|
37
36
|
class Semantic
|
38
|
-
|
39
37
|
attr_accessor :data_dictionary_version
|
40
38
|
|
41
39
|
def initialize(version, version_strings)
|
42
40
|
@data_dictionary_version = Spout::Helpers::Version.new(version)
|
43
|
-
@versions = version_strings.collect{ |vs| Spout::Helpers::Version.new(vs) }.sort_by(&:rank)
|
41
|
+
@versions = version_strings.collect { |vs| Spout::Helpers::Version.new(vs) }.sort_by(&:rank)
|
44
42
|
end
|
45
43
|
|
46
44
|
def valid_versions
|
47
|
-
@versions.select{ |v| v.major == major
|
45
|
+
@versions.select { |v| v.major == major && v.minor == minor }
|
48
46
|
end
|
49
47
|
|
50
48
|
def selected_folder
|
51
|
-
if valid_versions.size == 0
|
49
|
+
if valid_versions.size == 0 || valid_versions.collect(&:string).include?(version)
|
52
50
|
version
|
53
51
|
else
|
54
52
|
valid_versions.collect(&:string).last
|
@@ -74,8 +72,6 @@ module Spout
|
|
74
72
|
def build
|
75
73
|
@data_dictionary_version.build
|
76
74
|
end
|
77
|
-
|
78
75
|
end
|
79
|
-
|
80
76
|
end
|
81
77
|
end
|
@@ -13,15 +13,15 @@ module Spout
|
|
13
13
|
|
14
14
|
attr_reader :url
|
15
15
|
|
16
|
-
def initialize(url, filename, version, token,
|
17
|
-
|
16
|
+
def initialize(url, filename, version, token, slug, folder)
|
18
17
|
@params = {}
|
19
|
-
@params[
|
20
|
-
@params[
|
21
|
-
@params[
|
18
|
+
@params['version'] = version
|
19
|
+
@params['auth_token'] = token if token
|
20
|
+
@params['dataset'] = slug if slug
|
21
|
+
@params['folder'] = folder if folder
|
22
22
|
begin
|
23
|
-
file = File.open(filename,
|
24
|
-
@params[
|
23
|
+
file = File.open(filename, 'rb')
|
24
|
+
@params['file'] = file
|
25
25
|
|
26
26
|
mp = Multipart::MultipartPost.new
|
27
27
|
@query, @headers = mp.prepare_query(@params)
|
@@ -42,36 +42,33 @@ module Spout
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def post
|
45
|
-
|
46
|
-
|
47
|
-
http.post(@url.path, @query, @headers)
|
48
|
-
end
|
49
|
-
JSON.parse(response.body)
|
50
|
-
rescue
|
51
|
-
nil
|
45
|
+
response = @http.start do |http|
|
46
|
+
http.post(@url.path, @query, @headers)
|
52
47
|
end
|
48
|
+
JSON.parse(response.body)
|
49
|
+
rescue
|
50
|
+
nil
|
53
51
|
end
|
54
52
|
end
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
|
-
|
59
56
|
module Multipart
|
60
57
|
class Param
|
61
58
|
attr_accessor :k, :v
|
62
|
-
def initialize(
|
59
|
+
def initialize(k, v)
|
63
60
|
@k = k
|
64
61
|
@v = v
|
65
62
|
end
|
66
63
|
|
67
64
|
def to_multipart
|
68
|
-
|
65
|
+
"Content-Disposition: form-data; name=\"#{k}\"\r\n\r\n#{v}\r\n"
|
69
66
|
end
|
70
67
|
end
|
71
68
|
|
72
69
|
class FileParam
|
73
70
|
attr_accessor :k, :filename, :content
|
74
|
-
def initialize(
|
71
|
+
def initialize(k, filename, content)
|
75
72
|
@k = k
|
76
73
|
@filename = filename
|
77
74
|
@content = content
|
@@ -79,23 +76,24 @@ module Multipart
|
|
79
76
|
|
80
77
|
def to_multipart
|
81
78
|
mime_type = 'application/octet-stream'
|
82
|
-
|
79
|
+
"Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{filename}\"\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Type: #{mime_type}\r\n\r\n" + content + "\r\n"
|
83
80
|
end
|
84
81
|
end
|
82
|
+
|
85
83
|
class MultipartPost
|
86
84
|
BOUNDARY = 'a#41-93r1-^Õ-rule0000'
|
87
|
-
HEADER = {
|
85
|
+
HEADER = { 'Content-type' => "multipart/form-data, boundary=#{BOUNDARY} " }
|
88
86
|
|
89
|
-
def prepare_query
|
87
|
+
def prepare_query(params)
|
90
88
|
fp = []
|
91
|
-
params.each
|
89
|
+
params.each do |k, v|
|
92
90
|
if v.respond_to?(:read)
|
93
91
|
fp.push(FileParam.new(k, v.path, v.read))
|
94
92
|
else
|
95
|
-
fp.push(Param.new(k,v))
|
93
|
+
fp.push(Param.new(k, v))
|
96
94
|
end
|
97
|
-
|
98
|
-
query = fp.collect {|p| "
|
95
|
+
end
|
96
|
+
query = fp.collect { |p| "--#{BOUNDARY}\r\n" + p.to_multipart }.join('') + "--#{BOUNDARY}--"
|
99
97
|
return query, HEADER
|
100
98
|
end
|
101
99
|
end
|
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
|
5
5
|
require 'spout/models/subject'
|
6
6
|
require 'spout/helpers/semantic'
|
7
|
+
require 'spout/models/empty'
|
7
8
|
|
8
9
|
module Spout
|
9
10
|
module Helpers
|
@@ -32,74 +33,84 @@ module Spout
|
|
32
33
|
def load_subjects_from_csvs_part_one!
|
33
34
|
@subjects = []
|
34
35
|
|
35
|
-
available_folders = (Dir.exist?('csvs') ? Dir.entries('csvs').select{|e| File.directory? File.join('csvs', e) }.reject{|e| [
|
36
|
+
available_folders = (Dir.exist?('csvs') ? Dir.entries('csvs').select { |e| File.directory? File.join('csvs', e) }.reject { |e| ['.', '..'].include?(e) }.sort : [])
|
36
37
|
|
37
38
|
@semantic = Spout::Helpers::Semantic.new(@standard_version, available_folders)
|
38
39
|
|
39
40
|
@csv_directory = @semantic.selected_folder
|
40
41
|
|
41
|
-
|
42
|
-
@csv_files
|
42
|
+
csv_root = File.join('csvs', @csv_directory)
|
43
|
+
@csv_files = Dir.glob("#{csv_root}/**/*.csv").sort
|
44
|
+
|
45
|
+
if @csv_directory != @standard_version
|
46
|
+
puts "\n#{@csv_files.size == 0 ? 'No CSVs found' : 'Parsing files' } in " + "#{csv_root}".colorize(:white) + ' for dictionary version ' + @standard_version.to_s.colorize(:green) + "\n"
|
47
|
+
else
|
48
|
+
puts "\n#{@csv_files.size == 0 ? 'No CSVs found' : 'Parsing files' } in " + "#{csv_root}".colorize(:white) + "\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
last_folder = nil
|
52
|
+
@csv_files.each do |csv_file|
|
53
|
+
relative_path = csv_file.gsub(%r{^#{csv_root}}, '')
|
54
|
+
current_file = File.basename(relative_path)
|
55
|
+
current_folder = relative_path.gsub(/#{current_file}$/, '')
|
43
56
|
count = 1 # Includes counting the header row
|
44
|
-
|
45
|
-
|
57
|
+
puts " #{current_folder}".colorize(:white) if current_folder.to_s != '' && current_folder != last_folder
|
58
|
+
print " #{current_file}"
|
59
|
+
last_folder = current_folder
|
60
|
+
CSV.parse(File.open(csv_file, 'r:iso-8859-1:utf-8'){ |f| f.read }, headers: true, header_converters: lambda { |h| h.to_s.downcase }) do |line|
|
46
61
|
row = line.to_hash
|
47
62
|
count += 1
|
48
|
-
print "\
|
63
|
+
print "\r #{current_file} " + "##{count}".colorize(:yellow) if (count % 10 == 0)
|
49
64
|
@subjects << Spout::Models::Subject.create do |t|
|
50
65
|
t._visit = row[@visit]
|
66
|
+
t._csv = File.basename(csv_file)
|
51
67
|
|
52
|
-
row.each do |key,value|
|
68
|
+
row.each do |key, value|
|
53
69
|
method = key.to_s.downcase
|
54
|
-
|
70
|
+
next unless @valid_ids.include?(method) || @valid_ids.size == 0
|
55
71
|
unless t.respond_to?(method)
|
56
72
|
t.class.send(:define_method, "#{method}") { instance_variable_get("@#{method}") }
|
57
|
-
t.class.send(:define_method, "#{method}=") { |
|
73
|
+
t.class.send(:define_method, "#{method}=") { |v| instance_variable_set("@#{method}", v) }
|
58
74
|
end
|
59
|
-
|
60
75
|
@all_methods[method] ||= []
|
61
76
|
@all_methods[method] = @all_methods[method] | [csv_file]
|
62
|
-
|
63
|
-
|
77
|
+
if value.nil?
|
78
|
+
t.send("#{method}=", Spout::Models::Empty.new)
|
79
|
+
else
|
64
80
|
t.send("#{method}=", value)
|
65
81
|
end
|
66
82
|
end
|
67
83
|
end
|
84
|
+
|
68
85
|
# puts "Memory Used: " + (`ps -o rss -p #{$$}`.strip.split.last.to_i / 1024).to_s + " MB" if count % 1000 == 0
|
69
|
-
break if
|
86
|
+
break if !@number_of_rows.nil? && count - 1 >= @number_of_rows
|
70
87
|
end
|
71
|
-
print "\rParsing #{csv_file} - Row ##{count}"
|
72
|
-
puts "\n"
|
73
|
-
end
|
74
88
|
|
75
|
-
|
76
|
-
puts "
|
77
|
-
else
|
78
|
-
puts "#{@csv_files.size == 0 ? 'No CSVs found' : 'Using dataset' } in " + "csvs/#{@standard_version}/".colorize( :green ) + "\n\n"
|
89
|
+
print "\r #{current_file} " + "##{count}".colorize(:green)
|
90
|
+
puts "\n"
|
79
91
|
end
|
80
|
-
|
81
92
|
end
|
82
93
|
|
83
94
|
def load_subjects_from_csvs_part_two!
|
84
95
|
variable_count = @variable_files.count
|
85
|
-
print
|
96
|
+
print 'Converting numeric values to floats'
|
86
97
|
@variable_files.each_with_index do |variable_file, index|
|
87
|
-
print "\rConverting numeric values to floats:#{
|
98
|
+
print "\rConverting numeric values to floats:#{'% 3d' % ((index + 1) * 100 / variable_count)}%"
|
88
99
|
json = JSON.parse(File.read(variable_file)) rescue json = nil
|
89
100
|
next unless json
|
90
|
-
next unless @valid_ids.include?(json[
|
91
|
-
next unless
|
92
|
-
method
|
101
|
+
next unless @valid_ids.include?(json['id'].to_s.downcase) || @valid_ids.size == 0
|
102
|
+
next unless %w(numeric integer).include?(json['type'])
|
103
|
+
method = json['id'].to_s.downcase
|
93
104
|
next unless Spout::Models::Subject.method_defined?(method)
|
94
105
|
|
95
106
|
domain_json = get_domain(json)
|
96
107
|
# Make all domain options nil for numerics/integers
|
97
108
|
if domain_json
|
98
|
-
domain_values = domain_json.collect{|option_hash| option_hash['value']}
|
99
|
-
@subjects.each{ |s| domain_values.include?(s.send(method)) ? s.send("#{method}=", nil) : nil }
|
109
|
+
domain_values = domain_json.collect { |option_hash| option_hash['value'] }
|
110
|
+
@subjects.each { |s| domain_values.include?(s.send(method)) ? s.send("#{method}=", nil) : nil }
|
100
111
|
end
|
101
112
|
|
102
|
-
@subjects.each{ |s| s.send(method)
|
113
|
+
@subjects.each { |s| !s.send(method).nil? ? s.send("#{method}=", s.send("#{method}").to_f) : nil }
|
103
114
|
end
|
104
115
|
puts "\n"
|
105
116
|
@subjects
|
@@ -109,7 +120,7 @@ module Spout
|
|
109
120
|
@variable_files.each do |variable_file|
|
110
121
|
json = JSON.parse(File.read(variable_file)) rescue json = nil
|
111
122
|
next unless json
|
112
|
-
next unless [
|
123
|
+
next unless ['choices'].include?(json['type'])
|
113
124
|
domain = json['domain'].to_s.downcase
|
114
125
|
@all_domains << domain
|
115
126
|
end
|
@@ -129,8 +140,6 @@ module Spout
|
|
129
140
|
def get_domain(json)
|
130
141
|
get_json(json['domain'], 'domain')
|
131
142
|
end
|
132
|
-
|
133
|
-
|
134
143
|
end
|
135
144
|
end
|
136
145
|
end
|
@@ -1,18 +1,17 @@
|
|
1
1
|
module Spout
|
2
2
|
module Helpers
|
3
3
|
class TableFormatting
|
4
|
-
|
5
4
|
# def initialize(number)
|
6
5
|
# @number = number
|
7
6
|
# end
|
8
7
|
|
9
|
-
def self.number_with_delimiter(number, delimiter =
|
8
|
+
def self.number_with_delimiter(number, delimiter = ',')
|
10
9
|
number.to_s.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
|
11
10
|
end
|
12
11
|
|
13
12
|
# type: :count or :decimal
|
14
13
|
def self.format_number(number, type, format = nil)
|
15
|
-
if number
|
14
|
+
if number.nil?
|
16
15
|
format_nil(number)
|
17
16
|
elsif type == :count
|
18
17
|
format_count(number)
|
@@ -31,10 +30,9 @@ module Spout
|
|
31
30
|
# 1000 -> '1,000'
|
32
31
|
# Input (Numeric) -> Output (String)
|
33
32
|
def self.format_count(number)
|
34
|
-
(number == 0 || number
|
33
|
+
(number == 0 || number.nil?) ? '-' : number_with_delimiter(number)
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
36
|
# decimal:
|
39
37
|
# 0 -> '0.0'
|
40
38
|
# 10 -> '10.0'
|
@@ -43,7 +41,10 @@ module Spout
|
|
43
41
|
# 12412423.42252525 -> '12,412,423.4'
|
44
42
|
# Input (Numeric) -> Output (String)
|
45
43
|
def self.format_decimal(number, format)
|
46
|
-
|
44
|
+
precision = 1
|
45
|
+
precision = -Math.log10(number.abs).floor if number.abs < 1.0 && number != 0
|
46
|
+
|
47
|
+
number = number_with_delimiter(number.round(precision))
|
47
48
|
number = format % number if format
|
48
49
|
number
|
49
50
|
end
|
data/lib/spout/models/bucket.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
1
|
module Spout
|
2
2
|
module Models
|
3
|
+
# Defines a continuous or discrete bucket for tables and graphs
|
3
4
|
class Bucket
|
4
|
-
|
5
5
|
attr_accessor :start, :stop
|
6
6
|
|
7
|
-
def initialize(start, stop)
|
7
|
+
def initialize(start, stop, discrete: false)
|
8
8
|
@start = start
|
9
9
|
@stop = stop
|
10
|
+
@discrete = discrete
|
10
11
|
end
|
11
12
|
|
12
13
|
def in_bucket?(value)
|
13
|
-
value >= @start
|
14
|
+
value >= @start && value <= @stop
|
14
15
|
end
|
15
16
|
|
16
17
|
def display_name
|
18
|
+
return "#{@start}" if @discrete
|
17
19
|
"#{@start} to #{@stop}"
|
18
20
|
end
|
19
|
-
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -51,7 +51,7 @@ module Spout
|
|
51
51
|
else
|
52
52
|
domain_file = Dir.glob("domains/**/#{@json['domain'].to_s.downcase}.json", File::FNM_CASEFOLD).first
|
53
53
|
if domain_json = JSON.parse(File.read(domain_file)) rescue false
|
54
|
-
return domain_json.
|
54
|
+
return domain_json.is_a?(Array)
|
55
55
|
end
|
56
56
|
false
|
57
57
|
end
|
@@ -4,6 +4,8 @@ require 'spout/models/form'
|
|
4
4
|
|
5
5
|
module Spout
|
6
6
|
module Models
|
7
|
+
# Creates a structure that contains a dictionaries variables, domains, and
|
8
|
+
# forms
|
7
9
|
class Dictionary
|
8
10
|
attr_accessor :variables, :domains, :forms
|
9
11
|
attr_accessor :app_path
|
@@ -44,7 +46,7 @@ module Spout
|
|
44
46
|
private
|
45
47
|
|
46
48
|
def json_files(type)
|
47
|
-
Dir.glob(File.join(@app_path, type,
|
49
|
+
Dir.glob(File.join(@app_path, type, '**', '*.json'))
|
48
50
|
end
|
49
51
|
|
50
52
|
def load_type!(method)
|
data/lib/spout/models/domain.rb
CHANGED
@@ -5,9 +5,7 @@ require 'spout/models/option'
|
|
5
5
|
|
6
6
|
module Spout
|
7
7
|
module Models
|
8
|
-
|
9
8
|
class Domain < Spout::Models::Record
|
10
|
-
|
11
9
|
attr_accessor :id, :folder, :options
|
12
10
|
attr_reader :errors
|
13
11
|
|
@@ -26,11 +24,11 @@ module Spout
|
|
26
24
|
nil
|
27
25
|
end
|
28
26
|
rescue => e
|
29
|
-
@errors << "Parsing error found in #{@id}.json: #{e.message}"
|
27
|
+
@errors << "Parsing error found in #{@id}.json: #{e.message}" unless file_name.nil?
|
30
28
|
nil
|
31
29
|
end
|
32
30
|
|
33
|
-
if json
|
31
|
+
if json.is_a? Array
|
34
32
|
@id = file_name.to_s.gsub(/^(.*)\/|\.json$/, '').downcase
|
35
33
|
@options = (json || []).collect do |option|
|
36
34
|
Spout::Models::Option.new(option)
|
@@ -38,10 +36,13 @@ module Spout
|
|
38
36
|
elsif json
|
39
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]"
|
40
38
|
end
|
41
|
-
|
42
39
|
end
|
43
40
|
|
44
|
-
|
41
|
+
def deploy_params
|
42
|
+
{ name: id, folder: folder.to_s.gsub(%r{/$}, ''),
|
43
|
+
options: options.collect(&:deploy_params),
|
44
|
+
spout_version: Spout::VERSION::STRING }
|
45
|
+
end
|
45
46
|
end
|
46
47
|
end
|
47
48
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spout
|
2
|
+
module Models
|
3
|
+
# Used for empty values, these values exist in that the column is defined
|
4
|
+
# in the CSV, however the cell is blank. This is to differentiate this
|
5
|
+
# value from nil, where the subject row exists, but the column for the
|
6
|
+
# is not specified.
|
7
|
+
class Empty
|
8
|
+
def to_f
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
'Empty'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/spout/models/form.rb
CHANGED
@@ -9,8 +9,7 @@ require 'spout/models/record'
|
|
9
9
|
module Spout
|
10
10
|
module Models
|
11
11
|
class Form < Spout::Models::Record
|
12
|
-
|
13
|
-
attr_accessor :id, :display_name, :code_book
|
12
|
+
attr_accessor :id, :folder, :display_name, :code_book
|
14
13
|
attr_accessor :errors
|
15
14
|
|
16
15
|
def initialize(file_name, dictionary_root)
|
@@ -26,8 +25,8 @@ module Spout
|
|
26
25
|
nil
|
27
26
|
end
|
28
27
|
|
29
|
-
if json
|
30
|
-
%w(
|
28
|
+
if json.is_a? Hash
|
29
|
+
%w(display_name code_book).each do |method|
|
31
30
|
instance_variable_set("@#{method}", json[method])
|
32
31
|
end
|
33
32
|
|
@@ -35,9 +34,13 @@ module Spout
|
|
35
34
|
elsif json
|
36
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}"
|
37
36
|
end
|
38
|
-
|
39
37
|
end
|
40
38
|
|
39
|
+
def deploy_params
|
40
|
+
{ name: id, folder: folder.to_s.gsub(%r{/$}, ''),
|
41
|
+
display_name: display_name, code_book: code_book,
|
42
|
+
spout_version: Spout::VERSION::STRING }
|
43
|
+
end
|
41
44
|
end
|
42
45
|
end
|
43
46
|
end
|