Class: Castanaut::Movie

Inherits:
Object
  • Object
show all
Defined in:
lib/castanaut/movie.rb

Overview

The movie class is the containing context within which screenplays are invoked. It provides a number of basic stage directions for your screenplays, and can be extended with plugins.

If you’re working to make Castanaut compatible with your operating system, you must make sure that all methods in this class work correctly.

Direct Known Subclasses

OS::MacOSX::Movie

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(screenplay = nil, monitor = true) ⇒ Movie

Creates the movie. If a screenplay is provided here, it will be run.

If monitor is true, we'll monitor the kill file (FILE_RUNNING) -
if it is deleted, we abort.


31
32
33
34
35
36
37
38
39
# File 'lib/castanaut/movie.rb', line 31

def initialize(screenplay = nil, monitor = true)
  if self.class == Castanaut::Movie
    raise "#{self} is an abstract class. Try the spawn method."
  end

  if screenplay
    monitor ? _play_and_monitor(screenplay) : _play(screenplay)
  end
end

Class Method Details

.register(name) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/castanaut/movie.rb', line 11

def self.register(name)
  unless reg = Castanaut::Movie.instance_variable_get(:@movie_classes)
    reg = Castanaut::Movie.instance_variable_set(:@movie_classes, {})
  end
  self.instance_variable_set(:@name, name)
  reg.update(self => name)
end

.spawn(screenplay = nil, monitor = true) ⇒ Object



20
21
22
23
24
# File 'lib/castanaut/movie.rb', line 20

def self.spawn(screenplay = nil, monitor = true)
  reg = Castanaut::Movie.instance_variable_get(:@movie_classes)
  klass = reg.keys.detect { |k| k.platform_supported? }
  klass.new(screenplay, monitor)
end

Instance Method Details

#_play(screenplay) ⇒ Object

Simply plays the screenplay in the current thread.



43
44
45
46
47
48
49
# File 'lib/castanaut/movie.rb', line 43

def _play(screenplay)
  unless File.exists?(@screenplay_path = screenplay)
    raise Castanaut::Exceptions::ScreenplayNotFound
  end
  eval(IO.read(@screenplay_path), binding)
  roll_credits
end

#_play_and_monitor(screenplay) ⇒ Object

Plays the screenplay in a separate thread, and monitors the killfile (which is at FILE_RUNNING) - if it is deleted, the screenplay will abort.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/castanaut/movie.rb', line 55

def _play_and_monitor(screenplay)
  unless File.exists?(@screenplay_path = screenplay)
    raise Castanaut::Exceptions::ScreenplayNotFound
  end

  File.open(FILE_RUNNING, 'w') {|f| f.write('')}

  begin
    # We run the movie in a separate thread; in the main thread we
    # continue to check the "running" file flag and kill the movie if
    # it is removed.
    movie = Thread.new do
      begin
        eval(IO.read(@screenplay_path), binding)
      rescue => e
        @e = e
      ensure
        File.unlink(FILE_RUNNING) if File.exists?(FILE_RUNNING)
      end
    end

    while File.exists?(FILE_RUNNING)
      sleep 0.5
      break unless movie.alive?
    end

    if movie.alive?
      movie.kill
      raise Castanaut::Exceptions::AbortedByUser
    end

    raise @e if @e
  rescue => e
    puts "ABNORMAL EXIT: #{e.message}\n" + e.backtrace.join("\n")
  ensure
    roll_credits
    File.unlink(FILE_RUNNING) if File.exists?(FILE_RUNNING)
  end
end

#at_end_of_movie(&blk) ⇒ Object

This stage direction is slightly different to the other ones. It collects a set of directions to be executed when the movie ends, or when it is aborted by the user. Mostly, it’s used for cleaning up stuff. Here’s an example:

ishowu_start_recording
at_end_of_movie do
  ishowu_stop_recording
end
move to(100, 100) # ... et cetera

You can use this multiple times in your screenplay – remember that if the movie is aborted by the user before this direction is used, its contents won’t be executed. So in general, create an at_end_of_movie block after every action that you want to revert (like in the example above).



