strelka 0.0.1pre4

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.
Files changed (40) hide show
  1. data/.gemtest +0 -0
  2. data/History.rdoc +4 -0
  3. data/IDEAS.textile +174 -0
  4. data/Manifest.txt +38 -0
  5. data/README.rdoc +66 -0
  6. data/Rakefile +64 -0
  7. data/bin/leash +403 -0
  8. data/data/strelka/apps/strelka-admin +65 -0
  9. data/data/strelka/apps/strelka-setup +26 -0
  10. data/data/strelka/bootstrap-config.rb +34 -0
  11. data/data/strelka/templates/admin/console.tmpl +21 -0
  12. data/data/strelka/templates/layout.tmpl +30 -0
  13. data/lib/strelka/app/defaultrouter.rb +85 -0
  14. data/lib/strelka/app/filters.rb +70 -0
  15. data/lib/strelka/app/parameters.rb +64 -0
  16. data/lib/strelka/app/plugins.rb +205 -0
  17. data/lib/strelka/app/routing.rb +140 -0
  18. data/lib/strelka/app/templating.rb +157 -0
  19. data/lib/strelka/app.rb +175 -0
  20. data/lib/strelka/behavior/plugin.rb +36 -0
  21. data/lib/strelka/constants.rb +53 -0
  22. data/lib/strelka/httprequest.rb +52 -0
  23. data/lib/strelka/logging.rb +241 -0
  24. data/lib/strelka/mixins.rb +143 -0
  25. data/lib/strelka/process.rb +19 -0
  26. data/lib/strelka.rb +40 -0
  27. data/spec/data/layout.tmpl +3 -0
  28. data/spec/data/main.tmpl +1 -0
  29. data/spec/lib/constants.rb +32 -0
  30. data/spec/lib/helpers.rb +134 -0
  31. data/spec/strelka/app/defaultrouter_spec.rb +215 -0
  32. data/spec/strelka/app/parameters_spec.rb +74 -0
  33. data/spec/strelka/app/plugins_spec.rb +167 -0
  34. data/spec/strelka/app/routing_spec.rb +139 -0
  35. data/spec/strelka/app/templating_spec.rb +169 -0
  36. data/spec/strelka/app_spec.rb +160 -0
  37. data/spec/strelka/httprequest_spec.rb +54 -0
  38. data/spec/strelka/logging_spec.rb +72 -0
  39. data/spec/strelka_spec.rb +27 -0
  40. metadata +226 -0
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mongrel2/handler'
4
+ require 'strelka' unless defined?( Strelka )
5
+
6
+
7
+ # The application base class.
8
+ class Strelka::App < Mongrel2::Handler
9
+ include Strelka::Loggable,
10
+ Strelka::Constants
11
+
12
+ # Load the plugin system
13
+ require 'strelka/app/plugins'
14
+ include Strelka::App::Plugins
15
+
16
+
17
+ @default_content_type = nil
18
+
19
+ ### Get/set the Content-type of requests that don't set one. Leaving this unset will
20
+ ### leave the Content-type unset.
21
+ def self::default_content_type( newtype=nil )
22
+ @default_content_type = newtype if newtype
23
+ return @default_content_type
24
+ end
25
+
26
+
27
+ #################################################################
28
+ ### I N S T A N C E M E T H O D S
29
+ #################################################################
30
+
31
+ ######
32
+ public
33
+ ######
34
+
35
+ ### The main Mongrel2 entrypoint -- accept Strelka::Requests and return
36
+ ### Strelka::Responses.
37
+ def handle( request )
38
+ response = nil
39
+
40
+ # Run fixup hooks on the request
41
+ request = self.fixup_request( request )
42
+
43
+ # Dispatch the request after allowing plugins to to their thing
44
+ status_info = catch( :finish ) do
45
+ response = self.handle_request( request )
46
+ nil # rvalue for the catch
47
+ end
48
+
49
+ # Status response
50
+ if status_info
51
+ self.log.debug "Preparing a status response: %p" % [ status_info ]
52
+ return self.prepare_status_response( request, status_info )
53
+
54
+ # Normal response
55
+ else
56
+ self.log.debug "Preparing a regular response: %p" % [ response ]
57
+ response ||= request.response
58
+ return self.fixup_response( request, response )
59
+ end
60
+
61
+ rescue => err
62
+ # Propagate Spec failures
63
+ raise if err.class.name =~ /^RSpec::/
64
+
65
+ msg = "%s: %s %s" % [ err.class.name, err.message, err.backtrace.first ]
66
+ self.log.error( msg )
67
+ err.backtrace[ 1..-1 ].each {|frame| self.log.debug(' ' + frame) }
68
+
69
+ status_info = { :status => HTTP::SERVER_ERROR, :message => msg }
70
+ return self.prepare_status_response( request, status_info )
71
+ end
72
+
73
+
74
+ #########
75
+ protected
76
+ #########
77
+
78
+ ### Make any changes to the +request+ that are necessary before handling it and
79
+ ### return it. This is an alternate extension-point for plugins that
80
+ ### wish to modify or replace the request before the request cycle is
81
+ ### started.
82
+ def fixup_request( request )
83
+ self.log.debug "Fixing up request: %p" % [ request ]
84
+ request = super
85
+ self.log.debug " after fixup: %p" % [ request ]
86
+
87
+ return request
88
+ end
89
+
90
+
91
+ ### Handle the request and return a +response+. This is the main extension-point
92
+ ### for the plugin system. Without being overridden or extended by plugins, this
93
+ ### method just returns the default Mongrel2::HTTPRequest#response.
94
+ def handle_request( request, &block )
95
+ self.log.debug "Strelka::App#handle_request"
96
+ if block
97
+ return super( request, &block )
98
+ else
99
+ return super( request ) {|r| r.response }
100
+ end
101
+ end
102
+
103
+
104
+ ### Make any changes to the +response+ that are necessary before handing it to
105
+ ### Mongrel and return it. This is an alternate extension-point for plugins that
106
+ ### wish to modify or replace the response after the whole request cycle is
107
+ ### completed.
108
+ def fixup_response( request, response )
109
+ self.log.debug "Fixing up response: %p" % [ response ]
110
+ self.fixup_response_content_type( response )
111
+ self.fixup_head_response( response ) if request.verb == :HEAD
112
+ self.log.debug " after fixup: %p" % [ response ]
113
+
114
+ super
115
+ end
116
+
117
+
118
+ ### If the +response+ doesn't yet have a Content-type header, and the app has
119
+ ### defined a default (via App.default_content_type), set it to the default.
120
+ def fixup_response_content_type( response )
121
+ restype = response.content_type
122
+
123
+ if !restype
124
+ if (( default = self.class.default_content_type ))
125
+ self.log.debug "Setting default content type"
126
+ response.content_type = default
127
+ else
128
+ self.log.debug "No default content type"
129
+ end
130
+ else
131
+ self.log.debug "Content type already set: %p" % [ restype ]
132
+ end
133
+ end
134
+
135
+
136
+ ### Remove the entity body of responses to HEAD requests.
137
+ def fixup_head_response( response )
138
+ self.log.debug "Truncating entity body of HEAD response."
139
+ response.headers.content_length = response.get_content_length
140
+ response.body = ''
141
+ end
142
+
143
+
144
+ ### Abort the current execution and return a response with the specified
145
+ ### http_status code immediately. The specified +message+ will be logged,
146
+ ### and will be included in any message that is returned as part of the
147
+ ### response. The +otherstuff+ hash can be used to pass headers, etc.
148
+ def finish_with( http_status, message, otherstuff={} )
149
+ status_info = otherstuff.merge( :status => http_status, :message => message )
150
+ throw :finish, status_info
151
+ end
152
+
153
+
154
+ ### Create a response to specified +request+ based on the specified +status_code+
155
+ ### and +message+.
156
+ def prepare_status_response( request, status_info )
157
+ status_code, message = status_info.values_at( :status, :message )
158
+ self.log.info "Non-OK response: %d (%s)" % [ status_code, message ]
159
+
160
+ response = request.response
161
+ response.reset
162
+ response.status = status_code
163
+
164
+ # Some status codes allow explanatory text to be returned; some forbid it. Append the
165
+ # message for those that allow one.
166
+ unless request.verb == :HEAD || HTTP::BODILESS_HTTP_RESPONSE_CODES.include?( status_code )
167
+ response.content_type = 'text/plain'
168
+ response.puts( message )
169
+ end
170
+
171
+ return response
172
+ end
173
+
174
+ end # class Strelka::App
175
+
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rspec'
4
+
5
+ require 'strelka'
6
+ require 'strelka/app'
7
+ require 'strelka/app/plugins'
8
+
9
+
10
+ # This is a shared behavior for specs which different Strelka::App
11
+ # plugins share in common. If you're creating a Strelka::App plugin,
12
+ # you can test its conformity to the expectations placed on them by
13
+ # adding this to your spec:
14
+ #
15
+ # require 'strelka/behavior/plugin'
16
+ #
17
+ # describe YourPlugin do
18
+ #
19
+ # it_should_behave_like "A Strelka::App Plugin"
20
+ #
21
+ # end
22
+
23
+ shared_examples_for "A Strelka::App Plugin" do
24
+
25
+ let( :plugin ) do
26
+ described_class
27
+ end
28
+
29
+
30
+ it "extends Strelka::App::Plugin" do
31
+ plugin.should be_a( Strelka::App::Plugin )
32
+ end
33
+
34
+ end
35
+
36
+
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/ruby
2
+ #encoding: utf-8
3
+
4
+ require 'mongrel2/constants'
5
+ require 'strelka' unless defined?( Strelka )
6
+
7
+
8
+ # A collection of constants that are shared across the library
9
+ module Strelka::Constants
10
+
11
+ # Import Mongrel2's constants, too
12
+ include Mongrel2::Constants
13
+
14
+ # Override the path to the default Sqlite configuration database
15
+ # remove_const( :DEFAULT_CONFIG_URI )
16
+ DEFAULT_CONFIG_URI = 'strelka.sqlite'
17
+
18
+ # The admin server port
19
+ DEFAULT_ADMIN_PORT = 7337
20
+
21
+
22
+ # Extend Mongrel2's HTTP constants collection
23
+ module HTTP
24
+ include Mongrel2::Constants::HTTP
25
+
26
+ # The list of valid verbs for regular HTTP
27
+ RFC2616_VERBS = %w[OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT]
28
+
29
+ # A regex for matching RFC2616 verbs
30
+ RFC2616_VERB_REGEX = Regexp.union( RFC2616_VERBS )
31
+
32
+ # The list of HTTP verbs considered "safe"
33
+ SAFE_RFC2616_VERBS = %w[GET HEAD]
34
+
35
+ # The list of HTTP verbs considered "idempotent"
36
+ IDEMPOTENT_RFC2616_VERBS = %w[OPTIONS GET HEAD PUT DELETE TRACE]
37
+
38
+ # A registry of HTTP status codes that don't allow an entity body
39
+ # in the response.
40
+ BODILESS_HTTP_RESPONSE_CODES = [
41
+ CONTINUE,
42
+ SWITCHING_PROTOCOLS,
43
+ PROCESSING,
44
+ NO_CONTENT,
45
+ RESET_CONTENT,
46
+ NOT_MODIFIED,
47
+ USE_PROXY,
48
+ ]
49
+
50
+ end # module HTTP
51
+
52
+ end # module Strelka::Constants
53
+
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'uri'
4
+
5
+ require 'mongrel2/httprequest'
6
+ require 'strelka' unless defined?( Strelka )
7
+
8
+ # An HTTP request class.
9
+ class Strelka::HTTPRequest < Mongrel2::HTTPRequest
10
+ include Strelka::Loggable,
11
+ Strelka::Constants
12
+
13
+ # Set Mongrel2 to use Strelka's request class for HTTP requests
14
+ register_request_type( self, *HTTP::RFC2616_VERBS )
15
+
16
+
17
+ ### Initialize some additional stuff for Strelka requests.
18
+ def initialize( * ) # :notnew:
19
+ super
20
+ @uri = nil
21
+ @verb = self.headers[:method].to_sym
22
+ end
23
+
24
+
25
+ ######
26
+ public
27
+ ######
28
+
29
+ # The HTTP verb of the request (as a Symbol)
30
+ attr_accessor :verb
31
+
32
+
33
+ ### Return a URI object parsed from the URI of the request.
34
+ def uri
35
+ return @uri ||= URI( self.headers.uri )
36
+ end
37
+
38
+
39
+ ### Return the portion of the Request's path that was routed by Mongrel2. This and the
40
+ ### #app_path make up the #path.
41
+ def pattern
42
+ return self.headers.pattern
43
+ end
44
+
45
+
46
+ ### Return the portion of the Request's path relative to the application's
47
+ ### Mongrel2 route.
48
+ def app_path
49
+ return self.path[ self.pattern.length .. -1 ]
50
+ end
51
+
52
+ end # class Strelka::HTTPRequest
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'logger'
4
+ require 'date'
5
+
6
+ require 'strelka' unless defined?( Strelka )
7
+ require 'strelka/mixins'
8
+
9
+
10
+ # A mixin that provides a top-level logging subsystem based on Logger.
11
+ module Strelka::Logging
12
+
13
+ ### Logging
14
+ # Log levels
15
+ LOG_LEVELS = {
16
+ 'debug' => Logger::DEBUG,
17
+ 'info' => Logger::INFO,
18
+ 'warn' => Logger::WARN,
19
+ 'error' => Logger::ERROR,
20
+ 'fatal' => Logger::FATAL,
21
+ }.freeze
22
+ LOG_LEVEL_NAMES = LOG_LEVELS.invert.freeze
23
+
24
+
25
+ ### Inclusion hook
26
+ def self::extended( mod )
27
+ super
28
+
29
+ class << mod
30
+ # the log formatter that will be used when the logging subsystem is reset
31
+ attr_accessor :default_log_formatter
32
+
33
+ # the logger that will be used when the logging subsystem is reset
34
+ attr_accessor :default_logger
35
+
36
+ # the logger that's currently in effect
37
+ attr_accessor :logger
38
+ alias_method :log, :logger
39
+ alias_method :log=, :logger=
40
+ end
41
+
42
+ mod.default_logger = mod.logger = Logger.new( $stderr )
43
+ mod.default_logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
44
+ mod.default_log_formatter = Strelka::Logging::Formatter.new( mod.default_logger )
45
+ end
46
+
47
+
48
+ ### Reset the global logger object to the default
49
+ def reset_logger
50
+ self.logger = self.default_logger
51
+ self.logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
52
+ self.logger.formatter = self.default_log_formatter
53
+ end
54
+
55
+
56
+ ### Returns +true+ if the global logger has not been set to something other than
57
+ ### the default one.
58
+ def using_default_logger?
59
+ return self.logger == self.default_logger
60
+ end
61
+
62
+
63
+ # A alternate formatter for Logger instances.
64
+ class Formatter < Logger::Formatter
65
+
66
+ # The format to output unless debugging is turned on
67
+ DEFAULT_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"
68
+
69
+ # The format to output if debugging is turned on
70
+ DEFAULT_DEBUG_FORMAT = "[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"
71
+
72
+
73
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
74
+ def initialize( logger, format=DEFAULT_FORMAT, debug=DEFAULT_DEBUG_FORMAT ) # :notnew:
75
+ @logger = logger
76
+ @format = format
77
+ @debug_format = debug
78
+
79
+ super()
80
+ end
81
+
82
+ ######
83
+ public
84
+ ######
85
+
86
+ # The Logger object associated with the formatter
87
+ attr_accessor :logger
88
+
89
+ # The logging format string
90
+ attr_accessor :format
91
+
92
+ # The logging format string that's used when outputting in debug mode
93
+ attr_accessor :debug_format
94
+
95
+
96
+ ### Log using either the DEBUG_FORMAT if the associated logger is at ::DEBUG level or
97
+ ### using FORMAT if it's anything less verbose.
98
+ def call( severity, time, progname, msg )
99
+ args = [
100
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
101
+ time.usec, # %2$d
102
+ Process.pid, # %3$d
103
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
104
+ severity, # %5$s
105
+ progname, # %6$s
106
+ msg # %7$s
107
+ ]
108
+
109
+ if @logger.level == Logger::DEBUG
110
+ return self.debug_format % args
111
+ else
112
+ return self.format % args
113
+ end
114
+ end
115
+ end # class LogFormatter
116
+
117
+
118
+ # A ANSI-colorized formatter for Logger instances.
119
+ class ColorFormatter < Logger::Formatter
120
+ extend Strelka::ANSIColorUtilities
121
+
122
+ # Color settings
123
+ LEVEL_FORMATS = {
124
+ :debug => colorize( :bold, :black ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s {%6$s} -- %7$s\n"},
125
+ :info => colorize( :normal ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
126
+ :warn => colorize( :bold, :yellow ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
127
+ :error => colorize( :red ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
128
+ :fatal => colorize( :bold, :red, :on_white ) {"[%1$s.%2$06d %3$d/%4$s] %5$5s -- %7$s\n"},
129
+ }
130
+
131
+
132
+ ### Initialize the formatter with a reference to the logger so it can check for log level.
133
+ def initialize( logger, settings={} ) # :notnew:
134
+ settings = LEVEL_FORMATS.merge( settings )
135
+
136
+ @logger = logger
137
+ @settings = settings
138
+
139
+ super()
140
+ end
141
+
142
+ ######
143
+ public
144
+ ######
145
+
146
+ # The Logger object associated with the formatter
147
+ attr_accessor :logger
148
+
149
+ # The formats, by level
150
+ attr_accessor :settings
151
+
152
+
153
+ ### Log using the format associated with the severity
154
+ def call( severity, time, progname, msg )
155
+ args = [
156
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
157
+ time.usec, # %2$d
158
+ Process.pid, # %3$d
159
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
160
+ severity, # %5$s
161
+ progname, # %6$s
162
+ msg # %7$s
163
+ ]
164
+
165
+ return self.settings[ severity.downcase.to_sym ] % args
166
+ end
167
+ end # class LogFormatter
168
+
169
+
170
+ # An alternate formatter for Logger instances that outputs +div+ HTML
171
+ # fragments.
172
+ class HtmlFormatter < Logger::Formatter
173
+
174
+ # The default HTML fragment that'll be used as the template for each log message.
175
+ HTML_LOG_FORMAT = %q{
176
+ <div class="log-message %5$s">
177
+ <span class="log-time">%1$s.%2$06d</span>
178
+ [
179
+ <span class="log-pid">%3$d</span>
180
+ /
181
+ <span class="log-tid">%4$s</span>
182
+ ]
183
+ <span class="log-level">%5$s</span>
184
+ :
185
+ <span class="log-name">%6$s</span>
186
+ <span class="log-message-text">%7$s</span>
187
+ </div>
188
+ }
189
+
190
+ ### Override the logging formats with ones that generate HTML fragments
191
+ def initialize( logger, format=HTML_LOG_FORMAT ) # :notnew:
192
+ @logger = logger
193
+ @format = format
194
+ super()
195
+ end
196
+
197
+
198
+ ######
199
+ public
200
+ ######
201
+
202
+ # The HTML fragment that will be used as a format() string for the log
203
+ attr_accessor :format
204
+
205
+
206
+ ### Return a log message composed out of the arguments formatted using the
207
+ ### formatter's format string
208
+ def call( severity, time, progname, msg )
209
+ args = [
210
+ time.strftime( '%Y-%m-%d %H:%M:%S' ), # %1$s
211
+ time.usec, # %2$d
212
+ Process.pid, # %3$d
213
+ Thread.current == Thread.main ? 'main' : Thread.object_id, # %4$s
214
+ severity.downcase, # %5$s
215
+ progname, # %6$s
216
+ html_escape( msg ).gsub(/\n/, '<br />') # %7$s
217
+ ]
218
+
219
+ return self.format % args
220
+ end
221
+
222
+
223
+ #######
224
+ private
225
+ #######
226
+
227
+ ### Return a copy of the specified +string+ with HTML special characters escaped as
228
+ ### HTML entities.
229
+ def html_escape( string )
230
+ return string.
231
+ gsub( /&/, '&amp;' ).
232
+ gsub( /</, '&lt;' ).
233
+ gsub( />/, '&gt;' )
234
+ end
235
+
236
+ end # class HtmlLogFormatter
237
+
238
+ end # module Strelka
239
+
240
+ # vim: set nosta noet ts=4 sw=4:
241
+
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'logger'
4
+
5
+ require 'strelka' unless defined?( Strelka )
6
+ require 'strelka/constants'
7
+
8
+
9
+ module Strelka
10
+
11
+ # Add logging to a Strelka class. Including classes get #log and #log_debug methods.
12
+ module Loggable
13
+
14
+ # A logging proxy class that wraps calls to the logger into calls that include
15
+ # the name of the calling class.
16
+ class ClassNameProxy
17
+
18
+ ### Create a new proxy for the given +klass+.
19
+ def initialize( klass, force_debug=false )
20
+ @classname = klass.name
21
+ @force_debug = force_debug
22
+ end
23
+
24
+ ### Delegate debug messages to the global logger with the appropriate class name.
25
+ def debug( msg=nil, &block )
26
+ Strelka.logger.add( Logger::DEBUG, msg, @classname, &block )
27
+ end
28
+
29
+ ### Delegate info messages to the global logger with the appropriate class name.
30
+ def info( msg=nil, &block )
31
+ return self.debug( msg, &block ) if @force_debug
32
+ Strelka.logger.add( Logger::INFO, msg, @classname, &block )
33
+ end
34
+
35
+ ### Delegate warn messages to the global logger with the appropriate class name.
36
+ def warn( msg=nil, &block )
37
+ return self.debug( msg, &block ) if @force_debug
38
+ Strelka.logger.add( Logger::WARN, msg, @classname, &block )
39
+ end
40
+
41
+ ### Delegate error messages to the global logger with the appropriate class name.
42
+ def error( msg=nil, &block )
43
+ return self.debug( msg, &block ) if @force_debug
44
+ Strelka.logger.add( Logger::ERROR, msg, @classname, &block )
45
+ end
46
+
47
+ ### Delegate fatal messages to the global logger with the appropriate class name.
48
+ def fatal( msg=nil, &block )
49
+ Strelka.logger.add( Logger::FATAL, msg, @classname, &block )
50
+ end
51
+
52
+ end # ClassNameProxy
53
+
54
+ #########
55
+ protected
56
+ #########
57
+
58
+ ### Copy constructor -- clear the original's log proxy.
59
+ def initialize_copy( original )
60
+ @log_proxy = @log_debug_proxy = nil
61
+ super
62
+ end
63
+
64
+ ### Return the proxied logger.
65
+ def log
66
+ @log_proxy ||= ClassNameProxy.new( self.class )
67
+ end
68
+
69
+ ### Return a proxied "debug" logger that ignores other level specification.
70
+ def log_debug
71
+ @log_debug_proxy ||= ClassNameProxy.new( self.class, true )
72
+ end
73
+
74
+ end # module Loggable
75
+
76
+ # A collection of ANSI color utility functions
77
+ module ANSIColorUtilities
78
+
79
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
80
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
81
+ ANSI_ATTRIBUTES = {
82
+ 'clear' => 0,
83
+ 'reset' => 0,
84
+ 'bold' => 1,
85
+ 'dark' => 2,
86
+ 'underline' => 4,
87
+ 'underscore' => 4,
88
+ 'blink' => 5,
89
+ 'reverse' => 7,
90
+ 'concealed' => 8,
91
+
92
+ 'black' => 30, 'on_black' => 40,
93
+ 'red' => 31, 'on_red' => 41,
94
+ 'green' => 32, 'on_green' => 42,
95
+ 'yellow' => 33, 'on_yellow' => 43,
96
+ 'blue' => 34, 'on_blue' => 44,
97
+ 'magenta' => 35, 'on_magenta' => 45,
98
+ 'cyan' => 36, 'on_cyan' => 46,
99
+ 'white' => 37, 'on_white' => 47
100
+ }
101
+
102
+ ###############
103
+ module_function
104
+ ###############
105
+
106
+ ### Create a string that contains the ANSI codes specified and return it
107
+ def ansi_code( *attributes )
108
+ attributes.flatten!
109
+ attributes.collect! {|at| at.to_s }
110
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
111
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
112
+
113
+ if attributes.empty?
114
+ return ''
115
+ else
116
+ return "\e[%sm" % attributes
117
+ end
118
+ end
119
+
120
+
121
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling
122
+ ### line-endings, color reset, etc.
123
+ def colorize( *args )
124
+ string = ''
125
+
126
+ if block_given?
127
+ string = yield
128
+ else
129
+ string = args.shift
130
+ end
131
+
132
+ ending = string[/(\s)$/] || ''
133
+ string = string.rstrip
134
+
135
+ return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
136
+ end
137
+
138
+ end # module ANSIColorUtilities
139
+
140
+ end # module Strelka
141
+
142
+ # vim: set nosta noet ts=4 sw=4:
143
+