lunokhod 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ *.sw?
2
+ .rbx
3
+ Gemfile.lock
File without changes
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - rbx-19mode
4
+ - jruby-19mode
5
+ - 1.9.3
@@ -0,0 +1,4 @@
1
+ 0.0.1.pre (2013-03-08)
2
+ ----------------------
3
+
4
+ First public release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ * Implement proper operator precedence for dependencies (see
2
+ lib/lunokhod/rule_parsing.rb)
3
+ * Test suite
4
+ * Webpage generator
5
+ * Ruby program generator (to be used in tandem with the webpage for server-side
6
+ validation, querying, etc.)
@@ -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
@@ -0,0 +1,3 @@
1
+ require 'lunokhod/compiler'
2
+ require 'lunokhod/parser'
3
+ require 'lunokhod/error_report'
@@ -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