verbvector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ pkg/*
2
+ *.swp
3
+ *.gem
4
+ .bundle
5
+ rdoc/
6
+ LOG
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in verbvector.gemspec
4
+ gemspec
data/README.textile ADDED
@@ -0,0 +1,96 @@
1
+ h1. Lingustics::Verbs::Verbvector::VerbvectorGenerator
2
+
3
+ This is, what I hope will become a burgeoning array of tools for those interested in languages and linguistics. Any given language's conjugation can be seen as a _vector_, an intersection of several _aspects_ that singularly identify the correct grammatical choice for a given utterance.
4
+
5
+ In Latin, for example, this is defined by *voice*, *mood*, *tense*, *number*, and *person*. Other languages have a more- or less-specific vector. The function of this module, therefore, is to make the exhaustive generation of these vectors simple.
6
+
7
+ h1. Usage
8
+
9
+ Usage is performed by passing a DSL into the module. The module then returns a module based on the _language_ parameter. This resultant module can be mixed-in to your class (say, *LanguageVerb*) to define all the possible method calls (for playing nice with responds_to in case *LanguageVerb* uses metaprogramming in its responses)
10
+
11
+ h1. DSL
12
+
13
+ Initial attempts at producing a verb tense generator wound up producing an incredibly confusing snarl of @eval@ statments and other oddities and wound up neither being specific enough to be easily used nor portable enough to be generally helpful.
14
+
15
+ The DSL is simple and recognizes the following statements:
16
+
17
+ h2. language :LanguageName _block_
18
+
19
+ The first statement. It takes a block that will be further processed and which contains other defining statements for the :Language. The only accepted argument is a symbol for the Language name.
20
+
21
+ h2. all_vectors :position _block_
22
+
23
+ This defines an aspect common to *all* vectors. Aspects are defined in an anonymous hash included in the block. The :position symbol is not presently used but instead stacks from left to right. This may have reason to change.
24
+
25
+ Inside the block one gives the _aspect_ as a symbol and then gives an array containing the valid specifications for that aspect.
26
+
27
+ Example:
28
+
29
+ <pre>
30
+ {
31
+ :voice => %w(active passive),
32
+ :mood => %w(indicative subjunctive imperative)
33
+ }
34
+ </pre>
35
+
36
+ h2. vectors_that Regexp _block_
37
+
38
+ This defines an aspect common to *all* vectors. Aspects are defined in an anonymous hash included in the block. Thus for all existing vectors in the vector stack that match the Regexp, this aspect/specification will be added
39
+
40
+ <pre>
41
+ {
42
+ :tense => %w(present imperfect future
43
+ perfect pastperfect futureperfect)
44
+ }
45
+ </pre>
46
+
47
+ h2. exception (:remove|:add), :branch _block_
48
+
49
+ Languages are not perfectly rational in their combinations. Occasionally you may need to manually add a vector or, more likely, prune a vector.
50
+
51
+ h1. Example
52
+
53
+ _Taken from the test/ directory_
54
+
55
+ <pre>
56
+
57
+ Lingustics::Verbs::Verbvector::VerbvectorGenerator.new do
58
+ language :Latin do
59
+ all_vectors :start_with do
60
+ {
61
+ :voice => %w(active passive),
62
+ :mood => %w(indicative subjunctive imperative)
63
+ }
64
+ end
65
+ vectors_that /.*_indicative_mood/ do
66
+ {
67
+ :tense => %w(present imperfect future
68
+ perfect pastperfect futureperfect)
69
+ }
70
+ end
71
+ vectors_that /.*_subjunctive_mood/ do
72
+ {
73
+ :tense => %w(present imperfect
74
+ perfect pastperfect)
75
+ }
76
+ end
77
+ vectors_that /.*_imperative_mood/ do
78
+ {
79
+ :tense => %w(present)
80
+ }
81
+ end
82
+ all_vectors :end_with do
83
+ {
84
+ :number => %w(singular plural),
85
+ :person => %w(first second third)
86
+ }
87
+ end
88
+ exception :remove, :passive_voice_imperative_mood_present_tense
89
+ end
90
+ end
91
+
92
+ </pre>
93
+
94
+ h1. Author
95
+
96
+ Steven G. Harms
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ #Added to get testing working
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test)
7
+
8
+ require "rake/rdoctask"
9
+ Rake::RDocTask.new do |rd|
10
+ rd.rdoc_files.include("lib/**/*.rb")
11
+ rd.rdoc_dir = "rdoc"
12
+ end
data/lib/verbvector.rb ADDED
@@ -0,0 +1,295 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require "verbvector"
3
+ require 'pp'
4
+
5
+ # Generalized module for handling lingustics processing
6
+ module Linguistics
7
+ # Generalized module related to the conjugation of verbs
8
+ module Verbs
9
+ # Each language's specific verb conjugation can be described in terms of a
10
+ # verbal vector, the particular intersection of its voice, tense, number,
11
+ # and person.
12
+ module Verbvector
13
+ # A class designed to generate a module that can be mixed in into a
14
+ # given type of verb (French, Latin, Spanish). I'm not trying to be
15
+ # obscure, but I'm trying to be very generalizable.
16
+ class VerbvectorGenerator
17
+ attr_reader :language, :aspect_list, :vector_list, :cluster_methods,
18
+ :respondable_methods
19
+
20
+ # Class methods go here
21
+ class << self
22
+ end
23
+
24
+ # Initialization
25
+ #
26
+ # Takes the descriptive block of the tense structure in a DSL format
27
+ def initialize(&b)
28
+ @aspect_list = []
29
+ @vector_list = []
30
+ @respondable_methods = []
31
+ @cluster_methods = {}
32
+ @language = ""
33
+
34
+ # Let's remember the difference between instance_ and class_eval.
35
+ #
36
+ # "class_eval sets things up as if you were in the body of a class
37
+ # definition, so method definitions will define instance methods:"
38
+ #
39
+ # "instance_eval on a class acts as if you were working inside the
40
+ # singleton class of self. Therefore, any methods you define will
41
+ # become class methods." - prog ruby 1.9 v3, prag programmers p.388
42
+ #
43
+ # As such, instance_eval here is about to apply this eval to the
44
+ # eigenclass. That means any instance of this class will have this
45
+ # method.
46
+ instance_eval &b
47
+
48
+ @aspect_list.sort!
49
+ end
50
+
51
+ # Vectors are specified at their most atomic
52
+ # Sometimes it is handy to return all the values that match
53
+ # "up to" a given aspect and then uniq'ify them
54
+
55
+ def match_vector_upto_aspect(s)
56
+ @vector_list.compact.sort.grep(/#{s}/).map{ |x|
57
+ x.sub(/(^.*#{s}).*/,%q(\1))
58
+ }.uniq
59
+ end
60
+
61
+ # Creates the anonymous module based on the contents of @vector_list.
62
+ # The method names in this module are just stubs _except_ those that
63
+ # are loaded into the @cluster_methods hash. By generating all the
64
+ # method names we allow +responds_to?+ to work as expected.
65
+
66
+ def method_extension_module
67
+ v = @vector_list
68
+ c = @cluster_methods
69
+ l = @language
70
+ r = @respondable_methods
71
+
72
+ Module.new do
73
+ # This defines instance methods on the Module
74
+ # m.instance_methods #=> [:say_foo, :say_bar, :say_bat]
75
+
76
+ # Note, you can't use @someArray in the iteration because
77
+ # self has changed to this anonymous module. Since a block
78
+ # is a binding, it has the local context (including 'v')
79
+ # bundled up with it -- despte self having changed!
80
+ # Therefore, the following works.
81
+
82
+ # Define a method for each name in vector_list.
83
+ raise("Language was not defined." ) if l.nil?
84
+
85
+ v.each do |m|
86
+ r << "#{m}"
87
+ define_method "#{l.downcase}_#{m}".to_sym do
88
+ end
89
+ end
90
+
91
+ # Write something to spit out the vectors as well.
92
+ define_method :vector_list do
93
+ return v
94
+ end
95
+
96
+ define_method :respondable_methods do
97
+ return r
98
+ end
99
+
100
+ # Spit out the clustered methods
101
+ c.each_pair do |k,v|
102
+ define_method k do
103
+ v.call
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+
110
+ # Language takes a symbol for +l+ the language whose verb we seek to
111
+ # model. It then takes a block for the sub-specification of the verbs
112
+ # of that language.
113
+ def language(*l,&b)
114
+ return @language if (l[0].nil? and not @language.nil?)
115
+ @language = l[0]
116
+ instance_eval &b
117
+ end
118
+
119
+ # Method generates tense vectors based on aspects that are assumed to
120
+ # apply to all possible vectors. These would be seen as the most
121
+ # general aspects possible For example, while only *some* vectors are
122
+ # present tense, *all* vectors have a voice.
123
+
124
+ def all_vectors(position,&b)
125
+ # Make sure there is a block given
126
+ return unless (block_given? or yield.first)
127
+
128
+ # Sentinel condition for stopping recursive call
129
+ return @vector_list unless yield.first
130
+
131
+ # Provided that there was a block, collect the DSL hash into
132
+ # specifications
133
+ specifications = yield
134
+
135
+ # Extract the first k/v
136
+ specification = specifications.first
137
+
138
+ # Based on the key for each row, take that content and postpend
139
+ # it.to_s to the ending of each value held in the hash element value
140
+ expanded_specification = combinatorialize(specification)
141
+
142
+ # Remove the expanded value from the specifications hash
143
+ specifications.delete(specification[0])
144
+
145
+ # Keep a record of aspects we have seen
146
+ @aspect_list.push specification[0]
147
+ @aspect_list.uniq!
148
+
149
+ # If it's the first go round put the first set of values in. In
150
+ # general these should be the leftmost and theremfore most general
151
+ if @vector_list.empty?
152
+ @vector_list = expanded_specification
153
+ else
154
+ # If there's already a definition in the tens list, for each of
155
+ # the _existing_ values add the array of strings seen in
156
+ # expanded_specifications thereunto. Hold them in 'temp' and then
157
+ # set @vector_list to temp.
158
+ temp = []
159
+ @vector_list.each do |base|
160
+ expanded_specification.each do |u|
161
+ temp.push base+"_#{u}"
162
+ end
163
+ end
164
+ @vector_list = temp
165
+ end
166
+
167
+ # Recursive call, sentnel contition is at the top of the method
168
+ all_vectors(position) do
169
+ specifications
170
+ end
171
+
172
+ instance_eval &b
173
+ end
174
+
175
+ # Method appends vector definitions _if_ the +condition+ (a RegEx) is satisfied
176
+ def vectors_that(condition,&b)
177
+ matching_stems = @vector_list.grep condition
178
+ temp = []
179
+
180
+ specifications = yield
181
+
182
+ # Extract the first k/v
183
+ specification = specifications.first
184
+
185
+ # Based on the key for each row, take that content and postpend
186
+ # it.to_s to the ending of each value held in the hash element value
187
+ expanded_specification = combinatorialize(specification)
188
+
189
+ # Remove the expanded value from the specifications hash
190
+ specifications.delete(specification[0])
191
+
192
+ # Keep a record of aspects we have seen
193
+ @aspect_list.push specification[0]
194
+ @aspect_list.uniq!
195
+
196
+ # So we grepped the desired stems and stored them in matching_stems
197
+ # First we delete those stems (becasue we're going to further specify) them
198
+ matching_stems.each do |x|
199
+ @vector_list.delete x
200
+ expanded_specification.each do |u|
201
+ temp.push x+"_#{u}"
202
+ end
203
+ end
204
+
205
+ # Combine the original list with the freshly expanded list
206
+ @vector_list = (@vector_list + temp).sort
207
+ end
208
+
209
+ # Languages are not entirely rational, while something _ought_ exist
210
+ # by the rules of rational combination, some times they simply _don't_
211
+ # exist. That's what this method is for.
212
+ #
213
+ # +action+ :: +:remove+ or +:add+
214
+ # +id+ :: method name to remove
215
+ # _block_ :: used to add
216
+ def exception(action, id, &b)
217
+ if action == :remove
218
+ @vector_list.delete_if {|x| x =~ /#{id.to_s}/ }
219
+ elsif action == :add
220
+ end
221
+ end
222
+
223
+ # Method to take a hash key where the key is an _aspect_ and the value
224
+ # is an array of specifications valid for that _aspect_.
225
+ def combinatorialize(h)
226
+ results = []
227
+ h[1].each do |k|
228
+ results.push "#{k}_#{h[0]}"
229
+ end
230
+ results
231
+ end
232
+
233
+ # Method to allow "clusters" of simiar vectors to be identified (see:
234
+ # +match_upto+) based on the 0th string match. These clusters can be
235
+ # called by the method namd provided as the 2nd argument (0-index)
236
+ #
237
+ # This allows
238
+ # active_voice_indicative_mood_imperfect_tense_singular_number_third_p
239
+ # erson and other such to be 'clustered' as
240
+ # active_voice_indicative_mood_imperfect_tense. This means the actual
241
+ # method will probably be done in the "cluster" and some sort of
242
+ # secondary logic (method_missing) will do the final resolution
243
+ #
244
+ # Nevertheless, per the logic of this library, by defining all the
245
+ # atomic, we play nice and give respond_to? all the information it
246
+ # needs
247
+ #
248
+ # *Example:* <tt>cluster_on /active_voice.*third/, "as method", :active_thirds</tt>
249
+ #
250
+ #
251
+ # This means you want to collect several method names that match the Regexp and make them identifiable by a call to the
252
+ # method +active_thirds+
253
+ #
254
+ # Alternatively, you might want to use a String or Symbol (making use of match_upto).
255
+ #
256
+ # *Example:* <tt>cluster_on :tense, "as method", :tense_list</tt>
257
+ #
258
+ # +match+ :: The String or Regex match_upto will use for matching
259
+ # +junk+ :: Syntactic sugar, a string that makes the DSL look sensible
260
+ # +method_name+ :: The method in the anonymous module that returns the matched method names. See +create_module+
261
+ def cluster_on(match, junk, method_name)
262
+
263
+ clustered_matches =
264
+ if match.class == Regexp
265
+ @vector_list.grep match
266
+ elsif match.class.to_s =~ /^(String|Symbol)/
267
+ # Get the items that match_upto the specified clustering token
268
+ match_vector_upto_aspect(match.to_s)
269
+ else
270
+ # This shouldn't happen, we should get a Regexp or a String/Symbol
271
+ raise "Didn't fire for clustered match: #{match}"
272
+ end
273
+
274
+ unless clustered_matches.nil?
275
+ # No, this should not be done:
276
+ # ...and add them to the @vector_list.
277
+ # @vector_list += clustered_matches
278
+ # Clustered methods need to be defined, for real, somewhere. We
279
+ # should not claim to respond to them here, but rather let the
280
+ # framework using verbvector have the responsibility for
281
+ # implementation.
282
+
283
+
284
+ # Now, define a Proc that can correspond to arg[2]
285
+ @cluster_methods[method_name] = Proc.new do
286
+ clustered_matches
287
+ end
288
+ end
289
+
290
+ end
291
+
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,8 @@
1
+ module Linguistics
2
+ module Verbs
3
+ module Verbvector
4
+ # Version of the gem
5
+ VERSION = "0.0.1"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ active_voice_imperative_mood_present_tense
2
+ active_voice_indicative_mood_present_tense
3
+ active_voice_indicative_mood_imperfect_tense
4
+ active_voice_indicative_mood_future_tense
5
+ active_voice_indicative_mood_perfect_tense
6
+ active_voice_indicative_mood_pastperfect_tense
7
+ active_voice_indicative_mood_futureperfect_tense
8
+ passive_voice_indicative_mood_present_tense
9
+ passive_voice_indicative_mood_imperfect_tense
10
+ passive_voice_indicative_mood_future_tense
11
+ passive_voice_indicative_mood_perfect_tense
12
+ passive_voice_indicative_mood_pastperfect_tense
13
+ passive_voice_indicative_mood_futureperfect_tense
14
+ active_voice_subjunctive_mood_present_tense
15
+ active_voice_subjunctive_mood_imperfect_tense
16
+ active_voice_subjunctive_mood_perfect_tense
17
+ active_voice_subjunctive_mood_pastperfect_tense
18
+ passive_voice_subjunctive_mood_present_tense
19
+ passive_voice_subjunctive_mood_imperfect_tense
20
+ passive_voice_subjunctive_mood_perfect_tense
21
+ passive_voice_subjunctive_mood_pastperfect_tense
@@ -0,0 +1,135 @@
1
+ require "test/unit"
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+ require "verbvector"
5
+
6
+ class TestVerbVector < Test::Unit::TestCase
7
+ def setup
8
+ @vv =
9
+ Linguistics::Verbs::Verbvector::VerbvectorGenerator.new do
10
+ language :Latin do
11
+ all_vectors :start_with do
12
+ {
13
+ :voice => %w(active passive),
14
+ :mood => %w(indicative subjunctive imperative)
15
+ }
16
+ end
17
+ vectors_that /.*_indicative_mood/ do
18
+ {
19
+ :tense => %w(present imperfect future
20
+ perfect pastperfect futureperfect)
21
+ }
22
+ end
23
+ vectors_that /.*_subjunctive_mood/ do
24
+ {
25
+ :tense => %w(present imperfect
26
+ perfect pastperfect)
27
+ }
28
+ end
29
+ vectors_that /.*_imperative_mood/ do
30
+ {
31
+ :tense => %w(present)
32
+ }
33
+ end
34
+ all_vectors :end_with do
35
+ {
36
+ :number => %w(singular plural),
37
+ :person => %w(first second third)
38
+ }
39
+ end
40
+ exception :remove, :passive_voice_imperative_mood_present_tense
41
+ cluster_on :tense, "as method", :tense_list
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_basics
47
+ v = Linguistics::Verbs::Verbvector::VerbvectorGenerator.new do
48
+ end
49
+
50
+ assert_not_nil(v)
51
+ assert_equal(:Latin, @vv.language)
52
+ end
53
+
54
+ def test_vector_matcher
55
+ assert_equal 2, @vv.match_vector_upto_aspect("voice").length
56
+ assert_equal 5, @vv.match_vector_upto_aspect("mood").length
57
+ assert_equal 21, @vv.match_vector_upto_aspect("tense").length
58
+ end
59
+
60
+ def test_tense_resolution
61
+ tense_list = Array.new(File.open(File.join(File.dirname(__FILE__), *%w[fixtures tense_list])).read.split /\s/)
62
+ tense_list.sort!
63
+
64
+ tc = Class.new
65
+ tc.extend @vv.method_extension_module
66
+
67
+ assert_equal tense_list, tc.tense_list
68
+ end
69
+
70
+ def test_clustering_with_regex
71
+ t =
72
+ Linguistics::Verbs::Verbvector::VerbvectorGenerator.new do
73
+ language :Latin do
74
+ all_vectors :start_with do
75
+ {
76
+ :voice => %w(active passive),
77
+ :mood => %w(indicative subjunctive imperative)
78
+ }
79
+ end
80
+ vectors_that /.*_indicative_mood/ do
81
+ {
82
+ :tense => %w(present imperfect future
83
+ perfect pastperfect futureperfect)
84
+ }
85
+ end
86
+ vectors_that /.*_subjunctive_mood/ do
87
+ {
88
+ :tense => %w(present imperfect
89
+ perfect pastperfect)
90
+ }
91
+ end
92
+ vectors_that /.*_imperative_mood/ do
93
+ {
94
+ :tense => %w(present)
95
+ }
96
+ end
97
+ all_vectors :end_with do
98
+ {
99
+ :number => %w(singular plural),
100
+ :person => %w(first second third)
101
+ }
102
+ end
103
+ exception :remove, :passive_voice_imperative_mood_present_tense
104
+ cluster_on /active_voice.*third/, "as method", :active_thirds
105
+ end
106
+ end
107
+ tc = Class.new
108
+ tc.extend t.method_extension_module
109
+ assert_respond_to(tc, :active_thirds)
110
+ assert_equal(22, tc.active_thirds.length)
111
+ end
112
+
113
+ def test_extension
114
+ tc = Class.new
115
+ tc.extend @vv.method_extension_module
116
+ assert_respond_to(tc, :latin_active_voice_indicative_mood_imperfect_tense_singular_number_third_person)
117
+ end
118
+
119
+ def test_clustering
120
+ assert_respond_to(@vv, :vectors_that)
121
+ assert_respond_to(@vv, :cluster_on)
122
+
123
+ tc = Class.new
124
+ tc.extend @vv.method_extension_module
125
+
126
+ # Make sure that each cluster method is /not/ defined. We want these to
127
+ # be defined "for real," not as a proxy.
128
+
129
+ cms=@vv.cluster_methods[:tense_list].call
130
+ cms.each do |m|
131
+ assert_true not(tc.respond_to? m.to_sym)
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "verbvector/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "verbvector"
7
+ s.version = Linguistics::Verbs::Verbvector::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Steven G. Harms"]
10
+ s.email = ["github@sgharms.oib.com"]
11
+ s.homepage = "http://rubygems.org/gems/verbvector"
12
+ s.summary = %q{Generates the verb tense "vectors" based on a DSL description syntax.}
13
+ s.description = %q{Use a DSL to describe the verb tense framework of a language and have it generate methods corresponding to each unique vector.}
14
+
15
+ s.rubyforge_project = "verbvector"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: verbvector
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Steven G. Harms
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-02 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: Use a DSL to describe the verb tense framework of a language and have it generate methods corresponding to each unique vector.
17
+ email:
18
+ - github@sgharms.oib.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - .gitignore
27
+ - Gemfile
28
+ - README.textile
29
+ - Rakefile
30
+ - lib/verbvector.rb
31
+ - lib/verbvector/version.rb
32
+ - test/fixtures/tense_list
33
+ - test/test_generator.rb
34
+ - verbvector.gemspec
35
+ homepage: http://rubygems.org/gems/verbvector
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ requirements: []
56
+
57
+ rubyforge_project: verbvector
58
+ rubygems_version: 1.8.5
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Generates the verb tense "vectors" based on a DSL description syntax.
62
+ test_files:
63
+ - test/fixtures/tense_list
64
+ - test/test_generator.rb