detroit 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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