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,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
@@ -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