detroit 0.1.0 → 0.2.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.
@@ -1,80 +1,219 @@
1
1
  module Detroit
2
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
3
+ # Assembly encapsulates a `Assembly` file and it's service instance
4
+ # configurations.
5
+ class Assembly
13
6
 
14
- # Define a new assembly.
15
- def self.assembly(name, &block)
16
- assemblies[name.to_sym] = Assembly.new(name, &block)
17
- end
7
+ # Load Scedule file.
8
+ def self.load(input)
9
+ new(input)
10
+ end
18
11
 
19
- # The Assembly class encapsulates an *assembly system* which consists of
20
- # a set of interrelated assembly lines, or tracks.
21
- class Assembly
12
+ # Hash table of services.
13
+ attr :services
22
14
 
23
- # Name of the assembly system.
24
- attr :name
15
+ private
25
16
 
26
- # Returns a Hash of track names mapped to list of stops.
27
- attr :lines
17
+ # Initialize new Assembly instance.
18
+ def initialize(file, options={})
19
+ @project = options[:project]
28
20
 
29
- # Lines are also called `tracks`.
30
- alias_method :tracks, :lines
21
+ @services = {}
31
22
 
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
23
+ @file = (String === file ? File.new(file) : file)
38
24
 
39
- # Define an assembly line.
40
- def line(name, *stops)
41
- if stops.empty?
42
- @lines[name.to_sym]
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))
43
30
  else
44
- @lines[name.to_sym] = stops.map{ |s| s.to_sym }
31
+ text = @file.read
32
+ if /^---/ =~ text
33
+ @services = YAML.load(erb(text))
34
+ else
35
+ instance_eval(text, @file.path)
36
+ end
45
37
  end
46
38
  end
47
39
 
48
- # Lines are also called tracks.
49
- alias_method :track, :line
40
+ # Define a service.
41
+ def service(name, settings={}, &block)
42
+ if block
43
+ block_context = BlockContext.new(&block)
44
+ settings.update(block_context.settings)
45
+ end
46
+ @services[name.to_s] = settings.rekey(&:to_s)
47
+ end
50
48
 
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.
49
+ alias_method :tool, :service
50
+
51
+ #
54
52
  #
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
53
+ #
54
+ #
55
+ def custom(name, &block)
56
+ context = CustomContext.new(&block)
57
+ settings = context.settings
58
+ @services[name.to_s] = settings.rekey(&:to_s)
59
+ end
60
+
61
+ # Access to project data.
62
+ #
63
+ # NOTE: Thinking that the project should be relative
64
+ # to the Routine file itself, unless a `project` is passed
65
+ # in manually through the initializer. In the mean time,
66
+ # the project is just relative to the current working directory.
67
+ #
68
+ # TODO: Make configurable and use .ruby by default ?
69
+ def project
70
+ @project ||= POM::Project.find #(file_directory)
71
+ end
72
+
73
+ # Capitalized service names called as methods
74
+ # can also define a service.
75
+ def method_missing(sym, *args, &block)
76
+ service_class = sym.to_s
77
+ case service_class
78
+ when /^[A-Z]/
79
+ if Hash === args.last
80
+ args.last[:service] = service_class
62
81
  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
82
+ args << {:service=>service_class}
68
83
  end
84
+ case args.first
85
+ when String, Symbol
86
+ name = args.first
87
+ else
88
+ name = service_class.to_s.downcase
89
+ end
90
+ service(name, *args, &block)
69
91
  else
70
- track = tracks[name]
92
+ super(sym, *args, &block)
71
93
  end
72
- track
73
94
  end
74
95
 
