detroit 0.1.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/.ruby +45 -0
- data/COPYING.rdoc +19 -0
- data/EXAMPLE.md +188 -0
- data/GPL3.txt +675 -0
- data/HISTORY.rdoc +14 -0
- data/README.rdoc +139 -0
- data/bin/detroit +9 -0
- data/lib/detroit.rb +67 -0
- data/lib/detroit.yml +45 -0
- data/lib/detroit/application.rb +427 -0
- data/lib/detroit/assembly.rb +80 -0
- data/lib/detroit/config.rb +197 -0
- data/lib/detroit/control.rb +124 -0
- data/lib/detroit/core_ext.rb +139 -0
- data/lib/detroit/custom.rb +65 -0
- data/lib/detroit/dsl.rb +55 -0
- data/lib/detroit/schedule.rb +187 -0
- data/lib/detroit/service.rb +188 -0
- data/lib/detroit/standard_assembly.rb +52 -0
- data/lib/detroit/tool.rb +216 -0
- data/lib/detroit/tool/core_ext.rb +3 -0
- data/lib/detroit/tool/core_ext/facets.rb +11 -0
- data/lib/detroit/tool/core_ext/filetest.rb +29 -0
- data/lib/detroit/tool/core_ext/shell_extensions.rb +7 -0
- data/lib/detroit/tool/core_ext/to_actual_filename.rb +19 -0
- data/lib/detroit/tool/core_ext/to_console.rb +97 -0
- data/lib/detroit/tool/core_ext/to_list.rb +29 -0
- data/lib/detroit/tool/core_ext/to_yamlfrag.rb +9 -0
- data/lib/detroit/tool/core_ext/unfold_paragraphs.rb +27 -0
- data/lib/detroit/tool/email_utils.rb +288 -0
- data/lib/detroit/tool/project_utils.rb +41 -0
- data/lib/detroit/tool/shell_utils.rb +235 -0
- data/qed/01_schedule/02_initialize.md +57 -0
- data/qed/99_plugins/rdoc/rdoc-plugin.rdoc +22 -0
- data/qed/99_plugins/rdoc/sample/Syckfile +6 -0
- data/qed/99_plugins/rdoc/sample/lib/sandbox/.xxx +1 -0
- data/qed/99_plugins/rdoc/sample/lib/sandbox/hello.rb +5 -0
- data/qed/99_plugins/rdoc/sample/lib/sandbox/xxx.rb +6 -0
- data/qed/99_plugins/rdoc/sample/lib/xxx/bye.rb +4 -0
- data/qed/99_plugins/rdoc/sample/meta/name +1 -0
- data/qed/99_plugins/rdoc/sample/meta/version +1 -0
- data/qed/samples/example_project/.ruby +0 -0
- data/qed/samples/example_project/Schedule +9 -0
- data/qed/samples/example_project/lib/foo/.xxx +1 -0
- data/qed/samples/example_project/lib/foo/hello.rb +7 -0
- data/qed/samples/example_project/lib/foo/xxx.rb +6 -0
- data/qed/samples/example_project/lib/foo/xxx/bye.rb +4 -0
- data/qed/samples/example_project/meta/name +1 -0
- data/qed/samples/example_project/meta/version +1 -0
- data/qed/samples/example_schedule.rb +57 -0
- metadata +139 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
# Custom tool is used to create "quicky" services.
|
4
|
+
#
|
5
|
+
# This is a useful alternative to writing a full-blown plugin
|
6
|
+
# when the need is simple.
|
7
|
+
#
|
8
|
+
class Custom < Tool
|
9
|
+
|
10
|
+
# Default track(s) in which this plugin operates.
|
11
|
+
DEFAULT_TRACK = "main"
|
12
|
+
|
13
|
+
# Which track(s) to run this custom plugin.
|
14
|
+
attr_accessor :track
|
15
|
+
|
16
|
+
# Special writer to allow single track or a list of tracks.
|
17
|
+
def track=(val)
|
18
|
+
@track = val.to_list #[val].flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
# Plural alias for #track.
|
22
|
+
alias_accessor :tracks, :track
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Instantiate new custom plugin.
|
27
|
+
#
|
28
|
+
# FIXME: Custom#initialize seems to be running twice at startup. Why?
|
29
|
+
#
|
30
|
+
# This works by interpreting the service configuration as a hash of
|
31
|
+
# stop names to ruby code.
|
32
|
+
#
|
33
|
+
def initialize(options)
|
34
|
+
super(options)
|
35
|
+
options.each do |stop, script|
|
36
|
+
# skip specific names used for configuration
|
37
|
+
next if stop == 'service'
|
38
|
+
next if stop == 'tracks' or stop == 'track'
|
39
|
+
next if stop == 'active'
|
40
|
+
next if stop == 'priority'
|
41
|
+
# remaining options are names of track stops
|
42
|
+
#tracks.each do |t|
|
43
|
+
src = %{
|
44
|
+
def #{stop}
|
45
|
+
#{script}
|
46
|
+
end
|
47
|
+
}
|
48
|
+
(class << self; self; end).module_eval(src)
|
49
|
+
#end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set initial attribute defaults.
|
54
|
+
def initialize_defaults
|
55
|
+
@track = [DEFAULT_TRACK]
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
def method_missing(s, *a, &b)
|
60
|
+
super(s, *a, &b) if @context.respond_to?(s)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/detroit/dsl.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
# NOT YET IN USE.
|
4
|
+
module DSL
|
5
|
+
|
6
|
+
# Define a track.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# track :site do
|
11
|
+
# route :maintainence do
|
12
|
+
# stops :reset, :clean, :purge
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
def track(&block)
|
17
|
+
Track.new(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Define a service.
|
21
|
+
#
|
22
|
+
# Examples
|
23
|
+
#
|
24
|
+
# service :foo do
|
25
|
+
# reset do
|
26
|
+
# utime(0,0, project.path(:log) + 'foo.log')
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def service(name, &block)
|
31
|
+
Service.registry[name.to_s] ||= ServiceDSL.new(&block).service_class
|
32
|
+
end
|
33
|
+
|
34
|
+
# ServiceDSL is used to define services via the Detroit DSL.
|
35
|
+
class ServiceDSL < BasicObject
|
36
|
+
attr :service_class
|
37
|
+
|
38
|
+
def initialize(name, &block)
|
39
|
+
@service_class = Class.new(Detroit::Service)
|
40
|
+
end
|
41
|
+
|
42
|
+
def available(&block)
|
43
|
+
(class << @service_class; self; end).class_eval do
|
44
|
+
define_method(:available?, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(name, *args, &block)
|
49
|
+
@service_class.define_method(name, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
# Schedule encapsulates a `Schedule` file and it's service instance
|
4
|
+
# configurations.
|
5
|
+
class Schedule
|
6
|
+
|
7
|
+
# Load Scedule file.
|
8
|
+
def self.load(input)
|
9
|
+
new(input)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Hash table of services.
|
13
|
+
attr :services
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Initialize new Schedule instance.
|
18
|
+
def initialize(file, options={})
|
19
|
+
@project = options[:project]
|
20
|
+
|
21
|
+
@services = {}
|
22
|
+
|
23
|
+
@file = (String === file ? File.new(file) : file)
|
24
|
+
|
25
|
+
case File.extname(@file.path)
|
26
|
+
when '.rb'
|
27
|
+
instance_eval(@file.read, @file.path)
|
28
|
+
when '.yml', '.yaml'
|
29
|
+
@services = YAML.load(erb(@file.read))
|
30
|
+
else
|
31
|
+
text = @file.read
|
32
|
+
if /^---/ =~ text
|
33
|
+
@services = YAML.load(erb(text))
|
34
|
+
else
|
35
|
+
instance_eval(text, @file.path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Define a service.
|
41
|
+
def service(name, settings={}, &block)
|
42
|
+
if block
|
43
|
+
block_context = BlockContext.new(&block)
|
44
|
+
settings = block_context.settings
|
45
|
+
end
|
46
|
+
@services[name.to_s] = settings.rekey(&:to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Access to project data.
|
50
|
+
#
|
51
|
+
# NOTE: Thinking that the project should be relative
|
52
|
+
# to the Routine file itself, unless a `project` is passed
|
53
|
+
# in manually through the initializer. In the mean time,
|
54
|
+
# the project is just relative to the current working directory.
|
55
|
+
#
|
56
|
+
# TODO: Make configurable and use .ruby by default ?
|
57
|
+
def project
|
58
|
+
@project ||= POM::Project.find #(file_directory)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Capitalized service names called as methods
|
62
|
+
# can also define a service.
|
63
|
+
def method_missing(sym, *args, &block)
|
64
|
+
service_class = sym.to_s
|
65
|
+
case service_class
|
66
|
+
when /^[A-Z]/
|
67
|
+
if Hash === args.last
|
68
|
+
args.last[:service] = service_class
|
69
|
+
else
|
70
|
+
args << {:services=>service_class}
|
71
|
+
end
|
72
|
+
case args.first
|
73
|
+
when String, Symbol
|
74
|
+
name = args.first
|
75
|
+
else
|
76
|
+
name = service_class.to_s.downcase
|
77
|
+
end
|
78
|
+
service(name, *args, &block)
|
79
|
+
else
|
80
|
+
super(sym, *args, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Process Routine document via ERB.
|
87
|
+
def erb(text)
|
88
|
+
context = ERBContext.new(project)
|
89
|
+
ERB.new(text).result(context.__binding__)
|
90
|
+
end
|
91
|
+
|
92
|
+
# ERBContext provides the clean context to process a Routine
|
93
|
+
# as an ERB template.
|
94
|
+
class ERBContext
|
95
|
+
#
|
96
|
+
def initialize(project)
|
97
|
+
@project = project
|
98
|
+
end
|
99
|
+
|
100
|
+
# Access to a clean binding.
|
101
|
+
def __binding__
|
102
|
+
binding
|
103
|
+
end
|
104
|
+
|
105
|
+
# Provide access to project data.
|
106
|
+
def project
|
107
|
+
@project
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
def method_missing(name, *args)
|
112
|
+
if project.respond_to?(name)
|
113
|
+
project.__send__(name, *args)
|
114
|
+
elsif project.metadata.respond_to?(name)
|
115
|
+
project.metadata.__send__(name, *args)
|
116
|
+
else
|
117
|
+
super(name, *args)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
class BlockContext
|
124
|
+
#
|
125
|
+
attr :settings
|
126
|
+
|
127
|
+
#
|
128
|
+
def initialize(&block)
|
129
|
+
@settings = {}
|
130
|
+
if block.arity == 0
|
131
|
+
instance_eval(&block)
|
132
|
+
else
|
133
|
+
block.call(self)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
def set(name, value=nil, &block)
|
139
|
+
if block
|
140
|
+
block_context = BlockContext.new
|
141
|
+
block.call(block_context)
|
142
|
+
@settings[name.to_s] = block_context.settings
|
143
|
+
else
|
144
|
+
@settings[name.to_s] = value
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
def method_missing(symbol, value=nil, *args)
|
150
|
+
case name = symbol.to_s
|
151
|
+
when /=$/
|
152
|
+
@settings[name.chomp('=')] = value
|
153
|
+
else
|
154
|
+
super(symbol, value=nil, *args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
# NOTE: This is problematic, because a Scheudle file should really know from
|
162
|
+
# what file it was derived.
|
163
|
+
|
164
|
+
#
|
165
|
+
DOMAIN = "rubyworks.github.com/detroit,2011-05-27"
|
166
|
+
|
167
|
+
# TODO: If using Psych rather than Syck, then define a domain type.
|
168
|
+
|
169
|
+
#if defined?(Psych) #RUBY_VERSION >= '1.9'
|
170
|
+
# YAML::add_domain_type(DOMAIN, "schedule") do |type, hash|
|
171
|
+
# Schedule.load(hash)
|
172
|
+
# end
|
173
|
+
#else
|
174
|
+
YAML::add_builtin_type("schedule") do |type, value|
|
175
|
+
value
|
176
|
+
#case value
|
177
|
+
#when String
|
178
|
+
# Schedule.eval(value)
|
179
|
+
#when Hash
|
180
|
+
# Schedule.new(value)
|
181
|
+
#else
|
182
|
+
# raise "ERROR: Invalid Schedule"
|
183
|
+
#end
|
184
|
+
end
|
185
|
+
#end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
#
|
4
|
+
#def self.services
|
5
|
+
# @registry ||= {}
|
6
|
+
#end
|
7
|
+
|
8
|
+
# Service class wraps a Tool instance.
|
9
|
+
#
|
10
|
+
# TODO: change name ?
|
11
|
+
#
|
12
|
+
# TODO: Need to work on how to limit a service's tracks per-assembly.
|
13
|
+
class ServiceWrapper
|
14
|
+
attr :key
|
15
|
+
attr :tracks
|
16
|
+
attr :priority
|
17
|
+
attr :active
|
18
|
+
attr :service
|
19
|
+
#attr :options
|
20
|
+
|
21
|
+
# Set the priority. Priority determines the order which
|
22
|
+
# services on the same stop are run.
|
23
|
+
def priority=(integer)
|
24
|
+
@priority = integer.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set the tracks a service will be available on.
|
28
|
+
def tracks=(list)
|
29
|
+
@tracks = list.to_list
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
def active=(boolean)
|
34
|
+
@active = !!boolean
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create new ServiceWrapper.
|
38
|
+
def initialize(key, service_class, options)
|
39
|
+
@key = key
|
40
|
+
|
41
|
+
## set service defaults
|
42
|
+
@tracks = nil #service_class.tracks
|
43
|
+
@priority = 0
|
44
|
+
@active = true
|
45
|
+
|
46
|
+
self.active = options.delete('active') if !options['active'].nil?
|
47
|
+
self.tracks = options.delete('tracks') if options.key?('tracks')
|
48
|
+
self.priority = options.delete('priority') if options.key?('priority')
|
49
|
+
|
50
|
+
@service = service_class.new(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Does the service support the given stop.
|
54
|
+
def stop?(name)
|
55
|
+
@service.respond_to?(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Run the service stop procedure.
|
59
|
+
def invoke(name)
|
60
|
+
@service.__send__(name) # public_send
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
def inspect
|
65
|
+
"<#{self.class}:#{object_id} @key='#{key}'>"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
=begin
|
74
|
+
# Mixin module is added to Service and Tool.
|
75
|
+
module Serviceable
|
76
|
+
|
77
|
+
#
|
78
|
+
def self.included(base)
|
79
|
+
base.extend ClassRegistry
|
80
|
+
base.extend DomainLanguage
|
81
|
+
end
|
82
|
+
|
83
|
+
# Register new instance of the Service class.
|
84
|
+
module ClassRegistry
|
85
|
+
|
86
|
+
# Class-level attribute of registered Service subclasses.
|
87
|
+
#
|
88
|
+
# Returns a Hash.
|
89
|
+
def registry
|
90
|
+
Detroit.services
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: Probably should make a named registry instead.
|
94
|
+
def inherited(base)
|
95
|
+
return if base.name.to_s.empty?
|
96
|
+
if base.name !~ /Service$/
|
97
|
+
registry[base.basename.downcase] = base
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns a Class which is a new subclass of the current class.
|
102
|
+
def factory(&block)
|
103
|
+
Class.new(self, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
def options(service_class=self)
|
108
|
+
service_class.instance_methods.
|
109
|
+
select{ |m| m.to_s =~ /\w+=$/ && !%w{taguri=}.include?(m.to_s) }.
|
110
|
+
map{ |m| m.to_s.chomp('=') }
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# Service Domain language. This module extends the Service class,
|
116
|
+
# to provide a convenience interface for defining stops.
|
117
|
+
module DomainLanguage
|
118
|
+
## TODO: Err.. Is this being used?
|
119
|
+
#def init(&block)
|
120
|
+
# define_method(:init, &block)
|
121
|
+
#end
|
122
|
+
|
123
|
+
# Override the `tracks` method to limit the lines a service
|
124
|
+
# will work with by default. Generally this is not used,
|
125
|
+
# and a return value of +nil+ means all lines apply.
|
126
|
+
def tracks
|
127
|
+
end
|
128
|
+
|
129
|
+
# TODO: Perhaps deprecate this in favor of just defining an `availabe?`
|
130
|
+
# class method.
|
131
|
+
def available(&block)
|
132
|
+
@available = block if block
|
133
|
+
@available ||= nil
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
def available?(project)
|
138
|
+
return true unless available
|
139
|
+
@available.call(project)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
#attr_reader :service_name
|
144
|
+
|
145
|
+
#
|
146
|
+
def service_title
|
147
|
+
self.class.name
|
148
|
+
end
|
149
|
+
|
150
|
+
# TODO: Is this being used?
|
151
|
+
#def service_actions
|
152
|
+
# self.class.service_actions
|
153
|
+
#end
|
154
|
+
|
155
|
+
#
|
156
|
+
#def inspect
|
157
|
+
# "<#{self.class}:#{object_id}>"
|
158
|
+
#end
|
159
|
+
end
|
160
|
+
|
161
|
+
# The Service class is the base class for defining basic or delgated services.
|
162
|
+
class Service
|
163
|
+
include Serviceable
|
164
|
+
|
165
|
+
#
|
166
|
+
attr :options
|
167
|
+
|
168
|
+
def initialize(options={})
|
169
|
+
@options = options
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
end #module Detroit
|
175
|
+
|
176
|
+
# Provides a clean namespace for creating services.
|
177
|
+
module Detroit::Plugins
|
178
|
+
Service = Detroit::Service
|
179
|
+
Tool = Detroit::Tool
|
180
|
+
end
|
181
|
+
|
182
|
+
=end
|
183
|
+
|
184
|
+
# TOPLEVEL DSL?
|
185
|
+
#def service(name, &block)
|
186
|
+
# #Detroit.services[name] = Service.factory(&block)
|
187
|
+
# Detroit::Service.registry[name.to_s] = Detroit::Service.factory(&block)
|
188
|
+
#end
|