detroit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,52 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
# Standard assembly is the default. In the vast majority of
|
4
|
+
# cases this is all that will ever be used.
|
5
|
+
assembly :standard do
|
6
|
+
|
7
|
+
# Main track.
|
8
|
+
#
|
9
|
+
# TODO: Should :install comes before :verify b/c verfication might
|
10
|
+
# require a local installation?
|
11
|
+
track :main,
|
12
|
+
:prepare, # prepare services / ensure service requirements
|
13
|
+
:generate, # code generation
|
14
|
+
:compile, # compile source code
|
15
|
+
:test, # run tests and specifications
|
16
|
+
:analyze, # perform code analysis
|
17
|
+
:document, # generate documentation
|
18
|
+
:package, # create packages
|
19
|
+
:verify, # post package verification / integration tests
|
20
|
+
:install, # install the package locally (if need be)
|
21
|
+
:publish, # publish website/documentation
|
22
|
+
:release, # release packages
|
23
|
+
:deploy, # deploy system to servers
|
24
|
+
:promote # tell the world about your awesome work
|
25
|
+
|
26
|
+
# The site track is a subset of the main track used to
|
27
|
+
# isolate the distribution of documentation and uploading
|
28
|
+
# a project's website.
|
29
|
+
#
|
30
|
+
# prepare -> generate -> analyze -> document -> publish
|
31
|
+
#
|
32
|
+
track :site,
|
33
|
+
:prepare,
|
34
|
+
:generate,
|
35
|
+
:analyze,
|
36
|
+
:document,
|
37
|
+
:publish
|
38
|
+
|
39
|
+
# The attention track is a small subset of main track, used to
|
40
|
+
# isolate the sending of promotional materials, mainly release
|
41
|
+
# announcements.
|
42
|
+
#
|
43
|
+
# prepare -> generate -> promote
|
44
|
+
#
|
45
|
+
track :attn,
|
46
|
+
:prepare,
|
47
|
+
:generate,
|
48
|
+
:promote
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/detroit/tool.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
module Detroit
|
2
|
+
|
3
|
+
require 'detroit/tool/core_ext'
|
4
|
+
require 'detroit/tool/shell_utils'
|
5
|
+
require 'detroit/tool/project_utils'
|
6
|
+
require 'detroit/tool/email_utils'
|
7
|
+
|
8
|
+
# The Toold module provide an isolated namespace for
|
9
|
+
# Detoit's tools. This allows for general use of these
|
10
|
+
# tools by other applications, by including them into
|
11
|
+
# their own namespace.
|
12
|
+
module Tools
|
13
|
+
end
|
14
|
+
|
15
|
+
# Tool registry.
|
16
|
+
def self.tools
|
17
|
+
@tools ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
def self.services
|
22
|
+
tools
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add tool class to registry. If class name ends in `Tool`
|
26
|
+
# it will be considered a reusable base class and not be added.
|
27
|
+
def self.register_tool(tool_class)
|
28
|
+
name = tool_class.basename
|
29
|
+
return if name.empty?
|
30
|
+
return if name =~ /(Tool|Service)$/
|
31
|
+
tools[name.downcase] = tool_class
|
32
|
+
Tools.const_set(name, tool_class)
|
33
|
+
# TODO: Should we auto-create convenience method?
|
34
|
+
return tool_class
|
35
|
+
end
|
36
|
+
|
37
|
+
# This base class can be used for tools that do not need
|
38
|
+
# all of the utility methods provided by the regular Tool
|
39
|
+
# class.
|
40
|
+
class BasicTool
|
41
|
+
# Add an assembly to which the tool applies.
|
42
|
+
# By default the `standard` assembly is implied.
|
43
|
+
def self.assembly(assembly=nil)
|
44
|
+
@assembly ||= []
|
45
|
+
if assembly
|
46
|
+
@assembly << assembly.to_sym
|
47
|
+
@assembly.uniq!
|
48
|
+
end
|
49
|
+
@assembly
|
50
|
+
end
|
51
|
+
|
52
|
+
# Override the `tracks` method to limit the lines a service
|
53
|
+
# will work with by default. Generally this is not used,
|
54
|
+
# and a return value of +nil+ means all lines apply.
|
55
|
+
#--
|
56
|
+
# TODO: Rename to #lines ?
|
57
|
+
#++
|
58
|
+
def self.tracks
|
59
|
+
end
|
60
|
+
|
61
|
+
# Override this method if the tools availability is conditional.
|
62
|
+
def self.available?
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns list of writer method names.
|
67
|
+
def self.options(service_class=self)
|
68
|
+
service_class.instance_methods.
|
69
|
+
select{ |m| m.to_s =~ /\w+=$/ && !%w{taguri=}.include?(m.to_s) }.
|
70
|
+
map{ |m| m.to_s.chomp('=') }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a Class which is a new subclass of the current class.
|
74
|
+
def self.factory(&block)
|
75
|
+
Class.new(self, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# When inherited, add class to tool registry.
|
79
|
+
def self.inherited(base)
|
80
|
+
Detroit.register_tool(base)
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO: Needed? Rename?
|
84
|
+
def service_title
|
85
|
+
self.class.name
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Tool is the base class for all Detroit tools.
|
90
|
+
#
|
91
|
+
# Tool class is essentially the same as a Service class except that it is
|
92
|
+
# a subclass of RedTools::Tool. Use this class to build Detroit services
|
93
|
+
# with all the conveniences of a RedTools::Tool.
|
94
|
+
class Tool < BasicTool
|
95
|
+
include ShellUtils
|
96
|
+
include ProjectUtils
|
97
|
+
include EmailUtils
|
98
|
+
|
99
|
+
public
|
100
|
+
|
101
|
+
#
|
102
|
+
attr :options
|
103
|
+
|
104
|
+
# If applicable tools should override #current to allow tool users
|
105
|
+
# to know if the tool needs to be used. For example the RDoc tool
|
106
|
+
# can look to see if any the files it would document are newer that
|
107
|
+
# the previous generated set of docs.
|
108
|
+
#
|
109
|
+
# The method can return a String instead of `true`, to convey a
|
110
|
+
# custom message explaining that the tool need not be run. For example,
|
111
|
+
# the RDoc tool returns "RDocs are current (path/to/rdocs)".
|
112
|
+
def current?
|
113
|
+
false
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Create a new tool object.
|
119
|
+
#
|
120
|
+
# This sets up utility extensions and assigns options to setter attributes
|
121
|
+
# if they exist and values are not nil. That last point is important.
|
122
|
+
# You must use 'false' to purposely negate an option, as +nil+ will instead
|
123
|
+
# allow any default setting to be used.
|
124
|
+
#
|
125
|
+
def initialize(options={})
|
126
|
+
initialize_extension_defaults
|
127
|
+
|
128
|
+
initialize_requires
|
129
|
+
initialize_defaults
|
130
|
+
|
131
|
+
initialize_options(options)
|
132
|
+
|
133
|
+
initialize_extensions
|
134
|
+
end
|
135
|
+
|
136
|
+
# TODO: It would be best if an error were raised if an option is not
|
137
|
+
# supported, however for now only a warning will be issued, b/c of
|
138
|
+
# subclassing makes things more complicated.
|
139
|
+
def initialize_options(options)
|
140
|
+
@options = options
|
141
|
+
|
142
|
+
options.each do |k, v|
|
143
|
+
#send("#{k}=", v) unless v.nil? #if respond_to?("#{k}=") && !v.nil?
|
144
|
+
if respond_to?("#{k}=")
|
145
|
+
send("#{k}=", v) unless v.nil? #if respond_to?("#{k}=") && !v.nil?
|
146
|
+
else
|
147
|
+
warn "#{self.class.name} does not respond to `#{k}`."
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
def initialize_extension_defaults
|
154
|
+
super if defined?(super)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Require support libraries needed by this service.
|
158
|
+
#
|
159
|
+
# def initialize_requires
|
160
|
+
# require 'ostruct'
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
def initialize_requires
|
164
|
+
end
|
165
|
+
|
166
|
+
# When subclassing, put default instance variable settngs here.
|
167
|
+
#
|
168
|
+
# Examples
|
169
|
+
#
|
170
|
+
# def initialize_defaults
|
171
|
+
# @gravy = true
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
def initialize_defaults
|
175
|
+
end
|
176
|
+
|
177
|
+
# --- Odd Utilities -------------------------------------------------------
|
178
|
+
|
179
|
+
require 'facets/platform'
|
180
|
+
|
181
|
+
# Current platform.
|
182
|
+
def current_platform
|
183
|
+
Platform.local.to_s
|
184
|
+
end
|
185
|
+
|
186
|
+
# TODO: Is naming_policy really useful?
|
187
|
+
# TODO: How to set this in a more universal manner?
|
188
|
+
#
|
189
|
+
def naming_policy(*policies)
|
190
|
+
if policies.empty?
|
191
|
+
@naming_policy ||= ['down', 'ext']
|
192
|
+
else
|
193
|
+
@naming_policy = policies
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
#
|
199
|
+
def apply_naming_policy(name, ext)
|
200
|
+
naming_policy.each do |policy|
|
201
|
+
case policy.to_s
|
202
|
+
when /^low/, /^down/
|
203
|
+
name = name.downcase
|
204
|
+
when /^up/
|
205
|
+
name = name.upcase
|
206
|
+
when /^cap/
|
207
|
+
name = name.capitalize
|
208
|
+
when /^ext/
|
209
|
+
name = name + ".#{ext}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
name
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# I know some people will be deterred by the dependency on Facets b/c they
|
2
|
+
# see it as a "heavy" dependency. But really that is far from true, consider
|
3
|
+
# the facet that the following libs are all that it used.
|
4
|
+
|
5
|
+
require 'facets/array/not_empty'
|
6
|
+
require 'facets/module/basename'
|
7
|
+
require 'facets/module/alias_accessor'
|
8
|
+
require 'facets/kernel/yes' # pulls in #ask and #no? too.
|
9
|
+
require 'facets/kernel/silence' # FIXME ???
|
10
|
+
require 'facets/kernel/disable_warnings'
|
11
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module FileTest
|
2
|
+
|
3
|
+
# Return a cached list of the PATH environment variable.
|
4
|
+
# This is a support method used by #bin?
|
5
|
+
def command_paths
|
6
|
+
@command_paths ||= ENV['PATH'].split(/[:;]/)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Is a file a bin/ executable?
|
10
|
+
#
|
11
|
+
# TODO: Make more robust. Probably needs to be fixed for Windows.
|
12
|
+
def bin?(fname)
|
13
|
+
is_bin = command_paths.any? do |f|
|
14
|
+
FileTest.exist?(File.join(f, fname))
|
15
|
+
end
|
16
|
+
#is_bin ? File.basename(fname) : false
|
17
|
+
is_bin ? fname : false
|
18
|
+
end
|
19
|
+
|
20
|
+
## Is a file a task?
|
21
|
+
#
|
22
|
+
#def task?(path)
|
23
|
+
# task = File.dirname($0) + "/#{path}"
|
24
|
+
# task.chomp!('!')
|
25
|
+
# task if FileTest.file?(task) && FileTest.executable?(task)
|
26
|
+
#end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# Find actual filename (casefolding) and returns it.
|
4
|
+
# Returns nil if no file is found.
|
5
|
+
|
6
|
+
def to_actual_filename
|
7
|
+
Dir.glob(self, File::FNM_CASEFOLD).first
|
8
|
+
end
|
9
|
+
|
10
|
+
# Find actual filename (casefolding) and replace string with it.
|
11
|
+
# If file not found, string remains the same and method returns nil.
|
12
|
+
|
13
|
+
def to_actual_filename!
|
14
|
+
filename = to_actual_filename
|
15
|
+
replace(filename) if filename
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# TODO: Improve the naming scheme of these methods.
|
2
|
+
|
3
|
+
#
|
4
|
+
class Array #:nodoc:
|
5
|
+
|
6
|
+
# Convert an array into commandline parameters.
|
7
|
+
# The array is accepted in the format of Ruby
|
8
|
+
# method arguments --ie. [arg1, arg2, ..., hash]
|
9
|
+
|
10
|
+
def to_console
|
11
|
+
#flags = (Hash===last ? pop : {})
|
12
|
+
#flags = flags.to_console
|
13
|
+
#flags + ' ' + join(" ")
|
14
|
+
to_argv.join(' ')
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: DEPRECATE
|
18
|
+
alias_method :to_params, :to_console
|
19
|
+
|
20
|
+
#
|
21
|
+
def to_argv
|
22
|
+
flags = (Hash===last ? pop : {})
|
23
|
+
flags = flags.to_argv
|
24
|
+
flags + self
|
25
|
+
end
|
26
|
+
|
27
|
+
# def to_console
|
28
|
+
# flags = (Hash===last ? pop : {})
|
29
|
+
# flags = flags.collect do |f,v|
|
30
|
+
# m = f.to_s.size == 1 ? '-' : '--'
|
31
|
+
# case v
|
32
|
+
# when Array
|
33
|
+
# v.collect{ |e| "#{m}#{f} '#{e}'" }.join(' ')
|
34
|
+
# when true
|
35
|
+
# "#{m}#{f}"
|
36
|
+
# when false, nil
|
37
|
+
# ''
|
38
|
+
# else
|
39
|
+
# "#{m}#{f} '#{v}'"
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# return (flags + self).join(" ")
|
43
|
+
# end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class Hash
|
48
|
+
|
49
|
+
# Convert a Hash into command line arguments.
|
50
|
+
# The array is accepted in the format of Ruby
|
51
|
+
# method arguments --ie. [arg1, arg2, ..., hash]
|
52
|
+
def to_console
|
53
|
+
to_argv.join(' ')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Convert a Hash into command line parameters.
|
57
|
+
# The array is accepted in the format of Ruby
|
58
|
+
# method arguments --ie. [arg1, arg2, ..., hash]
|
59
|
+
def to_argv
|
60
|
+
flags = []
|
61
|
+
each do |f,v|
|
62
|
+
m = f.to_s.size == 1 ? '-' : '--'
|
63
|
+
case v
|
64
|
+
when Array
|
65
|
+
v.each{ |e| flags << "#{m}#{f}='#{e}'" }
|
66
|
+
when true
|
67
|
+
flags << "#{m}#{f}"
|
68
|
+
when false, nil
|
69
|
+
# nothing
|
70
|
+
else
|
71
|
+
flags << "#{m}#{f}='#{v}'"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
flags
|
75
|
+
end
|
76
|
+
|
77
|
+
# Turn a hash into arguments.
|
78
|
+
#
|
79
|
+
# h = { :list => [1,2], :base => "HI" }
|
80
|
+
# h.argumentize #=> [ [], { :list => [1,2], :base => "HI" } ]
|
81
|
+
# h.argumentize(:list) #=> [ [1,2], { :base => "HI" } ]
|
82
|
+
#
|
83
|
+
def argumentize(args_field=nil)
|
84
|
+
config = dup
|
85
|
+
if args_field
|
86
|
+
args = [config.delete(args_field)].flatten.compact
|
87
|
+
else
|
88
|
+
args = []
|
89
|
+
end
|
90
|
+
args << config
|
91
|
+
return args
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :command_vector, :argumentize
|
95
|
+
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
def to_list
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
class NilClass
|
10
|
+
|
11
|
+
def to_list
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class String
|
18
|
+
|
19
|
+
# Helper method for cleaning list options.
|
20
|
+
# This will split the option on ':' or ';'
|
21
|
+
# if it is a string, rather than an array.
|
22
|
+
# And it will make sure there are no nil elements.
|
23
|
+
|
24
|
+
def to_list
|
25
|
+
split(/[:;,\n]/)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|