strelka 0.11.0 → 0.12.0

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/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
+