angry_mob 0.1.0

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