272
273
274
275
# File 'lib/castanaut/movie.rb', line 272

def at_end_of_movie(&blk)
  @end_credits ||= []
  @end_credits << blk
end

#by(x, y) ⇒ Object

Get a hash representing specific screen co-ordinates *relative to the current mouse location.



185
186
187
188
# File 'lib/castanaut/movie.rb', line 185

def by(x, y)
  @cursor_loc ||= cursor_location
  to(@cursor_loc[:x] + x, @cursor_loc[:y] + y)
end

#click(btn = 'left') ⇒ Object

Send a mouse-click at the current mouse location.



340
341
342
# File 'lib/castanaut/movie.rb', line 340

def click(btn = 'left')
  not_supported('click')
end

#cursor(*options) ⇒ Object Also known as: move

Move the mouse cursor to the specified co-ordinates. Example:

cursor to(20, 20)


322
323
324
# File 'lib/castanaut/movie.rb', line 322

def cursor(*options)
  not_supported('cursor')
end

#cursor_locationObject

Get a hash representing the current mouse cursor co-ordinates.

Should return a hash with :x & :y keys.



333
334
335
# File 'lib/castanaut/movie.rb', line 333

def cursor_location
  not_supported('cursor_location')
end

#doubleclick(btn = 'left') ⇒ Object

Send a double-click at the current mouse location.



347
348
349
# File 'lib/castanaut/movie.rb', line 347

def doubleclick(btn = 'left')
  not_supported('doubleclick')
end

#drag(*options) ⇒ Object

“Drags” the mouse by (effectively) issuing a mousedown at the current mouse location, then moving the mouse to the specified coordinates, then issuing a mouseup.



378
379
380
# File 'lib/castanaut/movie.rb', line 378

def drag(*options)
  not_supported('drag')
end

#hit(key, *modifiers) ⇒ Object

Hit a single key on the keyboard (with optional modifiers).

Valid keys include any single character or any of the constants in keys.rb

Valid modifiers include one or more of the following:

Command
Ctrl
Alt
Shift

Examples:

hit Castanaut::Tab
hit 'a', Castanaut::Command


308
309
310
# File 'lib/castanaut/movie.rb', line 308

def hit(key, *modifiers)
  not_supported('hit')
end

#launch(app_name, *options) ⇒ Object Also known as: activate

Launch the application matching the string given in the first argument. If the options hash is given, it should contain the co-ordinates for the window.

Example:

launch "Firefox", at(10, 10, 800, 600)


395
396
397
# File 'lib/castanaut/movie.rb', line 395

def launch(app_name, *options)
  not_supported('launch')
end

#mousedown(btn = 'left') ⇒ Object

Press the button down at the current mouse location. Does not release the button until the mouseup method is invoked.



362
363
364
# File 'lib/castanaut/movie.rb', line 362

def mousedown(btn = 'left')
  not_supported('mousedown')
end

#mouseup(btn = 'left') ⇒ Object

Releases the mouse button pressed by a previous mousedown.



369
370
371
# File 'lib/castanaut/movie.rb', line 369

def mouseup(btn = 'left')
  not_supported('mouseup')
end

#offset(x, y) ⇒ Object

The result of this method can be added to a co-ordinates hash, offsetting the top and left values by the given margins.



194
195
196
# File 'lib/castanaut/movie.rb', line 194

def offset(x, y)
  { :offset => { :x => x, :y => y } }
end

#pause(seconds) ⇒ Object

Don’t do anything for the specified number of seconds (can be portions of a second).



107
108
109
# File 'lib/castanaut/movie.rb', line 107

def pause(seconds)
  sleep seconds
end

#perform(label) ⇒ Object

Groups directions into labelled blocks. This lets you skip (see below) to the end of the block if you need to.

perform "Build CouchDB from source" do
  launch "Terminal"
  type "./configure"
  hit Enter
  ...
end


122
123
124
125
126
# File 'lib/castanaut/movie.rb', line 122

def perform(label)
  yield
rescue Castanaut::Exceptions::SkipError => e
  puts "Skipping remaining directions in '#{label}'"  unless e.quiet?
