angry_mob 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 (125) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +123 -0
  3. data/bin/mob +139 -0
  4. data/lib/angry_mob.rb +28 -0
  5. data/lib/angry_mob/act.rb +111 -0
  6. data/lib/angry_mob/act/scheduler.rb +143 -0
  7. data/lib/angry_mob/action.rb +11 -0
  8. data/lib/angry_mob/builder.rb +115 -0
  9. data/lib/angry_mob/extend.rb +10 -0
  10. data/lib/angry_mob/extend/array.rb +30 -0
  11. data/lib/angry_mob/extend/blank.rb +108 -0
  12. data/lib/angry_mob/extend/blankslate.rb +109 -0
  13. data/lib/angry_mob/extend/dictionary.rb +140 -0
  14. data/lib/angry_mob/extend/hash.rb +67 -0
  15. data/lib/angry_mob/extend/object.rb +21 -0
  16. data/lib/angry_mob/extend/pathname.rb +23 -0
  17. data/lib/angry_mob/extend/string.rb +8 -0
  18. data/lib/angry_mob/log.rb +28 -0
  19. data/lib/angry_mob/mob.rb +77 -0
  20. data/lib/angry_mob/mob_loader.rb +115 -0
  21. data/lib/angry_mob/node.rb +44 -0
  22. data/lib/angry_mob/notifier.rb +76 -0
  23. data/lib/angry_mob/target.rb +257 -0
  24. data/lib/angry_mob/target/arguments.rb +71 -0
  25. data/lib/angry_mob/target/call.rb +57 -0
  26. data/lib/angry_mob/target/default_resource_locator.rb +11 -0
  27. data/lib/angry_mob/target/defaults.rb +23 -0
  28. data/lib/angry_mob/target/mother.rb +66 -0
  29. data/lib/angry_mob/target/notify.rb +57 -0
  30. data/lib/angry_mob/target/tracking.rb +96 -0
  31. data/lib/angry_mob/ui.rb +247 -0
  32. data/lib/angry_mob/util.rb +11 -0
  33. data/lib/angry_mob/vendored.rb +8 -0
  34. data/lib/angry_mob/version.rb +3 -0
  35. data/vendor/angry_hash/Rakefile +17 -0
  36. data/vendor/angry_hash/VERSION +1 -0
  37. data/vendor/angry_hash/angry_hash.gemspec +47 -0
  38. data/vendor/angry_hash/examples/accessors_eg.rb +46 -0
  39. data/vendor/angry_hash/examples/creation_eg.rb +43 -0
  40. data/vendor/angry_hash/examples/dsl.eg.rb +18 -0
  41. data/vendor/angry_hash/examples/dup_eg.rb +86 -0
  42. data/vendor/angry_hash/examples/eg_helper.rb +24 -0
  43. data/vendor/angry_hash/examples/merge_eg.rb +135 -0
  44. data/vendor/angry_hash/lib/angry_hash.rb +215 -0
  45. data/vendor/angry_hash/lib/angry_hash/dsl.rb +44 -0
  46. data/vendor/angry_hash/lib/angry_hash/extension_tracking.rb +12 -0
  47. data/vendor/angry_hash/lib/angry_hash/merge_string.rb +58 -0
  48. data/vendor/json/COPYING +58 -0
  49. data/vendor/json/GPL +340 -0
  50. data/vendor/json/README +360 -0
  51. data/vendor/json/lib/json/common.rb +371 -0
  52. data/vendor/json/lib/json/pure.rb +77 -0
  53. data/vendor/json/lib/json/pure/generator.rb +443 -0
  54. data/vendor/json/lib/json/pure/parser.rb +303 -0
  55. data/vendor/json/lib/json/version.rb +8 -0
  56. data/vendor/thor/CHANGELOG.rdoc +89 -0
  57. data/vendor/thor/LICENSE +20 -0
  58. data/vendor/thor/README.rdoc +297 -0
  59. data/vendor/thor/Thorfile +69 -0
  60. data/vendor/thor/bin/rake2thor +86 -0
  61. data/vendor/thor/bin/thor +6 -0
  62. data/vendor/thor/lib/thor.rb +244 -0
  63. data/vendor/thor/lib/thor/actions.rb +275 -0
  64. data/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  65. data/vendor/thor/lib/thor/actions/directory.rb +91 -0
  66. data/vendor/thor/lib/thor/actions/empty_directory.rb +134 -0
  67. data/vendor/thor/lib/thor/actions/file_manipulation.rb +223 -0
  68. data/vendor/thor/lib/thor/actions/inject_into_file.rb +104 -0
  69. data/vendor/thor/lib/thor/base.rb +540 -0
  70. data/vendor/thor/lib/thor/core_ext/file_binary_read.rb +9 -0
  71. data/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  72. data/vendor/thor/lib/thor/core_ext/ordered_hash.rb +100 -0
  73. data/vendor/thor/lib/thor/error.rb +30 -0
  74. data/vendor/thor/lib/thor/group.rb +271 -0
  75. data/vendor/thor/lib/thor/invocation.rb +180 -0
  76. data/vendor/thor/lib/thor/parser.rb +4 -0
  77. data/vendor/thor/lib/thor/parser/argument.rb +67 -0
  78. data/vendor/thor/lib/thor/parser/arguments.rb +150 -0
  79. data/vendor/thor/lib/thor/parser/option.rb +128 -0
  80. data/vendor/thor/lib/thor/parser/options.rb +169 -0
  81. data/vendor/thor/lib/thor/rake_compat.rb +66 -0
  82. data/vendor/thor/lib/thor/runner.rb +314 -0
  83. data/vendor/thor/lib/thor/shell.rb +83 -0
  84. data/vendor/thor/lib/thor/shell/basic.rb +239 -0
  85. data/vendor/thor/lib/thor/shell/color.rb +108 -0
  86. data/vendor/thor/lib/thor/task.rb +102 -0
  87. data/vendor/thor/lib/thor/util.rb +224 -0
  88. data/vendor/thor/lib/thor/version.rb +3 -0
  89. data/vendor/thor/spec/actions/create_file_spec.rb +170 -0
  90. data/vendor/thor/spec/actions/directory_spec.rb +131 -0
  91. data/vendor/thor/spec/actions/empty_directory_spec.rb +91 -0
  92. data/vendor/thor/spec/actions/file_manipulation_spec.rb +271 -0
  93. data/vendor/thor/spec/actions/inject_into_file_spec.rb +135 -0
  94. data/vendor/thor/spec/actions_spec.rb +292 -0
  95. data/vendor/thor/spec/base_spec.rb +263 -0
  96. data/vendor/thor/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  97. data/vendor/thor/spec/core_ext/ordered_hash_spec.rb +115 -0
  98. data/vendor/thor/spec/fixtures/application.rb +2 -0
  99. data/vendor/thor/spec/fixtures/bundle/execute.rb +6 -0
  100. data/vendor/thor/spec/fixtures/bundle/main.thor +1 -0
  101. data/vendor/thor/spec/fixtures/doc/%file_name%.rb.tt +1 -0
  102. data/vendor/thor/spec/fixtures/doc/README +3 -0
  103. data/vendor/thor/spec/fixtures/doc/config.rb +1 -0
  104. data/vendor/thor/spec/fixtures/group.thor +90 -0
  105. data/vendor/thor/spec/fixtures/invoke.thor +112 -0
  106. data/vendor/thor/spec/fixtures/script.thor +145 -0
  107. data/vendor/thor/spec/fixtures/task.thor +10 -0
  108. data/vendor/thor/spec/group_spec.rb +171 -0
  109. data/vendor/thor/spec/invocation_spec.rb +107 -0
  110. data/vendor/thor/spec/parser/argument_spec.rb +47 -0
  111. data/vendor/thor/spec/parser/arguments_spec.rb +64 -0
  112. data/vendor/thor/spec/parser/option_spec.rb +202 -0
  113. data/vendor/thor/spec/parser/options_spec.rb +292 -0
  114. data/vendor/thor/spec/rake_compat_spec.rb +68 -0
  115. data/vendor/thor/spec/runner_spec.rb +210 -0
  116. data/vendor/thor/spec/shell/basic_spec.rb +205 -0
  117. data/vendor/thor/spec/shell/color_spec.rb +41 -0
  118. data/vendor/thor/spec/shell_spec.rb +34 -0
  119. data/vendor/thor/spec/spec.opts +1 -0
  120. data/vendor/thor/spec/spec_helper.rb +54 -0
  121. data/vendor/thor/spec/task_spec.rb +69 -0
  122. data/vendor/thor/spec/thor_spec.rb +237 -0
  123. data/vendor/thor/spec/util_spec.rb +163 -0
  124. data/vendor/thor/thor.gemspec +120 -0
  125. metadata +199 -0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2010 PLUS2 Pty. Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND
