stretchy 0.3.6 → 0.3.7

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.
@@ -2,10 +2,31 @@ require 'stretchy/clauses/boost_clause'
2
2
 
3
3
  module Stretchy
4
4
  module Clauses
5
+ #
6
+ # Boost documents that match a free-text query. Most
7
+ # options will be passed into {#initialize}, but you
8
+ # can also chain `.not` onto it. Calling `.where` or
9
+ # `.match` from here will apply filters (*not boosts*)
10
+ # and return to the base state
11
+ #
12
+ # @author [atevans]
13
+ #
5
14
  class BoostMatchClause < BoostClause
6
15
 
7
16
  delegate [:range, :geo] => :where
8
17
 
18
+ #
19
+ # Adds a match query to the boost functions.
20
+ #
21
+ # @overload initialize(base, opts_or_string)
22
+ # @param base [Base] Base query to copy data from
23
+ # @param opts_or_string [String] String to do a free-text match across the document
24
+ #
25
+ # @overload initialize(base, opts_or_string)
26
+ # @param base [Base] Base query to copy data from
27
+ # @param options = {} [Hash] Fields and values to match via full-text search
28
+ #
29
+ # @return [BoostMatchClause] Boost clause in match context, with queries applied
9
30
  def initialize(base, opts_or_string = {}, options = {})
10
31
  super(base)
11
32
  if opts_or_string.is_a?(Hash)
@@ -17,14 +38,52 @@ module Stretchy
17
38
  end
18
39
  end
19
40
 
41
+ #
42
+ # Switches to inverse context, and applies filters as inverse
43
+ # options (ie, documents that *do not* match the query will
44
+ # be boosted)
45
+ #
46
+ # @overload not(opts_or_string)
47
+ # @param [String] String that must not match anywhere in the document
48
+ #
49
+ # @overload not(opts_or_string)
50
+ # @param opts_or_string [Hash] Fields and values that should not match in the document
51
+ #
52
+ # @return [BoostMatchClause] Query with inverse matching boost function applied
20
53
  def not(opts_or_string = {}, options = {})
21
54
  self.class.new(self, opts_or_string, options.merge(inverse: !inverse?))
22
55
  end
23
56
 
57
+ #
58
+ # Returns to the base context; filters passed here
59
+ # will be used to filter documents.
60
+ #
61
+ # @example Returning to base context
62
+ # query.boost.match('string').where(other_field: 64)
63
+ #
64
+ # @example Staying in boost context
65
+ # query.boost.match('string').boost.where(other_field: 99)
66
+ #
67
+ # @see {WhereClause#initialize}
68
+ #
69
+ # @return [WhereClause] Query with where clause applied
24
70
  def where(*args)
25
71
  WhereClause.new(self, *args)
26
72
  end
27
73
 
74
+ #
75
+ # Returns to the base context. Queries passed here
76
+ # will be used to filter documents.
77
+ #
78
+ # @example Returning to base context
79
+ # query.boost.match(message: 'curse word').match('username')
80
+ #
81
+ # @example Staying in boost context
82
+ # query.boost.match(message: 'happy word').boost.match('love')
83
+ #
84
+ # @see {MatchClause#initialize}
85
+ #
86
+ # @return [MatchClause] Base context with match queries applied
28
87
  def match(*args)
29
88
  MatchClause.new(self, *args)
30
89
  end
@@ -2,27 +2,95 @@ require 'stretchy/clauses/boost_clause'
2
2
 
3
3
  module Stretchy
4
4
  module Clauses
5
+ #
6
+ # Boosts documents that match certain filters. Most filters will
7
+ # be passed into {#initialize}, but you can also use `.range` and
8
+ # `.geo` .
9
+ #
10
+ # @author [atevans]
11
+ #
5
12
  class BoostWhereClause < BoostClause
6
13
 
14
+ #
15
+ # Generates a boost that matches a set of filters.
16
+ #
17
+ # @param base [Base] Query to copy data from.
18
+ # @param options = {} [Hash] Fields and values to filter on.
19
+ #
20
+ # @see {WhereClause#initialize}
21
+ #
22
+ # @return [BoostWhereClause] Query with filter boosts applied
7
23
  def initialize(base, options = {})
8
24
  super(base, options)
9
25
  where_function(:init, options)
10
26
  self
