detroit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.ruby +45 -0
  2. data/COPYING.rdoc +19 -0
  3. data/EXAMPLE.md +188 -0
  4. data/GPL3.txt +675 -0
  5. data/HISTORY.rdoc +14 -0
  6. data/README.rdoc +139 -0
  7. data/bin/detroit +9 -0
  8. data/lib/detroit.rb +67 -0
  9. data/lib/detroit.yml +45 -0
  10. data/lib/detroit/application.rb +427 -0
  11. data/lib/detroit/assembly.rb +80 -0
  12. data/lib/detroit/config.rb +197 -0
  13. data/lib/detroit/control.rb +124 -0
  14. data/lib/detroit/core_ext.rb +139 -0
  15. data/lib/detroit/custom.rb +65 -0
  16. data/lib/detroit/dsl.rb +55 -0
  17. data/lib/detroit/schedule.rb +187 -0
  18. data/lib/detroit/service.rb +188 -0
  19. data/lib/detroit/standard_assembly.rb +52 -0
  20. data/lib/detroit/tool.rb +216 -0
  21. data/lib/detroit/tool/core_ext.rb +3 -0
  22. data/lib/detroit/tool/core_ext/facets.rb +11 -0
  23. data/lib/detroit/tool/core_ext/filetest.rb +29 -0
  24. data/lib/detroit/tool/core_ext/shell_extensions.rb +7 -0
  25. data/lib/detroit/tool/core_ext/to_actual_filename.rb +19 -0
  26. data/lib/detroit/tool/core_ext/to_console.rb +97 -0
  27. data/lib/detroit/tool/core_ext/to_list.rb +29 -0
  28. data/lib/detroit/tool/core_ext/to_yamlfrag.rb +9 -0
  29. data/lib/detroit/tool/core_ext/unfold_paragraphs.rb +27 -0
  30. data/lib/detroit/tool/email_utils.rb +288 -0
  31. data/lib/detroit/tool/project_utils.rb +41 -0
  32. data/lib/detroit/tool/shell_utils.rb +235 -0
  33. data/qed/01_schedule/02_initialize.md +57 -0
  34. data/qed/99_plugins/rdoc/rdoc-plugin.rdoc +22 -0
  35. data/qed/99_plugins/rdoc/sample/Syckfile +6 -0
  36. data/qed/99_plugins/rdoc/sample/lib/sandbox/.xxx +1 -0
  37. data/qed/99_plugins/rdoc/sample/lib/sandbox/hello.rb +5 -0
  38. data/qed/99_plugins/rdoc/sample/lib/sandbox/xxx.rb +6 -0
  39. data/qed/99_plugins/rdoc/sample/lib/xxx/bye.rb +4 -0
  40. data/qed/99_plugins/rdoc/sample/meta/name +1 -0
  41. data/qed/99_plugins/rdoc/sample/meta/version +1 -0
  42. data/qed/samples/example_project/.ruby +0 -0
  43. data/qed/samples/example_project/Schedule +9 -0
  44. data/qed/samples/example_project/lib/foo/.xxx +1 -0
  45. data/qed/samples/example_project/lib/foo/hello.rb +7 -0
  46. data/qed/samples/example_project/lib/foo/xxx.rb +6 -0
  47. data/qed/samples/example_project/lib/foo/xxx/bye.rb +4 -0
  48. data/qed/samples/example_project/meta/name +1 -0
  49. data/qed/samples/example_project/meta/version +1 -0
  50. data/qed/samples/example_schedule.rb +57 -0
  51. metadata +139 -0