17
+ NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,123 @@
1
+ # This is AngryMob
2
+ AngryMob (AM) is the automated system configuration component of [YesMaster](http://yesmasterapp.com).
3
+
4
+ It combines:
5
+
6
+ * convenient configuration data (`the node`)
7
+ * idempotent code to ensure the configuration of the parts of a system (`targets`)
8
+ * and a method of controlling the flow of the setup (`acts`)
9
+
10
+ AngryMob values having:
11
+
12
+ * zero external dependencies.
13
+ * a small, simple core.
14
+ * Rubiness.
15
+
16
+ Having *zero external dependencies* means that AM can be used from the very moment a bare ruby interpreter is installed on a server.
17
+
18
+ A *small, simple core* means that debugging and testing the code is simplified. It also allows AM to be embedded directly into other projects.
19
+
20
+ Please note that **AngryMob is a reaction to Chef**. Despite this, I retain the utmost respect the Chef movement and the guys who've built it.
21
+
22
+ AM purposely omits or eschews the following features you might be familiar with from Chef:
23
+
24
+ * **The client and server and its dependencies.** AM operates similarly to "chef-solo" or is combined with other components for network based "chef-client"-like operation.
25
+ * **A built in, fixed cookbook layout and metadata.** You can define your own ResourceLocator, or use the basic supplied one.
26
+ * **A single copy nirvana implementation.** Again, define it yourself if you want it.
27
+ * **The puppet resource/provider separation.** I don't have a particular solution to supporting multiple operating systems, but its strongly YAGNI for now.
28
+ * **Exhaustive system discovery like Ohai.** I think Ohai is great, but running it before every AM run to find the hostname and IP is excessive.
29
+ * **Type-checking of data.** This is ruby, where ducks rule the roost.
30
+
31
+ ### Status
32
+ AngryMob is young and fluid, but I'm using it a lot in the maintainence of VPSes at PLUS2.
33
+
34
+ In particular, AM lacks any unit-level specs or tests so far. The servers it maintains are valid functional testing in my eyes ;)
35
+
36
+ ### Taxonomy
37
+
38
+ * The `node` is a glob of data representing the thing you're configuring.
39
+ * `targets` are things you want to configure.
40
+ * `acts` are groups of target invocations. They can also schedule the execution of other acts.
41
+ * `mobs` are groups of acts and targets.
42
+
43
+ #### The Node
44
+ The Node is a place to hang your data. In essence its a `Hash` (though more specifically its an [AngryHash](http://github.com/plus2/angry_hash), which is similar to `Hashie::Mash`)
45
+
46
+ #### Targets
47
+ Targets are parts of the server which you want to configure, for example:
48
+
49
+ * directories
50
+ * templates
51
+ * users
52
+
53
+ *Note* the targets interface is likely to change soon. Its overly complicated right now.
54
+
55
+ Targets perform a similar role to Chef/puppet resource/providers.
56
+
57
+ **TODO** talk about Target definition dsl and SingletonTargets
58
+
59
+ #### Acts
60
+ Acts are groups of targets. They're conceptually similar to a rake task. Subsequent acts can be scheduled.
61
+
62
+ #### Mobs
63
+ Mobs are groups of target definitions and acts. They can be mixed and matched.
64
+
65
+ ##### common mob
66
+
67
+ http://github.com/plus2/common_mob
68
+
69
+ A small toolkit of handy targets:
70
+
71
+ * dir
72
+ * file
73
+ * template - ERB
74
+ * symlink
75
+ * tarball
76
+ * fetch
77
+ * git
78
+ * user
79
+ * apt - apt-get package
80
+ * gem - a rubygem
81
+ * block - a ruby block
82
+ * sh - a shell command
83
+ * service - a super-class for defining ubuntu service targets
84
+
85
+ ## The mob command
86
+
87
+ "chef-solo"-like command for setting an AngryMob on your server.
88
+
89
+ **TODO** write!
90
+
91
+ ## Meta
92
+
93
+ AngryMob was written by [Lachie Cox](http://github.com/lachie) for [PLUS2](http://plus2.com.au) and [YesMaster](http://yesmasterapp.com).
94
+
95
+ It lives at [http://github.com/plus2/angry_mob](http://github.com/plus2/angry_mob).
96
+
97
+ Please try it out and send us feedback via the github page.
98
+
99
+ ## License
100
+
101
+ Copyright (c) 2010 PLUS2
102
+
103
+ Permission is hereby granted, free of charge, to any person
104
+ obtaining a copy of this software and associated documentation
105
+ files (the "Software"), to deal in the Software without
106
+ restriction, including without limitation the rights to use,
107
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
108
+ copies of the Software, and to permit persons to whom the
109
+ Software is furnished to do so, subject to the following
110
+ conditions:
111
+
112
+ The above copyright notice and this permission notice shall be
113
+ included in all copies or substantial portions of the Software.
114
+
115
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
116
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
117
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
118
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
119
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
120
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
121
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
122
+ OTHER DEALINGS IN THE SOFTWARE.
123
+
data/bin/mob ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require 'pathname'
5
+ @root = Pathname(__FILE__).dirname.parent.expand_path
6
+
7
+ %w{json thor angry_hash}.each do |lib|
8
+ root = @root+'vendor'+lib
9
+ $: << (root+'lib')
10
+ end
11
+
12
+ require 'thor'
13
+
14
+ $:.unshift @root+'lib'
15
+ require 'angry_mob'
16
+
17
+ class MobCLI < Thor
18
+
19
+ default_task 'riot'
20
+ desc 'riot', 'riot the mob'
21
+ method_option :nodename , :type => :string, :required => true
22
+ method_option :json_file, :type => :string, :required => true
23
+ method_option :act , :type => :string, :required => true
24
+
25
+ method_option :hooks , :type => :string
26
+
27
+ method_option :debug , :type => :boolean, :default => false
28
+ method_option :dry_run , :type => :boolean, :default => false
29
+ method_option :allow_missing_act, :type => :boolean, :default => false
30
+
31
+ method_option :mobs, :type => :array, :default => []
32
+
33
+ attr_reader :config
34
+ def riot
35
+ begin
36
+ @config = AngryHash[ options ]
37
+
38
+ load_hooks(config.hooks)
39
+
40
+ @hooks.pre
41
+
42
+ ui = AngryMob::UI.new(:debug => config.debug?)
43
+
44
+ attributes = build_attributes
45
+
46
+ @hooks.attrs( attributes )
47
+
48
+ AngryMob::Mob.ui = ui # XXX not threadsafe, but what is?
49
+ mob_loader = AngryMob::MobLoader.new
50
+
51
+ config.mobs.each {|mob_path| mob_loader.add_mob(mob_path)}
52
+
53
+ mob = mob_loader.to_mob
54
+ mob.riot!( config.nodename, attributes )
55
+
56
+ rescue
57
+ $stdout.flush
58
+ $stderr.flush
59
+
60
+ puts "\nerror [#{$!.class}] #{$!}"
61
+ $!.backtrace.each {|b| puts " #{b}"}
62
+ exit(1)
63
+ end
64
+ end
65
+
66
+ protected
67
+ def build_attributes
68
+ attributes = load_attributes
69
+
70
+ attributes.acts = [ config.act ]
71
+
72
+ if config.allow_missing_act?
73
+ attributes.raise_on_missing_act = false
74
+ end
75
+
76
+ attributes.dry_run = config.dry_run?
77
+
78
+ attributes
79
+ end
80
+
81
+ def load_attributes
82
+ return AngryHash.new unless config.json_file?
83
+
84
+ if config.json_file.to_s[/^https?:/]
85
+ require 'net/http'
86
+ require 'uri'
87
+ json = Net::HTTP.get(URI.parse(config.json_file))
88
+ else
89
+ json = Pathname(config.json_file.tapp("loading attributes from")).expand_path.read
90
+ end
91
+
92
+ require 'json/pure'
93
+ attributes = JSON.parse( json )
94
+
95
+ AngryHash[attributes || {}]
96
+ end
97
+
98
+ def load_hooks(file)
99
+ @hooks = MobHooks.new( self, file )
100
+ end
101
+ end
102
+
103
+
104
+ class MobHooks
105
+ attr_reader :cli
106
+ def initialize(cli,file=nil)
107
+ @cli = cli
108
+ if file
109
+ @building = true
110
+ instance_eval( Pathname(file).read )
111
+ @building = false
112
+ end
113
+ end
114
+
115
+ def pre(&block)
116
+ if building?
117
+ pre_blocks << block
118
+ else
119
+ pre_blocks.each {|b| b.call}
120
+ end
121
+ end
122
+
123
+ def attrs( *args, &block )
124
+ if building?
125
+ attr_blocks << block
126
+ else
127
+ attr_blocks.each {|b| b.call(*args)}
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ def building?; @building end
134
+ def pre_blocks ; @pre_blocks ||= [] end
135
+ def attr_blocks; @attr_blocks ||= [] end
136
+ end
137
+
138
+
139
+ MobCLI.start
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+ here = Pathname(__FILE__).dirname
3
+
4
+ require 'angry_hash'
5
+
6
+ # XXX duckpunches aren't good for not stepping on other codes' toes
7
+ require 'angry_mob/extend'
8
+
9
+ class AngryMob
10
+ autoload :Mob , 'angry_mob/mob'
11
+ autoload :Node , 'angry_mob/node'
12
+
13
+ autoload :Target , 'angry_mob/target'
14
+ autoload :SingletonTarget, 'angry_mob/singleton_target'
15
+ autoload :Action , 'angry_mob/action'
16
+
17
+ autoload :Notifier , 'angry_mob/notifier'
18
+
19
+ autoload :Act , 'angry_mob/act'
20
+
21
+ autoload :Log , 'angry_mob/log'
22
+ autoload :UI , 'angry_mob/ui'
23
+ autoload :Util , 'angry_mob/util'
24
+
25
+ autoload :Builder , 'angry_mob/builder'
26
+ autoload :MobLoader , 'angry_mob/mob_loader'
27
+
28
+ end
@@ -0,0 +1,111 @@
1
+ class AngryMob
2
+ # A `Builder::Act` groups target calls.
3
+ class Act
4
+ autoload :Scheduler, "angry_mob/act/scheduler"
5
+
6
+ attr_reader :mob, :name, :definition_file
7
+
8
+ def initialize(name,multi,&blk)
9
+ @name = name
10
+ @multi = multi
11
+ @blk = blk
12
+ end
13
+
14
+ def ui; mob.ui end
15
+ def log(message); mob.ui.log message end
16
+
17
+ def multi?; !!@multi end
18
+
19
+ # Binds the act to the mob and the file from which it came.
20
+ def bind(mob,file)
21
+ @mob = mob
22
+ @definition_file = file
23
+
24
+ mob.act_scheduler.add_act @name, self
25
+ end
26
+
27
+ def self.synthesise(mob,name,&blk)
28
+ act = new(name,true,&blk)
29
+ act.bind(mob,"name")
30
+ act.run!
31
+ end
32
+
33
+ #### Compilation
34
+
35
+ # Executes the block via `instance_exec`
36
+ def run!(*arguments)
37
+ ui.push("act '#{name}'", :bubble => true) do
38
+ @running = true
39
+
40
+ instance_exec *arguments, &@blk
41
+
42
+ @running = false
43
+ end
44
+ end
45
+
46
+ # bundler + rubygems clusterfuck
47
+ def gem(*args,&blk)
48
+ __run_target(:gem,*args,&blk)
49
+ end
50
+
51
+ # TODO - de-mm
52
+ def method_missing(nickname,*args,&blk)
53
+ return super unless @running
54
+ __run_target(nickname,*args,&blk)
55
+ end
56
+
57
+ # Schedules a target, adding call-location context along the way.
58
+ def __run_target(nickname,*args,&blk)
59
+ call = mob.target_mother.target_call(nickname,*args,&blk)
60
+
61
+ call.merge_defaults(defaults.defaults_for(nickname))
62
+ call.call(self)
63
+
64
+ call.target
65
+ end
66
+
67
+ def in_sub_act(*args,&blk)
68
+ sub_act = self.class.new("#{name}-sub-act",true,&blk)
69
+ sub_act.bind(@mob,@definition_file)
70
+ sub_act.run!(*args)
71
+ end
72
+
73
+ #### Definition helpers
74
+
75
+ def defaults
76
+ @defaults ||= Target::Defaults.new
77
+ end
78
+
79
+ def notify
80
+ Target::Notify.new(self)
81
+ end
82
+
83
+ def notifications
84
+ mob.notifier
85
+ end
86
+
87
+ # directly schedule a call on the delayed list
88
+ def later
89
+ n = Target::Notify.new(self)
90
+ mob.notifier.schedule_notification n
91
+ n
92
+ end
93
+
94
+ def node
95
+ mob.node
96
+ end
97
+
98
+ def act_now act_name, *args
99
+ mob.act_scheduler.act_now(act_name,*args)
100
+ end
101
+
102
+ def schedule_act act_name
103
+ mob.act_scheduler.schedule_act(act_name)
104
+ end
105
+
106
+ def schedule_acts_matching(regex,&block)
107
+ mob.act_scheduler.schedule_acts_matching(regex,&block)
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,143 @@
1
+ class AngryMob
2
+ class Act
3
+ class Scheduler
4
+ attr_writer :node
5
+ attr_reader :acted, :mob
6
+
7
+ def initialize(mob)
8
+ @mob = mob
9
+ reset!
10
+ end
11
+
12
+ def ui; @mob.ui end
13
+
14
+ def reset!
15
+ @act_names = nil
16
+ @acted = []
17
+ end
18
+
19
+ def act_names
20
+ @act_names ||= @node.acts || []
21
+ end
22
+
23
+ def acts
24
+ @acts ||= Dictionary.new
25
+ end
26
+
27
+ def add_act(name,act)
28
+ acts[name.to_s] = act
29
+ end
30
+
31
+ def run!
32
+ ui.task "running acts #{act_names.inspect}"
33
+ each_act {|act| act_now(act)}
34
+ ui.good "finished running acts"
35
+ finalise_acts!
36
+ end
37
+
38
+ def each_act
39
+ while act_name = next_act
40
+ ui.debug "acting out #{act_name}"
41
+
42
+ act = acts[act_name]
43
+
44
+ unless act
45
+ act_missing!(act_name)
46
+ next
47
+ end
48
+
49
+ yield act
50
+ end
51
+ @iterating = false
52
+ end
53
+
54
+ def start_iterating!(with=act_names)
55
+ unless @iterating
56
+ @iterating_act_names = with.dup
57
+ @iterating = true
58
+ end
59
+ end
60
+
61
+ def next_act
62
+ start_iterating!
63
+ @iterating_act_names.shift
64
+ end
65
+
66
+ def schedule_act(*acts)
67
+ raise(CompilationError, "schedule_act called when not compiling") unless @iterating
68
+ ui.info "scheduling #{acts.inspect}"
69
+ @iterating_act_names += acts
70
+ end
71
+
72
+ def schedule_acts_matching(regex=nil,&block)
73
+ raise(CompilationError, "schedule_act called when not compiling") unless @iterating
74
+
75
+ act_keys = acts.keys
76
+
77
+ acts_to_schedule = if regex
78
+ act_keys.grep(regex)
79
+ else
80
+ act_keys.select(&block)
81
+ end
82
+
83
+ schedule_act(*acts_to_schedule)
84
+ end
85
+
86
+ def finalise_acts!
87
+ # notifications only act as necessary, so its safe to run them all.
88
+ ui.task "running notifications"
89
+ to_notify = acts.keys.select {|k| k[%r{^notifications_for/}]}
90
+
91
+ unless to_notify.empty?
92
+ ui.info "running notifiers #{to_notify.inspect}"
93
+
94
+ start_iterating!(to_notify)
95
+ each_act {|act| act_now(act)}
96
+ end
97
+
98
+
99
+ # finalisers should only run if the act of the same name has run first
100
+ ui.task "running finalisations"
101
+
102
+ to_finalise = acted.map {|name| "finalise/#{name}"} & acts.keys
103
+ unless to_finalise.empty?
104
+ ui.info "running acts finalisers #{to_finalise.inspect}"
105
+
106
+ start_iterating!(to_finalise)
107
+ each_act {|act| act_now(act)}
108
+ end
109
+ end
110
+
111
+ def raise_on_missing_act?
112
+ !( FalseClass === mob.node.raise_on_missing_act )
113
+ end
114
+
115
+ def act_missing!(name)
116
+ raise(AngryMob::MobError, "no act named '#{name}'") if raise_on_missing_act?
117
+ end
118
+
119
+ def act_now(act_name,*arguments)
120
+ if AngryMob::Act === act_name
121
+ act = act_name
122
+ act_name = act.name
123
+ else
124
+ act = acts[act_name]
125
+ end
126
+
127
+ unless act
128
+ act_missing!(act_name)
129
+ return
130
+ end
131
+
132
+ if !act.multi? && acted.include?(act_name)
133
+ ui.skipped! "(not re-running act #{act_name} - already run)"
134
+ return
135
+ end
136
+
137
+ acted << act_name
138
+
139
+ act.run!(*arguments)
140
+ end
141
+ end
142
+ end
143
+ end