75
- # Did I mention that `line` and `track` are synonyms?
76
- alias_method :get_line, :get_track
96
+ private
97
+
98
+ # Process Routine document via ERB.
99
+ def erb(text)
100
+ context = ERBContext.new(project)
101
+ ERB.new(text).result(context.__binding__)
102
+ end
103
+
104
+ # ERBContext provides the clean context to process a Routine
105
+ # as an ERB template.
106
+ class ERBContext
107
+ #
108
+ def initialize(project)
109
+ @project = project
110
+ end
111
+
112
+ # Access to a clean binding.
113
+ def __binding__
114
+ binding
115
+ end
116
+
117
+ # Provide access to project data.
118
+ def project
119
+ @project
120
+ end
121
+
122
+ #
123
+ def method_missing(name, *args)
124
+ if project.respond_to?(name)
125
+ project.__send__(name, *args)
126
+ elsif project.metadata.respond_to?(name)
127
+ project.metadata.__send__(name, *args)
128
+ else
129
+ super(name, *args)
130
+ end
131
+ end
132
+ end
133
+
134
+ #
135
+ class BlockContext
136
+ #
137
+ attr :settings
138
+
139
+ #
140
+ def initialize(&b)
141
+ @settings = {}
142
+ b.arity == 1 ? b.call(self) : instance_eval(&b)
143
+ end
144
+
145
+ #
146
+ def set(name, value=nil, &block)
147
+ if block
148
+ block_context = BlockContext.new
149
+ block.call(block_context)
150
+ @settings[name.to_s] = block_context.settings
151
+ else
152
+ @settings[name.to_s] = value
153
+ end
154
+ end
155
+
156
+ #
157
+ def method_missing(symbol, value=nil, *args)
158
+ case name = symbol.to_s
159
+ when /=$/
160
+ @settings[name.chomp('=')] = value
161
+ else
162
+ super(symbol, value=nil, *args)
163
+ end
164
+ end
165
+ end
166
+
167
+ #
168
+ class CustomContext
169
+ #
170
+ attr :settings
171
+ #
172
+ def initialize(&b)
173
+ @settings = {}
174
+ b.arity == 0 ? instance_eval(&b) : b.call(self)
175
+ end
176
+ #
177
+ def method_missing(s,a=nil,*x,&b)
178
+ case s.to_s
179
+ when /=$/
180
+ @settings[s.to_s.chomp('=').to_sym] = b ? b : a
181
+ else
182
+ return @settings[s] unless a
183
+ @settings[s] = b ? b : a
184
+ end
185
+ end
186
+ def respond_to?(s)
187
+ @settings.key?(s.to_sym)
188
+ end
189
+ end
77
190
 
78
191
  end
79
192
 
193
+ # NOTE: This is problematic, because a Scheudle file should really know from
194
+ # what file it was derived.
195
+
196
+ #
197
+ DOMAIN = "rubyworks.github.com/detroit,2011-05-27"
198
+
199
+ # TODO: If using Psych rather than Syck, then define a domain type.
200
+
201
+ #if defined?(Psych) #RUBY_VERSION >= '1.9'
202
+ # YAML::add_domain_type(DOMAIN, "assembly") do |type, hash|
203
+ # Assembly.load(hash)
204
+ # end
205
+ #else
206
+ YAML::add_builtin_type("assembly") do |type, value|
207
+ value
208
+ #case value
209
+ #when String
210
+ # Assembly.eval(value)
211
+ #when Hash
212
+ # Assembly.new(value)
213
+ #else
214
+ # raise "ERROR: Invalid Assembly"
215
+ #end
216
+ end
217
+ #end
218
+
80
219
  end
@@ -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.assembly_systems
11
+ @assembly_system ||= {}
12
+ end
13
+
14
+ # Define a new assembly system.
15
+ def self.assembly_system(name, &block)
16
+ assembly_systems[name.to_sym] = AssemblySystem.new(name, &block)
17
+ end
18
+
19
+ # The AssemblySystem class encapsulates a set of interrelated
20
+ # assembly lines, or _tracks_.
21
+ class AssemblySystem
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
@@ -8,18 +8,18 @@ module Detroit
8
8
  # Configuration directory name (most likely a hidden "dot" directory).
9
9
  DIRECTORY = "detroit"
10
10
 
11
- # File identifier used to find a project's Schedule(s).
12
- FILE_EXTENSION = "schedule"
11
+ # File identifier used to find a project's Assembly(s).
12
+ FILE_EXTENSION = "assembly"
13
13
 
14
14
  # Current POM::Project object.
15
15
  #attr :project
16
16
 
17
- # The list of a project's routine files.
17
+ # The list of a project's assembly files.
18
18
  #
19
19
  # @return [Array<String>] routine files
