Module: Strelka::Discovery

Extended by:
Configurability, Loggability, MethodUtilities
Included in:
App, WebSocketServer
Defined in:
lib/strelka/discovery.rb

Overview

The Strelka application-discovery system.

This module provides a mechanism for registering Strelka apps and their resources for discovery by the strelka CLI and other systems.

It's responsible for three kinds of discovery:

  • Discovery of Strelka app files via Rubygems discovery
  • Discovery and loading of Strelka app classes by name
  • Discovery of data directories for strelka apps

As such it can be used in several different ways.

App File Discovery

If you have an app that you wish to be discoverable, create a lib/strelka/apps.rb file. This file will be added to those returned by the ::app_discovery_files call, which is the list loaded by ::discovered_apps.

App Discovery Registration

To add a name and file path to Strelka::Discovery.discovered_apps, you can call ::register_app. This will check to make sure no other apps are registered with the same name. To register several at the same time, call ::register_apps with a Hash of name => path pairs.

Loading Discovered Apps

To load a discovered app, call ::load with its registered name.

This will load the associated file and returns the first Ruby class to inherit from a discoverable app class like Strelka::App or Strelka::WebSocketServer.

Putting it all together

Say, for example, you were putting together an acme-apps gem for the Acme company that contained Strelka apps for a web store and a CMS. You could add a lib/strelka/apps.rb file to the acme-apps gem that contained the following:

# -*- ruby -*-
require 'strelka/discovery'

Strelka::Discovery.register_apps(
    'acme-store' => 'lib/acme/store.rb',
    'acme-cms' => 'lib/acme/cms.rb'
)

This would let you do:

$ gem install acme-apps
$ strelka start acme-store

Data Directory Discovery

If your app requires some filesystem resources, a good way to distribute these is in your gem's "data directory". This is a directory in your gem called data/«your gem name», and can be found via:

Gem.datadir( your_gem_name )

Strelka::Discoverable builds on top of this, and can return a Hash of glob patterns that will match the data directories of all gems that depend on Strelka, keyed by gem name. You can use this to populate search paths for templates, static assets, etc.

template_paths = Strelka::Discovery.discover_data_dirs.
   flat_map {|_, pattern| Dir.glob(pattern + '/templates') }

Making a class Discoverable

If you write your own app base class (e.g., Strelka::App, Strelka::WebSocketServer), you can make it discoverable by extending it with this module. You typically won't have to do this unless you're working on Strelka itself.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Class Method Details

.add_inherited_class(subclass) ⇒ Object

Register the given subclass as having inherited a class that has been extended with Discovery.



236
237
238
239
# File 'lib/strelka/discovery.rb', line 236

def self::add_inherited_class( subclass )
	self.log.debug "Registering discovered subclass %p" % [ subclass ]
	self.discovered_classes[ self.loading_file ] << subclass
end

.app_discovery_filesObject

Return an Array of app discovery hook files found in the latest installed gems and the current $LOAD_PATH.



168
169
170
# File 'lib/strelka/discovery.rb', line 168

def self::app_discovery_files
	return Gem.find_latest_files( self.app_discovery_file )
end

.discover_data_dirsObject

Return a Hash of glob patterns for matching data directories for the latest versions of all installed gems which have a dependency on Strelka, keyed by gem name.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/strelka/discovery.rb', line 176

def self::discover_data_dirs
	datadirs = {
		'' => self.local_data_dirs
	}

	# Find all the gems that depend on Strelka
	gems = Gem::Specification.latest_specs.find_all do |gemspec|
		gemspec.dependencies.find {|dep| dep.name == 'strelka'}
	end

	self.log.debug "Found %d gems with a Strelka dependency" % [ gems.length ]

	# Find all the files under those gems' data directories that match the application
	# pattern
	gems.sort.reverse.each do |gemspec|
		# Only look at the latest version of the gem
		next if datadirs.key?( gemspec.name )
		datadirs[ gemspec.name ] = File.join( gemspec.full_gem_path, "data", gemspec.name )
	end

	self.log.debug "  returning data directories: %p" % [ datadirs ]
	return datadirs
end

.discovered_appsObject

Return a Hash of apps discovered by loading #app_discovery_files.



153
154
155
156
157
158
159
160
161
162
163
# File 'lib/strelka/discovery.rb', line 153

def self::discovered_apps
	unless @discovered_apps
		@discovered_apps ||= {}
		self.app_discovery_files.each do |path|
			self.log.debug "Loading discovery file %p" % [ path ]
			Kernel.load( path )
		end
	end

	return @discovered_apps
end

.load(app_name) ⇒ Object

Attempt to load the file associated with the specified app_name and return the first Strelka::App class declared in the process.



203
204
205
206
207
208
# File 'lib/strelka/discovery.rb', line 203

def self::load( app_name )
	apps = self.discovered_apps or return nil
	file = apps[ app_name ] or return nil

	return self.load_file( file )
end

.load_file(file) ⇒ Object

Load the specified file and return the first class that extends Strelka::Discovery.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/strelka/discovery.rb', line 212

def self::load_file( file )
	self.log.debug "Loading application/s from %p" % [ file ]
	Thread.current[ :__loading_file ] = loading_file = file
	self.discovered_classes.delete( loading_file )

	Kernel.load( loading_file.to_s )

	new_subclasses = self.discovered_classes[ loading_file ]
	self.log.debug "  loaded %d new app class/es" % [ new_subclasses.size ]

	return new_subclasses.last
ensure
	Thread.current[ :__loading_file ] = nil
end

.loading_fileObject

Return the Pathname of the file being loaded by the current thread (if there is one)



229
230
231
# File 'lib/strelka/discovery.rb', line 229

def self::loading_file
	return Thread.current[ :__loading_file ]
end

.register_app(name, path) ⇒ Object

Register an app with the specified name that can be loaded from the given path.



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/strelka/discovery.rb', line 130

def self::register_app( name, path )
	@discovered_apps ||= {}

	if @discovered_apps.key?( name )
		warn "Can't register a second '%s' app at %s; already have one at %s" %
			[ name, path, @discovered_apps[name] ]
		return
	end

	self.log.debug "Registered app at %s as %p" % [ path, name ]
	@discovered_apps[ name ] = path
end

.register_apps(a_hash) ⇒ Object

Register multiple apps by passing a_hash of names and paths.



145
146
147
148
149
# File 'lib/strelka/discovery.rb', line 145

def self::register_apps( a_hash )
	a_hash.each do |name, path|
		self.register_app( name, path )
	end
end

Instance Method Details

#discovered_classesObject

The Hash of Strelka::App subclasses, keyed by the Pathname of the file they were loaded from, or nil if they weren't loaded via ::load.



114
# File 'lib/strelka/discovery.rb', line 114

singleton_attr_reader :discovered_classes

#inherited(subclass) ⇒ Object

Inheritance callback -- register the subclass with its parent for discovery.



243
244
245
246
247
# File 'lib/strelka/discovery.rb', line 243

def inherited( subclass )
	super
	Strelka::Discovery.log.info "%p inherited by discoverable class %p" % [ self, subclass ]
	Strelka::Discovery.add_inherited_class( subclass )
end

#loading_fileObject

The name of the file that's currently being loaded (if any)



118
# File 'lib/strelka/discovery.rb', line 118

singleton_attr_reader :loading_file