automate-it 0.9.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.hgignore +10 -0
- data/.loadpath +5 -0
- data/.project +17 -0
- data/CHANGES.txt +314 -0
- data/Hoe.rake +40 -0
- data/Manifest.txt +164 -0
- data/README.txt +40 -0
- data/Rakefile +256 -0
- data/TESTING.txt +57 -0
- data/TODO.txt +50 -0
- data/TUTORIAL.txt +391 -0
- data/automate-it.gemspec +25 -0
- data/bin/ai +3 -0
- data/bin/aifield +75 -0
- data/bin/aissh +93 -0
- data/bin/aitag +134 -0
- data/bin/automateit +133 -0
- data/docs/friendly_errors.txt +50 -0
- data/docs/previews.txt +86 -0
- data/examples/basic/Rakefile +26 -0
- data/examples/basic/config/automateit_env.rb +16 -0
- data/examples/basic/config/fields.yml +3 -0
- data/examples/basic/config/tags.yml +7 -0
- data/examples/basic/dist/README.txt +9 -0
- data/examples/basic/dist/myapp_server.erb +30 -0
- data/examples/basic/install.log +15 -0
- data/examples/basic/lib/README.txt +10 -0
- data/examples/basic/recipes/README.txt +4 -0
- data/examples/basic/recipes/install.rb +61 -0
- data/examples/basic/recipes/uninstall.rb +6 -0
- data/gpl.txt +674 -0
- data/helpers/cpan_wrapper.pl +220 -0
- data/helpers/which.cmd +7 -0
- data/lib/automateit.rb +55 -0
- data/lib/automateit/account_manager.rb +114 -0
- data/lib/automateit/account_manager/base.rb +138 -0
- data/lib/automateit/account_manager/etc.rb +128 -0
- data/lib/automateit/account_manager/nscd.rb +33 -0
- data/lib/automateit/account_manager/passwd_expect.rb +40 -0
- data/lib/automateit/account_manager/passwd_pty.rb +69 -0
- data/lib/automateit/account_manager/posix.rb +138 -0
- data/lib/automateit/address_manager.rb +88 -0
- data/lib/automateit/address_manager/base.rb +171 -0
- data/lib/automateit/address_manager/bsd.rb +28 -0
- data/lib/automateit/address_manager/freebsd.rb +59 -0
- data/lib/automateit/address_manager/linux.rb +42 -0
- data/lib/automateit/address_manager/openbsd.rb +66 -0
- data/lib/automateit/address_manager/portable.rb +37 -0
- data/lib/automateit/address_manager/sunos.rb +34 -0
- data/lib/automateit/cli.rb +85 -0
- data/lib/automateit/common.rb +65 -0
- data/lib/automateit/constants.rb +35 -0
- data/lib/automateit/download_manager.rb +48 -0
- data/lib/automateit/edit_manager.rb +321 -0
- data/lib/automateit/error.rb +10 -0
- data/lib/automateit/field_manager.rb +103 -0
- data/lib/automateit/interpreter.rb +631 -0
- data/lib/automateit/package_manager.rb +257 -0
- data/lib/automateit/package_manager/apt.rb +27 -0
- data/lib/automateit/package_manager/cpan.rb +101 -0
- data/lib/automateit/package_manager/dpkg.rb +54 -0
- data/lib/automateit/package_manager/egg.rb +64 -0
- data/lib/automateit/package_manager/gem.rb +201 -0
- data/lib/automateit/package_manager/pear.rb +95 -0
- data/lib/automateit/package_manager/pecl.rb +80 -0
- data/lib/automateit/package_manager/portage.rb +69 -0
- data/lib/automateit/package_manager/yum.rb +65 -0
- data/lib/automateit/platform_manager.rb +49 -0
- data/lib/automateit/platform_manager/darwin.rb +30 -0
- data/lib/automateit/platform_manager/debian.rb +26 -0
- data/lib/automateit/platform_manager/freebsd.rb +29 -0
- data/lib/automateit/platform_manager/gentoo.rb +26 -0
- data/lib/automateit/platform_manager/lsb.rb +44 -0
- data/lib/automateit/platform_manager/openbsd.rb +28 -0
- data/lib/automateit/platform_manager/struct.rb +80 -0
- data/lib/automateit/platform_manager/sunos.rb +39 -0
- data/lib/automateit/platform_manager/uname.rb +29 -0
- data/lib/automateit/platform_manager/windows.rb +40 -0
- data/lib/automateit/plugin.rb +7 -0
- data/lib/automateit/plugin/base.rb +32 -0
- data/lib/automateit/plugin/driver.rb +256 -0
- data/lib/automateit/plugin/manager.rb +224 -0
- data/lib/automateit/project.rb +493 -0
- data/lib/automateit/root.rb +17 -0
- data/lib/automateit/service_manager.rb +93 -0
- data/lib/automateit/service_manager/chkconfig.rb +39 -0
- data/lib/automateit/service_manager/rc_update.rb +37 -0
- data/lib/automateit/service_manager/sysv.rb +139 -0
- data/lib/automateit/service_manager/update_rcd.rb +35 -0
- data/lib/automateit/shell_manager.rb +316 -0
- data/lib/automateit/shell_manager/base_link.rb +67 -0
- data/lib/automateit/shell_manager/link.rb +24 -0
- data/lib/automateit/shell_manager/portable.rb +523 -0
- data/lib/automateit/shell_manager/symlink.rb +32 -0
- data/lib/automateit/shell_manager/which_base.rb +30 -0
- data/lib/automateit/shell_manager/which_unix.rb +16 -0
- data/lib/automateit/shell_manager/which_windows.rb +20 -0
- data/lib/automateit/tag_manager.rb +127 -0
- data/lib/automateit/tag_manager/struct.rb +121 -0
- data/lib/automateit/tag_manager/tag_parser.rb +93 -0
- data/lib/automateit/tag_manager/yaml.rb +29 -0
- data/lib/automateit/template_manager.rb +56 -0
- data/lib/automateit/template_manager/base.rb +181 -0
- data/lib/automateit/template_manager/erb.rb +17 -0
- data/lib/ext/metaclass.rb +17 -0
- data/lib/ext/object.rb +18 -0
- data/lib/ext/shell_escape.rb +7 -0
- data/lib/hashcache.rb +22 -0
- data/lib/helpful_erb.rb +63 -0
- data/lib/inactive_support.rb +53 -0
- data/lib/inactive_support/basic_object.rb +6 -0
- data/lib/inactive_support/clean_logger.rb +127 -0
- data/lib/inactive_support/core_ext/array/extract_options.rb +19 -0
- data/lib/inactive_support/core_ext/blank.rb +50 -0
- data/lib/inactive_support/core_ext/class/attribute_accessors.rb +48 -0
- data/lib/inactive_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/inactive_support/core_ext/enumerable.rb +63 -0
- data/lib/inactive_support/core_ext/hash/keys.rb +54 -0
- data/lib/inactive_support/core_ext/module/aliasing.rb +70 -0
- data/lib/inactive_support/core_ext/numeric/time.rb +91 -0
- data/lib/inactive_support/core_ext/string/inflections.rb +153 -0
- data/lib/inactive_support/core_ext/symbol.rb +14 -0
- data/lib/inactive_support/core_ext/time/conversions.rb +96 -0
- data/lib/inactive_support/duration.rb +96 -0
- data/lib/inactive_support/inflections.rb +53 -0
- data/lib/inactive_support/inflector.rb +282 -0
- data/lib/nested_error.rb +33 -0
- data/lib/nitpick.rb +33 -0
- data/lib/queued_logger.rb +68 -0
- data/lib/tempster.rb +250 -0
- data/misc/index_gem_repository.rb +304 -0
- data/misc/setup_egg.rb +12 -0
- data/misc/setup_gem_dependencies.sh +6 -0
- data/misc/setup_rubygems.sh +21 -0
- metadata +279 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
# == FieldManager
|
2
|
+
#
|
3
|
+
# The FieldManager provides a way of accessing a hash of constants. These are
|
4
|
+
# useful for storing configuration data seperately from recipes. # Fields are
|
5
|
+
# typically stored in a Project's <tt>config/fields.yml</tt> file.
|
6
|
+
#
|
7
|
+
# Fields can also be queried from the Unix shell using +aifield+, run
|
8
|
+
# <tt>aifield --help</tt> for details.
|
9
|
+
class AutomateIt::FieldManager < AutomateIt::Plugin::Manager
|
10
|
+
alias_methods :lookup
|
11
|
+
|
12
|
+
# Lookup a field.
|
13
|
+
#
|
14
|
+
# For example, consider a <tt>field.yml</tt> that contains YAML like:
|
15
|
+
# foo: bar
|
16
|
+
# my_app:
|
17
|
+
# my_key: my_value
|
18
|
+
#
|
19
|
+
# With the above file, we can query the fields like this:
|
20
|
+
# lookup(:foo) # => "bar"
|
21
|
+
# lookup("foo") # => "bar"
|
22
|
+
# lookup("my_app#my_key") # => "my_value"
|
23
|
+
# lookup("my_app#my_branch") # => "my_value"
|
24
|
+
#
|
25
|
+
# You can get a reference to the entire hash:
|
26
|
+
# lookup("*")
|
27
|
+
#
|
28
|
+
# If a field isn't found, a IndexError is raised.
|
29
|
+
def lookup(search=nil) dispatch(search) end
|
30
|
+
end
|
31
|
+
|
32
|
+
# == FieldManager::BaseDriver
|
33
|
+
#
|
34
|
+
# Base class for all FieldManager drivers.
|
35
|
+
class AutomateIt::FieldManager::BaseDriver < AutomateIt::Plugin::Driver
|
36
|
+
end
|
37
|
+
|
38
|
+
# == FieldManager::Struct
|
39
|
+
#
|
40
|
+
# A FileManager driver that queries a data structure.
|
41
|
+
class AutomateIt::FieldManager::Struct < AutomateIt::FieldManager::BaseDriver
|
42
|
+
depends_on :nothing
|
43
|
+
|
44
|
+
def suitability(method, *args) # :nodoc:
|
45
|
+
return 1
|
46
|
+
end
|
47
|
+
|
48
|
+
# Options:
|
49
|
+
# * :struct -- Hash to use as the fields data structure.
|
50
|
+
def setup(opts={})
|
51
|
+
super(opts)
|
52
|
+
|
53
|
+
if opts[:struct]
|
54
|
+
@struct = opts[:struct]
|
55
|
+
else
|
56
|
+
@struct ||= {}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# See FieldManager#lookup
|
61
|
+
def lookup(search=nil)
|
62
|
+
return @struct if search.nil? or search == "*"
|
63
|
+
ref = @struct
|
64
|
+
for key in search.to_s.split("#")
|
65
|
+
ref = ref[key]
|
66
|
+
end
|
67
|
+
if ref
|
68
|
+
return ref
|
69
|
+
else
|
70
|
+
raise IndexError.new("can't find value for: #{search}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# == FieldManager::YAML
|
76
|
+
#
|
77
|
+
# A FieldManager driver that reads its data structure from a file.
|
78
|
+
class AutomateIt::FieldManager::YAML < AutomateIt::FieldManager::Struct
|
79
|
+
depends_on :nothing
|
80
|
+
|
81
|
+
def suitability(method, *args) # :nodoc:
|
82
|
+
return 5
|
83
|
+
end
|
84
|
+
|
85
|
+
# Options:
|
86
|
+
# * :file -- Filename to read data structure from. Contents will be
|
87
|
+
# parsed with ERB and then handed to YAML.
|
88
|
+
def setup(opts={})
|
89
|
+
if filename = opts.delete(:file)
|
90
|
+
contents = _read(filename)
|
91
|
+
binder = interpreter.send(:binding)
|
92
|
+
output = HelpfulERB.new(contents, filename).result(binder)
|
93
|
+
|
94
|
+
opts[:struct] = ::YAML::load(output)
|
95
|
+
end
|
96
|
+
super(opts)
|
97
|
+
end
|
98
|
+
|
99
|
+
def _read(filename)
|
100
|
+
return File.read(filename)
|
101
|
+
end
|
102
|
+
private :_read
|
103
|
+
end
|
@@ -0,0 +1,631 @@
|
|
1
|
+
module AutomateIt
|
2
|
+
# == Interpreter
|
3
|
+
#
|
4
|
+
# The Interpreter runs AutomateIt commands.
|
5
|
+
#
|
6
|
+
# The TUTORIAL.txt[link:files/TUTORIAL_txt.html] file provides hands-on examples
|
7
|
+
# for using the Interpreter.
|
8
|
+
#
|
9
|
+
# === Aliased methods
|
10
|
+
#
|
11
|
+
# The Interpreter provides shortcut aliases for certain plugin commands.
|
12
|
+
#
|
13
|
+
# For example, the following commands will run the same method:
|
14
|
+
#
|
15
|
+
# shell_manager.sh "ls"
|
16
|
+
#
|
17
|
+
# sh "ls"
|
18
|
+
#
|
19
|
+
# The full set of aliased methods:
|
20
|
+
#
|
21
|
+
# * cd -- AutomateIt::ShellManager#cd
|
22
|
+
# * chmod -- AutomateIt::ShellManager#chmod
|
23
|
+
# * chmod_R -- AutomateIt::ShellManager#chmod_R
|
24
|
+
# * chown -- AutomateIt::ShellManager#chown
|
25
|
+
# * chown_R -- AutomateIt::ShellManager#chown_R
|
26
|
+
# * chperm -- AutomateIt::ShellManager#chperm
|
27
|
+
# * cp -- AutomateIt::ShellManager#cp
|
28
|
+
# * cp_r -- AutomateIt::ShellManager#cp_r
|
29
|
+
# * edit -- AutomateIt::EditManager#edit
|
30
|
+
# * download -- AutomateIt::DownloadManager#download
|
31
|
+
# * hosts_tagged_with -- AutomateIt::TagManager#hosts_tagged_with
|
32
|
+
# * install -- AutomateIt::ShellManager#install
|
33
|
+
# * ln -- AutomateIt::ShellManager#ln
|
34
|
+
# * ln_s -- AutomateIt::ShellManager#ln_s
|
35
|
+
# * ln_sf -- AutomateIt::ShellManager#ln_sf
|
36
|
+
# * lookup -- AutomateIt::FieldManager#lookup
|
37
|
+
# * mkdir -- AutomateIt::ShellManager#mkdir
|
38
|
+
# * mkdir_p -- AutomateIt::ShellManager#mkdir_p
|
39
|
+
# * mktemp -- AutomateIt::ShellManager#mktemp
|
40
|
+
# * mktempdir -- AutomateIt::ShellManager#mktempdir
|
41
|
+
# * mktempdircd -- AutomateIt::ShellManager#mktempdircd
|
42
|
+
# * mv -- AutomateIt::ShellManager#mv
|
43
|
+
# * pwd -- AutomateIt::ShellManager#pwd
|
44
|
+
# * render -- AutomateIt::TemplateManager#render
|
45
|
+
# * rm -- AutomateIt::ShellManager#rm
|
46
|
+
# * rm_r -- AutomateIt::ShellManager#rm_r
|
47
|
+
# * rm_rf -- AutomateIt::ShellManager#rm_rf
|
48
|
+
# * rmdir -- AutomateIt::ShellManager#rmdir
|
49
|
+
# * sh -- AutomateIt::ShellManager#sh
|
50
|
+
# * tagged? -- AutomateIt::TagManager#tagged?
|
51
|
+
# * tags -- AutomateIt::TagManager#tags
|
52
|
+
# * tags_for -- AutomateIt::TagManager#tags_for
|
53
|
+
# * touch -- AutomateIt::ShellManager#touch
|
54
|
+
# * umask -- AutomateIt::ShellManager#umask
|
55
|
+
# * which -- AutomateIt::ShellManager#which
|
56
|
+
# * which! -- AutomateIt::ShellManager#which!
|
57
|
+
#
|
58
|
+
# === Embedding the Interpreter
|
59
|
+
#
|
60
|
+
# The AutomateIt Interpreter can be embedded inside a Ruby program:
|
61
|
+
#
|
62
|
+
# require 'rubygems'
|
63
|
+
# require 'automateit'
|
64
|
+
#
|
65
|
+
# interpreter = AutomateIt.new
|
66
|
+
#
|
67
|
+
# # Use the interpreter as an object:
|
68
|
+
# interpreter.sh "ls -la"
|
69
|
+
#
|
70
|
+
# # Have it execute a recipe:
|
71
|
+
# interpreter.invoke "myrecipe.rb"
|
72
|
+
#
|
73
|
+
# # Or execute recipes within a block
|
74
|
+
# interpreter.instance_eval do
|
75
|
+
# puts superuser?
|
76
|
+
# sh "ls -la"
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# See the #include_in and #add_method_missing_to methods for instructions on
|
80
|
+
# how to more easily dispatch commands from your program to the Interpreter
|
81
|
+
# instance.
|
82
|
+
class Interpreter < Common
|
83
|
+
include Nitpick
|
84
|
+
|
85
|
+
# Plugin instance that instantiated the Interpreter.
|
86
|
+
attr_accessor :parent
|
87
|
+
private :parent
|
88
|
+
private :parent=
|
89
|
+
|
90
|
+
# Access IRB instance from an interactive shell.
|
91
|
+
attr_accessor :irb
|
92
|
+
|
93
|
+
# Project path for this Interpreter. If no path is available, nil.
|
94
|
+
attr_accessor :project
|
95
|
+
|
96
|
+
# Hash of parameters to make available to the Interpreter. Mostly useful
|
97
|
+
# when needing to pass arguments to an embedded Interpreter before doing an
|
98
|
+
# #instance_eval.
|
99
|
+
attr_accessor :params
|
100
|
+
|
101
|
+
# The Interpreter throws friendly error messages by default that make it
|
102
|
+
# easier to see what's wrong with a recipe. These friendly messages display
|
103
|
+
# the cause, a snapshot of the problematic code, shortened paths, and only
|
104
|
+
# the relevant stack frames.
|
105
|
+
#
|
106
|
+
# However, if there's a bug in the AutomateIt internals, these friendly
|
107
|
+
# messages may inadvertently hide the cause, and it may be necessary to
|
108
|
+
# turn them off to figure out what's wrong.
|
109
|
+
#
|
110
|
+
# To turn off friendly exceptions:
|
111
|
+
#
|
112
|
+
# # From a recipe or the AutomateIt interactive shell:
|
113
|
+
# self.friendly_exceptions = false
|
114
|
+
#
|
115
|
+
# # For an embedded interpreter at instantiation:
|
116
|
+
# AutomateIt.new(:friendly_exceptions => false)
|
117
|
+
#
|
118
|
+
# # From the UNIX command line when invoking a recipe:
|
119
|
+
# automateit --trace myrecipe.rb
|
120
|
+
attr_accessor :friendly_exceptions
|
121
|
+
|
122
|
+
# Setup the Interpreter. This method is also called from Interpreter#new.
|
123
|
+
#
|
124
|
+
# Options for users:
|
125
|
+
# * :verbosity -- Alias for :log_level
|
126
|
+
# * :log_level -- Log level to use, defaults to Logger::INFO.
|
127
|
+
# * :preview -- Turn on preview mode, defaults to false.
|
128
|
+
# * :project -- Project directory to use.
|
129
|
+
# * :tags -- Array of tags to add to this run.
|
130
|
+
#
|
131
|
+
# Options for internal use:
|
132
|
+
# * :parent -- Parent plugin instance.
|
133
|
+
# * :log -- QueuedLogger instance.
|
134
|
+
# * :guessed_project -- Boolean of whether the project path was guessed. If
|
135
|
+
# guessed, won't throw exceptions if project wasn't found at the
|
136
|
+
# specified path. If not guessed, will throw exception in such a
|
137
|
+
# situation.
|
138
|
+
# * :friendly_exceptions -- Throw user-friendly exceptions that make it
|
139
|
+
# easier to see errors in recipes, defaults to true.
|
140
|
+
def setup(opts={})
|
141
|
+
super(opts.merge(:interpreter => self))
|
142
|
+
|
143
|
+
self.params ||= {}
|
144
|
+
|
145
|
+
if opts[:irb]
|
146
|
+
@irb = opts[:irb]
|
147
|
+
end
|
148
|
+
|
149
|
+
if opts[:parent]
|
150
|
+
@parent = opts[:parent]
|
151
|
+
end
|
152
|
+
|
153
|
+
if opts[:log]
|
154
|
+
@log = opts[:log]
|
155
|
+
elsif not defined?(@log) or @log.nil?
|
156
|
+
@log = QueuedLogger.new($stdout)
|
157
|
+
@log.level = Logger::INFO
|
158
|
+
end
|
159
|
+
|
160
|
+
if opts[:log_level] or opts[:verbosity]
|
161
|
+
@log.level = opts[:log_level] || opts[:verbosity]
|
162
|
+
end
|
163
|
+
|
164
|
+
if opts[:preview].nil? # can be false
|
165
|
+
self.preview = false unless preview?
|
166
|
+
else
|
167
|
+
self.preview = opts[:preview]
|
168
|
+
end
|
169
|
+
|
170
|
+
if opts[:friendly_exceptions].nil?
|
171
|
+
@friendly_exceptions = true unless defined?(@friendly_exceptions)
|
172
|
+
else
|
173
|
+
@friendly_exceptions = opts[:friendly_exceptions]
|
174
|
+
end
|
175
|
+
|
176
|
+
# Instantiate core plugins so they're available to the project
|
177
|
+
_instantiate_plugins
|
178
|
+
|
179
|
+
# Add optional run-time tags
|
180
|
+
tags.merge(opts[:tags]) if opts[:tags]
|
181
|
+
|
182
|
+
if project_path = opts[:project] || ENV["AUTOMATEIT_PROJECT"] || ENV["AIP"]
|
183
|
+
# Only load a project if we find its env file
|
184
|
+
env_file = File.join(project_path, "config", "automateit_env.rb")
|
185
|
+
if File.exists?(env_file)
|
186
|
+
@project = File.expand_path(project_path)
|
187
|
+
log.debug(PNOTE+"Loading project from path: #{@project}")
|
188
|
+
|
189
|
+
lib_files = Dir[File.join(@project, "lib", "*.rb")] + Dir[File.join(@project, "lib", "**", "init.rb")]
|
190
|
+
lib_files.each do |lib|
|
191
|
+
log.debug(PNOTE+"Loading project library: #{lib}")
|
192
|
+
invoke(lib)
|
193
|
+
end
|
194
|
+
|
195
|
+
tag_file = File.join(@project, "config", "tags.yml")
|
196
|
+
if File.exists?(tag_file)
|
197
|
+
log.debug(PNOTE+"Loading project tags: #{tag_file}")
|
198
|
+
tag_manager[:yaml].setup(:file => tag_file)
|
199
|
+
end
|
200
|
+
|
201
|
+
field_file = File.join(@project, "config", "fields.yml")
|
202
|
+
if File.exists?(field_file)
|
203
|
+
log.debug(PNOTE+"Loading project fields: #{field_file}")
|
204
|
+
field_manager[:yaml].setup(:file => field_file)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Instantiate project's plugins so they're available to the environment
|
208
|
+
_instantiate_plugins
|
209
|
+
|
210
|
+
if File.exists?(env_file)
|
211
|
+
log.debug(PNOTE+"Loading project env: #{env_file}")
|
212
|
+
invoke(env_file)
|
213
|
+
end
|
214
|
+
elsif not opts[:guessed_project]
|
215
|
+
raise ArgumentError.new("Couldn't find project at: #{project_path}")
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Hash of plugin tokens to plugin instances for this Interpreter.
|
221
|
+
attr_accessor :plugins
|
222
|
+
|
223
|
+
def _instantiate_plugins
|
224
|
+
@plugins ||= {}
|
225
|
+
# If a parent is defined, use it to prep the list and avoid re-instantiating it.
|
226
|
+
if defined?(@parent) and @parent and Plugin::Manager === @parent
|
227
|
+
@plugins[@parent.class.token] = @parent
|
228
|
+
end
|
229
|
+
plugin_classes = AutomateIt::Plugin::Manager.classes.reject{|t| t == @parent if @parent}
|
230
|
+
for klass in plugin_classes
|
231
|
+
_instantiate_plugin(klass)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
private :_instantiate_plugins
|
235
|
+
|
236
|
+
def _instantiate_plugin(klass)
|
237
|
+
token = klass.token
|
238
|
+
unless plugin = @plugins[token]
|
239
|
+
plugin = @plugins[token] = klass.new(:interpreter => self)
|
240
|
+
#puts "!!! ip #{token}"
|
241
|
+
unless respond_to?(token.to_sym)
|
242
|
+
self.class.send(:define_method, token) do
|
243
|
+
@plugins[token]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
_expose_plugin_methods(plugin)
|
247
|
+
end
|
248
|
+
plugin.instantiate_drivers
|
249
|
+
end
|
250
|
+
private :_instantiate_plugin
|
251
|
+
|
252
|
+
def _expose_plugin_methods(plugin)
|
253
|
+
return unless plugin.class.aliased_methods
|
254
|
+
plugin.class.aliased_methods.each do |method|
|
255
|
+
#puts "!!! epm #{method}"
|
256
|
+
unless respond_to?(method.to_sym)
|
257
|
+
# Must use instance_eval because methods created with define_method
|
258
|
+
# can't accept block as argument. This is a known Ruby 1.8 bug.
|
259
|
+
self.instance_eval <<-EOB
|
260
|
+
def #{method}(*args, &block)
|
261
|
+
@plugins[:#{plugin.class.token}].send(:#{method}, *args, &block)
|
262
|
+
end
|
263
|
+
EOB
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
private :_expose_plugin_methods
|
268
|
+
|
269
|
+
# Set the QueuedLogger instance for the Interpreter.
|
270
|
+
attr_writer :log
|
271
|
+
|
272
|
+
# Get or set the QueuedLogger instance for the Interpreter, a special
|
273
|
+
# wrapper around the Ruby Logger.
|
274
|
+
def log(value=nil)
|
275
|
+
if value.nil?
|
276
|
+
return defined?(@log) ? @log : nil
|
277
|
+
else
|
278
|
+
@log = value
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Set preview mode to +value+. See warnings in ShellManager to learn how to
|
283
|
+
# correctly write code for preview mode.
|
284
|
+
def preview(value)
|
285
|
+
self.preview = value
|
286
|
+
end
|
287
|
+
|
288
|
+
# Is Interpreter running in preview mode?
|
289
|
+
def preview?
|
290
|
+
@preview
|
291
|
+
end
|
292
|
+
|
293
|
+
# Preview a block of custom commands. When in preview mode, displays the
|
294
|
+
# +message+ but doesn't execute the +block+. When not previewing, will
|
295
|
+
# execute the block and not display the +message+.
|
296
|
+
#
|
297
|
+
# For example:
|
298
|
+
#
|
299
|
+
# preview_for("FOO") do
|
300
|
+
# puts "BAR"
|
301
|
+
# end
|
302
|
+
#
|
303
|
+
# In preview mode, this displays:
|
304
|
+
#
|
305
|
+
# => FOO
|
306
|
+
#
|
307
|
+
# When not previewing, displays:
|
308
|
+
#
|
309
|
+
# BAR
|
310
|
+
def preview_for(message, &block)
|
311
|
+
if preview?
|
312
|
+
log.info(message)
|
313
|
+
:preview
|
314
|
+
else
|
315
|
+
block.call
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# Set preview mode to +value.
|
320
|
+
def preview=(value)
|
321
|
+
@preview = value
|
322
|
+
end
|
323
|
+
|
324
|
+
# Set noop (no-operation mode) to +value+. Alias for #preview.
|
325
|
+
def noop(value)
|
326
|
+
self.noop = value
|
327
|
+
end
|
328
|
+
|
329
|
+
# Set noop (no-operation mode) to +value+. Alias for #preview=.
|
330
|
+
def noop=(value)
|
331
|
+
self.preview = value
|
332
|
+
end
|
333
|
+
|
334
|
+
# Are we in noop (no-operation) mode? Alias for #preview?.
|
335
|
+
def noop?
|
336
|
+
preview?
|
337
|
+
end
|
338
|
+
|
339
|
+
# Set writing to +value+. This is the opposite of #preview.
|
340
|
+
def writing(value)
|
341
|
+
self.writing = value
|
342
|
+
end
|
343
|
+
|
344
|
+
# Set writing to +value+. This is the opposite of #preview=.
|
345
|
+
def writing=(value)
|
346
|
+
self.preview = !value
|
347
|
+
end
|
348
|
+
|
349
|
+
# Is Interpreter writing? This is the opposite of #preview?.
|
350
|
+
def writing?
|
351
|
+
!preview?
|
352
|
+
end
|
353
|
+
|
354
|
+
# Does this platform provide euid (Effective User ID)?
|
355
|
+
def euid?
|
356
|
+
begin
|
357
|
+
euid
|
358
|
+
return true
|
359
|
+
rescue
|
360
|
+
return false
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Return the effective user id.
|
365
|
+
def euid
|
366
|
+
begin
|
367
|
+
return Process.euid
|
368
|
+
rescue NoMethodError => e
|
369
|
+
output = `id -u 2>&1`
|
370
|
+
raise e unless output and $?.exitstatus.zero?
|
371
|
+
begin
|
372
|
+
return output.match(/(\d+)/)[1].to_i
|
373
|
+
rescue IndexError
|
374
|
+
raise e
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
# Does the current user have superuser (root) privileges?
|
381
|
+
def superuser?
|
382
|
+
euid.zero?
|
383
|
+
end
|
384
|
+
|
385
|
+
# Create an Interpreter with the specified +opts+ and invoke
|
386
|
+
# the +recipe+. The opts are passed to #setup for parsing.
|
387
|
+
def self.invoke(recipe, opts={})
|
388
|
+
opts[:project] ||= File.join(File.dirname(recipe), "..")
|
389
|
+
AutomateIt.new(opts).invoke(recipe)
|
390
|
+
end
|
391
|
+
|
392
|
+
# Invoke the +recipe+. The recipe may be expressed as a relative or fully
|
393
|
+
# qualified path. When invoked within a project, the recipe can also be the
|
394
|
+
# name of a recipe.
|
395
|
+
#
|
396
|
+
# Example:
|
397
|
+
# invoke "/tmp/recipe.rb" # Run "/tmp/recipe.rb"
|
398
|
+
# invoke "recipe.rb" # Run "./recipe.rb". If not found and in a
|
399
|
+
# # project, will try running "recipes/recipe.rb"
|
400
|
+
# invoke "recipe" # Run "recipes/recipe.rb" in a project
|
401
|
+
def invoke(recipe)
|
402
|
+
filenames = [recipe]
|
403
|
+
filenames << File.join(project, "recipes", recipe) if project
|
404
|
+
filenames << File.join(project, "recipes", recipe + ".rb") if project
|
405
|
+
|
406
|
+
for filename in filenames
|
407
|
+
log.debug(PNOTE+" invoking "+filename)
|
408
|
+
if File.exists?(filename)
|
409
|
+
data = File.read(filename)
|
410
|
+
begin
|
411
|
+
return instance_eval(data, filename, 1)
|
412
|
+
rescue Exception => e
|
413
|
+
if @friendly_exceptions
|
414
|
+
# TODO Extract this routine and its companion in HelpfulERB
|
415
|
+
|
416
|
+
# Capture initial stack in case we add a debug/breakpoint after this
|
417
|
+
stack = caller
|
418
|
+
|
419
|
+
# Extract trace for recipe after the Interpreter#invoke call
|
420
|
+
preresult = []
|
421
|
+
for line in e.backtrace
|
422
|
+
# Stop at the Interpreter#invoke call
|
423
|
+
break if line == stack.first
|
424
|
+
preresult << line
|
425
|
+
end
|
426
|
+
|
427
|
+
# Extract the recipe filename
|
428
|
+
preresult.last.match(/^([^:]+):(\d+):in `invoke'/)
|
429
|
+
recipe = $1
|
430
|
+
|
431
|
+
# Extract trace for most recent block
|
432
|
+
result = []
|
433
|
+
for line in preresult
|
434
|
+
# Ignore manager wrapper and dispatch methods
|
435
|
+
next if line =~ %r{lib/automateit/.+manager\.rb:\d+:in `.+'$}
|
436
|
+
result << line
|
437
|
+
# Stop at the first mention of this recipe
|
438
|
+
break if line =~ /^#{recipe}/
|
439
|
+
end
|
440
|
+
|
441
|
+
# Extract line number
|
442
|
+
if e.is_a?(SyntaxError)
|
443
|
+
line_number = e.message.match(/^[^:]+:(\d+):/)[1].to_i
|
444
|
+
else
|
445
|
+
result.last.match(/^([^:]+):(\d+):in `invoke'/)
|
446
|
+
line_number = $2.to_i
|
447
|
+
end
|
448
|
+
|
449
|
+
msg = "Problem with recipe '#{recipe}' at line #{line_number}\n"
|
450
|
+
|
451
|
+
# Extract recipe text
|
452
|
+
begin
|
453
|
+
lines = File.read(recipe).split(/\n/)
|
454
|
+
|
455
|
+
min = line_number - 7
|
456
|
+
min = 0 if min < 0
|
457
|
+
|
458
|
+
max = line_number + 1
|
459
|
+
max = lines.size if max > lines.size
|
460
|
+
|
461
|
+
width = max.to_s.size
|
462
|
+
|
463
|
+
for i in min..max
|
464
|
+
n = i+1
|
465
|
+
marker = n == line_number ? "*" : ""
|
466
|
+
msg << "\n%2s %#{width}i %s" % [marker, n, lines[i]]
|
467
|
+
end
|
468
|
+
|
469
|
+
msg << "\n"
|
470
|
+
rescue Exception => e
|
471
|
+
# Ignore
|
472
|
+
end
|
473
|
+
|
474
|
+
msg << "\n(#{e.exception.class}) #{e.message}"
|
475
|
+
|
476
|
+
# Append shortened trace
|
477
|
+
for line in result
|
478
|
+
msg << "\n "+line
|
479
|
+
end
|
480
|
+
|
481
|
+
# Remove project path
|
482
|
+
msg.gsub!(/#{@project}\/?/, '') if @project
|
483
|
+
|
484
|
+
raise AutomateIt::Error.new(msg, e)
|
485
|
+
else
|
486
|
+
raise e
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
raise Errno::ENOENT.new(recipe)
|
492
|
+
end
|
493
|
+
|
494
|
+
# Path of this project's "dist" directory. If a project isn't available or
|
495
|
+
# the directory doesn't exist, this will throw a NotImplementedError.
|
496
|
+
def dist
|
497
|
+
if @project
|
498
|
+
result = File.join(@project, "dist/")
|
499
|
+
if File.directory?(result)
|
500
|
+
return result
|
501
|
+
else
|
502
|
+
raise NotImplementedError.new("can't find dist directory at: #{result}")
|
503
|
+
end
|
504
|
+
else
|
505
|
+
raise NotImplementedError.new("can't use dist without a project")
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
# Set value to share throughout the Interpreter. Use this instead of
|
510
|
+
# globals so that different Interpreters don't see each other's variables.
|
511
|
+
# Creates a method that returns the value and also adds a #params entry.
|
512
|
+
#
|
513
|
+
# Example:
|
514
|
+
# set :asdf, 9 # => 9
|
515
|
+
# asdf # => 9
|
516
|
+
#
|
517
|
+
# This is best used for frequently-used variables, like paths. For
|
518
|
+
# infrequently-used variables, use #lookup and #params. A good place to use
|
519
|
+
# the #set is in the Project's <tt>config/automateit_env.rb</tt> file so
|
520
|
+
# that paths are exposed to all recipes like this:
|
521
|
+
#
|
522
|
+
# set :helpers, project+"/helpers"
|
523
|
+
def set(key, value)
|
524
|
+
key = key.to_sym
|
525
|
+
params[key] = value
|
526
|
+
eval <<-HERE
|
527
|
+
def #{key}
|
528
|
+
return params[:#{key}]
|
529
|
+
end
|
530
|
+
HERE
|
531
|
+
value
|
532
|
+
end
|
533
|
+
|
534
|
+
# Retrieve a #params entry.
|
535
|
+
#
|
536
|
+
# Example:
|
537
|
+
# params[:foo] = "bar" # => "bar"
|
538
|
+
# get :foo # => "bar"
|
539
|
+
def get(key)
|
540
|
+
params[key.to_sym]
|
541
|
+
end
|
542
|
+
|
543
|
+
# Creates wrapper methods in +object+ to dispatch calls to an Interpreter instance.
|
544
|
+
#
|
545
|
+
# *WARNING*: This will overwrite all methods and variables in the target +object+ that have the same names as the Interpreter's methods. You should considerer specifying the +methods+ to limit the number of methods included to minimize surprises due to collisions. If +methods+ is left blank, will create wrappers for all Interpreter methods.
|
546
|
+
#
|
547
|
+
# For example, include an Interpreter instance into a Rake session, which will override the FileUtils commands with AutomateIt equivalents:
|
548
|
+
#
|
549
|
+
# # Rakefile
|
550
|
+
#
|
551
|
+
# require 'automateit'
|
552
|
+
# @ai = AutomateIt.new
|
553
|
+
# @ai.include_in(self, %w(preview? sh)) # Include #preview? and #sh methods
|
554
|
+
#
|
555
|
+
# task :default do
|
556
|
+
# puts preview? # Uses Interpreter#preview?
|
557
|
+
# sh "id" # Uses Interpreter#sh, not FileUtils#sh
|
558
|
+
# cp "foo", "bar" # Uses FileUtils#cp, not Interpreter#cp
|
559
|
+
# end
|
560
|
+
#
|
561
|
+
# For situations where you don't want to override any existing methods, consider using #add_method_missing_to.
|
562
|
+
def include_in(object, *methods)
|
563
|
+
methods = [methods].flatten
|
564
|
+
methods = unique_methods.reject{|t| t.to_s =~ /^_/} if methods.empty?
|
565
|
+
|
566
|
+
object.instance_variable_set(:@__automateit, self)
|
567
|
+
|
568
|
+
for method in methods
|
569
|
+
object.instance_eval <<-HERE
|
570
|
+
def #{method}(*args, &block)
|
571
|
+
@__automateit.send(:#{method}, *args, &block)
|
572
|
+
end
|
573
|
+
HERE
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
# Creates #method_missing in +object+ that dispatches calls to an Interpreter instance. If a #method_missing is already present, it will be preserved as a fall-back using #alias_method_chain.
|
578
|
+
#
|
579
|
+
# For example, add #method_missing to a Rake session to provide direct access to Interpreter instance's methods whose names don't conflict with the names existing variables and methods:
|
580
|
+
#
|
581
|
+
# # Rakefile
|
582
|
+
#
|
583
|
+
# require 'automateit'
|
584
|
+
# @ai = AutomateIt.new
|
585
|
+
# @ai.add_method_missing_to(self)
|
586
|
+
#
|
587
|
+
# task :default do
|
588
|
+
# puts preview? # Uses Interpreter#preview?
|
589
|
+
# sh "id" # Uses FileUtils#sh, not Interpreter#sh
|
590
|
+
# end
|
591
|
+
#
|
592
|
+
# For situations where it's necessary to override existing methods, such as the +sh+ call in the example, consider using #include_in.
|
593
|
+
def add_method_missing_to(object)
|
594
|
+
object.instance_variable_set(:@__automateit, self)
|
595
|
+
chain = object.respond_to?(:method_missing)
|
596
|
+
|
597
|
+
# XXX The solution below is evil and ugly, but I don't know how else to solve this. The problem is that I want to *only* alter the +object+ instance, and NOT its class. Unfortunately, #alias_method and #alias_method_chain only operate on classes, not instances, which makes them useless for this task.
|
598
|
+
|
599
|
+
template = <<-HERE
|
600
|
+
def method_missing<%=chain ? '_with_automateit' : ''%>(method, *args, &block)
|
601
|
+
### puts "mm+a(%s, %s)" % [method, args.inspect]
|
602
|
+
if @__automateit.respond_to?(method)
|
603
|
+
@__automateit.send(method, *args, &block)
|
604
|
+
else
|
605
|
+
<%-if chain-%>
|
606
|
+
method_missing_without_automateit(method, *args, &block)
|
607
|
+
<%-else-%>
|
608
|
+
super
|
609
|
+
<%-end-%>
|
610
|
+
end
|
611
|
+
end
|
612
|
+
<%-if chain-%>
|
613
|
+
@__method_missing_without_automateit = self.method(:method_missing)
|
614
|
+
|
615
|
+
def method_missing_without_automateit(*args)
|
616
|
+
### puts "mm-a %s" % args.inspect
|
617
|
+
@__method_missing_without_automateit.call(*args)
|
618
|
+
end
|
619
|
+
|
620
|
+
def method_missing(*args)
|
621
|
+
### puts "mm %s" % args.inspect
|
622
|
+
method_missing_with_automateit(*args)
|
623
|
+
end
|
624
|
+
<%-end-%>
|
625
|
+
HERE
|
626
|
+
|
627
|
+
text = ::HelpfulERB.new(template).result(binding)
|
628
|
+
object.instance_eval(text)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|