Class: AutomateIt::Interpreter
- Includes:
- Nitpick
- Defined in:
- lib/automateit/interpreter.rb
Overview
Interpreter
The Interpreter runs AutomateIt commands.
The TUTORIAL.txt file provides hands-on examples for using the Interpreter.
Aliased methods
The Interpreter provides shortcut aliases for certain plugin commands.
For example, the following commands will run the same method:
shell_manager.sh "ls"
sh "ls"
The full set of aliased methods:
-
cd – AutomateIt::ShellManager#cd
-
chmod – AutomateIt::ShellManager#chmod
-
chmod_R – AutomateIt::ShellManager#chmod_R
-
chown – AutomateIt::ShellManager#chown
-
chown_R – AutomateIt::ShellManager#chown_R
-
chperm – AutomateIt::ShellManager#chperm
-
cp – AutomateIt::ShellManager#cp
-
cp_r – AutomateIt::ShellManager#cp_r
-
edit – AutomateIt::EditManager#edit
-
download – AutomateIt::DownloadManager#download
-
hosts_tagged_with – AutomateIt::TagManager#hosts_tagged_with
-
install – AutomateIt::ShellManager#install
-
ln – AutomateIt::ShellManager#ln
-
ln_s – AutomateIt::ShellManager#ln_s
-
ln_sf – AutomateIt::ShellManager#ln_sf
-
lookup – AutomateIt::FieldManager#lookup
-
mkdir – AutomateIt::ShellManager#mkdir
-
mkdir_p – AutomateIt::ShellManager#mkdir_p
-
mktemp – AutomateIt::ShellManager#mktemp
-
mktempdir – AutomateIt::ShellManager#mktempdir
-
mktempdircd – AutomateIt::ShellManager#mktempdircd
-
mv – AutomateIt::ShellManager#mv
-
pwd – AutomateIt::ShellManager#pwd
-
render – AutomateIt::TemplateManager#render
-
rm – AutomateIt::ShellManager#rm
-
rm_r – AutomateIt::ShellManager#rm_r
-
rm_rf – AutomateIt::ShellManager#rm_rf
-
rmdir – AutomateIt::ShellManager#rmdir
-
sh – AutomateIt::ShellManager#sh
-
tagged? – AutomateIt::TagManager#tagged?
-
tags – AutomateIt::TagManager#tags
-
tags_for – AutomateIt::TagManager#tags_for
-
touch – AutomateIt::ShellManager#touch
-
umask – AutomateIt::ShellManager#umask
-
which – AutomateIt::ShellManager#which
-
which! – AutomateIt::ShellManager#which!
Embedding the Interpreter
The AutomateIt Interpreter can be embedded inside a Ruby program:
require 'rubygems'
require 'automateit'
interpreter = AutomateIt.new
# Use the interpreter as an object:
interpreter.sh "ls -la"
# Have it execute a recipe:
interpreter.invoke "myrecipe.rb"
# Or execute recipes within a block
interpreter.instance_eval do
puts superuser?
sh "ls -la"
end
See the #include_in and #add_method_missing_to methods for instructions on how to more easily dispatch commands from your program to the Interpreter instance.
Constant Summary
Constants included from Constants
Constants::HELPERS_DIR, Constants::INSTALL_DIR, Constants::PERROR, Constants::PEXEC, Constants::PNOTE, Constants::WARNING_BOILERPLATE
Instance Attribute Summary collapse
-
#friendly_exceptions ⇒ Object
The Interpreter throws friendly error messages by default that make it easier to see what’s wrong with a recipe.
-
#irb ⇒ Object
Access IRB instance from an interactive shell.
-
#log(value = nil) ⇒ Object
Get or set the QueuedLogger instance for the Interpreter, a special wrapper around the Ruby Logger.
-
#params ⇒ Object
Hash of parameters to make available to the Interpreter.
-
#plugins ⇒ Object
Hash of plugin tokens to plugin instances for this Interpreter.
-
#project ⇒ Object
Project path for this Interpreter.
Attributes inherited from Common
Class Method Summary collapse
-
.invoke(recipe, opts = {}) ⇒ Object
Create an Interpreter with the specified
opts
and invoke therecipe
.
Instance Method Summary collapse
-
#add_method_missing_to(object) ⇒ Object
Creates #method_missing in
object
that dispatches calls to an Interpreter instance. -
#dist ⇒ Object
Path of this project’s “dist” directory.
-
#euid ⇒ Object
Return the effective user id.
-
#euid? ⇒ Boolean
Does this platform provide euid (Effective User ID)?.
-
#get(key) ⇒ Object
Retrieve a #params entry.
-
#include_in(object, *methods) ⇒ Object
Creates wrapper methods in
object
to dispatch calls to an Interpreter instance. -
#invoke(recipe) ⇒ Object
Invoke the
recipe
. -
#noop(value) ⇒ Object
Set noop (no-operation mode) to
value
. -
#noop=(value) ⇒ Object
Set noop (no-operation mode) to
value
. -
#noop? ⇒ Boolean
Are we in noop (no-operation) mode? Alias for #preview?.
-
#preview(value) ⇒ Object
Set preview mode to
value
. -
#preview=(value) ⇒ Object
Set preview mode to +value.
-
#preview? ⇒ Boolean
Is Interpreter running in preview mode?.
-
#preview_for(message, &block) ⇒ Object
Preview a block of custom commands.
-
#set(key, value) ⇒ Object
Set value to share throughout the Interpreter.
-
#setup(opts = {}) ⇒ Object
Setup the Interpreter.
-
#superuser? ⇒ Boolean
Does the current user have superuser (root) privileges?.
-
#writing(value) ⇒ Object
Set writing to
value
. -
#writing=(value) ⇒ Object
Set writing to
value
. -
#writing? ⇒ Boolean
Is Interpreter writing? This is the opposite of #preview?.
Methods included from Nitpick
Methods included from Nitpick::ClassMethods
Methods inherited from Common
Constructor Details
This class inherits a constructor from AutomateIt::Common
Instance Attribute Details
#friendly_exceptions ⇒ Object
The Interpreter throws friendly error messages by default that make it easier to see what’s wrong with a recipe. These friendly messages display the cause, a snapshot of the problematic code, shortened paths, and only the relevant stack frames.
However, if there’s a bug in the AutomateIt internals, these friendly messages may inadvertently hide the cause, and it may be necessary to turn them off to figure out what’s wrong.
To turn off friendly exceptions:
# From a recipe or the AutomateIt interactive shell:
self.friendly_exceptions = false
# For an embedded interpreter at instantiation:
AutomateIt.new(:friendly_exceptions => false)
# From the UNIX command line when invoking a recipe:
automateit --trace myrecipe.rb
120 121 122 |
# File 'lib/automateit/interpreter.rb', line 120 def friendly_exceptions @friendly_exceptions end |
#irb ⇒ Object
Access IRB instance from an interactive shell.
91 92 93 |
# File 'lib/automateit/interpreter.rb', line 91 def irb @irb end |
#log(value = nil) ⇒ Object
Get or set the QueuedLogger instance for the Interpreter, a special wrapper around the Ruby Logger.
274 275 276 277 278 279 280 |
# File 'lib/automateit/interpreter.rb', line 274 def log(value=nil) if value.nil? return defined?(@log) ? @log : nil else @log = value end end |
#params ⇒ Object
Hash of parameters to make available to the Interpreter. Mostly useful when needing to pass arguments to an embedded Interpreter before doing an #instance_eval.
99 100 101 |
# File 'lib/automateit/interpreter.rb', line 99 def params @params end |
#plugins ⇒ Object
Hash of plugin tokens to plugin instances for this Interpreter.
221 222 223 |
# File 'lib/automateit/interpreter.rb', line 221 def plugins @plugins end |
#project ⇒ Object
Project path for this Interpreter. If no path is available, nil.
94 95 96 |
# File 'lib/automateit/interpreter.rb', line 94 def project @project end |
Class Method Details
.invoke(recipe, opts = {}) ⇒ Object
Create an Interpreter with the specified opts
and invoke the recipe
. The opts are passed to #setup for parsing.
387 388 389 390 |
# File 'lib/automateit/interpreter.rb', line 387 def self.invoke(recipe, opts={}) opts[:project] ||= File.join(File.dirname(recipe), "..") AutomateIt.new(opts).invoke(recipe) end |
Instance Method Details
#add_method_missing_to(object) ⇒ Object
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.
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:
# Rakefile
require 'automateit'
@ai = AutomateIt.new
@ai.add_method_missing_to(self)
task :default do
puts preview? # Uses Interpreter#preview?
sh "id" # Uses FileUtils#sh, not Interpreter#sh
end
For situations where it’s necessary to override existing methods, such as the sh
call in the example, consider using #include_in.
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 |
# File 'lib/automateit/interpreter.rb', line 593 def add_method_missing_to(object) object.instance_variable_set(:@__automateit, self) chain = object.respond_to?(:method_missing) # 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. template = <<-HERE def method_missing<%=chain ? '_with_automateit' : ''%>(method, *args, &block) ### puts "mm+a(%s, %s)" % [method, args.inspect] if @__automateit.respond_to?(method) @__automateit.send(method, *args, &block) else <%-if chain-%> method_missing_without_automateit(method, *args, &block) <%-else-%> super <%-end-%> end end <%-if chain-%> @__method_missing_without_automateit = self.method(:method_missing) def method_missing_without_automateit(*args) ### puts "mm-a %s" % args.inspect @__method_missing_without_automateit.call(*args) end def method_missing(*args) ### puts "mm %s" % args.inspect method_missing_with_automateit(*args) end <%-end-%> HERE text = ::HelpfulERB.new(template).result(binding) object.instance_eval(text) end |
#dist ⇒ Object
Path of this project’s “dist” directory. If a project isn’t available or the directory doesn’t exist, this will throw a NotImplementedError.
496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/automateit/interpreter.rb', line 496 def dist if @project result = File.join(@project, "dist/") if File.directory?(result) return result else raise NotImplementedError.new("can't find dist directory at: #{result}") end else raise NotImplementedError.new("can't use dist without a project") end end |
#euid ⇒ Object
Return the effective user id.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/automateit/interpreter.rb', line 365 def euid begin return Process.euid rescue NoMethodError => e output = `id -u 2>&1` raise e unless output and $?.exitstatus.zero? begin return output.match(/(\d+)/)[1].to_i rescue IndexError raise e end end end |
#euid? ⇒ Boolean
Does this platform provide euid (Effective User ID)?
355 356 357 358 359 360 361 362 |
# File 'lib/automateit/interpreter.rb', line 355 def euid? begin euid return true rescue return false end end |
#get(key) ⇒ Object
Retrieve a #params entry.
Example:
params[:foo] = "bar" # => "bar"
get :foo # => "bar"
539 540 541 |
# File 'lib/automateit/interpreter.rb', line 539 def get(key) params[key.to_sym] end |
#include_in(object, *methods) ⇒ Object
Creates wrapper methods in object
to dispatch calls to an Interpreter instance.
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.
For example, include an Interpreter instance into a Rake session, which will override the FileUtils commands with AutomateIt equivalents:
# Rakefile
require 'automateit'
@ai = AutomateIt.new
@ai.include_in(self, %w(preview? sh)) # Include #preview? and #sh methods
task :default do
puts preview? # Uses Interpreter#preview?
sh "id" # Uses Interpreter#sh, not FileUtils#sh
cp "foo", "bar" # Uses FileUtils#cp, not Interpreter#cp
end
For situations where you don’t want to override any existing methods, consider using #add_method_missing_to.
562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'lib/automateit/interpreter.rb', line 562 def include_in(object, *methods) methods = [methods].flatten methods = unique_methods.reject{|t| t.to_s =~ /^_/} if methods.empty? object.instance_variable_set(:@__automateit, self) for method in methods object.instance_eval <<-HERE def #{method}(*args, &block) @__automateit.send(:#{method}, *args, &block) end HERE end end |
#invoke(recipe) ⇒ Object
Invoke the recipe
. The recipe may be expressed as a relative or fully qualified path. When invoked within a project, the recipe can also be the name of a recipe.
Example:
invoke "/tmp/recipe.rb" # Run "/tmp/recipe.rb"
invoke "recipe.rb" # Run "./recipe.rb". If not found and in a
# project, will try running "recipes/recipe.rb"
invoke "recipe" # Run "recipes/recipe.rb" in a project
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 |
# File 'lib/automateit/interpreter.rb', line 401 def invoke(recipe) filenames = [recipe] filenames << File.join(project, "recipes", recipe) if project filenames << File.join(project, "recipes", recipe + ".rb") if project for filename in filenames log.debug(PNOTE+" invoking "+filename) if File.exists?(filename) data = File.read(filename) begin return instance_eval(data, filename, 1) rescue Exception => e if @friendly_exceptions # TODO Extract this routine and its companion in HelpfulERB # Capture initial stack in case we add a debug/breakpoint after this stack = caller # Extract trace for recipe after the Interpreter#invoke call preresult = [] for line in e.backtrace # Stop at the Interpreter#invoke call break if line == stack.first preresult << line end # Extract the recipe filename preresult.last.match(/^([^:]+):(\d+):in `invoke'/) recipe = $1 # Extract trace for most recent block result = [] for line in preresult # Ignore manager wrapper and dispatch methods next if line =~ %r{lib/automateit/.+manager\.rb:\d+:in `.+'$} result << line # Stop at the first mention of this recipe break if line =~ /^#{recipe}/ end # Extract line number if e.is_a?(SyntaxError) line_number = e..match(/^[^:]+:(\d+):/)[1].to_i else result.last.match(/^([^:]+):(\d+):in `invoke'/) line_number = $2.to_i end msg = "Problem with recipe '#{recipe}' at line #{line_number}\n" # Extract recipe text begin lines = File.read(recipe).split(/\n/) min = line_number - 7 min = 0 if min < 0 max = line_number + 1 max = lines.size if max > lines.size width = max.to_s.size for i in min..max n = i+1 marker = n == line_number ? "*" : "" msg << "\n%2s %#{width}i %s" % [marker, n, lines[i]] end msg << "\n" rescue Exception => e # Ignore end msg << "\n(#{e.exception.class}) #{e.}" # Append shortened trace for line in result msg << "\n "+line end # Remove project path msg.gsub!(/#{@project}\/?/, '') if @project raise AutomateIt::Error.new(msg, e) else raise e end end end end raise Errno::ENOENT.new(recipe) end |
#noop(value) ⇒ Object
Set noop (no-operation mode) to value
. Alias for #preview.
325 326 327 |
# File 'lib/automateit/interpreter.rb', line 325 def noop(value) self.noop = value end |
#noop=(value) ⇒ Object
Set noop (no-operation mode) to value
. Alias for #preview=.
330 331 332 |
# File 'lib/automateit/interpreter.rb', line 330 def noop=(value) self.preview = value end |
#noop? ⇒ Boolean
Are we in noop (no-operation) mode? Alias for #preview?.
335 336 337 |
# File 'lib/automateit/interpreter.rb', line 335 def noop? preview? end |
#preview(value) ⇒ Object
Set preview mode to value
. See warnings in ShellManager to learn how to correctly write code for preview mode.
284 285 286 |
# File 'lib/automateit/interpreter.rb', line 284 def preview(value) self.preview = value end |
#preview=(value) ⇒ Object
Set preview mode to +value.
320 321 322 |
# File 'lib/automateit/interpreter.rb', line 320 def preview=(value) @preview = value end |
#preview? ⇒ Boolean
Is Interpreter running in preview mode?
289 290 291 |
# File 'lib/automateit/interpreter.rb', line 289 def preview? @preview end |
#preview_for(message, &block) ⇒ Object
Preview a block of custom commands. When in preview mode, displays the message
but doesn’t execute the block
. When not previewing, will execute the block and not display the message
.
For example:
preview_for("FOO") do
puts "BAR"
end
In preview mode, this displays:
=> FOO
When not previewing, displays:
BAR
310 311 312 313 314 315 316 317 |
# File 'lib/automateit/interpreter.rb', line 310 def preview_for(, &block) if preview? log.info() :preview else block.call end end |
#set(key, value) ⇒ Object
Set value to share throughout the Interpreter. Use this instead of globals so that different Interpreters don’t see each other’s variables. Creates a method that returns the value and also adds a #params entry.
Example:
set :asdf, 9 # => 9
asdf # => 9
This is best used for frequently-used variables, like paths. For infrequently-used variables, use #lookup and #params. A good place to use the #set is in the Project’s config/automateit_env.rb
file so that paths are exposed to all recipes like this:
set :helpers, project+"/helpers"
523 524 525 526 527 528 529 530 531 532 |
# File 'lib/automateit/interpreter.rb', line 523 def set(key, value) key = key.to_sym params[key] = value eval <<-HERE def #{key} return params[:#{key}] end HERE value end |
#setup(opts = {}) ⇒ Object
Setup the Interpreter. This method is also called from Interpreter#new.
Options for users:
-
:verbosity – Alias for :log_level
-
:log_level – Log level to use, defaults to Logger::INFO.
-
:preview – Turn on preview mode, defaults to false.
-
:project – Project directory to use.
-
:tags – Array of tags to add to this run.
Options for internal use:
-
:parent – Parent plugin instance.
-
:log – QueuedLogger instance.
-
:guessed_project – Boolean of whether the project path was guessed. If guessed, won’t throw exceptions if project wasn’t found at the specified path. If not guessed, will throw exception in such a situation.
-
:friendly_exceptions – Throw user-friendly exceptions that make it easier to see errors in recipes, defaults to true.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/automateit/interpreter.rb', line 140 def setup(opts={}) super(opts.merge(:interpreter => self)) self.params ||= {} if opts[:irb] @irb = opts[:irb] end if opts[:parent] @parent = opts[:parent] end if opts[:log] @log = opts[:log] elsif not defined?(@log) or @log.nil? @log = QueuedLogger.new($stdout) @log.level = Logger::INFO end if opts[:log_level] or opts[:verbosity] @log.level = opts[:log_level] || opts[:verbosity] end if opts[:preview].nil? # can be false self.preview = false unless preview? else self.preview = opts[:preview] end if opts[:friendly_exceptions].nil? @friendly_exceptions = true unless defined?(@friendly_exceptions) else @friendly_exceptions = opts[:friendly_exceptions] end # Instantiate core plugins so they're available to the project _instantiate_plugins # Add optional run-time tags .merge(opts[:tags]) if opts[:tags] if project_path = opts[:project] || ENV["AUTOMATEIT_PROJECT"] || ENV["AIP"] # Only load a project if we find its env file env_file = File.join(project_path, "config", "automateit_env.rb") if File.exists?(env_file) @project = File.(project_path) log.debug(PNOTE+"Loading project from path: #{@project}") lib_files = Dir[File.join(@project, "lib", "*.rb")] + Dir[File.join(@project, "lib", "**", "init.rb")] lib_files.each do |lib| log.debug(PNOTE+"Loading project library: #{lib}") invoke(lib) end tag_file = File.join(@project, "config", "tags.yml") if File.exists?(tag_file) log.debug(PNOTE+"Loading project tags: #{tag_file}") tag_manager[:yaml].setup(:file => tag_file) end field_file = File.join(@project, "config", "fields.yml") if File.exists?(field_file) log.debug(PNOTE+"Loading project fields: #{field_file}") field_manager[:yaml].setup(:file => field_file) end # Instantiate project's plugins so they're available to the environment _instantiate_plugins if File.exists?(env_file) log.debug(PNOTE+"Loading project env: #{env_file}") invoke(env_file) end elsif not opts[:guessed_project] raise ArgumentError.new("Couldn't find project at: #{project_path}") end end end |
#superuser? ⇒ Boolean
Does the current user have superuser (root) privileges?
381 382 383 |
# File 'lib/automateit/interpreter.rb', line 381 def superuser? euid.zero? end |
#writing(value) ⇒ Object
Set writing to value
. This is the opposite of #preview.
340 341 342 |
# File 'lib/automateit/interpreter.rb', line 340 def writing(value) self.writing = value end |
#writing=(value) ⇒ Object
Set writing to value
. This is the opposite of #preview=.
345 346 347 |
# File 'lib/automateit/interpreter.rb', line 345 def writing=(value) self.preview = !value end |
#writing? ⇒ Boolean
Is Interpreter writing? This is the opposite of #preview?.
350 351 352 |
# File 'lib/automateit/interpreter.rb', line 350 def writing? !preview? end |