benhoskings-hammock 0.2.4
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.
- data/LICENSE +24 -0
- data/Manifest.txt +42 -0
- data/README.rdoc +105 -0
- data/Rakefile +27 -0
- data/lib/hammock.rb +25 -0
- data/lib/hammock/ajaxinate.rb +152 -0
- data/lib/hammock/callbacks.rb +107 -0
- data/lib/hammock/canned_scopes.rb +121 -0
- data/lib/hammock/constants.rb +7 -0
- data/lib/hammock/controller_attributes.rb +66 -0
- data/lib/hammock/export_scope.rb +74 -0
- data/lib/hammock/hamlink_to.rb +47 -0
- data/lib/hammock/javascript_buffer.rb +63 -0
- data/lib/hammock/logging.rb +98 -0
- data/lib/hammock/model_attributes.rb +38 -0
- data/lib/hammock/model_logging.rb +30 -0
- data/lib/hammock/monkey_patches/action_pack.rb +32 -0
- data/lib/hammock/monkey_patches/active_record.rb +227 -0
- data/lib/hammock/monkey_patches/array.rb +73 -0
- data/lib/hammock/monkey_patches/hash.rb +49 -0
- data/lib/hammock/monkey_patches/logger.rb +28 -0
- data/lib/hammock/monkey_patches/module.rb +27 -0
- data/lib/hammock/monkey_patches/numeric.rb +25 -0
- data/lib/hammock/monkey_patches/object.rb +61 -0
- data/lib/hammock/monkey_patches/route_set.rb +200 -0
- data/lib/hammock/monkey_patches/string.rb +197 -0
- data/lib/hammock/overrides.rb +32 -0
- data/lib/hammock/resource_mapping_hooks.rb +28 -0
- data/lib/hammock/resource_retrieval.rb +115 -0
- data/lib/hammock/restful_actions.rb +170 -0
- data/lib/hammock/restful_rendering.rb +114 -0
- data/lib/hammock/restful_support.rb +167 -0
- data/lib/hammock/route_drawing_hooks.rb +22 -0
- data/lib/hammock/route_for.rb +58 -0
- data/lib/hammock/scope.rb +120 -0
- data/lib/hammock/suggest.rb +36 -0
- data/lib/hammock/utils.rb +42 -0
- data/misc/scaffold.txt +83 -0
- data/misc/template.rb +17 -0
- data/tasks/hammock_tasks.rake +5 -0
- data/test/hammock_test.rb +8 -0
- metadata +129 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Hammock
|
2
|
+
module BufferedLoggerPatches
|
3
|
+
MixInto = ActiveSupport::BufferedLogger
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
|
9
|
+
# base.class_eval {
|
10
|
+
# alias_method_chain :fatal, :color
|
11
|
+
# }
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
|
20
|
+
def fatal_with_color message = nil, progname = nil, &block
|
21
|
+
first_line, other_lines = message.strip.split("\n", 2)
|
22
|
+
fatal_without_color "\n" + first_line.colorize('on red'), progname, &block
|
23
|
+
fatal_without_color other_lines.colorize('red') + "\n\n", progname, &block
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Hammock
|
2
|
+
module ModulePatches
|
3
|
+
MixInto = Module
|
4
|
+
LoadFirst = true
|
5
|
+
|
6
|
+
def self.included base # :nodoc:
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
base.send :extend, ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
|
16
|
+
def alias_method_chain_once target, feature
|
17
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
18
|
+
without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
|
19
|
+
|
20
|
+
unless [public_instance_methods, protected_instance_methods, private_instance_methods].flatten.include? without_method
|
21
|
+
alias_method_chain target, feature
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hammock
|
2
|
+
module NumericPatches
|
3
|
+
MixInto = Numeric
|
4
|
+
|
5
|
+
def self.included base # :nodoc:
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
base.send :extend, ClassMethods # TODO maybe include in the metaclass instead of extending the class?
|
8
|
+
|
9
|
+
base.class_eval {
|
10
|
+
alias kb kilobytes
|
11
|
+
alias mb megabytes
|
12
|
+
alias gb gigabytes
|
13
|
+
alias tb terabytes
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Hammock
|
2
|
+
module ObjectPatches
|
3
|
+
MixInto = Object
|
4
|
+
|
5
|
+
def self.included base # :nodoc:
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
|
9
|
+
base.class_eval {
|
10
|
+
alias is_an? is_a?
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
|
19
|
+
# Return +self+ after yielding to the given block.
|
20
|
+
#
|
21
|
+
# Useful for inline logging and diagnostics. Consider the following:
|
22
|
+
# @items.map {|i| process(i) }.join(", ")
|
23
|
+
# With +tap+, adding intermediate logging is simple:
|
24
|
+
# @items.map {|i| process(i) }.tap {|obj| log obj.inspect }.join(", ")
|
25
|
+
#--
|
26
|
+
# TODO Remove for Ruby 1.9
|
27
|
+
def tap
|
28
|
+
yield self
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# The reverse of <tt>Enumerable#include?</tt> - returns +true+ if +self+ is
|
33
|
+
# equal to one of the elements of +args+.
|
34
|
+
def in? *args
|
35
|
+
args.include? self
|
36
|
+
end
|
37
|
+
|
38
|
+
# A symbolized, underscored (i.e. reverse-camelized) representation of +self+.
|
39
|
+
#
|
40
|
+
# Examples:
|
41
|
+
#
|
42
|
+
# Hash.symbolize #=> :hash
|
43
|
+
# ActiveRecord::Base.symbolize #=> :"active_record/base"
|
44
|
+
# "GetThisCamelOffMyCase".symbolize #=> :get_this_camel_off_my_case
|
45
|
+
def symbolize
|
46
|
+
self.to_s.underscore.to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
# If +condition+ evaluates to true, return the result of sending +method_name+ to +self+; <tt>*args</tt> to +self+, otherwise, return +self+ as-is.
|
50
|
+
def send_if condition, method_name, *args
|
51
|
+
condition ? send(method_name, *args) : self
|
52
|
+
end
|
53
|
+
|
54
|
+
# If +condition+ evaluates to true, return the result of sending +method_name+ to +self+; <tt>*args</tt> to +self+, otherwise, return +self+ as-is.
|
55
|
+
def send_if_respond_to method_name, *args
|
56
|
+
send_if respond_to?(method_name), method_name, *args
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module Hammock
|
2
|
+
module RouteSetPatches
|
3
|
+
MixInto = ActionController::Routing::RouteSet
|
4
|
+
|
5
|
+
def self.included base # :nodoc:
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
|
9
|
+
base.class_eval {
|
10
|
+
attr_accessor :route_map
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def initialize_hammock_route_map
|
23
|
+
self.route_map = HammockResource.new
|
24
|
+
end
|
25
|
+
|
26
|
+
class HammockResource < ActionController::Resources::Resource
|
27
|
+
class HammockRoutePiece
|
28
|
+
attr_reader :resource, :routeable_as, :verb, :entity, :parent
|
29
|
+
|
30
|
+
def initialize resource
|
31
|
+
@resource = resource
|
32
|
+
end
|
33
|
+
|
34
|
+
def for verb, entity
|
35
|
+
routeable_as = resource.routeable_as(verb, entity)
|
36
|
+
|
37
|
+
if !routeable_as
|
38
|
+
raise "The verb '#{verb}' can't be applied to " + (entity.record? ? "#{entity.resource} records" : "the #{entity.resource} resource") + "."
|
39
|
+
elsif (:record == routeable_as) && entity.new_record?
|
40
|
+
raise "The verb '#{verb}' requires a #{entity.resource} with an ID (i.e. not a new record)."
|
41
|
+
elsif (:build == routeable_as) && entity.record? && !entity.new_record?
|
42
|
+
raise "The verb '#{verb}' requires either the #{entity.resource} resource, or a #{entity.resource} without an ID (i.e. a new record)."
|
43
|
+
else
|
44
|
+
@verb, @entity, @routeable_as = verb, entity, routeable_as
|
45
|
+
end
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def within parent
|
51
|
+
@parent = parent
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup?
|
56
|
+
!@entity.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
def path params = nil
|
60
|
+
raise_unless_setup_while_trying_to 'render a path'
|
61
|
+
|
62
|
+
buf = '/'
|
63
|
+
buf << entity.resource_name
|
64
|
+
buf << '/' + entity.to_param if entity.record? && !entity.new_record?
|
65
|
+
buf << '/' + verb.to_s unless verb.nil? or implied_verb?(verb)
|
66
|
+
|
67
|
+
buf = parent.path + buf unless parent.nil?
|
68
|
+
buf << param_str(params)
|
69
|
+
|
70
|
+
buf
|
71
|
+
end
|
72
|
+
|
73
|
+
def http_method
|
74
|
+
raise_unless_setup_while_trying_to 'extract the HTTP method'
|
75
|
+
resource.send("#{routeable_as}_routes")[verb]
|
76
|
+
end
|
77
|
+
|
78
|
+
def fake_http_method
|
79
|
+
http_method.in?(:get, :post) ? http_method : :post
|
80
|
+
end
|
81
|
+
|
82
|
+
def get?; :get == http_method end
|
83
|
+
def post?; :post == http_method end
|
84
|
+
def put?; :put == http_method end
|
85
|
+
def delete?; :delete == http_method end
|
86
|
+
|
87
|
+
def safe?
|
88
|
+
get? && !verb.in?(Hammock::Constants::ImpliedUnsafeActions)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def implied_verb? verb
|
94
|
+
verb.in? :index, :create, :show, :update, :destroy
|
95
|
+
end
|
96
|
+
|
97
|
+
def raise_unless_setup_while_trying_to task
|
98
|
+
raise "You have to call for(verb, entity) (and optionally within(parent)) on this HammockRoutePiece before you can #{task}." unless setup?
|
99
|
+
end
|
100
|
+
|
101
|
+
def param_str params
|
102
|
+
link_params = entity.record? ? entity.unsaved_attributes.merge(params || {}) : params
|
103
|
+
|
104
|
+
if link_params.blank?
|
105
|
+
''
|
106
|
+
else
|
107
|
+
'?' + {entity.base_model => link_params}.to_query
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
DefaultRecordVerbs = {
|
114
|
+
:show => :get,
|
115
|
+
:edit => :get,
|
116
|
+
:update => :put,
|
117
|
+
:destroy => :delete
|
118
|
+
}.freeze
|
119
|
+
DefaultResourceVerbs = {
|
120
|
+
:index => :get
|
121
|
+
}.freeze
|
122
|
+
DefaultBuildVerbs = {
|
123
|
+
:new => :get,
|
124
|
+
:create => :post
|
125
|
+
}.freeze
|
126
|
+
|
127
|
+
attr_reader :mdl, :parent, :children, :record_routes, :resource_routes, :build_routes
|
128
|
+
|
129
|
+
def initialize entity = nil, options = {}
|
130
|
+
@mdl = entity if entity.is_a?(Symbol)
|
131
|
+
@parent = options[:parent]
|
132
|
+
@children = {}
|
133
|
+
define_routes options
|
134
|
+
end
|
135
|
+
|
136
|
+
def ancestry
|
137
|
+
parent.nil? ? [] : parent.ancestry.push(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
def for verb, entities, options
|
141
|
+
raise "HammockResource#for requires an explicitly specified verb as its first argument." unless verb.is_a?(Symbol)
|
142
|
+
raise "You have to supply at least one record or resource." if entities.empty?
|
143
|
+
|
144
|
+
entity = entities.shift
|
145
|
+
|
146
|
+
if entities.empty?
|
147
|
+
piece_for verb, entity
|
148
|
+
else
|
149
|
+
children[entity.resource_sym].for(verb, entities, options).within piece_for(nil, entity)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def add entity, options, steps = nil
|
154
|
+
if steps.nil?
|
155
|
+
add entity, options, (options[:name_prefix] || '').chomp('_').split('_').map {|i| i.pluralize.underscore.to_sym }
|
156
|
+
elsif steps.empty?
|
157
|
+
add_child entity, options
|
158
|
+
else
|
159
|
+
children[steps.shift].add entity, options, steps
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def routeable_as verb, entity
|
164
|
+
if entity.record? && record_routes[verb || :show]
|
165
|
+
:record
|
166
|
+
elsif entity.resource? && resource_routes[verb || :index]
|
167
|
+
:resource
|
168
|
+
elsif !verb.nil? && build_routes[verb]
|
169
|
+
:build
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def define_routes options
|
176
|
+
@record_routes = DefaultRecordVerbs.dup.update(options[:member] || {})
|
177
|
+
@resource_routes = DefaultResourceVerbs.dup.update(options[:collection] || {})
|
178
|
+
@build_routes = DefaultBuildVerbs.dup.update(options[:build] || {})
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_child entity, options
|
182
|
+
child = HammockResource.new entity, options.merge(:parent => self)
|
183
|
+
children[child.mdl] = child
|
184
|
+
end
|
185
|
+
|
186
|
+
def piece_for verb, entity
|
187
|
+
child = children[entity.resource_sym]
|
188
|
+
|
189
|
+
if child.nil?
|
190
|
+
raise "No routes are defined for #{entity.resource}#{' within ' + ancestry.map {|r| r.mdl.to_s }.join(', ') unless ancestry.empty?}."
|
191
|
+
else
|
192
|
+
HammockRoutePiece.new(child).for(verb, entity)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Hammock
|
2
|
+
module StringPatches
|
3
|
+
MixInto = String
|
4
|
+
|
5
|
+
def self.included base # :nodoc:
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
# Generates a random string consisting of +length+ hexadecimal characters (i.e. matching [0-9a-f]{length}).
|
13
|
+
def af09 length = 1
|
14
|
+
(1..length).inject('') {|a, t|
|
15
|
+
a << rand(16).to_s(16)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generates a random string consisting of +length+ alphamuneric characters (i.e. matching [0-9a-zA-Z]{length}).
|
20
|
+
def azAZ09 length = 1
|
21
|
+
(1..length).inject('') {|a, t|
|
22
|
+
a << ((r = rand(62)) < 36 ? r.to_s(36) : (r - 26).to_s(36).upcase)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
|
30
|
+
# Returns true iff +str+ appears exactly at the start of +self+.
|
31
|
+
def starts_with? str
|
32
|
+
self[0, str.length] == str
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true iff +str+ appears exactly at the end of +self+.
|
36
|
+
def ends_with? str
|
37
|
+
self[-str.length, str.length] == str
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a duplicate of +self+, with +str+ prepended to it if it doesn't already start with +str+.
|
41
|
+
def start_with str
|
42
|
+
starts_with?(str) ? self : str + self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return a duplicate of +self+, with +str+ appended to it if it doesn't already end with +str+.
|
46
|
+
def end_with str
|
47
|
+
ends_with?(str) ? self : self + str
|
48
|
+
end
|
49
|
+
|
50
|
+
def possessive
|
51
|
+
"#{self}'#{'s' unless ends_with?('s')}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO any more to add?
|
55
|
+
NamePrefixes = %w[de den la von].freeze
|
56
|
+
|
57
|
+
def capitalize_name
|
58
|
+
split(' ').map {|term|
|
59
|
+
term.split('-').map {|term|
|
60
|
+
if NamePrefixes.include?(term)
|
61
|
+
term.downcase
|
62
|
+
elsif (term != term.downcase)
|
63
|
+
term
|
64
|
+
else # only capitalize words that are entirely lower case
|
65
|
+
term.capitalize
|
66
|
+
end
|
67
|
+
}.join('-')
|
68
|
+
}.join(' ')
|
69
|
+
end
|
70
|
+
|
71
|
+
def capitalize_name!
|
72
|
+
self.replace self.capitalize_name
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns whether this IP should be considered a valid one for a client to be using.
|
76
|
+
def valid_ip?
|
77
|
+
if production?
|
78
|
+
describe_as_ip == :public
|
79
|
+
else
|
80
|
+
describe_as_ip.in? :public, :private, :loopback
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a symbol describing the class of IP address +self+ represents, if any.
|
85
|
+
#
|
86
|
+
# Examples:
|
87
|
+
#
|
88
|
+
# "Hello world!".valid_ip? #=> false
|
89
|
+
# "192.168.".valid_ip? #=> false
|
90
|
+
# "127.0.0.1".valid_ip? #=> :loopback
|
91
|
+
# "172.24.137.6".valid_ip? #=> :private
|
92
|
+
# "169.254.1.142".valid_ip? #=> :self_assigned
|
93
|
+
# "72.9.108.122".valid_ip? #=> :public
|
94
|
+
def describe_as_ip
|
95
|
+
parts = strip.split('.')
|
96
|
+
bytes = parts.zip(parts.map(&:to_i)).map {|(str,val)|
|
97
|
+
val if ((1..255) === val) || (val == 0 && str == '0')
|
98
|
+
}.squash
|
99
|
+
|
100
|
+
if bytes.length != 4
|
101
|
+
false
|
102
|
+
elsif bytes.starts_with? 0 # Source hosts on "this" network
|
103
|
+
:reserved
|
104
|
+
elsif bytes.starts_with? 127 # Loopback network; RFC1700
|
105
|
+
:loopback
|
106
|
+
elsif bytes.starts_with? 10 # Class-A private; RFC1918
|
107
|
+
:private
|
108
|
+
elsif bytes.starts_with?(172) && ((16..31) === bytes[1]) # Class-B private; RFC1918
|
109
|
+
:private
|
110
|
+
elsif bytes.starts_with? 169, 254 # Link-local range; RFC3330/3927
|
111
|
+
bytes[2].in?(0, 255) ? :reserved : :self_assigned
|
112
|
+
elsif bytes.starts_with? 192, 0, 2 # TEST-NET - used as example.com IP
|
113
|
+
:reserved
|
114
|
+
elsif bytes.starts_with? 192, 88, 99 # 6-to-4 relay anycast; RFC3068
|
115
|
+
:reserved
|
116
|
+
elsif bytes.starts_with? 192, 168 # Class-C private; RFC1918
|
117
|
+
:private
|
118
|
+
elsif bytes.starts_with? 198, 18 # Benchmarking; RFC2544
|
119
|
+
:reserved
|
120
|
+
else
|
121
|
+
:public
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true if the string represents a valid email address.
|
126
|
+
def valid_email?
|
127
|
+
/^([a-z0-9\-\+\_\.]{2,})\@([a-z0-9\-]+\.)*([a-z0-9\-]{2,}\.)([a-z0-9\-]{2,})$/ =~ self
|
128
|
+
end
|
129
|
+
|
130
|
+
def colorize description = '', start_at = nil
|
131
|
+
if start_at.nil? || (cut_point = index(start_at)).nil?
|
132
|
+
Colorizer.colorize self, description
|
133
|
+
else
|
134
|
+
self[0...cut_point] + Colorizer.colorize(self[cut_point..-1], description)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def colorize! description = '', start_at = nil
|
139
|
+
replace colorize(description, start_at)
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
class Colorizer
|
145
|
+
HomeOffset = 29
|
146
|
+
LightOffset = 60
|
147
|
+
BGOffset = 10
|
148
|
+
LightRegex = /^light_/
|
149
|
+
ColorRegex = /^(light_)?none|gr[ae]y|red|green|yellow|blue|pink|cyan|white$/
|
150
|
+
CtrlRegex = /^bold|underlined?|blink(ing)?|reversed?$/
|
151
|
+
ColorOffsets = {
|
152
|
+
'none' => 0,
|
153
|
+
'gray' => 1, 'grey' => 1,
|
154
|
+
'red' => 2,
|
155
|
+
'green' => 3,
|
156
|
+
'yellow' => 4,
|
157
|
+
'blue' => 5,
|
158
|
+
'pink' => 6,
|
159
|
+
'cyan' => 7,
|
160
|
+
'white' => 8
|
161
|
+
}
|
162
|
+
CtrlOffsets = {
|
163
|
+
'bold' => 1,
|
164
|
+
'underline' => 4, 'underlined' => 4,
|
165
|
+
'blink' => 5, 'blinking' => 5,
|
166
|
+
'reverse' => 7, 'reversed' => 7
|
167
|
+
}
|
168
|
+
class << self
|
169
|
+
def colorize text, description
|
170
|
+
terms = " #{description} ".gsub(' light ', ' light_').gsub(' on ', ' on_').strip.split(/\s+/)
|
171
|
+
bg = terms.detect {|i| /on_#{ColorRegex}/ =~ i }
|
172
|
+
fg = terms.detect {|i| ColorRegex =~ i }
|
173
|
+
ctrl = terms.detect {|i| CtrlRegex =~ i }
|
174
|
+
|
175
|
+
"\e[#{"0;#{fg_for(fg)};#{bg_for(bg) || ctrl_for(ctrl)}"}m#{text}\e[0m"
|
176
|
+
end
|
177
|
+
|
178
|
+
def fg_for name
|
179
|
+
light = name.gsub!(LightRegex, '') unless name.nil?
|
180
|
+
(ColorOffsets[name] || 0) + HomeOffset + (light ? LightOffset : 0)
|
181
|
+
end
|
182
|
+
|
183
|
+
def bg_for name
|
184
|
+
# There's a hole in the table on bg=none, so we use BGOffset to the left
|
185
|
+
offset = fg_for((name || '').sub(/^on_/, ''))
|
186
|
+
offset + BGOffset unless offset == HomeOffset
|
187
|
+
end
|
188
|
+
|
189
|
+
def ctrl_for name
|
190
|
+
CtrlOffsets[name] || HomeOffset
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|