strelka 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/strelka.rb CHANGED
@@ -13,6 +13,7 @@ require 'configurability/config'
13
13
  # == Author/s
14
14
  #
15
15
  # * Michael Granger <ged@FaerieMUD.org>
16
+ # * Mahlon E. Smith <mahlon@martini.nu>
16
17
  #
17
18
  # :title: Strelka Web Application Framework
18
19
  # :main: README.rdoc
@@ -24,10 +25,10 @@ module Strelka
24
25
  log_as :strelka
25
26
 
26
27
  # Library version constant
27
- VERSION = '0.11.0'
28
+ VERSION = '0.12.0'
28
29
 
29
30
  # Version-control revision constant
30
- REVISION = %q$Revision: d794173d505f $
31
+ REVISION = %q$Revision: d7a7144b3fd9 $
31
32
 
32
33
  require 'strelka/mixins'
33
34
  require 'strelka/constants'
@@ -68,14 +69,8 @@ module Strelka
68
69
  ### Look up the application class of +appname+, optionally limiting it to the gem
69
70
  ### named +gemname+. Returns the first matching class, or raises an exception if no
70
71
  ### app class was found.
71
- def self::App( appname, gemname=nil )
72
- path, _ = Strelka::Discovery.find( appname, gemname )
73
- raise LoadError, "Can't find the %s app." % [ appname ] unless path
74
-
75
- apps = Strelka::Discovery.load( path ) or
76
- raise ScriptError "Loading %s didn't define a Strelka::App class." % [ path ]
77
-
78
- return apps.first
72
+ def self::App( appname )
73
+ return Strelka::Discovery.load( appname )
79
74
  end
80
75
 
81
76
  end # module Strelka
