erichummel-sunspot 1.2.1 → 2.0.0.pre.111215

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.gitignore +0 -1
  2. data/Gemfile +2 -1
  3. data/History.txt +30 -0
  4. data/Rakefile +5 -9
  5. data/lib/sunspot.rb +13 -3
  6. data/lib/sunspot/batcher.rb +62 -0
  7. data/lib/sunspot/class_set.rb +23 -0
  8. data/lib/sunspot/configuration.rb +7 -0
  9. data/lib/sunspot/dsl.rb +1 -1
  10. data/lib/sunspot/dsl/field_group.rb +57 -0
  11. data/lib/sunspot/dsl/field_query.rb +48 -0
  12. data/lib/sunspot/dsl/function.rb +13 -0
  13. data/lib/sunspot/dsl/paginatable.rb +5 -1
  14. data/lib/sunspot/dsl/restriction_with_near.rb +39 -0
  15. data/lib/sunspot/dsl/scope.rb +4 -4
  16. data/lib/sunspot/dsl/search.rb +2 -2
  17. data/lib/sunspot/dsl/standard_query.rb +2 -0
  18. data/lib/sunspot/indexer.rb +12 -7
  19. data/lib/sunspot/query.rb +3 -3
  20. data/lib/sunspot/query/bbox.rb +15 -0
  21. data/lib/sunspot/query/common_query.rb +13 -2
  22. data/lib/sunspot/query/dismax.rb +5 -1
  23. data/lib/sunspot/query/field_group.rb +36 -0
  24. data/lib/sunspot/query/geofilt.rb +16 -0
  25. data/lib/sunspot/query/highlighting.rb +8 -1
  26. data/lib/sunspot/query/pagination.rb +8 -4
  27. data/lib/sunspot/query/sort.rb +14 -0
  28. data/lib/sunspot/query/sort_composite.rb +3 -2
  29. data/lib/sunspot/search.rb +1 -1
  30. data/lib/sunspot/search/abstract_search.rb +53 -65
  31. data/lib/sunspot/search/field_group.rb +32 -0
  32. data/lib/sunspot/search/group.rb +50 -0
  33. data/lib/sunspot/search/hit.rb +21 -7
  34. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  35. data/lib/sunspot/search/paginated_collection.rb +5 -3
  36. data/lib/sunspot/session.rb +3 -1
  37. data/lib/sunspot/type.rb +21 -0
  38. data/lib/sunspot/util.rb +9 -0
  39. data/lib/sunspot/version.rb +1 -1
  40. data/spec/api/batcher_spec.rb +112 -0
  41. data/spec/api/class_set_spec.rb +24 -0
  42. data/spec/api/hit_enumerable_spec.rb +47 -0
  43. data/spec/api/indexer/batch_spec.rb +29 -3
  44. data/spec/api/query/function_spec.rb +9 -0
  45. data/spec/api/query/group_spec.rb +32 -0
  46. data/spec/api/query/highlighting_examples.rb +22 -0
  47. data/spec/api/query/ordering_pagination_examples.rb +21 -0
  48. data/spec/api/query/spatial_examples.rb +27 -0
  49. data/spec/api/query/standard_spec.rb +1 -0
  50. data/spec/api/search/hits_spec.rb +11 -0
  51. data/spec/api/search/paginated_collection_spec.rb +10 -0
  52. data/spec/api/search/results_spec.rb +6 -0
  53. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +0 -11
  54. data/spec/api/session_spec.rb +12 -0
  55. data/spec/api/sunspot_spec.rb +11 -0
  56. data/spec/helpers/indexer_helper.rb +0 -12
  57. data/spec/helpers/integration_helper.rb +8 -0
  58. data/spec/helpers/mock_session_helper.rb +13 -0
  59. data/spec/helpers/query_helper.rb +0 -12
  60. data/spec/helpers/search_helper.rb +0 -12
  61. data/spec/integration/dynamic_fields_spec.rb +2 -0
  62. data/spec/integration/faceting_spec.rb +14 -1
  63. data/spec/integration/field_grouping_spec.rb +66 -0
  64. data/spec/integration/geospatial_spec.rb +85 -0
  65. data/spec/integration/highlighting_spec.rb +22 -0
  66. data/spec/integration/indexing_spec.rb +23 -1
  67. data/spec/integration/keyword_search_spec.rb +1 -1
  68. data/spec/integration/local_search_spec.rb +1 -1
  69. data/spec/integration/more_like_this_spec.rb +1 -1
  70. data/spec/integration/scoped_search_spec.rb +1 -1
  71. data/spec/integration/stored_fields_spec.rb +2 -0
  72. data/spec/integration/test_pagination.rb +13 -2
  73. data/spec/integration/unicode_spec.rb +15 -0
  74. data/spec/mocks/connection.rb +4 -4
  75. data/spec/mocks/post.rb +1 -0
  76. data/spec/spec_helper.rb +21 -11
  77. data/sunspot.gemspec +42 -0
  78. data/tasks/rdoc.rake +2 -2
  79. metadata +95 -135
  80. data/VERSION.yml +0 -4
  81. data/bin/sunspot-installer +0 -19
  82. data/bin/sunspot-solr +0 -74
  83. data/installer/config/schema.yml +0 -95
  84. data/lib/sunspot/installer.rb +0 -31
  85. data/lib/sunspot/installer/library_installer.rb +0 -45
  86. data/lib/sunspot/installer/schema_builder.rb +0 -219
  87. data/lib/sunspot/installer/solrconfig_updater.rb +0 -76
  88. data/lib/sunspot/installer/task_helper.rb +0 -18
  89. data/lib/sunspot/server.rb +0 -152
  90. data/solr-1.3/etc/jetty.xml +0 -212
  91. data/solr-1.3/etc/webdefault.xml +0 -379
  92. data/solr-1.3/lib/jetty-6.1.3.jar +0 -0
  93. data/solr-1.3/lib/jetty-util-6.1.3.jar +0 -0
  94. data/solr-1.3/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  95. data/solr-1.3/lib/jsp-2.1/core-3.1.1.jar +0 -0
  96. data/solr-1.3/lib/jsp-2.1/jsp-2.1.jar +0 -0
  97. data/solr-1.3/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  98. data/solr-1.3/lib/servlet-api-2.5-6.1.3.jar +0 -0
  99. data/solr-1.3/solr/conf/elevate.xml +0 -36
  100. data/solr-1.3/solr/conf/protwords.txt +0 -21
  101. data/solr-1.3/solr/conf/schema.xml +0 -64
  102. data/solr-1.3/solr/conf/solrconfig.xml +0 -725
  103. data/solr-1.3/solr/conf/stopwords.txt +0 -57
  104. data/solr-1.3/solr/conf/synonyms.txt +0 -31
  105. data/solr-1.3/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  106. data/solr-1.3/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  107. data/solr-1.3/solr/lib/jsr108-0.01.jar +0 -0
  108. data/solr-1.3/solr/lib/locallucene.jar +0 -0
  109. data/solr-1.3/solr/lib/localsolr.jar +0 -0
  110. data/solr-1.3/start.jar +0 -0
  111. data/solr-1.3/webapps/solr.war +0 -0
  112. data/solr/README.txt +0 -42
  113. data/solr/etc/jetty.xml +0 -218
  114. data/solr/etc/webdefault.xml +0 -379
  115. data/solr/lib/jetty-6.1.3.jar +0 -0
  116. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  117. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  118. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  119. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  120. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  121. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  122. data/solr/solr/.gitignore +0 -1
  123. data/solr/solr/README.txt +0 -54
  124. data/solr/solr/conf/admin-extra.html +0 -31
  125. data/solr/solr/conf/elevate.xml +0 -36
  126. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +0 -246
  127. data/solr/solr/conf/protwords.txt +0 -21
  128. data/solr/solr/conf/schema.xml +0 -238
  129. data/solr/solr/conf/scripts.conf +0 -24
  130. data/solr/solr/conf/solrconfig.xml +0 -934
  131. data/solr/solr/conf/spellings.txt +0 -2
  132. data/solr/solr/conf/stopwords.txt +0 -58
  133. data/solr/solr/conf/synonyms.txt +0 -31
  134. data/solr/solr/conf/xslt/example.xsl +0 -132
  135. data/solr/solr/conf/xslt/example_atom.xsl +0 -67
  136. data/solr/solr/conf/xslt/example_rss.xsl +0 -66
  137. data/solr/solr/conf/xslt/luke.xsl +0 -337
  138. data/solr/start.jar +0 -0
  139. data/solr/webapps/solr.war +0 -0
  140. data/spec/api/server_spec.rb +0 -91
  141. data/spec/integration/spec_helper.rb +0 -7
