strelka 0.0.1pre4

Sign up to get free protection for your applications and to get access to all the features.
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
+