11
27
  end
12
28
 
29
+ #
30
+ # Returns to the base context; filters passed here
31
+ # will be used to filter documents.
32
+ #
33
+ # @example Returning to base context
34
+ # query.boost.where(number_field: 33).where(other_field: 64)
35
+ #
36
+ # @example Staying in boost context
37
+ # query.boost.where(number_field: 33).boost.where(other_field: 99)
38
+ #
39
+ # @see {WhereClause#initialize}
40
+ #
41
+ # @return [WhereClause] Query with where clause applied
13
42
  def where(*args)
14
43
  WhereClause.new(self, *args)
15
44
  end
16
45
 
46
+ #
47
+ # Returns to the base context. Queries passed here
48
+ # will be used to filter documents.
49
+ #
50
+ # @example Returning to base context
51
+ # query.boost.where(number_field: 89).match('username')
52
+ #
53
+ # @example Staying in boost context
54
+ # query.boost.where(number_field: 89).boost.match('love')
55
+ #
56
+ # @see {MatchClause#initialize}
57
+ #
58
+ # @return [MatchClause] Base context with match queries applied
17
59
  def match(*args)
18
60
  MatchClause.new(self, *args)
19
61
  end
20
62
 
63
+ #
64
+ # Applies a range filter with a min or max
65
+ # as a boost.
66
+ #
67
+ # @see {WhereClause#range}
68
+ #
69
+ # @see {Filters::RangeFilter}
70
+ #
71
+ # @see http://www.elastic.co/guide/en/elasticsearch/guide/master/_ranges.html Elastic Guides - Ranges
72
+ #
73
+ # @return [Base] Query in base context with range boost applied
21
74
  def range(*args)
22
75
  where_function(:range, *args)
23
76
  Base.new(self)
24
77
  end
25
78
 
79
+ #
80
+ # Boosts a document if it matches a geo filter.
81
+ # This is different than {BoostClause#near} -
82
+ # while `.near` applies a decay function that boosts
83
+ # based on how close a field is to a geo point,
84
+ # `.geo` applies a filter that either boosts or doesn't
85
+ # boost the document.
86
+ #
87
+ # @see {WhereFunction#geo}
88
+ #
89
+ # @see {Filters::GeoFilter}
90
+ #
91
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-filter.html Elastic Docs - Geo Distance Filter
92
+ #
93
+ # @return [Base] Query in base context with geo filter boost applied
26
94
  def geo(*args)
27
95
  where_function(:geo, *args)
28
96
  Base.new(self)
@@ -2,12 +2,49 @@ require 'stretchy/clauses/base'
2
2
 
3
3
  module Stretchy
4
4
  module Clauses
5
+ #
6
+ # A Match clause inherits the same state as any clause.
7
+ # There aren't any more specific methods to chain, as
8
+ # this clause only handles basic full-text searches.
9
+ #
10
+ # @author [atevans]
11
+ #
5
12
  class MatchClause < Base
6
13
 
14
+ #
15
+ # Creates a temporary MatchClause outside the main
16
+ # query scope by using a new {Base}. Primarily
17
+ # used in {BoostClause} for boosting on full-text
18
+ # matches.
19
+ #
20
+ # @param options = {} [Hash] Options to pass to the full-text match
21
+ #
22
+ # @return [MatchClause] Temporary clause outside current state
7
23
  def self.tmp(options = {})
8
24
  self.new(Base.new, options)
9
25
  end
10
26
 
27
+ #
28
+ # Creates a new state with a match query applied.
29
+ #
30
+ # @overload initialize(base, opts_or_string)
31
+ # @param [Base] Base clause to copy data from
32
+ # @param [String] Performs a full-text query for this string on all fields in the document.
33
+ #
34
+ # @overload initialize(base, opts_or_string)
35
+ # @param [Base] Base clause to copy data from
36
+ # @param [Hash] A hash of fields and values to perform full-text matches with
37
+ #
38
+ # @example A basic full-text match
39
+ # query.match("anywhere in document")
40
+ #
41
+ # @example A full-text search on specific fields
42
+ # query.match(
43
+ # my_field: "match in my_field",
44
+ # other_field: "match in other_field"
45
+ # )
46
+ #
47
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html Elastic Docs - Match Query
11
48
  def initialize(base, opts_or_str = {}, options = {})