end

#plugin(str) ⇒ Object

Adds custom methods to this movie instance, allowing you to perform additional actions. The str can be either the file name (e.g. ‘snapz_pro’) or the class name (e.g. ‘SnapzPro’). See the README.txt for more information.

FIXME: sort out this underscore/camelize mess.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/castanaut/movie.rb', line 236

def plugin(str)
  # copied stright from the Rails underscore helper
  str = str.to_s
  str.gsub!(/::/, '/')
  str.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
  str.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
  str.tr!("-", "_")
  str.downcase!
  fpath =
  begin
    require contextual_path("plugins", "#{str}.rb")
  rescue LoadError
    require File.join(LIBPATH, "plugins", "#{str}.rb")
  end
  # copied stright from the Rails camelize helper
  str = str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
  extend eval("Castanaut::Plugin::#{str}")
end

#run(cmd) ⇒ Object

Runs a shell command, performing fairly naive (but effective!) exit status handling. Returns the stdout result of the command.



202
203
204
205
206
# File 'lib/castanaut/movie.rb', line 202

def run(cmd)
  result = `#{cmd}`
  raise Castanaut::Exceptions::ExternalActionError if $?.exitstatus > 0
  result
end

#say(narrative) ⇒ Object

Use text-to-speech functionality to emulate a human voice saying the narrative text.



418
419
420
# File 'lib/castanaut/movie.rb', line 418

def say(narrative)
  not_supported('say')
end

#screen_sizeObject

Returns a region hash describing the entire screen area.

Should return a hash with :width & :height keys.



406
407
408
# File 'lib/castanaut/movie.rb', line 406

def screen_size
  not_supported('screen_size')
end

#script(filename) ⇒ Object

Loads a script from a file into a string, looking first in the scripts directory beneath the path where Castanaut was executed, and falling back to Castanaut’s gem path.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/castanaut/movie.rb', line 213

def script(filename)
  @cached_scripts ||= {}
  unless @cached_scripts[filename]
    fpath = contextual_path("scripts", filename)
    if File.exists?(fpath)
      scpt = IO.read(fpath)
    else
      scpt = IO.read(File.join(PATH, "scripts", filename))
    end
    @cached_scripts[filename] = scpt
  end

  @cached_scripts[filename]
end

#skip(options = {}) ⇒ Object

Lets you skip out of a perform block if you need to. Usually raised when some condition fails. For example:

perform “Point to heading” do

move to_element('h2') rescue skip
say "This is the heading."

end

Options:

:quiet - does not print message about the skip to STDOUT.


144
145
146
# File 'lib/castanaut/movie.rb', line 144

def skip(options = {})
  raise Castanaut::Exceptions::SkipError.new(options)
end

#to(l, t, w = nil, h = nil) ⇒ Object Also known as: at

Get a hash representing specific screen co-ordinates. Use in combination with cursor, drag, launch, and similar methods.



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/castanaut/movie.rb', line 167

def to(l, t, w = nil, h = nil)
  result = {
    :to => {
      :left => l,
      :top => t
    }
  }
  result[:to][:width] = w if w
  result[:to][:height] = h if h
  result
end

#tripleclick(btn = 'left') ⇒ Object

Send a triple-click at the current mouse location.



354
355
356
# File 'lib/castanaut/movie.rb', line 354

def tripleclick(btn = 'left')
  not_supported('tripleclick')
end

#type(str, opts = {}) ⇒ Object

Sends the characters into the active control in the active window.

Options are:

  • :speed - approximate umber of characters per second

    A speed of 0 types as quickly as possible. (default - 50)
    


289
290
291
# File 'lib/castanaut/movie.rb', line 289

def type(str, opts = {})
  not_supported('type')
end

#while_saying(narrative) ⇒ Object

Starts saying the narrative text, and simultaneously begins executing the given block. Waits until both are finished.



153
154
155
156
157
158
159
160
161
# File 'lib/castanaut/movie.rb', line 153

def while_saying(narrative)
  if block_given?
    fork { say(narrative) }
    yield
    Process.wait
  else
    say(narrative)
  end
end