@@ -0,0 +1,80 @@
1
+ module Detroit
2
+
3
+ # All assemblies and tracks have a maintenance sub-track.
4
+ # For this reason stop names `reset`, `clean` and `purge`
5
+ # are reserved names and MUST not be used as stop names
6
+ # in defining custom lines.
7
+ MAINTENANCE_TRACK = [:reset, :clean, :purge]
8
+
9
+ # Returns Hash of name and Circuit instance pairs.
10
+ def self.assemblies
11
+ @assemblies ||= {}
12
+ end
13
+
14
+ # Define a new assembly.
15
+ def self.assembly(name, &block)
16
+ assemblies[name.to_sym] = Assembly.new(name, &block)
17
+ end
18
+
19
+ # The Assembly class encapsulates an *assembly system* which consists of
20
+ # a set of interrelated assembly lines, or tracks.
21
+ class Assembly
22
+
23
+ # Name of the assembly system.
24
+ attr :name
25
+
26
+ # Returns a Hash of track names mapped to list of stops.
27
+ attr :lines
28
+
29
+ # Lines are also called `tracks`.
30
+ alias_method :tracks, :lines
31
+
32
+ # Create a new instance.
33
+ def initialize(name, &block)
34
+ @name = name.to_sym
35
+ @lines = {:maintenance => MAINTENANCE_TRACK}
36
+ instance_eval(&block) if block
37
+ end
38
+
39
+ # Define an assembly line.
40
+ def line(name, *stops)
41
+ if stops.empty?
42
+ @lines[name.to_sym]
43
+ else
44
+ @lines[name.to_sym] = stops.map{ |s| s.to_sym }
45
+ end
46
+ end
47
+
48
+ # Lines are also called tracks.
49
+ alias_method :track, :line
50
+
51
+ # Lookup track by name and (optional) stop. If the stop belongs
52
+ # to the maintenance sub-track then the maintenance sub-track will
53
+ # be returned instead of the track itself.
54
+ #
55
+ # The Application class uses this to simplify track lookup.
56
+ def get_track(name, stop=nil)
57
+ name = name.to_sym
58
+ if stop
59
+ stop = stop.to_sym
60
+ if MAINTENANCE_TRACK.include?(stop.to_sym)
61
+ track = MAINTENANCE_TRACK
62
+ else
63
+ track = tracks[name]
64
+ raise "Unknown track `#{name}'." unless track
65
+ unless track.include?(stop)
66
+ raise "Unknown stop `#{stop}` for track `#{name}'."
67
+ end
68
+ end
69
+ else
70
+ track = tracks[name]
71
+ end
72
+ track
73
+ end
74
+
75
+ # Did I mention that `line` and `track` are synonyms?
76
+ alias_method :get_line, :get_track
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,197 @@
1
+ module Detroit
2
+
3
+ # Detroit configuration. Configuration comes from a main +Routine+
4
+ # and/or +.routine+ files.
5
+ class Config
6
+ #instance_methods.each{ |m| private m unless /^__/ =~ m.to_s }
7
+
8
+ # Configuration directory name (most likely a hidden "dot" directory).
9
+ DIRECTORY = "detroit"
10
+
11
+ # File identifier used to find a project's Schedule(s).
12
+ FILE_EXTENSION = "schedule"
13
+
14
+ # Current POM::Project object.
15
+ #attr :project
16
+
17
+ # The list of a project's routine files.
18
+ #
19
+ # @return [Array<String>] routine files
20
+ attr :schedules
21
+
22
+ # Service configurations from Schedule or *.schedule files.
23
+ #
24
+ # @return [Hash] service settings
25
+ attr :services
26
+
27
+ # Service defaults. This is a mapping of service names to
28
+ # default settings. Very useful for when using the same
29
+ # service more than once.
30
+ #
31
+ # @return [Hash] default settings
32
+ attr :defaults
33
+
34
+ #
35
+ def initialize(schedule_files=nil)
36
+ if schedule_files && !schedule_files.empty?
37
+ @schedule_filenames = schedule_files
38
+ else
39
+ @schedule_filenames = nil
40
+ end
41
+
42
+ @schedules = {}
43
+ @services = {}
44
+ @defaults = {}
45
+
46
+ @loaded_plugins = {}
47
+
48
+ load_plugins
49
+ load_defaults
50
+ load_schedules
51
+ end
52
+
53
+ #--
54
+ # TODO: Use this, or pass in via initialize?
55
+ #++
56
+ def project
57
+ Detroit.project
58
+ end
59
+
60
+ # Load a plugin.
61
+ def load_plugin(name)
62
+ @loaded_plugins[name] ||= (
63
+ require "detroit-#{name}"
64
+ name
65
+ )
66
+ end
67
+
68
+ # Pre-load plugins using `.detroit/plugins.rb`.
69
+ def load_plugins
70
+ if file = project.root.glob('{.,}#{DIRECTORY}/plugins{,.rb}').first
71
+ require file
72
+ else
73
+ self.defaults = {}
74
+ end
75
+ end
76
+
77
+ # Load defaults from `.detroit/defaults.yml`.
78
+ def load_defaults
79
+ if file = project.root.glob('{.,}#{DIRECTORY}/defaults{,.yml,.yaml}').first
80
+ self.defaults = YAML.load(File.new(file))
81
+ else
82
+ self.defaults = {}
83
+ end
84
+ end
85
+
86
+ #
87
+ def load_schedules
88
+ schedule_filenames.each do |file|
89
+ load_schedule_file(file)
90
+ end
91
+ end
92
+
93
+ #
94
+ def load_schedule_file(file)
95
+ @schedules[file] = Schedule.load(File.new(file))
96
+ @services.merge!(schedules[file].services)
97
+ end
98
+
99
+ # Set defaults.
100
+ def defaults=(hash)
101
+ @defaults = hash.to_h
102
+ end
103
+
104
+ # If a `Schedule` or `.schedule` file exists, then it is returned. Otherwise
105
+ # all `*.schedule` files are loaded. To load `*.schedule` files from another
106
+ # directory add the directory to config options file.
107
+ def schedule_filenames
108
+ @schedule_filenames ||= (
109
+ files = []
110
+ ## match 'Schedule' or '.schedule' file
111
+ files = project.root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
112
+ ## only files
113
+ files = files.select{ |f| File.file?(f) }
114
+ ##
115
+ if files.empty?
116
+ ## match '.detroit/*.schedule' or 'detroit/*.schedule'
117
+ files += project.root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
118
+ ## match 'task/*.schedule' (OLD SCHOOL)
119
+ files += project.root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
120
+ ## only files
121
+ files = files.select{ |f| File.file?(f) }
122
+ end
123
+ files
124
+ )
125
+ end
126
+
127
+ #
128
+ def each(&block)
129
+ services.each(&block)
130
+ end
131
+
132
+ #
133
+ def size
134
+ services.size
135
+ end
136
+
137
+ =begin
138
+ # If using a `Routine` file and want to import antoher file then use
139
+ # `import:` entry.
140
+ def load_detroit_file(file)
141
+ #@dir = File.dirname(file)
142
+
143
+ schedules[file] =
144
+
145
+ # TODO: can we just read the first line of the file and go from there?
146
+ #text = File.read(file).strip
147
+
148
+ ## if yaml vs. ruby file
149
+ #if (/\A---/ =~ text || /\.(yml|yaml)$/ =~ File.extname(file))
150
+ # #data = parse_detroit_file_yaml(text, file)
151
+ # YAML.load(text)
152
+ #else
153
+ # data = parse_detroit_file_ruby(text, file)
154
+ #end
155
+
156
+ ## extract defaults
157
+ #if defaults = data.delete('defaults')
158
+ # @defaults.merge!(defaults)
159
+ #end
160
+
161
+ ## import other files
162
+ #if import = data.delete('import')
163
+ # [import].flatten.each do |glob|
164
+ # routine(glob)
165
+ # end
166
+ #end
167
+
168
+ ## require plugins
169
+ #if plugins = data.delete('plugins')
170
+ # [plugins].flatten.each do |file|
171
+ # require file
172
+ # end
173
+ #end
174
+
175
+ #@services.update(data)
176
+ end
177
+ =end
178
+
179
+ ## Parse a YAML-based routine.
180
+ #def parse_detroit_file_yaml(text, file)
181
+ # YAMLParser.parse(self, text, file)
182
+ #end
183
+
184
+ ## Parse a Ruby-based routine.
185
+ #def parse_detroit_file_ruby(text, file)
186
+ # RubyParser.parse(self, text, file)
187
+ #end
188
+
189
+ ## TODO: Should the +dir+ be relative to the file or project.root?
190
+ #def routine(glob)
191
+ # pattern = File.join(@dir, glob)
192
+ # Dir[pattern].each{ |f| load_detroit_file(f) }
193
+ #end
194
+
195
+ end
196
+
197
+ end
@@ -0,0 +1,124 @@
1
+ module Detroit
2
+
3
+ # The control module is a function module that extends
4
+ # the toplevel Detroit namespace module.
5
+ module Control
6
+
7
+ # Location of standard plugins.
8
+ #PLUGIN_DIRECTORY = File.dirname(__FILE__) + '/plugins'
9
+
10
+ # Returns Array of standard plugin file names.
11
+ #def standard_plugins
12
+ # Dir[PLUGIN_DIRECTORY + '/*.rb']
13
+ #end
14
+
15
+ # Universal acccess to the current project.
16
+ #
17
+ # TODO: Is Control#project being used?
18
+ def project
19
+ @project ||= POM::Project.find
20
+ end
21
+
22
+ # Returns Application given options.
23
+ def application(options={})
24
+ Application.new(options)
25
+ end
26
+
27
+ # Run the command line interface.
28
+ def cli(*argv)
29
+ cli_options = {
30
+ :schedules => [],
31
+ :trace=>nil, :trial=>nil, :debug=>nil, :quiet=>nil, :verbose=>nil,
32
+ :force=>nil, :multitask=>nil, :skip=>[]
33
+ }
34
+
35
+ cli_usage(cli_options).parse!(argv)
36
+
37
+ #if /\.schedule$/ =~ argv[0]
38
+ # job = argv[1]
39
+ # begin
40
+ # application(cli_options).runscript(argv[0], job)
41
+ # rescue => error
42
+ # $stderr.puts error.message
43
+ # exit -1
44
+ # end
45
+ #else
46
+ begin
47
+ application(cli_options).start(*argv)
48
+ rescue => error
49
+ if $DEBUG
50
+ raise error
51
+ else
52
+ $stderr.puts error.message
53
+ exit -1
54
+ end
55
+ end
56
+ #end
57
+ end
58
+
59
+ # Returns an instance of OptionParser.
60
+ def cli_usage(options)
61
+ @usage ||= (
62
+ OptionParser.new do |usage|
63
+ usage.banner = "Usage: detroit [<track>:]<stop> [options]"
64
+ usage.on('-m', '--multitask', "Run work elements in parallel.") do
65
+ options[:multitask] = true
66
+ end
67
+ usage.on('-S', '--skip [SERVICE]', 'Skip a service.') do |skip|
68
+ options[:skip] << skip
69
+ end
70
+
71
+ usage.on('-a', '--assembly=NAME', "Select assembly. Default is `standard'.") do |assembly|
72
+ options[:assembly] = assembly
73
+ end
74
+ usage.on('-s', '--schedule [FILE]', 'Use specific schedule file(s).') do |file|
75
+ options[:schedules] << file
76
+ end
77
+
78
+ usage.on('-F', '--force', "Force operations.") do
79
+ options[:force] = true
80
+ end
81
+ usage.on('--trace', "Run in TRACE mode.") do
82
+ #$TRACE = true
83
+ options[:trace] = true
84
+ end
85
+ usage.on('--trial', "Run in TRIAL mode (no disk writes).") do
86
+ #$TRIAL = true
87
+ options[:trial] = true
88
+ end
89
+ # TODO: do we really need verbose?
90
+ usage.on('--verbose', "Provided extra output.") do
91
+ options[:verbose] = true
92
+ end
93
+ usage.on('-q', '--quiet', "Run silently.") do
94
+ options[:quiet] = true
95
+ end
96
+
97
+ usage.on('-I=PATH', "Add directory to $LOAD_PATH") do |dirs|
98
+ dirs.to_list.each do |dir|
99
+ $LOAD_PATH.unshift(dir)
100
+ end
101
+ end
102
+ usage.on('--debug', "Run with $DEBUG set to true.") do
103
+ $DEBUG = true
104
+ options[:debug] = true # DEPRECATE?
105
+ end
106
+ usage.on('--warn', "Run with $VERBOSE set to true.") do
107
+ $VERBOSE = true # wish this were called $WARN
108
+ end
109
+ usage.on_tail('--help', "Display this help message.") do
110
+ puts usage
111
+ exit
112
+ end
113
+ usage.on_tail('--config', "Produce a configuration template.") do
114
+ puts application.config_template.to_yaml
115
+ exit
116
+ end
117
+ end
118
+ )
119
+ end
120
+
121
+ end
122
+
123
+ extend Control
124
+ end
@@ -0,0 +1,139 @@
1
+ #__DIR__ = File.dirname(__FILE__)
2
+
3
+ #Dir[File.join(__DIR__, 'core_ext', '*.rb')].each do |file|
4
+ # require file
5
+ #end
6
+
7
+ #require 'facets'
8
+ require 'facets/to_hash'
9
+ require 'facets/module/basename'
10
+ require 'facets/module/alias_accessor'
11
+ require 'facets/pathname'
12
+ #require 'facets/boolean'
13
+
14
+ class Array
15
+
16
+ def to_list
17
+ self
18
+ end
19
+
20
+ end
21
+
22
+ class NilClass
23
+
24
+ def to_list
25
+ []
26
+ end
27
+
28
+ end
29
+
30
+ class String
31
+
32
+ # Helper method for cleaning list options.
33
+ # This will split the option on ':' or ';'
34
+ # if it is a string, rather than an array.
35
+ # And it will make sure there are no nil elements.
36
+
37
+ def to_list
38
+ split(/[:;,\n]/).map{ |s| s.strip }
39
+ end
40
+
41
+ end
42
+
43
+ # TODO: Replace these with facets/shellwords !!!
44
+
45
+ # TODO: Belongs in Redtools, no?
46
+
47
+ #
48
+ class Array #:nodoc:
49
+
50
+ # Convert an array into commandline parameters.
51
+ # The array is accepted in the format of Ruby
52
+ # method arguments --ie. [arg1, arg2, ..., hash]
53
+
54
+ def to_console
55
+ #flags = (Hash===last ? pop : {})
56
+ #flags = flags.to_console
57
+ #flags + ' ' + join(" ")
58
+ to_argv.join(' ')
59
+ end
60
+
61
+ # TODO: DEPRECATE
62
+ alias_method :to_params, :to_console
63
+
64
+ #
65
+ def to_argv
66
+ flags = (Hash===last ? pop : {})
67
+ flags = flags.to_argv
68
+ flags + self
69
+ end
70
+
71
+ # def to_console
72
+ # flags = (Hash===last ? pop : {})
73
+ # flags = flags.collect do |f,v|
74
+ # m = f.to_s.size == 1 ? '-' : '--'
75
+ # case v
76
+ # when Array
77
+ # v.collect{ |e| "#{m}#{f} '#{e}'" }.join(' ')
78
+ # when true
79
+ # "#{m}#{f}"
80
+ # when false, nil
81
+ # ''
82
+ # else
83
+ # "#{m}#{f} '#{v}'"
84
+ # end
85
+ # end
86
+ # return (flags + self).join(" ")
87
+ # end
88
+
89
+ end
90
+
91
+ class Hash
92
+
93
+ # Convert a Hash into command line arguments.
94
+ # The array is accepted in the format of Ruby
95
+ # method arguments --ie. [arg1, arg2, ..., hash]
96
+ def to_console
97
+ to_argv.join(' ')
98
+ end
99
+
100
+ # Convert a Hash into command line parameters.
101
+ # The array is accepted in the format of Ruby
102
+ # method arguments --ie. [arg1, arg2, ..., hash]
103
+ def to_argv
104
+ flags = map do |f,v|
105
+ m = f.to_s.size == 1 ? '-' : '--'
106
+ case v
107
+ when Array
108
+ v.collect{ |e| "#{m}#{f}='#{e}'" }.join(' ')
109
+ when true
110
+ "#{m}#{f}"
111
+ when false, nil
112
+ ''
113
+ else
114
+ "#{m}#{f}='#{v}'"
115
+ end
116
+ end
117
+ end
118
+
119
+ # Turn a hash into arguments.
120
+ #
121
+ # h = { :list => [1,2], :base => "HI" }
122
+ # h.argumentize #=> [ [], { :list => [1,2], :base => "HI" } ]
123
+ # h.argumentize(:list) #=> [ [1,2], { :base => "HI" } ]
124
+ #
125
+ def argumentize(args_field=nil)
126
+ config = dup
127
+ if args_field
128
+ args = [config.delete(args_field)].flatten.compact
129
+ else
130
+ args = []
131
+ end
132
+ args << config
133
+ return args
134
+ end
135
+
136
+ alias_method :command_vector, :argumentize
137
+
138
+ end
139
+