@@ -1,4 +0,0 @@
1
- ---
2
- :minor: 10
3
- :patch: 8
4
- :major: 0
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'optparse'
4
- require File.join(File.dirname(__FILE__), '..', 'lib', 'sunspot', 'installer')
5
-
6
- home = ARGV.select { |arg| arg !~ /^-/ }.last || Dir.pwd
7
- options = {}
8
-
9
- OptionParser.new do |opts|
10
- opts.banner = "Usage: sunspot-install [options] [solr-home]"
11
- opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
12
- options[:verbose] = v
13
- end
14
- opts.on('-f', '--[no-]force', 'Overwrite schema and solrconfig files with Sunspot default, instead of modifying them (recommended for new Solr installation)') do |f|
15
- options[:force] = f
16
- end
17
- end.parse!
18
-
19
- Sunspot::Installer.execute(home, options)
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env ruby
2
- begin
3
- require 'fileutils'
4
- require 'tempfile'
5
- require 'tmpdir'
6
- require 'optparse'
7
- require File.join(File.dirname(__FILE__), '..', 'lib', 'sunspot', 'server')
8
- rescue LoadError => e
9
- if require 'rubygems'
10
- retry
11
- else
12
- raise(e)
13
- end
14
- end
15
-
16
- server = Sunspot::Server.new
17
-
18
- OptionParser.new do |opts|
19
- opts.banner = "Usage: sunspot-solr start [options]"
20
-
21
- opts.on '-p', '--port=PORT', 'Port on which to run Solr (default 8983)' do |p|
22
- server.port = p
23
- end
24
-
25
- opts.on '-d', '--data-directory=DIRECTORY', 'Solr data directory' do |d|
26
- server.solr_data_dir = File.expand_path(d)
27
- end
28
-
29
- opts.on '-s', '--solr-home=HOME', 'Solr home directory (should contain conf/ directory)' do |s|
30
- server.solr_home = File.expand_path(s)
31
- end
32
-
33
- opts.on '-j', '--solr-jar=JAR', 'Solr start jar' do |j|
34
- server.solr_jar = File.expand_path(j)
35
- end
36
-
37
- opts.on '--pid-dir=PID_DIR', 'Directory for pid files' do |pd|
38
- server.pid_dir = File.expand_path(pd)
39
- end
40
-
41
- opts.on '-l', '--log-level=LOG_LEVEL', 'Solr logging level' do |l|
42
- server.log_level = l
43
- end
44
-
45
- opts.on '--log-file=LOG_FILE', 'Path to Solr log file' do |lf|
46
- server.log_file = File.expand_path(lf)
47
- end
48
-
49
- opts.on '--max-memory=MEMORY', 'Specify the maximum size of the memory allocation pool' do |mm|
50
- server.max_memory = mm
51
- end
52
-
53
- opts.on '--min-memory=MEMORY', 'Specify the initial size of the memory allocation pool' do |mm|
54
- server.min_memory = mm
55
- end
56
- end.parse!
57
-
58
- begin
59
- case ARGV[0]
60
- when 'start'
61
- server.start
62
- when 'run'
63
- server.run
64
- when 'stop'
65
- server.stop
66
- when 'restart'
67
- server.stop
68
- server.start
69
- else
70
- abort("Usage: sunspot-solr (start|stop|run)")
71
- end
72
- rescue Sunspot::Server::ServerError => e
73
- abort(e.message)
74
- end
@@ -1,95 +0,0 @@
1
- types: !omap
2
- - text:
3
- class: TextField
4
- suffix: text
5
- omit_norms: false
6
- tokenizer: StandardTokenizerFactory
7
- filters:
8
- - StandardFilterFactory
9
- - LowerCaseFilterFactory
10
- invariants:
11
- multiValued: true
12
- - boolean:
13
- class: BoolField
14
- suffix: b
15
- invariants:
16
- termVectors: valse
17
- - date:
18
- class: DateField
19
- suffix: d
20
- invariants:
21
- termVectors: valse
22
- - rand:
23
- class: RandomSortField
24
- - sdouble:
25
- class: SortableDoubleField
26
- suffix: e
27
- invariants:
28
- termVectors: valse
29
- - sfloat:
30
- class: SortableFloatField
31
- suffix: f
32
- invariants:
33
- termVectors: valse
34
- - sint:
35
- class: SortableIntField
36
- suffix: i
37
- invariants:
38
- termVectors: valse
39
- - slong:
40
- class: SortableLongField
41
- suffix: l
42
- invariants:
43
- termVectors: valse
44
- - string:
45
- class: StrField
46
- suffix: s
47
- invariants:
48
- termVectors: valse
49
- - tint:
50
- class: TrieIntField
51
- suffix: it
52
- invariants:
53
- termVectors: valse
54
- - tfloat:
55
- class: TrieFloatField
56
- suffix: ft
57
- invariants:
58
- termVectors: valse
59
- - tdouble:
60
- class: TrieDoubleField
61
- suffix: et
62
- invariants:
63
- termVectors: valse
64
- - tdate:
65
- class: TrieDateField
66
- suffix: dt
67
- invariants:
68
- termVectors: valse
69
- fixed: !omap
70
- - id:
71
- type: string
72
- attributes: [stored]
73
- - type:
74
- type: string
75
- attributes: [multiValued]
76
- - class_name:
77
- type: string
78
- - text:
79
- type: string
80
- attributes: [multiValued]
81
- - lat:
82
- type: tdouble
83
- attributes: [stored]
84
- - lng:
85
- type: tdouble
86
- attributes: [stored]
87
- - 'random_*':
88
- type: rand
89
- - '_local*':
90
- type: tdouble
91
-
92
- variants: !omap
93
- - multiValued: m
94
- - stored: s
95
- - termVectors: v
@@ -1,31 +0,0 @@
1
- %w(task_helper library_installer schema_builder solrconfig_updater).each do |file|
2
- require File.join(File.dirname(__FILE__), 'installer', file)
3
- end
4
-
5
- module Sunspot
6
- class Installer
7
- class <<self
8
- def execute(solr_home, options = {})
9
- new(solr_home, options).execute
10
- end
11
-
12
- private :new
13
- end
14
-
15
- def initialize(solr_home, options)
16
- @solr_home, @options = solr_home, options
17
- end
18
-
19
- def execute
20
- SchemaBuilder.execute(
21
- File.join(@solr_home, 'conf', 'schema.xml'),
22
- @options
23
- )
24
- SolrconfigUpdater.execute(
25
- File.join(@solr_home, 'conf', 'solrconfig.xml'),
26
- @options
27
- )
28
- LibraryInstaller.execute(File.join(@solr_home, 'lib'), @options)
29
- end
30
- end
31
- end
@@ -1,45 +0,0 @@
1
- require 'fileutils'
2
-
3
- module Sunspot
4
- class Installer
5
- class LibraryInstaller
6
- class <<self
7
- def execute(library_path, options)
8
- new(library_path, options).execute
9
- end
10
- end
11
-
12
- def initialize(library_path, options)
13
- @library_path = library_path
14
- @verbose = !!options[:verbose]
15
- @force = !!options[:force]
16
- end
17
-
18
- def execute
19
- sunspot_library_path = File.join(File.dirname(__FILE__), '..', '..',
20
- '..', 'solr', 'solr', 'lib')
21
- return if File.expand_path(sunspot_library_path) == File.expand_path(@library_path)
22
- FileUtils.mkdir_p(@library_path)
23
- Dir.glob(File.join(sunspot_library_path, '*.jar')).each do |jar|
24
- jar = File.expand_path(jar)
25
- dest = File.join(@library_path, File.basename(jar))
26
- if File.exist?(dest)
27
- if @force
28
- say("Removing existing library #{dest}")
29
- else
30
- next
31
- end
32
- end
33
- say("Copying #{jar} => #{dest}")
34
- FileUtils.cp(jar, dest)
35
- end
36
- end
37
-
38
- def say(message)
39
- if @verbose
40
- STDOUT.puts(message)
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,219 +0,0 @@
1
- require 'yaml'
2
- require 'rexml/rexml'
3
- require 'rexml/document'
4
- require 'fileutils'
5
-
6
- module Sunspot
7
- class Installer
8
- #
9
- # This class modifies an existing Solr schema.xml file to work with Sunspot.
10
- # It makes the minimum necessary changes to the schema, adding fields and
11
- # types only when they don't already exist. It also comments all fields and
12
- # types that Sunspot needs, whether or not they were preexisting, so that
13
- # users can modify the resulting schema without unwittingly breaking it.
14
- #
15
- class SchemaBuilder
16
- include TaskHelper
17
-
18
- CONFIG_PATH = File.join(
19
- File.dirname(__FILE__), '..', '..', '..', 'installer', 'config', 'schema.yml'
20
- )
21
-
22
- class <<self
23
- def execute(schema_path, options = {})
24
- new(schema_path, options).execute
25
- end
26
-
27
- private :new
28
- end
29
-
30
- def initialize(schema_path, options = {})
31
- @schema_path = schema_path
32
- @config = File.open(CONFIG_PATH) { |f| YAML.load(f) }
33
- @verbose = !!options[:verbose]
34
- @force = !!options[:force]
35
- end
36
-
37
- def execute
38
- if @force
39
- FileUtils.mkdir_p(File.dirname(@schema_path))
40
- source_path = File.join(File.dirname(__FILE__), '..', '..', '..', 'solr', 'solr', 'conf', 'schema.xml')
41
- FileUtils.cp(source_path, @schema_path)
42
- say("Copied default schema.xml to #{@schema_path}")
43
- else
44
- @document = File.open(@schema_path) do |f|
45
- Nokogiri::XML(
46
- f, nil, nil,
47
- Nokogiri::XML::ParseOptions::DEFAULT_XML |
48
- Nokogiri::XML::ParseOptions::NOBLANKS
49
- )
50
- end
51
- @root = @document.root
52
- @added_types = Set[]
53
- add_fixed_fields
54
- add_dynamic_fields
55
- set_misc_settings
56
- original_path = "#{@schema_path}.orig"
57
- FileUtils.cp(@schema_path, original_path)
58
- say("Saved backup of original to #{original_path}")
59
- File.open(@schema_path, 'w') do |file|
60
- @document.write_xml_to(file, :indent => 2)
61
- end
62
- say("Wrote schema to #{@schema_path}")
63
- end
64
- end
65
-
66
- private
67
-
68
- def add_fixed_fields
69
- @config['fixed'].each do |name, options|
70
- maybe_add_field(name, options['type'], :indexed, *Array(options['attributes']))
71
- end
72
- end
73
-
74
- def add_dynamic_fields
75
- @config['types'].each do |type, options|
76
- if suffix = options['suffix']
77
- variant_combinations(options).each do |variants|
78
- variants_suffix = variants.map { |variant| variant[1] || '' }.join
79
- maybe_add_field("*_#{suffix}#{variants_suffix}", type, *variants.map { |variant| variant[0] })
80
- end
81
- end
82
- end
83
- end
84
-
85
- def maybe_add_field(name, type, *flags)
86
- node_name = name =~ /\*/ ? 'dynamicField' : 'field'
87
- if field_node = fields_node.xpath(%Q(#{node_name}[@name="#{name}"])).first
88
- say("Using existing #{node_name} #{name.inspect}")
89
- add_comment(field_node)
90
- return
91
- end
92
- maybe_add_type(type)
93
- say("Adding field #{name.inspect}")
94
- attributes = {
95
- 'name' => name,
96
- 'type' => type,
97
- 'indexed' => 'true',
98
- 'stored' => 'false',
99
- 'multiValued' => 'false'
100
- }
101
- flags.each do |flag|
102
- attributes[flag.to_s] = 'true'
103
- end
104
- field_node = add_element(fields_node, node_name, attributes)
105
- add_comment(field_node)
106
- end
107
-
108
- def maybe_add_type(type)
109
- unless @added_types.include?(type)
110
- @added_types << type
111
- if type_node = types_node.xpath(%Q(fieldType[@name="#{type}"])).first ||
112
- types_node.xpath(%Q(fieldtype[@name="#{type}"])).first
113
- say("Using existing type #{type.inspect}")
114
- add_comment(type_node)
115
- return
116
- end
117
- say("Adding type #{type.inspect}")
118
- type_config = @config['types'][type]
119
- type_node = add_element(
120
- types_node,
121
- 'fieldType',
122
- 'name' => type,
123
- 'class' => to_solr_class(type_config['class']),
124
- 'omitNorms' => type_config['omit_norms'].nil? ? 'true' : type_config['omit_norms'].to_s
125
- )
126
- if type_config['tokenizer']
127
- add_analyzer(type_node, type_config)
128
- end
129
- add_comment(type_node)
130
- end
131
- end
132
-
133
- def add_analyzer(node, config)
134
- analyzer_node = add_element(node, 'analyzer')
135
- add_element(
136
- analyzer_node,
137
- 'tokenizer',
138
- 'class' => to_solr_class(config['tokenizer'])
139
- )
140
- Array(config['filters']).each do |filter|
141
- add_element(analyzer_node, 'filter', 'class' => to_solr_class(filter))
142
- end
143
- end
144
-
145
- def set_misc_settings
146
- if @root.xpath('uniqueKey[normalize-space()="id"]').any?
147
- say('Unique key already set')
148
- else
149
- say("Creating unique key node")
150
- add_element(@root, 'uniqueKey').content = 'id'
151
- end
152
- say('Setting default operator to AND')
153
- solr_query_parser_node = @root.xpath('solrQueryParser').first ||
154
- add_element(@root, 'solrQueryParser')
155
- solr_query_parser_node['defaultOperator'] = 'AND'
156
- end
157
-
158
- def add_comment(node)
159
- comment_message = " *** This #{node.name} is used by Sunspot! *** "
160
- unless (comment = previous_non_text_sibling(node)) && comment.comment? && comment.content =~ /Sunspot/
161
- node.add_previous_sibling(Nokogiri::XML::Comment.new(@document, comment_message))
162
- end
163
- end
164
-
165
- def types_node
166
- @types_node ||= @root.xpath('/schema/types').first
167
- end
168
-
169
- def fields_node
170
- @fields_node ||= @root.xpath('/schema/fields').first
171
- end
172
-
173
- def to_solr_class(class_name)
174
- if class_name =~ /\./
175
- class_name
176
- else
177
- "solr.#{class_name}"
178
- end
179
- end
180
-
181
- def previous_non_text_sibling(node)
182
- if previous_sibling = node.previous_sibling
183
- if previous_sibling.node_type == :text
184
- previous_non_text_sibling(previous_sibling)
185
- else
186
- previous_sibling
187
- end
188
- end
189
- end
190
-
191
- #
192
- # All of the possible combinations of variants
193
- #
194
- def variant_combinations(type_options)
195
- invariants = type_options['invariants'] || {}
196
- combinations = []
197
- variants = @config['variants'].reject { |name, suffix| invariants.has_key?(name) }
198
- 0.upto(2 ** variants.length - 1) do |b|
199
- combinations << combination = []
200
- variants.each_with_index do |variant, i|
201
- combination << variant if b & 1<<i > 0
202
- end
203
- invariants.each do |name, value|
204
- if value
205
- combination << [name]
206
- end
207
- end
208
- end
209
- combinations
210
- end
211
-
212
- def say(message)
213
- if @verbose
214
- STDOUT.puts(message)
215
- end
216
- end
217
- end
218
- end
219
- end