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.
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
+