lunokhod 0.0.1.pre
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.
- data/.gitignore +3 -0
- data/.gitmodules +0 -0
- data/.travis.yml +5 -0
- data/CHANGELOG +4 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README +65 -0
- data/Rakefile +8 -0
- data/TODO +6 -0
- data/bin/lunokhod-driver +31 -0
- data/kitchen_sink_survey.rb +282 -0
- data/lib/lunokhod.rb +3 -0
- data/lib/lunokhod/ast.rb +263 -0
- data/lib/lunokhod/backends/debug.rb +137 -0
- data/lib/lunokhod/backends/webpage.rb +191 -0
- data/lib/lunokhod/backends/webpage/evaluator.js +24 -0
- data/lib/lunokhod/backends/webpage/jquery.min.js +5 -0
- data/lib/lunokhod/backends/webpage/page.html.erb +18 -0
- data/lib/lunokhod/compiler.rb +65 -0
- data/lib/lunokhod/condition_parsing.rb +188 -0
- data/lib/lunokhod/error_report.rb +44 -0
- data/lib/lunokhod/parser.rb +208 -0
- data/lib/lunokhod/rule_parser.kpeg +19 -0
- data/lib/lunokhod/rule_parser.rb +638 -0
- data/lib/lunokhod/rule_parsing.rb +41 -0
- data/lib/lunokhod/verifier.rb +155 -0
- data/lib/lunokhod/version.rb +3 -0
- data/lib/lunokhod/visitation.rb +12 -0
- data/lunokhod.gemspec +28 -0
- data/spec/lunokhod/error_report_spec.rb +62 -0
- data/spec/lunokhod/parser_spec.rb +87 -0
- data/spec/spec_helper.rb +6 -0
- metadata +181 -0
data/.gitignore
ADDED
data/.gitmodules
ADDED
File without changes
|
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 David Yip
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
1. Lunokhod
|
2
|
+
|
3
|
+
Lunokhod is a set of tools for parsing and compiling Surveyor[1] surveys.
|
4
|
+
|
5
|
+
2. Relationship to Surveyor
|
6
|
+
|
7
|
+
Surveyor does not provide access to a survey parse tree. It also does not
|
8
|
+
expose any public hooks for compiling Surveyor surveys to any form except the
|
9
|
+
form expected by Surveyor's ActionView partials and compatible views.
|
10
|
+
|
11
|
+
Lunokhod is intended to be a more flexible design. It provides tools to
|
12
|
+
generate an AST of a survey, perform some basic verifications on it (e.g.
|
13
|
+
checking for unresolvable or duplicate question references), and a compiler
|
14
|
+
that provides hooks for various backends. A backend that prints out the parse
|
15
|
+
tree is provided as an example. A backend that generates surveys as a Web page
|
16
|
+
with Javascript providing dependency evaluation logic is in progress.
|
17
|
+
|
18
|
+
Currently, Lunokhod is not at feature parity with Surveyor. Nevertheless,
|
19
|
+
the present state of Lunokhod is still useful for survey tasks where
|
20
|
+
loading surveys into a Rails application is not necessary and/or
|
21
|
+
time-consuming. Tasks that generate products based on survey structure (e.g.
|
22
|
+
generating or verifying translation files) often fall into this category.
|
23
|
+
|
24
|
+
3. Development and usage
|
25
|
+
|
26
|
+
You will need a Ruby 1.9 implementation; Lunokhod makes heavy use of Ruby
|
27
|
+
1.9 features, such as BasicObject and fibers. Lunokhod is known to work
|
28
|
+
on Rubinius 2.0.0.rc1, JRuby 1.7.2, and Ruby 1.9.3.
|
29
|
+
|
30
|
+
The short version:
|
31
|
+
|
32
|
+
$ bundle install
|
33
|
+
$ ruby bin/lunokhod-driver [SURVEYOR SURVEY]
|
34
|
+
|
35
|
+
By default, lunokhod-driver will use the pretty-printer backend. Read the
|
36
|
+
backend and AST to see how to use the tree visitor and what properties you can
|
37
|
+
access on each node.
|
38
|
+
|
39
|
+
Survey data may be piped in over standard input:
|
40
|
+
|
41
|
+
$ cat kitchen_sink_survey.rb | bin/lunokhod-driver
|
42
|
+
|
43
|
+
You can use the webpage backend by setting the BACKEND environment variable:
|
44
|
+
|
45
|
+
$ BACKEND=Webpage ruby bin/lunokhod-driver [SURVEY]
|
46
|
+
|
47
|
+
Keep in mind, however, that the webpage backend is currently very incomplete.
|
48
|
+
|
49
|
+
Lunokhod does not have an automated test suite, but should.
|
50
|
+
|
51
|
+
4. Authorship and license
|
52
|
+
|
53
|
+
Lunokhod was written by David Yip at NUBIC[2] when he wanted a faster way
|
54
|
+
to work with Surveyor surveys.
|
55
|
+
|
56
|
+
Lunokhod is released under the MIT license. See LICENSE for details.
|
57
|
+
|
58
|
+
5. Special thanks
|
59
|
+
|
60
|
+
How to destroy angels_, Kimbra, Little Boots, The Reign of Kindo, Dot Dot Dot.
|
61
|
+
|
62
|
+
[1]: https://github.com/NUBIC/surveyor
|
63
|
+
[2]: http://projects.nubic.northwestern.edu/
|
64
|
+
|
65
|
+
# vim:ts=2:sw=2:et:tw=78
|
data/Rakefile
ADDED
data/TODO
ADDED
data/bin/lunokhod-driver
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'lunokhod/backends/debug'
|
6
|
+
require 'lunokhod/backends/webpage'
|
7
|
+
require 'lunokhod/version'
|
8
|
+
require 'lunokhod'
|
9
|
+
|
10
|
+
backend = Lunokhod::Backends.const_get(ENV['BACKEND'] || 'Debug')
|
11
|
+
$stderr.puts "Lunokhod #{Lunokhod::VERSION} - backend: #{backend}"
|
12
|
+
|
13
|
+
file = ARGV[0]
|
14
|
+
|
15
|
+
data = file ? File.read(file) : $stdin.read
|
16
|
+
|
17
|
+
p = Lunokhod::Parser.new(data, file || '(stdin)')
|
18
|
+
p.parse
|
19
|
+
|
20
|
+
ep = Lunokhod::ErrorReport.new(p.surveys)
|
21
|
+
ep.run
|
22
|
+
|
23
|
+
if ep.errors?
|
24
|
+
$stderr.puts ep
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
b = backend.new
|
29
|
+
c = Lunokhod::Compiler.new(p.surveys, b)
|
30
|
+
c.compile
|
31
|
+
b.write
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
survey "Kitchen Sink survey" do
|
3
|
+
translations :es => 'translations/languages.es.yml',
|
4
|
+
:he => 'translations/languages.he.yml'
|
5
|
+
|
6
|
+
section_one "One" do
|
7
|
+
g_hello "Hello" do
|
8
|
+
q_name "What is your name?"
|
9
|
+
a_name :string, :help_text => "My name is..."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
section "Basic questions" do
|
14
|
+
# A label is a question that accepts no answers
|
15
|
+
label "These questions are examples of the basic supported input types"
|
16
|
+
|
17
|
+
# A basic question with radio buttons
|
18
|
+
question_1 "What is your favorite color?", :pick => :one
|
19
|
+
answer "red"
|
20
|
+
answer "blue"
|
21
|
+
answer "green"
|
22
|
+
answer "yellow"
|
23
|
+
answer :other
|
24
|
+
|
25
|
+
# A basic question with checkboxes
|
26
|
+
# "question" and "answer" may be abbreviated as "q" and "a"
|
27
|
+
q_2 "Choose the colors you don't like", :pick => :any
|
28
|
+
a_1 "red"
|
29
|
+
a_2 "blue"
|
30
|
+
a_3 "green"
|
31
|
+
a_4 "yellow"
|
32
|
+
a :omit
|
33
|
+
|
34
|
+
# A dependent question, with conditions and rule to logically join them
|
35
|
+
# the question's reference identifier is "2a", and the answer's reference_identifier is "1"
|
36
|
+
# question reference identifiers used in conditions need to be unique on a survey for the lookups to work
|
37
|
+
q_2a "Please explain why you don't like this color?"
|
38
|
+
a_1 "explanation", :text, :help_text => "Please give an explanation for each color you don't like"
|
39
|
+
dependency :rule => "A or B or C or D"
|
40
|
+
condition_A :q_2, "==", :a_1
|
41
|
+
condition_B :q_2, "==", :a_2
|
42
|
+
condition_C :q_2, "==", :a_3
|
43
|
+
condition_D :q_2, "==", :a_4
|
44
|
+
|
45
|
+
# A dependant question demonstrating the count operator. The
|
46
|
+
# dependency condition checks the answer count for the referenced question.
|
47
|
+
# It understands conditions of the form count> count< count>= count<=
|
48
|
+
# count!=
|
49
|
+
q_2b "Please explain why you dislike so many colors?"
|
50
|
+
a_1 "explanation", :text
|
51
|
+
dependency :rule => "Z"
|
52
|
+
condition_Z :q_2, "count>2"
|
53
|
+
|
54
|
+
# When :pick isn't specified, the default is :none (no checkbox or radio button)
|
55
|
+
q_montypython3 "What... is your name? (e.g. It is 'Arthur', King of the Britons)"
|
56
|
+
a_1 :string
|
57
|
+
|
58
|
+
# Dependency conditions can refer to any value, not just answer_id. An answer_reference still needs to be specified, to know which answer you would like to check
|
59
|
+
q_montypython4 "What... is your quest? (e.g. To seek the Holy Grail)"
|
60
|
+
a_1 :string
|
61
|
+
dependency :rule => "A"
|
62
|
+
condition_A :q_montypython3, "==", {:string_value => "It is 'Arthur', King of the Britons", :answer_reference => "1"}
|
63
|
+
|
64
|
+
# http://www.imdb.com/title/tt0071853/quotes
|
65
|
+
q_montypython5 "What... is the air-speed velocity of an unladen swallow? (e.g. What do you mean? An African or European swallow?)"
|
66
|
+
a_1 :string
|
67
|
+
dependency :rule => "A"
|
68
|
+
condition_A :q_montypython4, "==", {:string_value => "To seek the Holy Grail", :answer_reference => "1"}
|
69
|
+
|
70
|
+
label "Huh? I-- I don't know that! Auuuuuuuugh!"
|
71
|
+
dependency :rule => "A"
|
72
|
+
condition_A :q_montypython5, "==", {:string_value => "What do you mean? An African or European swallow?", :answer_reference => "1"}
|
73
|
+
|
74
|
+
q_cooling_1 "How do you cool your home?", :pick => :one
|
75
|
+
a_1 "Fans"
|
76
|
+
a_2 "Window AC"
|
77
|
+
a_3 "Central AC"
|
78
|
+
a_4 "Passive"
|
79
|
+
|
80
|
+
# When using !=, also use count>0 if you want to make sure the user responds
|
81
|
+
q_cooling_2 "How much does it cost to run your non-passive cooling solutions? (This question hidden until you respond 'How do you cool your home?')"
|
82
|
+
dependency :rule => "A and B"
|
83
|
+
condition_A :q_cooling_1, "!=", :a_4
|
84
|
+
condition_B :q_cooling_1, "count>0"
|
85
|
+
a_1 "$", :float
|
86
|
+
|
87
|
+
# Using != alone means the dependent question is shown by default
|
88
|
+
label "Please consider passive cooling solutions (This question always shown, unless you respond 'Passive')"
|
89
|
+
dependency :rule => "A"
|
90
|
+
condition_A :q_cooling_1, "!=", :a_4
|
91
|
+
|
92
|
+
# Surveys, sections, questions, groups, and answers all take the following reference arguments
|
93
|
+
# :reference_identifier # usually from paper
|
94
|
+
# :data_export_identifier # data export
|
95
|
+
# :common_namespace # maping to a common vocab
|
96
|
+
# :common_identifier # maping to a common vocab
|
97
|
+
q "Get me started on an improv sketch", :reference_identifier => "improv_start", :data_export_identifier => "i_s", :common_namespace => "sketch comedy questions", :common_identifier => "get me started"
|
98
|
+
a "who", :string
|
99
|
+
a "what", :string
|
100
|
+
a "where", :string
|
101
|
+
|
102
|
+
# Various types of responses can be accepted, and validated
|
103
|
+
q "How many pets do you own?"
|
104
|
+
a :integer
|
105
|
+
validation :rule => "A"
|
106
|
+
condition_A ">=", :integer_value => 0
|
107
|
+
|
108
|
+
# Surveys, sections, questions, groups, and answers also take a custom css class for covenience in custom styling
|
109
|
+
q "What is your address?", :custom_class => 'address'
|
110
|
+
a :text, :custom_class => 'mapper'
|
111
|
+
# validations can use regexp values
|
112
|
+
validation :rule => "A"
|
113
|
+
condition_A "=~", :regexp => "[0-9a-zA-z\. #]"
|
114
|
+
|
115
|
+
# Questions, groups, and answers take a custom renderer (a partial in the application's views dir)
|
116
|
+
# defaults are "/partials/question_group", "/partials/question", "/partials/answer", so the custom renderers should have a different name
|
117
|
+
q "Pick your favorite date AND time" #, :custom_renderer => "/partials/custom_question"
|
118
|
+
a :datetime
|
119
|
+
|
120
|
+
q_time_lunch "What time do you usually take a lunch break?"
|
121
|
+
a_1 :time
|
122
|
+
|
123
|
+
q "When would you like to meet for dinner?"
|
124
|
+
a :date
|
125
|
+
|
126
|
+
# Sliders deprecate to select boxes when javascript is off
|
127
|
+
# Valid Ruby code may be used to shorten repetitive tasks
|
128
|
+
q "Adjust the slider to reflect your level of awesomeness", :pick => :one, :display_type => :slider
|
129
|
+
(1..10).to_a.each{|num| a num.to_s}
|
130
|
+
|
131
|
+
q "How much do you like Ruby?", :pick => :one, :display_type => :slider
|
132
|
+
["not at all", "a little", "some", "a lot", "a ton"].each{|level| a level}
|
133
|
+
|
134
|
+
# The "|" pipe is used to place labels before or after the input elements
|
135
|
+
q "How much money do you want?"
|
136
|
+
a "$|USD", :float
|
137
|
+
|
138
|
+
# Questions may be grouped
|
139
|
+
group "How much oil do you use per day?", :display_type => :inline do
|
140
|
+
q "Quantity"
|
141
|
+
a :float
|
142
|
+
|
143
|
+
q "Unit", :pick => :one, :display_type => :dropdown
|
144
|
+
a "Barrels"
|
145
|
+
a "Gallons"
|
146
|
+
a "Quarts"
|
147
|
+
end
|
148
|
+
|
149
|
+
q "Choose your Illinois county", :pick => :one, :display_type => :dropdown
|
150
|
+
["Adams","Alexander","Bond", "Boone",
|
151
|
+
"Brown","Bureau","Calhoun","Carroll","Cass",
|
152
|
+
"Champaign", "Christian", "Clark","Clay",
|
153
|
+
"Clinton", "Coles", "Cook", "Crawford","Cumberland","DeKalb",
|
154
|
+
"De Witt","Douglas","DuPage","Edgar", "Edwards",
|
155
|
+
"Effingham","Fayette", "Ford","Franklin","Fulton",
|
156
|
+
"Gallatin","Greene", "Grundy", "Hamilton","Hancock",
|
157
|
+
"Hardin","Henderson","Henry","Iroquois","Jackson",
|
158
|
+
"Jasper", "Jefferson","Jersey","Jo Daviess","Johnson",
|
159
|
+
"Kane","Kankakee","Kendall","Knox", "La Salle",
|
160
|
+
"Lake", "Lawrence", "Lee","Livingston","Logan",
|
161
|
+
"Macon","Macoupin","Madison","Marion","Marshall", "Mason",
|
162
|
+
"Massac","McDonough","McHenry","McLean","Menard",
|
163
|
+
"Mercer","Monroe", "Montgomery","Morgan","Moultrie",
|
164
|
+
"Ogle","Peoria","Perry","Piatt","Pike","Pope",
|
165
|
+
"Pulaski","Putnam","Randolph","Richland","Rock Island",
|
166
|
+
"Saline","Sangamon","Schuyler","Scott","Shelby",
|
167
|
+
"St. Clair","Stark", "Stephenson","Tazewell","Union",
|
168
|
+
"Vermilion","Wabash","Warren","Washington","Wayne",
|
169
|
+
"White","Whiteside","Will","Williamson","Winnebago","Woodford"].each{ |county| a county}
|
170
|
+
|
171
|
+
# When an is_exclusive answer is checked, it unchecks all other options and disables them (using Javascript)
|
172
|
+
q "Choose your favorite meats", :display_type => :inline, :pick => :any
|
173
|
+
a "Chicken"
|
174
|
+
a "Pork"
|
175
|
+
a "Beef"
|
176
|
+
a "Shellfish"
|
177
|
+
a "Fish"
|
178
|
+
a "I don't eat meats!!!", :is_exclusive => true
|
179
|
+
|
180
|
+
q "All out of ideas for questions?", :pick => :one, :display_type => :inline
|
181
|
+
a "yes"
|
182
|
+
a "maybe"
|
183
|
+
a "no"
|
184
|
+
a "I don't know"
|
185
|
+
end
|
186
|
+
|
187
|
+
section "Complicated questions" do
|
188
|
+
|
189
|
+
# Grids are useful for repeated questions with the same set of answers, which are specified before the questions
|
190
|
+
grid "Tell us how often do you cover these each day" do
|
191
|
+
a "1"
|
192
|
+
a "2"
|
193
|
+
a "3"
|
194
|
+
q "Head", :pick => :one
|
195
|
+
q "Knees", :pick => :one
|
196
|
+
q "Toes", :pick => :one
|
197
|
+
end
|
198
|
+
|
199
|
+
# "grid" is a shortcut for group with :display_type => :grid
|
200
|
+
# The answers will be repeated every 10 rows, but long grids aren't recommended as they are generally tedious
|
201
|
+
grid "Tell us how you feel today day" do
|
202
|
+
a "-2"
|
203
|
+
a "-1"
|
204
|
+
a "0"
|
205
|
+
a "1"
|
206
|
+
a "2"
|
207
|
+
q "down|up" , :pick => :one
|
208
|
+
q "sad|happy", :pick => :one
|
209
|
+
q "limp|perky", :pick => :one
|
210
|
+
end
|
211
|
+
|
212
|
+
grid "For each of the car types checked, what type of options would you prefer?" do
|
213
|
+
a "Leather seats"
|
214
|
+
a "Shiny rims"
|
215
|
+
a "Subwoofer"
|
216
|
+
a "Sunroof"
|
217
|
+
a "Turbocharger"
|
218
|
+
q "SUV", :pick => :any
|
219
|
+
q "Sedan", :pick => :any
|
220
|
+
q "Coupe", :pick => :any
|
221
|
+
q "Convertible", :pick => :any
|
222
|
+
q "Minivan", :pick => :any
|
223
|
+
q "Crossover", :pick => :any
|
224
|
+
q "Wagon", :pick => :any
|
225
|
+
q "Other", :pick => :any
|
226
|
+
end
|
227
|
+
|
228
|
+
q "Please rank the following foods based on how much you like them"
|
229
|
+
a "|pizza", :integer
|
230
|
+
a "|salad", :integer
|
231
|
+
a "|sushi", :integer
|
232
|
+
a "|ice cream", :integer
|
233
|
+
a "|breakfast ceral", :integer
|
234
|
+
|
235
|
+
# :other, :string allows someone to specify both the "other" and some other information
|
236
|
+
q "Choose your favorite utensils and enter frequency of use (daily, weekly, monthly, etc...)", :pick => :any
|
237
|
+
a "spoon", :string
|
238
|
+
a "fork", :string
|
239
|
+
a "knife", :string
|
240
|
+
a :other, :string
|
241
|
+
|
242
|
+
q "What is your birth date?", :pick => :one
|
243
|
+
a "I was born on", :date
|
244
|
+
a "Refused"
|
245
|
+
|
246
|
+
q "At what time were you born?", :pick => :any
|
247
|
+
a "I was born at", :time
|
248
|
+
a "This time is approximate"
|
249
|
+
|
250
|
+
q "When would you like to schedule your next appointment?"
|
251
|
+
a :datetime
|
252
|
+
|
253
|
+
q_car "Do you own a car?", :pick => :one
|
254
|
+
a_y "Yes"
|
255
|
+
a_n "No"
|
256
|
+
|
257
|
+
# Repeaters allow multiple responses to a question or set of questions
|
258
|
+
repeater "Tell us about the cars you own" do
|
259
|
+
dependency :rule => "A"
|
260
|
+
condition_A :q_car, "==", :a_y
|
261
|
+
q "Make", :pick => :one, :display_type => :dropdown
|
262
|
+
a "Toyota"
|
263
|
+
a "Ford"
|
264
|
+
a "GMChevy"
|
265
|
+
a "Ferrari"
|
266
|
+
a "Tesla"
|
267
|
+
a "Honda"
|
268
|
+
a "Other weak brand"
|
269
|
+
q "Model"
|
270
|
+
a :string
|
271
|
+
q "Year"
|
272
|
+
a :string
|
273
|
+
end
|
274
|
+
|
275
|
+
repeater "Tell us the name and age of your siblings" do
|
276
|
+
q "Sibling"
|
277
|
+
a "Name", :string
|
278
|
+
a "Age|years", :integer
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|
data/lib/lunokhod.rb
ADDED
data/lib/lunokhod/ast.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'lunokhod/condition_parsing'
|
2
|
+
require 'lunokhod/rule_parsing'
|
3
|
+
require 'uuidtools'
|
4
|
+
|
5
|
+
module Lunokhod
|
6
|
+
module Ast
|
7
|
+
LUNOKHOD_V0_NAMESPACE = UUIDTools::UUID.parse('ecab6cb2-8755-11e2-8caf-b8f6b111aef5')
|
8
|
+
|
9
|
+
module Identifiable
|
10
|
+
def identity
|
11
|
+
@identity ||= ident(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ident(o)
|
15
|
+
case o
|
16
|
+
when NilClass, TrueClass, FalseClass, Numeric, Symbol, String then o.to_s
|
17
|
+
when Hash then o.keys.sort.map { |k| ident([k, o[k]]) }.flatten.join
|
18
|
+
when Array then o.map { |v| ident(v) }.flatten.join
|
19
|
+
when Struct then o.values.map { |v| ident(v) }.flatten.join
|
20
|
+
else raise "Cannot derive identity of #{o.class}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def uuid
|
25
|
+
UUIDTools::UUID.sha1_create(LUNOKHOD_V0_NAMESPACE, identity)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module CommonOptions
|
30
|
+
%w(help_text custom_class display_type pick).each do |m|
|
31
|
+
class_eval <<-END
|
32
|
+
def #{m}
|
33
|
+
options[:#{m}]
|
34
|
+
end
|
35
|
+
END
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module SurveyorTag
|
40
|
+
##
|
41
|
+
# Surveyor interprets tags from two places:
|
42
|
+
#
|
43
|
+
# 1. the method tag
|
44
|
+
# 2. the :reference_identifier option
|
45
|
+
#
|
46
|
+
# This override is not intentional; it's just that the method's tag and
|
47
|
+
# the :reference_identifier option end up in the same column in
|
48
|
+
# Surveyor's database.
|
49
|
+
#
|
50
|
+
# There is no documented precedence rule, but analysis of Surveyor code
|
51
|
+
# and experiments have shown me that #2 usually overrides #1.
|
52
|
+
def surveyor_tag
|
53
|
+
options[:reference_identifier] || tag
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Survey < Struct.new(:line, :name, :options, :sections, :translations, :source)
|
58
|
+
include CommonOptions
|
59
|
+
include Identifiable
|
60
|
+
|
61
|
+
attr_accessor :parent
|
62
|
+
|
63
|
+
def initialize(*)
|
64
|
+
super
|
65
|
+
|
66
|
+
self.sections ||= []
|
67
|
+
self.translations ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def children
|
71
|
+
translations + sections
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Translation < Struct.new(:line, :lang, :path)
|
76
|
+
include Identifiable
|
77
|
+
|
78
|
+
attr_accessor :parent
|
79
|
+
|
80
|
+
def children
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Section < Struct.new(:line, :tag, :name, :options, :questions)
|
86
|
+
include CommonOptions
|
87
|
+
include Identifiable
|
88
|
+
include SurveyorTag
|
89
|
+
|
90
|
+
attr_accessor :parent
|
91
|
+
|
92
|
+
alias_method :survey, :parent
|
93
|
+
|
94
|
+
def initialize(*)
|
95
|
+
super
|
96
|
+
|
97
|
+
self.questions ||= []
|
98
|
+
end
|
99
|
+
|
100
|
+
def children
|
101
|
+
questions
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Label < Struct.new(:line, :text, :tag, :options, :dependencies)
|
106
|
+
include CommonOptions
|
107
|
+
include Identifiable
|
108
|
+
include SurveyorTag
|
109
|
+
|
110
|
+
attr_accessor :parent
|
111
|
+
|
112
|
+
def initialize(*)
|
113
|
+
super
|
114
|
+
|
115
|
+
self.dependencies ||= []
|
116
|
+
end
|
117
|
+
|
118
|
+
def children
|
119
|
+
dependencies
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Question < Struct.new(:line, :text, :tag, :options, :answers, :dependencies)
|
124
|
+
include CommonOptions
|
125
|
+
include Identifiable
|
126
|
+
include SurveyorTag
|
127
|
+
|
128
|
+
attr_accessor :parent
|
129
|
+
|
130
|
+
def initialize(*)
|
131
|
+
super
|
132
|
+
|
133
|
+
self.answers ||= []
|
134
|
+
self.dependencies ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
def children
|
138
|
+
answers + dependencies
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Answer < Struct.new(:line, :text, :type, :other, :tag, :validations, :options)
|
143
|
+
include CommonOptions
|
144
|
+
include Identifiable
|
145
|
+
include SurveyorTag
|
146
|
+
|
147
|
+
attr_accessor :parent
|
148
|
+
|
149
|
+
def initialize(*)
|
150
|
+
super
|
151
|
+
|
152
|
+
self.validations ||= []
|
153
|
+
self.other ||= false
|
154
|
+
end
|
155
|
+
|
156
|
+
def children
|
157
|
+
validations
|
158
|
+
end
|
159
|
+
|
160
|
+
def other?
|
161
|
+
other
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Dependency < Struct.new(:line, :rule, :conditions)
|
166
|
+
include Identifiable
|
167
|
+
include RuleParsing
|
168
|
+
|
169
|
+
attr_accessor :parent
|
170
|
+
|
171
|
+
def initialize(*)
|
172
|
+
super
|
173
|
+
|
174
|
+
self.conditions ||= []
|
175
|
+
end
|
176
|
+
|
177
|
+
def children
|
178
|
+
conditions
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class Validation < Struct.new(:line, :rule, :conditions)
|
183
|
+
include Identifiable
|
184
|
+
include RuleParsing
|
185
|
+
|
186
|
+
attr_accessor :parent
|
187
|
+
|
188
|
+
def initialize(*)
|
189
|
+
super
|
190
|
+
|
191
|
+
self.conditions ||= []
|
192
|
+
end
|
193
|
+
|
194
|
+
def children
|
195
|
+
conditions
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Group < Struct.new(:line, :tag, :name, :options, :questions, :dependencies)
|
200
|
+
include CommonOptions
|
201
|
+
include Identifiable
|
202
|
+
include SurveyorTag
|
203
|
+
|
204
|
+
attr_accessor :parent
|
205
|
+
|
206
|
+
def initialize(*)
|
207
|
+
super
|
208
|
+
|
209
|
+
self.questions ||= []
|
210
|
+
self.dependencies ||= []
|
211
|
+
end
|
212
|
+
|
213
|
+
def children
|
214
|
+
questions + dependencies
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
class Condition < Struct.new(:line, :tag, :predicate)
|
219
|
+
include Identifiable
|
220
|
+
include ConditionParsing
|
221
|
+
|
222
|
+
attr_accessor :parent
|
223
|
+
|
224
|
+
def children
|
225
|
+
[]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class Grid < Struct.new(:line, :tag, :text, :questions, :answers)
|
230
|
+
include Identifiable
|
231
|
+
|
232
|
+
attr_accessor :parent
|
233
|
+
|
234
|
+
def initialize(*)
|
235
|
+
super
|
236
|
+
|
237
|
+
self.answers ||= []
|
238
|
+
self.questions ||= []
|
239
|
+
end
|
240
|
+
|
241
|
+
def children
|
242
|
+
questions + answers
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class Repeater < Struct.new(:line, :tag, :text, :questions, :dependencies)
|
247
|
+
include Identifiable
|
248
|
+
|
249
|
+
attr_accessor :parent
|
250
|
+
|
251
|
+
def initialize(*)
|
252
|
+
super
|
253
|
+
|
254
|
+
self.questions ||= []
|
255
|
+
self.dependencies ||= []
|
256
|
+
end
|
257
|
+
|
258
|
+
def children
|
259
|
+
questions + dependencies
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|