12
49
  super(base)
13
50
  if opts_or_str.is_a?(Hash)
@@ -21,14 +58,73 @@ module Stretchy
21
58
  end
22
59
  end
23
60
 
61
+ #
62
+ # Switches to inverted context. Matches applied here work the same way as
63
+ # {#initialize}, but returned documents must **not** match these filters.
64
+ #
65
+ # @overload not(opts_or_str)
66
+ # @param [String] A string that must not be matched anywhere in the document
67
+ # @overload not(opts_or_str)
68
+ # @param [Hash] A hash of fields and strings that must not be matched in those fields
69
+ #
70
+ # @return [MatchClause] inverted query state with match filters applied
71
+ #
72
+ # @example Inverted full-text
73
+ # query.match.not("hello")
74
+ #
75
+ # @example Inverted full-text matching for specific fields
76
+ # query.match.not(
77
+ # my_field: "not_match_1",
78
+ # other_field: "not_match_2"
79
+ # )
24
80
  def not(opts_or_str = {}, options = {})
25
81
  self.class.new(self, opts_or_str, options.merge(inverse: true, should: should?))
26
82
  end
27
83
 
84
+ #
85
+ # Switches to `should` context. Applies full-text matches
86
+ # that are not required, but boost the relevance score for
87
+ # matching documents.
88
+ #
89
+ # Can be chained with {#not}
90
+ #
91
+ # @overload not(opts_or_str)
92
+ # @param [String] A string that should be matched anywhere in the document
93
+ # @overload not(opts_or_str)
94
+ # @param [Hash] A hash of fields and strings that should be matched in those fields
95
+ #
96
+ # @param opts_or_str = {} [type] [description]
97
+ # @param options = {} [type] [description]
98
+ #
99
+ # @return [MatchClause] query state with should filters added
100
+ #
101
+ # @example Should match with full-text
102
+ # query.match.should("anywhere")
103
+ #
104
+ # @example Should match specific fields
105
+ # query.match.should(
106
+ # field_one: "one",
107
+ # field_two: "two"
108
+ # )
109
+ #
110
+ # @example Should not match
111
+ # query.match.should.not(
112
+ # field_one: "one",
113
+ # field_two: "two"
114
+ # )
115
+ #
116
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html Elastic Docs - Bool Query
28
117
  def should(opts_or_str = {}, options = {})
29
118
  self.class.new(self, opts_or_str, options.merge(should: true))
30
119
  end
31
120
 
121
+ #
122
+ # Converts this match context to a set of boosts
123
+ # to use in a {Stretchy::Queries::FunctionScoreQuery}
124
+ #
125
+ # @param weight = nil [Numeric] Weight of generated boost
126
+ #
127
+ # @return [Stretchy::Boosts::FilterBoost] boost containing these match parameters
32
128
  def to_boost(weight = nil)
33
129
  weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
34
130
  Stretchy::Boosts::FilterBoost.new(
@@ -39,6 +135,10 @@ module Stretchy
39
135
  )
40
136
  end
41
137
 
138
+ #
139
+ # Accessor for `@should`
140
+ #
141
+ # @return [true, false] `@should`
42
142
  def should?
43
143
  !!@should
44
144
  end
@@ -2,12 +2,62 @@ require 'stretchy/clauses/base'
2
2
 
3
3
  module Stretchy
4
4
  module Clauses
5
+ #
6
+ # A Where clause inherits the same state as any clause,
7
+ # but has a few possible states to transition to. You
8
+ # can call {#range} and {#geo} to add their respective
9
+ # filters, or you can transition to the inverted state
10
+ # via {#not}, or the `should` state via {#should}
11
+ #
12
+ # ### STATES:
13
+ # * **inverted:** any filters added in this state will be
14
+ # inverted, ie the document must **NOT** match said
15
+ # filters.
16
+ # * **should:** any filters added in this state will be
17
+ # applied to a `should` block. Documents which do
18
+ # not match these filters will be returned, but
19
+ # documents which do match will have a higher
20
+ # relevance score.
21
+ #
22
+ # @author [atevans]
23
+ #
5
24
  class WhereClause < Base
6
25
 
