strelka 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.rdoc +8 -0
- data/Manifest.txt +5 -0
- data/README.rdoc +25 -10
- data/Rakefile +2 -2
- data/bin/strelka +2 -411
- data/lib/strelka.rb +5 -10
- data/lib/strelka/cli.rb +393 -0
- data/lib/strelka/command/config.rb +35 -0
- data/lib/strelka/command/discover.rb +29 -0
- data/lib/strelka/command/start.rb +40 -0
- data/lib/strelka/discovery.rb +151 -97
- data/spec/strelka/cli_spec.rb +85 -0
- data/spec/strelka/discovery_spec.rb +69 -81
- data/spec/strelka_spec.rb +0 -12
- metadata +49 -45
- metadata.gz.sig +0 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'strelka/cli' unless defined?( Strelka::CLI )
|
5
|
+
|
6
|
+
|
7
|
+
# Command to show discovered Strelka apps
|
8
|
+
module Strelka::CLI::Discover
|
9
|
+
extend Strelka::CLI::Subcommand
|
10
|
+
|
11
|
+
desc 'Show installed Strelka apps'
|
12
|
+
command :discover do |cmd|
|
13
|
+
|
14
|
+
cmd.action do |globals, options, args|
|
15
|
+
prompt.say( headline_string "Searching for Strelka applications..." )
|
16
|
+
|
17
|
+
apps = Strelka::Discovery.discovered_apps
|
18
|
+
|
19
|
+
if apps.empty?
|
20
|
+
prompt.say "None found."
|
21
|
+
else
|
22
|
+
rows = apps.sort_by {|name, path| name }
|
23
|
+
display_table( rows )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end # module Strelka::CLI::Discover
|
29
|
+
|
@@ -0,0 +1,40 @@
|
|
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::Start
|
9
|
+
extend Strelka::CLI::Subcommand
|
10
|
+
|
11
|
+
desc 'Start a Strelka app'
|
12
|
+
arg :GEMNAME, :optional
|
13
|
+
arg :APPNAME
|
14
|
+
command :start do |cmd|
|
15
|
+
|
16
|
+
cmd.action do |globals, options, args|
|
17
|
+
appname = args.pop
|
18
|
+
gemname = args.pop
|
19
|
+
path = nil
|
20
|
+
|
21
|
+
gem( gemname ) if gemname
|
22
|
+
|
23
|
+
app = if File.exist?( appname )
|
24
|
+
Strelka::Discovery.load_file( appname ) or
|
25
|
+
exit_now!( "Didn't find an app while loading %p!" % [appname] )
|
26
|
+
else
|
27
|
+
Strelka::Discovery.load( appname ) or
|
28
|
+
exit_now!( "Couldn't find the %p app!" % [appname] )
|
29
|
+
end
|
30
|
+
|
31
|
+
Strelka::CLI.prompt.say "Starting %s (%p)." % [ appname, app ]
|
32
|
+
Strelka.load_config( options[:c] ) if options[:c]
|
33
|
+
unless_dryrun( "starting the app" ) do
|
34
|
+
app.run
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end # module Strelka::CLI::Start
|
40
|
+
|
data/lib/strelka/discovery.rb
CHANGED
@@ -7,6 +7,83 @@ require 'strelka' unless defined?( Strelka )
|
|
7
7
|
|
8
8
|
|
9
9
|
# The Strelka application-discovery system.
|
10
|
+
#
|
11
|
+
# This module provides a mechanism for registering Strelka apps and their
|
12
|
+
# resources for discovery by the strelka CLI and other systems.
|
13
|
+
#
|
14
|
+
# It's responsible for three kinds of discovery:
|
15
|
+
#
|
16
|
+
# - Discovery of Strelka app files via Rubygems discovery
|
17
|
+
# - Discovery and loading of Strelka app classes by name
|
18
|
+
# - Discovery of data directories for strelka apps
|
19
|
+
#
|
20
|
+
# As such it can be used in several different ways.
|
21
|
+
#
|
22
|
+
# == \App File \Discovery
|
23
|
+
#
|
24
|
+
# If you have an app that you wish to be discoverable, create a
|
25
|
+
# <tt>lib/strelka/apps.rb</tt> file. This file will be added to those returned
|
26
|
+
# by the ::app_discovery_files call, which is the list loaded by
|
27
|
+
# ::discovered_apps.
|
28
|
+
#
|
29
|
+
# == \App \Discovery Registration
|
30
|
+
#
|
31
|
+
# To add a name and file path to Strelka::Discovery.discovered_apps, you can
|
32
|
+
# call ::register_app. This will check to make sure no other apps are registered
|
33
|
+
# with the same name. To register several at the same time, call
|
34
|
+
# ::register_apps with a Hash of <tt>name => path</tt> pairs.
|
35
|
+
#
|
36
|
+
# == Loading Discovered Apps
|
37
|
+
#
|
38
|
+
# To load a discovered app, call ::load with its registered name.
|
39
|
+
#
|
40
|
+
# This will load the associated file and returns the first Ruby class to inherit
|
41
|
+
# from a discoverable app class like Strelka::App or Strelka::WebSocketServer.
|
42
|
+
#
|
43
|
+
# === Putting it all together
|
44
|
+
#
|
45
|
+
# Say, for example, you were putting together an <tt>acme-apps</tt> gem for the
|
46
|
+
# Acme company that contained Strelka apps for a web store and a CMS. You could
|
47
|
+
# add a <tt>lib/strelka/apps.rb</tt> file to the <tt>acme-apps</tt> gem that
|
48
|
+
# contained the following:
|
49
|
+
#
|
50
|
+
# # -*- ruby -*-
|
51
|
+
# require 'strelka/discovery'
|
52
|
+
#
|
53
|
+
# Strelka::Discovery.register_apps(
|
54
|
+
# 'acme-store' => 'lib/acme/store.rb',
|
55
|
+
# 'acme-cms' => 'lib/acme/cms.rb'
|
56
|
+
# )
|
57
|
+
#
|
58
|
+
# This would let you do:
|
59
|
+
#
|
60
|
+
# $ gem install acme-apps
|
61
|
+
# $ strelka start acme-store
|
62
|
+
#
|
63
|
+
#
|
64
|
+
# == Data Directory \Discovery
|
65
|
+
#
|
66
|
+
# If your app requires some filesystem resources, a good way to distribute these
|
67
|
+
# is in your gem's "data directory". This is a directory in your gem called
|
68
|
+
# <tt>data/«your gem name»</tt>, and can be found via:
|
69
|
+
#
|
70
|
+
# Gem.datadir( your_gem_name )
|
71
|
+
#
|
72
|
+
# Strelka::Discoverable builds on top of this, and can return a Hash of glob
|
73
|
+
# patterns that will match the data directories of all gems that depend on
|
74
|
+
# Strelka, keyed by gem name. You can use this to populate search paths for
|
75
|
+
# templates, static assets, etc.
|
76
|
+
#
|
77
|
+
# template_paths = Strelka::Discovery.discover_data_dirs.
|
78
|
+
# flat_map {|_, pattern| Dir.glob(pattern + '/templates') }
|
79
|
+
#
|
80
|
+
# == Making a class Discoverable
|
81
|
+
#
|
82
|
+
# If you write your own app base class (e.g., Strelka::App,
|
83
|
+
# Strelka::WebSocketServer), you can make it discoverable by extending it with
|
84
|
+
# this module. You typically won't have to do this unless you're working on
|
85
|
+
# Strelka itself.
|
86
|
+
#
|
10
87
|
module Strelka::Discovery
|
11
88
|
extend Loggability,
|
12
89
|
Configurability,
|
@@ -22,7 +99,7 @@ module Strelka::Discovery
|
|
22
99
|
|
23
100
|
# Default config
|
24
101
|
CONFIG_DEFAULTS = {
|
25
|
-
|
102
|
+
app_discovery_file: 'strelka/apps.rb',
|
26
103
|
local_data_dirs: 'data/*',
|
27
104
|
}.freeze
|
28
105
|
|
@@ -30,11 +107,11 @@ module Strelka::Discovery
|
|
30
107
|
##
|
31
108
|
# The Hash of Strelka::App subclasses, keyed by the Pathname of the file they were
|
32
109
|
# loaded from, or +nil+ if they weren't loaded via ::load.
|
33
|
-
singleton_attr_reader :
|
110
|
+
singleton_attr_reader :discovered_classes
|
34
111
|
|
35
112
|
##
|
36
|
-
# The glob(3) pattern for matching
|
37
|
-
singleton_attr_accessor :
|
113
|
+
# The glob(3) pattern for matching the discovery hook file.
|
114
|
+
singleton_attr_accessor :app_discovery_file
|
38
115
|
|
39
116
|
##
|
40
117
|
# The glob(3) pattern for matching local data directories during discovery. Local
|
@@ -46,11 +123,55 @@ module Strelka::Discovery
|
|
46
123
|
singleton_attr_reader :loading_file
|
47
124
|
|
48
125
|
|
49
|
-
#
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@app_glob_pattern = CONFIG_DEFAULTS[:app_glob_pattern]
|
126
|
+
# Class instance variables
|
127
|
+
@discovered_classes = Hash.new {|h,k| h[k] = [] }
|
128
|
+
@app_discovery_file = CONFIG_DEFAULTS[:app_discovery_file]
|
53
129
|
@local_data_dirs = CONFIG_DEFAULTS[:local_data_dirs]
|
130
|
+
@discovered_apps = nil
|
131
|
+
|
132
|
+
|
133
|
+
### Register an app with the specified +name+ that can be loaded from the given
|
134
|
+
### +path+.
|
135
|
+
def self::register_app( name, path )
|
136
|
+
@discovered_apps ||= {}
|
137
|
+
|
138
|
+
if @discovered_apps.key?( name )
|
139
|
+
raise "Can't register a second '%s' app at %s; already have one at %s" %
|
140
|
+
[ name, path, @discovered_apps[name] ]
|
141
|
+
end
|
142
|
+
|
143
|
+
self.log.debug "Registered app at %s as %p" % [ path, name ]
|
144
|
+
@discovered_apps[ name ] = path
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
### Register multiple apps by passing +a_hash+ of names and paths.
|
149
|
+
def self::register_apps( a_hash )
|
150
|
+
a_hash.each do |name, path|
|
151
|
+
self.register_app( name, path )
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
### Return a Hash of apps discovered by loading #app_discovery_files.
|
157
|
+
def self::discovered_apps
|
158
|
+
unless @discovered_apps
|
159
|
+
@discovered_apps ||= {}
|
160
|
+
self.app_discovery_files.each do |path|
|
161
|
+
self.log.debug "Loading discovery file %p" % [ path ]
|
162
|
+
Kernel.load( path )
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
return @discovered_apps
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
### Return an Array of app discovery hook files found in the latest installed gems and
|
171
|
+
### the current $LOAD_PATH.
|
172
|
+
def self::app_discovery_files
|
173
|
+
return Gem.find_latest_files( self.app_discovery_file )
|
174
|
+
end
|
54
175
|
|
55
176
|
|
56
177
|
### Configure the App. Override this if you wish to add additional configuration
|
@@ -59,7 +180,7 @@ module Strelka::Discovery
|
|
59
180
|
def self::configure( config=nil )
|
60
181
|
config = self.defaults.merge( config || {} )
|
61
182
|
|
62
|
-
self.
|
183
|
+
self.app_discovery_file = config[:app_discovery_file]
|
63
184
|
self.local_data_dirs = config[:local_data_dirs]
|
64
185
|
end
|
65
186
|
|
@@ -92,103 +213,36 @@ module Strelka::Discovery
|
|
92
213
|
end
|
93
214
|
|
94
215
|
|
95
|
-
###
|
96
|
-
###
|
97
|
-
def self::
|
98
|
-
|
216
|
+
### Attempt to load the file associated with the specified +app_name+ and return
|
217
|
+
### the first Strelka::App class declared in the process.
|
218
|
+
def self::load( app_name )
|
219
|
+
apps = self.discovered_apps or return nil
|
220
|
+
file = apps[ app_name ] or return nil
|
99
221
|
|
100
|
-
self.
|
101
|
-
pattern = File.join( dir, self.app_glob_pattern )
|
102
|
-
appfiles[ gemname ] = Pathname.glob( pattern )
|
103
|
-
end
|
104
|
-
|
105
|
-
return appfiles
|
222
|
+
return self.load_file( file )
|
106
223
|
end
|
107
224
|
|
108
225
|
|
109
|
-
###
|
110
|
-
def self::
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
self.log.debug "Loading apps from %d discovered paths" % [ app_paths.length ]
|
115
|
-
app_paths.each do |gemname, paths|
|
116
|
-
self.log.debug " loading gem %s" % [ gemname ]
|
117
|
-
gem( gemname ) unless gemname == ''
|
118
|
-
|
119
|
-
self.log.debug " loading apps from %s: %d handlers" % [ gemname, paths.length ]
|
120
|
-
paths.each do |path|
|
121
|
-
classes = begin
|
122
|
-
self.load( path )
|
123
|
-
rescue StandardError, ScriptError => err
|
124
|
-
self.log.error "%p while loading Strelka apps from %s: %s" %
|
125
|
-
[ err.class, path, err.message ]
|
126
|
-
self.log.debug "Backtrace: %s" % [ err.backtrace.join("\n\t") ]
|
127
|
-
[]
|
128
|
-
end
|
129
|
-
self.log.debug " loaded app classes: %p" % [ classes ]
|
130
|
-
|
131
|
-
discovered_apps += classes
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
return discovered_apps
|
136
|
-
end
|
137
|
-
|
226
|
+
### Load the specified +file+ and return the first class that extends Strelka::Discovery.
|
227
|
+
def self::load_file( file )
|
228
|
+
self.log.debug "Loading application/s from %p" % [ file ]
|
229
|
+
Thread.current[ :__loading_file ] = loading_file = file
|
230
|
+
self.discovered_classes.delete( loading_file )
|
138
231
|
|
139
|
-
|
140
|
-
### the gem it's from. If the optional +gemname+ is given, only consider apps from that gem.
|
141
|
-
### Raises a RuntimeError if no app with the given +appname+ was found.
|
142
|
-
def self::find( appname, gemname=nil )
|
143
|
-
discovered_apps = self.discover_paths
|
144
|
-
|
145
|
-
path = nil
|
146
|
-
if gemname
|
147
|
-
discovered_apps[ gemname ].each do |apppath|
|
148
|
-
self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
|
149
|
-
if apppath.basename('.rb').to_s == appname
|
150
|
-
path = apppath
|
151
|
-
break
|
152
|
-
end
|
153
|
-
end
|
154
|
-
else
|
155
|
-
self.log.debug "No gem name; searching them all:"
|
156
|
-
discovered_apps.each do |disc_gemname, paths|
|
157
|
-
self.log.debug " %s: %d paths" % [ disc_gemname, paths.length ]
|
158
|
-
path = paths.find do |apppath|
|
159
|
-
self.log.debug " %s (%s)" % [ apppath, apppath.basename('.rb') ]
|
160
|
-
self.log.debug " %p vs. %p" % [ apppath.basename('.rb').to_s, appname ]
|
161
|
-
apppath.basename('.rb').to_s == appname
|
162
|
-
end or next
|
163
|
-
gemname = disc_gemname
|
164
|
-
break
|
165
|
-
end
|
166
|
-
end
|
232
|
+
Kernel.load( loading_file.to_s )
|
167
233
|
|
168
|
-
|
169
|
-
|
170
|
-
msg << " in the #{gemname} gem" if gemname
|
171
|
-
raise( msg )
|
172
|
-
end
|
173
|
-
self.log.debug " found: %s" % [ path ]
|
234
|
+
new_subclasses = self.discovered_classes[ loading_file ]
|
235
|
+
self.log.debug " loaded %d new app class/es" % [ new_subclasses.size ]
|
174
236
|
|
175
|
-
return
|
237
|
+
return new_subclasses.first
|
238
|
+
ensure
|
239
|
+
Thread.current[ :__loading_file ] = nil
|
176
240
|
end
|
177
241
|
|
178
242
|
|
179
|
-
###
|
180
|
-
|
181
|
-
|
182
|
-
self.log.debug "Loading application/s from %p" % [ file ]
|
183
|
-
@loading_file = Pathname( file ).expand_path
|
184
|
-
self.subclasses.delete( @loading_file )
|
185
|
-
Kernel.load( @loading_file.to_s )
|
186
|
-
new_subclasses = self.subclasses[ @loading_file ]
|
187
|
-
self.log.debug " loaded %d new app class/es" % [ new_subclasses.size ]
|
188
|
-
|
189
|
-
return new_subclasses
|
190
|
-
ensure
|
191
|
-
@loading_file = nil
|
243
|
+
### Return the Pathname of the file being loaded by the current thread (if there is one)
|
244
|
+
def self::loading_file
|
245
|
+
return Thread.current[ :__loading_file ]
|
192
246
|
end
|
193
247
|
|
194
248
|
|
@@ -196,7 +250,7 @@ module Strelka::Discovery
|
|
196
250
|
### with Discovery.
|
197
251
|
def self::add_inherited_class( subclass )
|
198
252
|
self.log.debug "Registering discovered subclass %p" % [ subclass ]
|
199
|
-
self.
|
253
|
+
self.discovered_classes[ self.loading_file ] << subclass
|
200
254
|
end
|
201
255
|
|
202
256
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../helpers'
|
5
|
+
|
6
|
+
require 'tempfile'
|
7
|
+
require 'rspec'
|
8
|
+
|
9
|
+
require 'strelka/cli'
|
10
|
+
|
11
|
+
describe Strelka::CLI do
|
12
|
+
|
13
|
+
before( :all ) do
|
14
|
+
testcommands = Module.new
|
15
|
+
testcommands.extend( Strelka::CLI::Subcommand )
|
16
|
+
testcommands.module_eval do
|
17
|
+
command :test_output do |cmd|
|
18
|
+
cmd.action do
|
19
|
+
prompt.say "Test command!"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
command :test_dryrun do |cmd|
|
24
|
+
cmd.action do
|
25
|
+
unless_dryrun( "Running the test." ) do
|
26
|
+
$stdout.puts "Ran it!"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
after( :each ) do
|
34
|
+
described_class.reset_prompt
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "output redirection" do
|
38
|
+
|
39
|
+
it "uses STDERR for user interaction" do
|
40
|
+
expect {
|
41
|
+
described_class.run([ 'test_output' ])
|
42
|
+
}.to output( /Test command!\n/ ).to_stderr
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it "redirects its output to STDOUT when run with `-o -`" do
|
47
|
+
expect {
|
48
|
+
described_class.run([ '-o', '-', 'test_output' ])
|
49
|
+
}.to output( /Test command!\n/ ).to_stdout
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "redirects its output to the named file when run with `-o filename`" do
|
54
|
+
tmpfile = Dir::Tmpname.create( 'strelka-command-fileout' ) { }
|
55
|
+
|
56
|
+
begin
|
57
|
+
described_class.run([ '-o', tmpfile, 'test_output' ])
|
58
|
+
expect( IO.read(tmpfile) ).to match( /Test command!\n/ )
|
59
|
+
ensure
|
60
|
+
File.unlink( tmpfile ) if tmpfile && File.exist?( tmpfile )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
describe "dry-run mode" do
|
68
|
+
|
69
|
+
it "executes the protected block if dry-run mode isn't enabled" do
|
70
|
+
expect {
|
71
|
+
described_class.run([ 'test_dryrun' ])
|
72
|
+
}.to output( /Ran it!/ ).to_stdout
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
it "doesn't execute the block if dry-run mode *is* enabled" do
|
77
|
+
expect {
|
78
|
+
described_class.run([ '-n', 'test_dryrun' ])
|
79
|
+
}.to_not output( /Ran it!/ ).to_stdout
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|