20
- attr :schedules
20
+ attr :assemblies
21
21
 
22
- # Service configurations from Schedule or *.schedule files.
22
+ # Service configurations from Assembly or *.assembly files.
23
23
  #
24
24
  # @return [Hash] service settings
25
25
  attr :services
@@ -32,22 +32,22 @@ module Detroit
32
32
  attr :defaults
33
33
 
34
34
  #
35
- def initialize(schedule_files=nil)
36
- if schedule_files && !schedule_files.empty?
37
- @schedule_filenames = schedule_files
35
+ def initialize(assembly_files=nil)
36
+ if assembly_files && !assembly_files.empty?
37
+ @assembly_filenames = assembly_files
38
38
  else
39
- @schedule_filenames = nil
39
+ @assembly_filenames = nil
40
40
  end
41
41
 
42
- @schedules = {}
43
- @services = {}
44
- @defaults = {}
42
+ @assemblies = {}
43
+ @services = {}
44
+ @defaults = {}
45
45
 
46
46
  @loaded_plugins = {}
47
47
 
48
48
  load_plugins
49
49
  load_defaults
50
- load_schedules
50
+ load_assemblies
51
51
  end
52
52
 
53
53
  #--
@@ -60,8 +60,14 @@ module Detroit
60
60
  # Load a plugin.
61
61
  def load_plugin(name)
62
62
  @loaded_plugins[name] ||= (
63
- require "detroit-#{name}"
64
- name
63
+ begin
64
+ require "detroit-#{name}"
65
+ rescue LoadError => e
66
+ $stderr.puts "ERROR: #{e.message.capitalize}"
67
+ $stderr.puts " Perhaps `gem install detroit-#{name}`?"
68
+ exit -1
69
+ end
70
+ name # true ?
65
71
  )
66
72
  end
67
73
 
@@ -84,16 +90,16 @@ module Detroit
84
90
  end
85
91
 
86
92
  #
87
- def load_schedules
88
- schedule_filenames.each do |file|
89
- load_schedule_file(file)
93
+ def load_assemblies
94
+ assembly_filenames.each do |file|
95
+ load_assembly_file(file)
90
96
  end
91
97
  end
92
98
 
93
99
  #
94
- def load_schedule_file(file)
95
- @schedules[file] = Schedule.load(File.new(file))
96
- @services.merge!(schedules[file].services)
100
+ def load_assembly_file(file)
101
+ @assemblies[file] = Assembly.load(File.new(file))
102
+ @services.merge!(assemblies[file].services)
97
103
  end
98
104
 
99
105
  # Set defaults.
@@ -101,21 +107,21 @@ module Detroit
101
107
  @defaults = hash.to_h
102
108
  end
103
109
 
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
110
+ # If a `Assembly` or `.assembly` file exists, then it is returned. Otherwise
111
+ # all `*.assembly` files are loaded. To load `*.assembly` files from another
106
112
  # directory add the directory to config options file.
107
- def schedule_filenames
108
- @schedule_filenames ||= (
113
+ def assembly_filenames
114
+ @assembly_filenames ||= (
109
115
  files = []
110
- ## match 'Schedule' or '.schedule' file
116
+ ## match 'Assembly' or '.assembly' file
111
117
  files = project.root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
112
118
  ## only files
113
119
  files = files.select{ |f| File.file?(f) }
114
120
  ##
115
121
  if files.empty?
116
- ## match '.detroit/*.schedule' or 'detroit/*.schedule'
122
+ ## match '.detroit/*.assembly' or 'detroit/*.assembly'
117
123
  files += project.root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
118
- ## match 'task/*.schedule' (OLD SCHOOL)
124
+ ## match 'task/*.assembly' (OLD SCHOOL)
119
125
  files += project.root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
120
126
  ## only files
121
127
  files = files.select{ |f| File.file?(f) }
@@ -140,7 +146,7 @@ module Detroit
140
146
  def load_detroit_file(file)
141
147
  #@dir = File.dirname(file)
142
148
 
143
- schedules[file] =
149
+ assemblies[file] =
144
150
 
145
151
  # TODO: can we just read the first line of the file and go from there?
146
152
  #text = File.read(file).strip