26
+ #
27
+ # Creates a temporary context by initializing a new Base object.
28
+ # Used primarily in {BoostWhereClause}
29
+ #
30
+ # @param options = {} [Hash] Options to filter on
31
+ #
32
+ # @return [WhereClause] A clause outside the main query context
7
33
  def self.tmp(options = {})
8
34
  self.new(Base.new, options)
9
35
  end
10
36
 
37
+ #
38
+ # Options passed to the initializer will be interpreted as filters
39
+ # to be added to the query. This is similar to ActiveRecord's `where`
40
+ # method.
41
+ #
42
+ # @param base [Base] Used to intialize the new state from the previous clause
43
+ # @param options = {} [Hash] filters to be applied to the new state
44
+ # @option options [true, false] :inverted (nil) Whether the new state is inverted
45
+ # @option options [true, false] :should (nil) Whether the new state is should
46
+ #
47
+ # @example Apply ActiveRecord-like filters
48
+ # query.where(
49
+ # string_field: "string",
50
+ # must_not_exist: nil,
51
+ # in_range: 27..33,
52
+ # included_in: [47, 23, 86]
53
+ # )
54
+ #
55
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-filter.html Elastic Docs - Terms Filter
56
+ #
57
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-exists-filter.html Elastic Docs - Exists Filter
58
+ #
59
+ # @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-filter.html Elastic Docs - Range Filter
60
+ #
11
61
  def initialize(base, options = {})
12
62
  super(base)
13
63
  @inverse = options.delete(:inverse)
@@ -15,15 +65,62 @@ module Stretchy
15
65
  add_params(options)
16
66
  end
17
67
 
68
+ #
69
+ # Accessor for `@should`
70
+ #
71
+ # @return [true, false] `@should`
18
72
  def should?
19
73
  !!@should
20
74
  end
21
75
 
76
+ #
77
+ # Add a range filter to the current context. While
78
+ # you can pass a `Range` object to {#where}, this
79
+ # allows you to specify an open-ended range, such
80
+ # as only specifying the minimum or maximum value.
81
+ #
82
+ # @param field [String, Symbol] The field to filter with this range
83
+ # @param options = {} [Hash] Options for the range
84
+ # @option options [Numeric] :min (nil) Minimum. Ranges are _inclusive_ by default
85
+ # @option options [Numeric] :max (nil) Maximum. Ranges are _inclusive_ by default
86
+ # @option options [true, false] :exclusive (nil) Overrides default and makes the range exclusive -
87
+ # equivalent to passing both `:exclusive_min` and `:exclusive_max`
88
+ # @option options [true, false] :exclusive_min (nil) Overrides default and makes the minimum exclusive
89
+ # @option options [true, false] :exclusive_max (nil) Overrides default and makes the maximum exclusive
90
+ #
91
+ # @return [self] query state with range filter applied
92
+ #
93
+ # @example Adding a range filter
94
+ # query.where.range(:my_range_field,
95
+ # min: 33,
96
+ # exclusive: true
97
+ # )
22
98
  def range(field, options = {})
23
99
  get_storage(:ranges)[field] = Stretchy::Types::Range.new(options)
24
100
  self
25
101
  end
26
102
 
103
+ #
104
+ # Adds a geo distance filter to the current context.
105
+ # Documents must have a `geo_point` field that is within
106
+ # the specified distance of the passed parameters.
107
+ #
108
+ # @param field [String, Symbol] The field this filter will be applied to.
109
+ # @param options = {} [Hash] Options for the geo distance filter
110
+ # @option options [String] :distance (nil) The maximum distance from the specified origin.
111
+ # Use an Elastic distance format such as `'21mi'` or `'37km'`
112
+ # @option options [Float] :lat (nil) The latitude of the origin point. Can also be specified as `:latitude`
113
+ # @option options [Float] :lng (nil) The longitude of the origin point.
114
+ # Can also be specified as `:lon` or `:longitude`
115
+ #
116
+ # @return [self] query state with geo distance filter applied
117
+ #
118
+ # @example Searching by distance from a point
119
+ # query.where.geo(:coords,
120
+ # distance: '27km',
121
+ # lat: 33.3,
122
+ # lng: 29.2
123
+ # )
27
124
  def geo(field, options = {})
