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.
@@ -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