@@ -0,0 +1,393 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'loggability'
5
+ require 'highline'
6
+ require 'gli'
7
+
8
+ require 'strelka' unless defined?( Strelka )
9
+ require 'strelka/constants'
10
+ require 'strelka/mixins'
11
+
12
+
13
+ # The command-line interface to Strelka.
14
+ module Strelka::CLI
15
+ extend Strelka::MethodUtilities,
16
+ Loggability,
17
+ GLI::App
18
+ include Strelka::Constants
19
+
20
+
21
+ # Write logs to Strelka's logger
22
+ log_to :strelka
23
+
24
+
25
+ # Make a HighLine color scheme
26
+ COLOR_SCHEME = HighLine::ColorScheme.new do |scheme|
27
+ scheme[:header] = [ :bold, :yellow ]
28
+ scheme[:subheader] = [ :bold, :white ]
29
+ scheme[:key] = [ :white ]
30
+ scheme[:value] = [ :bold, :white ]
31
+ scheme[:error] = [ :red ]
32
+ scheme[:warning] = [ :yellow ]
33
+ scheme[:message] = [ :reset ]
34
+ end
35
+
36
+
37
+ #
38
+ # GLI
39
+ #
40
+
41
+ # Set up global[:description] and options
42
+ program_desc 'Strelka'
43
+
44
+ # The command version
45
+ version Strelka::VERSION
46
+
47
+ # Use an OpenStruct for options instead of a Hash
48
+ use_openstruct( true )
49
+
50
+ # Subcommand options are independent of global[:ones]
51
+ subcommand_option_handling :normal
52
+
53
+ # Strict argument validation
54
+ arguments :strict
55
+
56
+
57
+ # Custom parameter types
58
+ accept Array do |value|
59
+ value.strip.split(/\s*,\s*/)
60
+ end
61
+ accept Pathname do |value|
62
+ Pathname( value.strip )
63
+ end
64
+
65
+
66
+ # Global options
67
+ desc "Specify the config file to load"
68
+ flag [:c, :config], type: Pathname
69
+
70
+ desc "Override the Strelka data directory"
71
+ flag [:D, :datadir], default_value: '.', type: Pathname
72
+
73
+ desc 'Enable debugging output'
74
+ switch [:d, :debug]
75
+
76
+ desc 'Enable verbose output'
77
+ switch [:v, :verbose]
78
+
79
+ desc 'Set log level to LEVEL (one of %s)' % [Loggability::LOG_LEVELS.keys.join(', ')]
80
+ default_value Loggability[self].level
81
+ flag [:l, :loglevel], must_match: Loggability::LOG_LEVELS.keys
82
+
83
+ desc "Don't actually do anything, just show what would happen."
84
+ switch [:n, 'dry-run']
85
+
86
+ desc "Write the output to FILE instead of STDERR. Specify " +
87
+ "'-' to write command output to STDOUT instead."
88
+ flag [:o, 'output-file'], type: Pathname
89
+
90
+ desc "Additional Ruby libs to require before doing anything."
91
+ flag [:r, 'requires'], type: Array
92
+
93
+
94
+ #
95
+ # GLI Event callbacks
96
+ #
97
+
98
+ pre do |global, command, options, args|
99
+ if loglevel = global[:loglevel]
100
+ Loggability.level = loglevel.to_sym
101
+ else
102
+ Loggability.level = :fatal
103
+ end
104
+
105
+ # Set the datadir override if it's given
106
+ if global.datadir
107
+ self.log.debug "Using data dir option: %s" % [ global.datadir ]
108
+ Strelka::Discovery.local_data_dirs = global.datadir
109
+ end
110
+
111
+ # Include a 'lib' directory if there is one
112
+ $LOAD_PATH.unshift( 'lib' ) if File.directory?( 'lib' )
113
+
114
+ self.require_additional_libs( global.requires ) if global.requires
115
+ self.load_config( global )
116
+ self.install_highline_colorscheme
117
+ self.setup_output( global )
118
+
119
+ true
120
+ end
121
+
122
+
123
+ # Close the output file immediately after the command executes
124
+ post do |*|
125
+ self.outfile.close if self.outfile
126
+ end
127
+
128
+
129
+ ##
130
+ # Registered subcommand modules
131
+ singleton_attr_accessor :subcommand_modules
132
+
133
+ ##
134
+ # The IO opened to the output file
135
+ singleton_attr_accessor :outfile
136
+
137
+
138
+ ### Overridden -- Add registered subcommands immediately before running.
139
+ def self::run( * )
140
+ self.add_registered_subcommands
141
+ super
142
+ end
143
+
144
+
145
+ ### Add the specified +mod+ule containing subcommands to the 'strelka' command.
146
+ def self::register_subcommands( mod )
147
+ self.subcommand_modules ||= []
148
+ self.subcommand_modules.push( mod )
149
+ mod.extend( GLI::App, GLI::AppSupport, Strelka::Constants, Loggability )
150
+ mod.log_to( :strelka )
151
+ end
152
+
153
+
154
+ ### Add the commands from the registered subcommand modules.
155
+ def self::add_registered_subcommands
156
+ self.subcommand_modules ||= []
157
+ self.subcommand_modules.each do |mod|
158
+ merged_commands = mod.commands.merge( self.commands )
159
+ self.commands.update( merged_commands )
160
+ command_objs = self.commands_declaration_order | self.commands.values
161
+ self.commands_declaration_order.replace( command_objs )
162
+ end
163
+ end
164
+
165
+
166
+ ### Return the HighLine prompt used by the command to communicate with the
167
+ ### user.
168
+ def self::prompt
169
+ @prompt ||= HighLine.new( $stdin, $stderr )
170
+ end
171
+
172
+
173
+ ### If the command's output was redirected to a file, return the open File object
174
+ ### for it.
175
+ def self::outfile
176
+ return @outfile
177
+ end
178
+
179
+
180
+ ### Discard the existing HighLine prompt object if one existed. Mostly useful for
181
+ ### testing.
182
+ def self::reset_prompt
183
+ @prompt = nil
184
+ end
185
+
186
+
187
+ ### Load any additional Ruby libraries given with the -r global option.
188
+ def self::require_additional_libs( requires)
189
+ requires.each do |path|
190
+ path = "strelka/#{path}" unless path.start_with?( 'strelka/' )
191
+ require( path )
192
+ end
193
+ end
194
+
195
+
196
+ ### Install the color scheme used by HighLine
197
+ def self::install_highline_colorscheme
198
+ HighLine.color_scheme = HighLine::ColorScheme.new do |cs|
199
+ cs[:headline] = [ :bold, :white, :on_black ]
200
+ cs[:success] = [ :green ]
201
+ cs[:error] = [ :bold, :red ]
202
+ cs[:highlight] = [ :bold, :yellow ]
203
+ cs[:search_hit] = [ :black, :on_white ]
204
+ cs[:prompt] = [ :cyan ]
205
+ cs[:even_row] = [ :bold ]
206
+ cs[:odd_row] = [ :normal ]
207
+ end
208
+ end
209
+
210
+
211
+ ### Load the config file using either strelka-base's config-loader if available, or
212
+ ### fall back to DEFAULT_CONFIG_FILE
213
+ def self::load_config( global={} )
214
+ Strelka.load_config( global.config ) if global.config
215
+
216
+ # Set up the logging formatter
217
+ Loggability.format_with( :color ) if $stdout.tty?
218
+ end
219
+
220
+
221
+ ### Set up the output levels and globals based on the associated +global+ options.
222
+ def self::setup_output( global )
223
+
224
+ # Turn on Ruby debugging and/or verbosity if specified
225
+ if global[:n]
226
+ $DRYRUN = true
227
+ Loggability.level = :warn
228
+ else
229
+ $DRYRUN = false
230
+ end
231
+
232
+ if global[:verbose]
233
+ $VERBOSE = true
234
+ Loggability.level = :info
235
+ end
236
+
237
+ if global[:debug]
238
+ $DEBUG = true
239
+ Loggability.level = :debug
240
+ end
241
+
242
+ if (( filename = global[:o] ))
243
+ if filename.to_s == '-'
244
+ @prompt = HighLine.new( $stdin, $stdout )
245
+ else
246
+ @outfile = filename.open( 'w', encoding: 'utf-8' )
247
+ HighLine.use_color = false
248
+ @prompt = HighLine.new( $stdin, @outfile )
249
+ end
250
+ end
251
+ end
252
+
253
+
254
+ # Write the error to the log on exceptions.
255
+ on_error do |exception|
256
+ case exception
257
+ when OptionParser::ParseError, GLI::CustomExit
258
+ self.log.debug( exception )
259
+ else
260
+ self.log.error( exception )
261
+ end
262
+
263
+ exception.backtrace.each {|frame| self.log.debug(frame) }
264
+
265
+ true
266
+ end
267
+
268
+
269
+ #
270
+ # GLI subcommands
271
+ #
272
+
273
+
274
+ # Convenience module for subcommand registration syntax sugar.
275
+ module Subcommand
276
+
277
+ ### Extension callback -- register the extending object as a subcommand.
278
+ def self::extended( mod )
279
+ Strelka::CLI.log.debug "Registering subcommands from %p" % [ mod ]
280
+ Strelka::CLI.register_subcommands( mod )
281
+ end
282
+
283
+
284
+ ###############
285
+ module_function
286
+ ###############
287
+
288
+ ### Get the prompt (a Highline object)
289
+ def prompt
290
+ return Strelka::CLI.prompt
291
+ end
292
+
293
+
294
+ ### Return the specified +text+ as a Highline::String for convenient formatting,
295
+ ### color, etc.
296
+ def hl( text )
297
+ return HighLine::String.new( text.to_s )
298
+ end
299
+
300
+
301
+ ### Return the specified +string+ in the 'headline' ANSI color.
302
+ def headline_string( string )
303
+ return hl( string ).color( :headline )
304
+ end
305
+
306
+
307
+ ### Return the specified +string+ in the 'highlight' ANSI color.
308
+ def highlight_string( string )
309
+ return hl( string ).color( :highlight )
310
+ end
311
+
312
+
313
+ ### Return the specified +string+ in the 'success' ANSI color.
314
+ def success_string( string )
315
+ return hl( string ).color( :success )
316
+ end
317
+
318
+
319
+ ### Return the specified +string+ in the 'error' ANSI color.
320
+ def error_string( string )
321
+ return hl( string ).color( :error )
322
+ end
323
+
324
+
325
+ ### Output a table with the given +rows+.
326
+ def display_table( rows )
327
+ colwidths = rows.transpose.map do |colvals|
328
+ colvals.map {|val| visible_chars(val) }.max
329
+ end
330
+
331
+ rows.each do |row|
332
+ row_string = row.zip( colwidths ).inject( '' ) do |accum, (val, colsize)|
333
+ padding = ' ' * (colsize - visible_chars(val) + 2)
334
+ accum + val.to_s + padding
335
+ end
336
+
337
+ Strelka::CLI.prompt.say( row_string + "\n" )
338
+ end
339
+ end
340
+
341
+
342
+ ### Return the count of visible (i.e., non-control) characters in the given +string+.
343
+ def visible_chars( string )
344
+ return string.to_s.gsub(/\e\[.*?m/, '').scan( /\P{Cntrl}/ ).size
345
+ end
346
+
347
+
348
+ ### In dry-run mode, output the description instead of running the provided block and
349
+ ### return the +return_value+.
350
+ ### If dry-run mode is not enabled, yield to the block.
351
+ def unless_dryrun( description, return_value=true )
352
+ if $DRYRUN
353
+ self.log.warn( "DRYRUN> #{description}" )
354
+ return return_value
355
+ else
356
+ return yield
357
+ end
358
+ end
359
+ alias_method :unless_dry_run, :unless_dryrun
360
+
361
+ end # module Subcommand
362
+
363
+
364
+ ### Register one or more subcommands with the 'strelka' command shell. The given
365
+ ### block will be evaluated in the context of Strelka::CLI.
366
+ def self::register( &block )
367
+ self.instance_eval( &block )
368
+ end
369
+
370
+
371
+ ### Custom command loader. The default one is silly.
372
+ def self::load_commands( path )
373
+ self.log.debug "Load commands from %s" % [ path ]
374
+ Pathname.glob( path + '*.rb' ).each do |rbfile|
375
+ self.log.debug " loading %s..." % [ rbfile ]
376
+ require( rbfile )
377
+ end
378
+ end
379
+
380
+
381
+ # Load commands from any files in the specified directory relative to LOAD_PATHs
382
+ def self::commands_from( subdir )
383
+ $LOAD_PATH.map {|path| Pathname(path) }.each do |libdir|
384
+ command_dir = libdir.expand_path + subdir
385
+ load_commands( command_dir )
386
+ end
387
+ end
388
+
389
+
390
+ commands_from 'strelka/command'
391
+
392
+ end # class Strelka::CLICommand
393
+
@@ -0,0 +1,35 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'strelka/cli' unless defined?( Strelka::CLI )
5
+
6
+
7
+ # Command to start a Strelka application
8
+ module Strelka::CLI::Config
9
+ extend Strelka::CLI::Subcommand
10
+
11
+ desc 'Dump a config file for the specified GEM (or local apps)'
12
+ arg :GEM, :optional
13
+ command :config do |cmd|
14
+
15
+ cmd.action do |globals, options, args|
16
+ gemname = args.shift
17
+ discovery_name = gemname || ''
18
+
19
+ prompt.say( headline_string "Dumping config for %s" % [ gemname || 'local apps' ] )
20
+ discovered_apps = Strelka::Discovery.discover_apps
21
+
22
+ raise ArgumentError, "No apps discovered" unless discovered_apps.key?( discovery_name )
23
+
24
+ discovered_apps[ discovery_name ].each do |apppath|
25
+ prompt.say " loading %s (%s)" % [ apppath, apppath.basename('.rb') ]
26
+ Strelka::Discovery.load( apppath )
27
+ end
28
+
29
+ prompt.say " dumping config:"
30
+ $stdout.puts Configurability.default_config.dump
31
+ end
32
+ end
33
+
34
+ end # module Strelka::CLI::Config
35
+