28
125
  get_storage(:geos)[field] = {
29
126
  distance: options[:distance],
@@ -32,14 +129,65 @@ module Stretchy
32
129
  self
33
130
  end
34
131
 
132
+ #
133
+ # Switches current state to inverted. Options passed
134
+ # here are equivalent to those passed to {#initialize},
135
+ # except documents *must not* match these filters.
136
+ #
137
+ # Can be chained with {#should} to produce inverted should queries
138
+ #
139
+ # @param options = {} [Hash] Options to filter on
140
+ #
141
+ # @return [WhereClause] inverted query state with not filters applied.
142
+ #
143
+ # @example Inverting filters
144
+ # query.where.not(
145
+ # must_exist: nil,
146
+ # not_matching: "this string",
147
+ # not_in: [45, 67, 99],
148
+ # not_in_range: 89..23
149
+ # )
150
+ #
151
+ # @example Inverted should filters
152
+ # query.should.not(
153
+ # match_field: [:these, "options"]
154
+ # )
35
155
  def not(options = {})
36
156
  self.class.new(self, options.merge(inverse: true, should: should?))
37
157
  end
38
158
 
159
+ #
160
+ # Switches the current state to `should`. Options passed
161
+ # here are equivalent to those passed to {#initialize},
162
+ # except documents which do not match are still returned
163
+ # with a lower score than documents which do match.
164
+ #
165
+ # Can be chained with {#not} to produce inverted should queries
166
+ #
167
+ # @param options = {} [Hash] Options to filter on
168
+ #
169
+ # @return [WhereClause] should query state with should filters applied
170
+ #
171
+ # @example Specifying should options
172
+ # query.should(
173
+ # field: [99, 27]
174
+ # )
175
+ #
176
+ # @example Inverted should options
177
+ # query.should.not(
178
+ # exists_field: nil
179
+ # )
39
180
  def should(options = {})
40
181
  self.class.new(self, options.merge(should: true))
41
182
  end
42
183
 
184
+ #
185
+ # Converts the current context into a boost to
186
+ # be passed into a {FunctionScoreQuery}.
187
+ #
188
+ # @param weight = nil [Numeric] A weight for the {FunctionScoreQuery}
189
+ #
190
+ # @return [Boosts::FilterBoost] A boost including all the current filters
43
191
  def to_boost(weight = nil)
44
192
  weight ||= Stretchy::Boosts::FilterBoost::DEFAULT_WEIGHT
45
193
 
@@ -26,7 +26,14 @@ module Stretchy
26
26
  end
27
27
 
28
28
  def response
29
- @response ||= Stretchy.search(type: type, body: request, from: offset, size: limit)
29
+ params = {
30
+ type: type,
31
+ body: request,
32
+ from: offset,
33
+ size: limit
34
+ }
35
+ params[:explain] = true if clause.get_explain
36
+ @response ||= Stretchy.search(params)
30
37
  end
31
38
 
32
39
  def ids
@@ -41,6 +48,18 @@ module Stretchy
41
48
  end
42
49
  alias :results :hits
43
50
 
51
+ def scores
52
+ @scores ||= Hash[response['hits']['hits'].map do |hit|
53
+ [hit['_id'], hit['_score']]
54
+ end]
55
+ end
56
+
57
+ def explanations
58
+ @scores ||= Hash[response['hits']['hits'].map do |hit|
59
+ [hit['_id'], hit['_explanation']]
60
+ end]
61
+ end
62
+
44
63
  def took
45
64
  @took ||= response['took']
46
65
  end
@@ -8,12 +8,14 @@ module Stretchy
8
8
  end
9
9
  end
10
10
 
11
- # used for ensuring a concistent index in specs
11
+ # used for ensuring a consistent index in specs
12
12
  def refresh
13
+ Stretchy.log("Refreshing index: #{index_name}")
13
14
  client.indices.refresh index: index_name
14
15
  end
15
16
 
16
17
  def count
18
+ Stretchy.log("Counting all documents in index: #{index_name}")
17
19
  client.cat.count(index: index_name).split(' ')[2].to_i
18
20
  end
19
21
 
@@ -30,7 +32,10 @@ module Stretchy
30
32
  params[field] = options[field] if options[field]
31
33
  end
32
34
 
33
- client.search(params)
35
+ Stretchy.log("Querying Elastic:", params)
36
+ response = client.search(params)
37
+ Stretchy.log("Received response:", response)
38
+ response
34
39
  end
35
40
 
36
41
  def index(options = {})
@@ -38,7 +43,16 @@ module Stretchy
38
43
  type = options[:type]
39
44
  body = options[:body]
40
45
  id = options[:id] || options['id'] || body['id'] || body['_id'] || body[:id] || body[:_id]
41
- client.index(index: index, type: type, id: id, body: body)
46
+ params = {
47
+ index: index,
48
+ type: type,
49
+ id: id,
50
+ body: body
51
+ }
52
+ Stretchy.log("Indexing document:", params)
53
+ response = client.index(params)
54
+ Stretchy.log("Received response:", response)
55
+ response
42
56
  end
43
57
 
44
58
  def bulk(options = {})
@@ -51,23 +65,29 @@ module Stretchy
51
65
  document
52
66
  ]
53
67
  end
54
- client.bulk body: requests
68
+ Stretchy.log("Bulk indexing documents:", {body: requests})
69
+ response = client.bulk body: requests
70
+ Stretchy.log("Received response:", response)
55
71
  end
56
72
 
57
73
  def exists(_index_name = index_name)
74
+ Stretchy.log("Checking index existence for: #{_index_name}")
58
75
  client.indices.exists(index: _index_name)
59
76
  end
60
77
  alias :exists? :exists
61
78
 
62
79
  def delete(_index_name = index_name)
80
+ Stretchy.log("Deleting index: #{_index_name}")
63
81
  client.indices.delete(index: _index_name) if exists?(_index_name)
64
82
  end
65
83
 
66
84
  def create(_index_name = index_name)
85
+ Stretchy.log("Creating index: #{_index_name}")
67
86
  client.indices.create(index: _index_name) unless exists?(_index_name)
68
87
  end
69
88
 
70
89
  def mapping(_index_name, _type, _body)
90
+ Stretchy.log("Putting mapping:", {index_name: _index_name, type: _type, body: _body})
71
91
  client.indices.put_mapping(index: _index_name, type: _type, body: _body)
72
92
  end
73
93
 
@@ -0,0 +1,71 @@
1
+ module Stretchy
2
+ module Utils
3
+ class Colorize
4
+ COLORS = {
5
+ "default" => "38",
6
+ "black" => "30",
7
+ "red" => "31",
8
+ "green" => "32",
9
+ "brown" => "33",
10
+ "blue" => "34",
11
+ "purple" => "35",
12
+ "cyan" => "36",
13
+ "gray" => "37",
14
+ "dark gray" => "1;30",
15
+ "light red" => "1;31",
16
+ "light green" => "1;32",
17
+ "yellow" => "1;33",
18
+ "light blue" => "1;34",
19
+ "light purple" => "1;35",
20
+ "light cyan" => "1;36",
21
+ "white" => "1;37"
22
+ }.freeze
23
+
24
+ BG_COLORS = {
25
+ "default" => "0",
26
+ "black" => "40",
27
+ "red" => "41",
28
+ "green" => "42",
29
+ "brown" => "43",
30
+ "blue" => "44",
31
+ "purple" => "45",
32
+ "cyan" => "46",
33
+ "gray" => "47",
34
+ "dark gray" => "100",
35
+ "light red" => "101",
36
+ "light green" => "102",
37
+ "yellow" => "103",
38
+ "light blue" => "104",
39
+ "light purple" => "105",
40
+ "light cyan" => "106",
41
+ "white" => "107"
42
+ }.freeze
43
+
44
+ def colorize(string, color = "default", bg = "default")
45
+ color_code = COLORS[color]
46
+ bg_code = BG_COLORS[bg]
47
+ return "\033[#{bg_code};#{color_code}m#{string}\033[0m"
48
+ end
49
+
50
+ module ClassMethods
51
+ def colors
52
+ Colorize::COLORS
53
+ end
54
+
55
+ def bgs
56
+ Colorize::BG_COLORS
57
+ end
58
+
59
+ Colorize::COLORS.keys.each do |color|
60
+ define_method color do |string|
61
+ color_code = colors[color]
62
+ bg_code = bgs["default"]
63
+ string.split("\n").map{|s| "\033[#{bg_code};#{color_code}m#{s}\033[0m" }.join("\n")
64
+ end
65
+ end
66
+ end
67
+
68
+ extend ClassMethods
69
+ end
70
+ end
71
+ end