coderifous-tornado 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +93 -0
- data/Rakefile +30 -0
- data/examples/tornado-cubenot_site.rb +35 -0
- data/lib/applications/finder.rb +8 -0
- data/lib/applications/firefox.rb +9 -0
- data/lib/applications/safari.rb +6 -0
- data/lib/applications/terminal.rb +67 -0
- data/lib/applications/textmate.rb +11 -0
- data/lib/generic_application.rb +160 -0
- data/lib/osx.rb +35 -0
- data/lib/tornado.rb +24 -0
- data/test/helpers/test_helpers.rb +37 -0
- data/test/test_application_finder.rb +21 -0
- data/test/test_application_osx.rb +60 -0
- data/test/test_application_terminal.rb +67 -0
- data/test/test_generic_application.rb +125 -0
- data/test/test_tornado.rb +27 -0
- data/tornado.gemspec +33 -0
- metadata +97 -0
data/README.markdown
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
Tornado
|
2
|
+
=======
|
3
|
+
|
4
|
+
Tornado is a ruby gem that let's you write ruby scripts that looks like this:
|
5
|
+
|
6
|
+
tell OSX do
|
7
|
+
hide_everything
|
8
|
+
end
|
9
|
+
|
10
|
+
tell Terminal do
|
11
|
+
open_new_tab("~/dev/web_site")
|
12
|
+
|
13
|
+
open_new_tab(DEV_DIR) do
|
14
|
+
tail "~/dev/web_site/log/*.log"
|
15
|
+
end
|
16
|
+
|
17
|
+
open_new_tab("~/dev/web_site") do
|
18
|
+
run "autotest"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
tell Textmate do
|
23
|
+
minimize_all_windows
|
24
|
+
open DEV_DIR
|
25
|
+
resize 400, "100%"
|
26
|
+
reposition :east
|
27
|
+
end
|
28
|
+
|
29
|
+
tell Firefox do
|
30
|
+
open_new_tab("http://web-site.local")
|
31
|
+
end
|
32
|
+
|
33
|
+
Documentation
|
34
|
+
-------------
|
35
|
+
|
36
|
+
None yet - this thing is new.
|
37
|
+
|
38
|
+
Why
|
39
|
+
-----
|
40
|
+
|
41
|
+
Tornado sits on top of Appscript, which is an Apple event bridge.
|
42
|
+
Appscript is GREAT, I wanted something even higher level. Something
|
43
|
+
that would just let me easily do the common things I want to do when
|
44
|
+
switching between projects: open terminals, run commands, open the
|
45
|
+
project in my text editor, minimize/hide other apps, etc.
|
46
|
+
|
47
|
+
See below for code comparisons:
|
48
|
+
|
49
|
+
Using Appscript directly:
|
50
|
+
-------------------------
|
51
|
+
|
52
|
+
# hide all apps
|
53
|
+
|
54
|
+
se = Appscript.app("System Events")
|
55
|
+
se.keystroke("h", :using => [ :command_down, :option_down ])
|
56
|
+
|
57
|
+
# open a tab in Terminal and run "autotest"
|
58
|
+
|
59
|
+
t = Appscript.app("Terminal")
|
60
|
+
t.launch unless t.is_running?
|
61
|
+
se.application_processes["Terminal"].keystroke("t", :using => :command_down)
|
62
|
+
tab = current_window.tabs.last
|
63
|
+
term.do_script("cd ~/dev/web_site && clear && autotest", :in => tab)
|
64
|
+
|
65
|
+
Using Tornado:
|
66
|
+
--------------
|
67
|
+
|
68
|
+
# hide all apps
|
69
|
+
|
70
|
+
tell OSX do
|
71
|
+
hide_everything
|
72
|
+
end
|
73
|
+
|
74
|
+
# open a tab in Terminal and run "autotest"
|
75
|
+
tell Terminal do
|
76
|
+
open_new_tab("~/dev/web_site") do
|
77
|
+
run "autotest"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
Copyright and License
|
83
|
+
---------------------
|
84
|
+
|
85
|
+
The MIT License
|
86
|
+
|
87
|
+
Copyright© 2008 Jim Garvin
|
88
|
+
|
89
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
90
|
+
|
91
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
92
|
+
|
93
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
%w[rubygems rake rake/clean fileutils].each { |f| require f }
|
2
|
+
$: << "lib"
|
3
|
+
require File.dirname(__FILE__) + '/lib/tornado'
|
4
|
+
require 'echoe'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rcov/rcovtask'
|
7
|
+
|
8
|
+
Echoe.new('tornado', Tornado::VERSION) do |p|
|
9
|
+
p.description = "Ruby DSL for easily launching, arranging and doing other simple tasks with Mac OS X applications."
|
10
|
+
p.url = "http://github.com/coderifous/tornado"
|
11
|
+
p.author = "Jim Garvin"
|
12
|
+
p.email = "jim at thegarvin dot com"
|
13
|
+
p.ignore_pattern = ["rdoc/*", "rcov/*"]
|
14
|
+
p.development_dependencies = []
|
15
|
+
p.runtime_dependencies = ['rb-appscript >=0.5.1']
|
16
|
+
end
|
17
|
+
|
18
|
+
Rcov::RcovTask.new do |t|
|
19
|
+
t.libs << "test" << "test/helpers"
|
20
|
+
t.pattern = 'test/test_*.rb'
|
21
|
+
t.output_dir = 'rcov'
|
22
|
+
t.verbose = true
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::TestTask.new do |t|
|
26
|
+
t.libs << "test" << "test/helpers"
|
27
|
+
t.test_files = FileList['test/test_*.rb']
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'tornado'
|
5
|
+
|
6
|
+
DEV_DIR = "~/dev/cubenot_site"
|
7
|
+
|
8
|
+
tell OSX do
|
9
|
+
hide_everything
|
10
|
+
end
|
11
|
+
|
12
|
+
tell Terminal do
|
13
|
+
open_new_tab(DEV_DIR)
|
14
|
+
|
15
|
+
open_new_tab(DEV_DIR) do
|
16
|
+
tail "~/dev/cubenot_site/log/*.log"
|
17
|
+
end
|
18
|
+
|
19
|
+
open_new_tab(DEV_DIR) do
|
20
|
+
run "autotest"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
tell Textmate do
|
25
|
+
minimize_all_windows
|
26
|
+
open DEV_DIR
|
27
|
+
resize 400, "100%", :animate => true
|
28
|
+
reposition :south_east, :animate => true
|
29
|
+
end
|
30
|
+
|
31
|
+
tell Firefox do
|
32
|
+
open_new_tab("http://cubenot-site.local")
|
33
|
+
end
|
34
|
+
|
35
|
+
Textmate.activate
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Tornado::Applications::Terminal
|
2
|
+
|
3
|
+
def open_new_tab(path=nil, &block)
|
4
|
+
Terminal.ensure_launched
|
5
|
+
Terminal.keystroke("t", :using => :command_down)
|
6
|
+
tab = Tab.new(app.windows.first.tabs.last)
|
7
|
+
if path
|
8
|
+
tab.cd(path)
|
9
|
+
tab.clear
|
10
|
+
end
|
11
|
+
if block_given?
|
12
|
+
tab.instance_eval(&block)
|
13
|
+
end
|
14
|
+
tab
|
15
|
+
end
|
16
|
+
|
17
|
+
# For some reason (a bug), we have to add the window's height to both y-values in the coordinates
|
18
|
+
def position_top_left_corner_at(x,y, animate=false)
|
19
|
+
b = current_bounds
|
20
|
+
app.windows[0].bounds.set([x, y + height, x + width, y + height + height])
|
21
|
+
end
|
22
|
+
|
23
|
+
# same bug is addressed here
|
24
|
+
def resize(w, h, animate=false)
|
25
|
+
b = current_bounds
|
26
|
+
w = absolutize_size(w, :width)
|
27
|
+
h = absolutize_size(h, :height)
|
28
|
+
app.windows[0].bounds.set([ b[0], b[1] + height, b[0] + w, b[1] + h + height ])
|
29
|
+
end
|
30
|
+
|
31
|
+
def absolutize_size(size, width_or_height)
|
32
|
+
if size.to_s[-1].chr == "%"
|
33
|
+
OSX.send("desktop_#{width_or_height}") * (size.to_f / 100)
|
34
|
+
else
|
35
|
+
size + height
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Tab
|
40
|
+
attr_reader :tab
|
41
|
+
|
42
|
+
def initialize(tab)
|
43
|
+
@tab = tab
|
44
|
+
end
|
45
|
+
|
46
|
+
def run(cmd)
|
47
|
+
Terminal.do_script(cmd, :in => @tab)
|
48
|
+
end
|
49
|
+
|
50
|
+
def tail(path)
|
51
|
+
run "tail -f #{path}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear
|
55
|
+
run "clear"
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear_scrollback
|
59
|
+
@tab.selected.set(true)
|
60
|
+
Terminal.keystroke("k", :using => :command_down)
|
61
|
+
end
|
62
|
+
|
63
|
+
def cd(path)
|
64
|
+
run "cd #{path}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Tornado::Applications::Textmate
|
2
|
+
def open(*args)
|
3
|
+
args.each do |file_path|
|
4
|
+
file_path.sub!(/~/, ENV["HOME"])
|
5
|
+
app.open(file_path)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# in case I jack up textmate's drawer again:
|
11
|
+
# defaults write com.macromates.textmate OakProjectDrawerWidth 100
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'osx'
|
2
|
+
|
3
|
+
module GenericActions
|
4
|
+
def close_all_windows
|
5
|
+
app.windows.close
|
6
|
+
end
|
7
|
+
|
8
|
+
def minimize_all_windows
|
9
|
+
app.activate
|
10
|
+
app.windows.get.select { |w| w.visible.get }.each do |window|
|
11
|
+
window.miniaturized.set(true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def wait_for_me!
|
16
|
+
sleep(0.1) while !is_running?
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_launched
|
20
|
+
unless is_running?
|
21
|
+
app.launch
|
22
|
+
wait_for_me!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def keystroke(*args)
|
27
|
+
OSX[name].keystroke(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# blindly pass on unrecognized methods to the appscript proxy
|
31
|
+
def method_missing(meth, *args)
|
32
|
+
app.send(meth, *args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module WindowPositionAndResizeActions
|
37
|
+
def current_bounds
|
38
|
+
app.windows[0].bounds.get
|
39
|
+
end
|
40
|
+
|
41
|
+
def position(cardinal_point, opts={})
|
42
|
+
x, y = nil, nil
|
43
|
+
case cardinal_point
|
44
|
+
when :north_east
|
45
|
+
x = OSX.desktop_width - width
|
46
|
+
y = 0
|
47
|
+
when :south_east
|
48
|
+
x = OSX.desktop_width - width
|
49
|
+
y = OSX.desktop_height - height
|
50
|
+
when :north_west
|
51
|
+
x, y = 0, 0
|
52
|
+
when :south_west
|
53
|
+
x = 0
|
54
|
+
y = OSX.desktop_height - height
|
55
|
+
end
|
56
|
+
position_top_left_corner_at(x, y, opts)
|
57
|
+
end
|
58
|
+
|
59
|
+
def position_top_left_corner_at(x, y, opts={})
|
60
|
+
b = current_bounds
|
61
|
+
end_bounds = [x, y, x + width, y + height]
|
62
|
+
if opts[:animate]
|
63
|
+
transition_to_bounds(b, end_bounds)
|
64
|
+
else
|
65
|
+
app.windows[0].bounds.set(end_bounds)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def width
|
70
|
+
b = current_bounds
|
71
|
+
b[2] - b[0]
|
72
|
+
end
|
73
|
+
|
74
|
+
def height
|
75
|
+
b = current_bounds
|
76
|
+
b[3] - b[1]
|
77
|
+
end
|
78
|
+
|
79
|
+
def resize(w, h, opts={})
|
80
|
+
b = current_bounds
|
81
|
+
w = absolutize_size(w, :width)
|
82
|
+
h = absolutize_size(h, :height)
|
83
|
+
end_bounds = [ b[0], b[1], b[0] + w, b[1] + h ]
|
84
|
+
if opts[:animate]
|
85
|
+
transition_to_bounds(b, end_bounds)
|
86
|
+
else
|
87
|
+
app.windows[0].bounds.set(end_bounds)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def transition_to_bounds(start_bounds, end_bounds)
|
92
|
+
# puts "From #{start_bounds.inspect} to #{end_bounds.inspect}"
|
93
|
+
steps = 50
|
94
|
+
factor = 100 / steps
|
95
|
+
1.upto(steps) do |step|
|
96
|
+
frac = step.to_f * factor / 100
|
97
|
+
step_bounds = []
|
98
|
+
0.upto(3) do |x|
|
99
|
+
step_bounds[x] = start_bounds[x] + ((end_bounds[x] - start_bounds[x]) * frac)
|
100
|
+
end
|
101
|
+
# puts "bounds: #{step_bounds.inspect}"
|
102
|
+
app.windows[0].bounds.set(step_bounds)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def absolutize_size(size, width_or_height)
|
107
|
+
if size.to_s[-1].chr == "%"
|
108
|
+
OSX.send("desktop_#{width_or_height}") * (size.to_f / 100)
|
109
|
+
else
|
110
|
+
size
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module ExtensionHelpers
|
116
|
+
def extension
|
117
|
+
Tornado::Applications.const_get(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
def extension_path
|
121
|
+
"applications/#{name.downcase.tr(" ", '_')}.rb"
|
122
|
+
end
|
123
|
+
|
124
|
+
def load_extension!
|
125
|
+
begin
|
126
|
+
require extension_path
|
127
|
+
self.extend(extension)
|
128
|
+
rescue NameError => e
|
129
|
+
STDERR.puts "Extension not loaded: Expected #{extension_path} to define module Tornado::Applications::#{name}"
|
130
|
+
STDERR.puts caller
|
131
|
+
rescue LoadError => e
|
132
|
+
STDERR.puts "Extension not found: #{extension_path}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class GenericApplication
|
138
|
+
include GenericActions
|
139
|
+
include ExtensionHelpers
|
140
|
+
include WindowPositionAndResizeActions
|
141
|
+
|
142
|
+
attr_accessor :app, :name
|
143
|
+
|
144
|
+
def initialize(opts)
|
145
|
+
opts.each_pair { |k,v| self.send("#{k}=", v) }
|
146
|
+
self.app = Appscript.app(name)
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.find_by_string(name)
|
150
|
+
if Appscript.app(name)
|
151
|
+
application = GenericApplication.new(:name => name)
|
152
|
+
application.load_extension!
|
153
|
+
Kernel.const_set(name, application)
|
154
|
+
return application
|
155
|
+
else
|
156
|
+
STDERR.puts "Couldn't find application named: #{name}"
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/osx.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class OSX
|
2
|
+
class << self
|
3
|
+
def hide_everything
|
4
|
+
system_events.keystroke("h", :using => [ :command_down, :option_down ])
|
5
|
+
end
|
6
|
+
|
7
|
+
def open_location(*args)
|
8
|
+
args.each { |url| system_events.open_location(url) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def system_events
|
12
|
+
@system_events ||= Appscript.app("System Events")
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](app_name)
|
16
|
+
system_events.application_processes[app_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def running_applications
|
20
|
+
system_events.application_processes.get.collect { |a| a.name.get }
|
21
|
+
end
|
22
|
+
|
23
|
+
def desktop_size
|
24
|
+
@desktop_size ||= Finder.desktop.window.bounds.get
|
25
|
+
end
|
26
|
+
|
27
|
+
def desktop_width
|
28
|
+
desktop_size[2]
|
29
|
+
end
|
30
|
+
|
31
|
+
def desktop_height
|
32
|
+
desktop_size[3]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/tornado.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'appscript'
|
3
|
+
|
4
|
+
module Tornado
|
5
|
+
VERSION = "0.0.1"
|
6
|
+
|
7
|
+
def tell(app, &block)
|
8
|
+
if block_given?
|
9
|
+
app.instance_eval &block
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def const_missing(name)
|
14
|
+
return GenericApplication.find_by_string(name.to_s) || super(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
module Applications; end
|
18
|
+
end
|
19
|
+
|
20
|
+
Object.extend(Tornado)
|
21
|
+
extend(Tornado)
|
22
|
+
|
23
|
+
require 'generic_application'
|
24
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'mocha'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'tornado'
|
6
|
+
|
7
|
+
module TestHelpers
|
8
|
+
def assert_responds_to(object, *methods)
|
9
|
+
methods.each do |m|
|
10
|
+
assert object.respond_to?(m), "failed to respond to '#{m}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def assert_application_not_defined(app_name)
|
15
|
+
assert ! application_defined?(app_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_application_defined(app_name)
|
19
|
+
assert application_defined?(app_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def assert_extension_not_defined(app_name)
|
23
|
+
assert ! Tornado::Applications.const_defined?(app_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def application_defined?(app_name)
|
27
|
+
Kernel.const_defined?(app_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ensure_application_undefined(app_name)
|
31
|
+
Kernel.send(:remove_const, app_name) if application_defined?(app_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def first_mention_of(*args)
|
35
|
+
# called solely for side effect of arguments being mentioned
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'helpers/test_helpers'
|
2
|
+
|
3
|
+
class ApplicationFinderTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The Finder application" do
|
6
|
+
|
7
|
+
setup do
|
8
|
+
Appscript.stubs(:app).returns(mock)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "be able to minimize all windows" do
|
12
|
+
Finder.app.expects(:activate)
|
13
|
+
mock2 = mock { expects(:set).with(true) }
|
14
|
+
mock1 = mock { expects(:collapsed).returns(mock2) }
|
15
|
+
Finder.app.expects(:windows).returns(mock1)
|
16
|
+
Finder.minimize_all_windows
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'helpers/test_helpers'
|
2
|
+
|
3
|
+
class ApplicationOSXTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The tornado pseudo-app OSX" do
|
6
|
+
|
7
|
+
should "return the desktop size" do
|
8
|
+
bounds = mock { expects(:get).returns([0,0,1440,900]) }
|
9
|
+
window = mock { expects(:bounds).returns(bounds) }
|
10
|
+
desktop = mock { expects(:window).returns(window) }
|
11
|
+
Finder.expects(:desktop).returns(desktop)
|
12
|
+
assert_equal [0,0,1440,900], OSX.desktop_size
|
13
|
+
end
|
14
|
+
|
15
|
+
should "use the System Events app" do
|
16
|
+
Appscript.expects(:app).with("System Events")
|
17
|
+
OSX.instance_variable_set(:@system_events, nil)
|
18
|
+
OSX.system_events
|
19
|
+
end
|
20
|
+
|
21
|
+
should "use system events to hide all windows" do
|
22
|
+
OSX.system_events.expects(:keystroke).with("h", :using => [ :command_down, :option_down ])
|
23
|
+
OSX.hide_everything
|
24
|
+
end
|
25
|
+
|
26
|
+
should "use system events to open urls" do
|
27
|
+
OSX.system_events.expects(:open_location).with("foo")
|
28
|
+
OSX.open_location("foo")
|
29
|
+
end
|
30
|
+
|
31
|
+
should "use system events to access application processes by name" do
|
32
|
+
processes = mock("processes"){ expects(:[] => "Foo") }
|
33
|
+
OSX.system_events.expects(:application_processes).returns(processes)
|
34
|
+
OSX["Foo"]
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with running applications %w(Foo Bar Baz)" do
|
38
|
+
|
39
|
+
setup do
|
40
|
+
list = [
|
41
|
+
stub(:name => stub(:get => "Foo")),
|
42
|
+
stub(:name => stub(:get => "Bar")),
|
43
|
+
stub(:name => stub(:get => "Baz"))
|
44
|
+
]
|
45
|
+
processes = mock() do
|
46
|
+
expects(:get).returns(list).at_least_once
|
47
|
+
end
|
48
|
+
OSX.system_events.expects(:application_processes).returns(processes).at_least_once
|
49
|
+
end
|
50
|
+
|
51
|
+
should "use system events to list application processes by name" do
|
52
|
+
list = OSX.running_applications
|
53
|
+
assert_equal %w(Foo Bar Baz), list
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'helpers/test_helpers'
|
2
|
+
|
3
|
+
class ApplicationTerminalTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The Terminal application" do
|
6
|
+
|
7
|
+
context "with a tab" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
Terminal
|
11
|
+
@tab = Tornado::Applications::Terminal::Tab.new(stub)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "be able to call run on tab" do
|
15
|
+
Terminal.expects(:do_script).with("foo", :in => @tab.tab)
|
16
|
+
@tab.run "foo"
|
17
|
+
end
|
18
|
+
|
19
|
+
should "be able to call clear on tab" do
|
20
|
+
@tab.expects(:run).with("clear")
|
21
|
+
@tab.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
should "be able to call clear scroll back" do
|
25
|
+
@tab.tab.expects(:selected).returns(mock { expects(:set).with(true) })
|
26
|
+
Terminal.expects(:keystroke).with("k", :using => :command_down)
|
27
|
+
@tab.clear_scrollback
|
28
|
+
end
|
29
|
+
|
30
|
+
should "be able to change directory" do
|
31
|
+
@tab.expects(:run).with("cd /path")
|
32
|
+
@tab.cd("/path")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with mocked out tab" do
|
38
|
+
|
39
|
+
setup do
|
40
|
+
Terminal.expects(:ensure_launched)
|
41
|
+
Terminal.expects(:keystroke).with("t", :using => :command_down)
|
42
|
+
@tab = mock
|
43
|
+
Tornado::Applications::Terminal::Tab.expects(:new).returns(@tab)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "be able to open_new_tab without a path" do
|
47
|
+
Terminal.open_new_tab
|
48
|
+
end
|
49
|
+
|
50
|
+
should "be able to open_new_tab with a path" do
|
51
|
+
@tab.expects(:cd).with("/path/foo")
|
52
|
+
@tab.expects(:clear)
|
53
|
+
Terminal.open_new_tab("/path/foo")
|
54
|
+
end
|
55
|
+
|
56
|
+
should "be able to open_new_tab with a block" do
|
57
|
+
@tab.expects(:some_method)
|
58
|
+
Terminal.open_new_tab do
|
59
|
+
some_method
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'helpers/test_helpers'
|
2
|
+
|
3
|
+
class GenericApplicationTest < Test::Unit::TestCase
|
4
|
+
include TestHelpers
|
5
|
+
|
6
|
+
context "A GenericApplication" do
|
7
|
+
|
8
|
+
setup do
|
9
|
+
Appscript.stubs(:app).returns(mock("app"))
|
10
|
+
OSX.stubs(:desktop_size).returns([0, 0, 1024, 768])
|
11
|
+
@generic_app = GenericApplication.new(:name => "FakeApplication")
|
12
|
+
end
|
13
|
+
|
14
|
+
should "be able to resize first window with absolute values" do
|
15
|
+
bounds = mock { expects(:set).with([100 , 100, 500, 500]) }
|
16
|
+
windows = [ mock(:bounds => bounds) ]
|
17
|
+
@generic_app.app.expects(:windows).returns(windows)
|
18
|
+
@generic_app.expects(:current_bounds).returns([100,100,200,200])
|
19
|
+
@generic_app.resize 400, 400
|
20
|
+
end
|
21
|
+
|
22
|
+
should "be able to resize first window with percent values" do
|
23
|
+
bounds = mock { expects(:set).with([100 , 100, 612, 484]) }
|
24
|
+
windows = [ mock(:bounds => bounds) ]
|
25
|
+
@generic_app.app.expects(:windows).returns(windows)
|
26
|
+
@generic_app.expects(:current_bounds).returns([100,100,200,200])
|
27
|
+
@generic_app.resize "50%", "50%"
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with position bzz" do
|
31
|
+
setup do
|
32
|
+
@generic_app.stubs(:current_bounds).returns([100,100,200,200])
|
33
|
+
@bounds = mock
|
34
|
+
windows = [ mock(:bounds => @bounds) ]
|
35
|
+
@generic_app.app.expects(:windows).returns(windows)
|
36
|
+
end
|
37
|
+
|
38
|
+
should "be able to position first window in :north_east" do
|
39
|
+
@bounds.expects(:set).with([924, 0, 1024, 100])
|
40
|
+
@generic_app.position :north_east
|
41
|
+
end
|
42
|
+
|
43
|
+
should "be able to position first window in :south_east" do
|
44
|
+
@bounds.expects(:set).with([924, 668, 1024, 768])
|
45
|
+
@generic_app.position :south_east
|
46
|
+
end
|
47
|
+
|
48
|
+
should "be able to position first window in :north_west" do
|
49
|
+
@bounds.expects(:set).with([0, 0, 100, 100])
|
50
|
+
@generic_app.position :north_west
|
51
|
+
end
|
52
|
+
|
53
|
+
should "be able to position first window in :south_west" do
|
54
|
+
@bounds.expects(:set).with([0, 668, 100, 768])
|
55
|
+
@generic_app.position :south_west
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
should "be able to close all windows" do
|
60
|
+
windows = mock("windows") { expects(:close) }
|
61
|
+
@generic_app.app.expects(:windows).returns(windows)
|
62
|
+
@generic_app.close_all_windows
|
63
|
+
end
|
64
|
+
|
65
|
+
should "be able to minimize all windows" do
|
66
|
+
stub_get_true = stub(:get => true, :set => nil)
|
67
|
+
stub_get_false = stub(:get => false)
|
68
|
+
@generic_app.app.expects(:activate)
|
69
|
+
window = mock("window") do
|
70
|
+
expects(:visible).returns(stub_get_true).times(3)
|
71
|
+
expects(:miniaturized).returns(stub_get_true).times(3)
|
72
|
+
end
|
73
|
+
non_visible_window = mock("non_visible_window") do
|
74
|
+
expects(:visible).returns(stub_get_false)
|
75
|
+
expects(:miniaturized).never
|
76
|
+
end
|
77
|
+
windows = mock(:get => [window, window, window, non_visible_window])
|
78
|
+
@generic_app.app.expects(:windows).returns(windows)
|
79
|
+
@generic_app.minimize_all_windows
|
80
|
+
end
|
81
|
+
|
82
|
+
should "be able to wait_for_me" do
|
83
|
+
@generic_app.expects(:sleep).times(2)
|
84
|
+
@generic_app.expects(:is_running?).times(3).returns(false, false, true)
|
85
|
+
@generic_app.wait_for_me!
|
86
|
+
end
|
87
|
+
|
88
|
+
should "be able to ensure_launched" do
|
89
|
+
@generic_app.expects(:is_running?).returns(false)
|
90
|
+
@generic_app.app.expects(:launch)
|
91
|
+
@generic_app.expects(:wait_for_me!)
|
92
|
+
@generic_app.ensure_launched
|
93
|
+
end
|
94
|
+
|
95
|
+
should "be able to send keystrokes" do
|
96
|
+
key, options = "h", { :using => :command_down }
|
97
|
+
receiver = mock { expects(:keystroke).with(key, options) }
|
98
|
+
OSX.expects(:[]).with("FakeApplication").returns(receiver)
|
99
|
+
@generic_app.keystroke(key, options)
|
100
|
+
end
|
101
|
+
|
102
|
+
should "send unrecognized methods to app" do
|
103
|
+
@generic_app.app.expects(:a_method_that_does_not_exist)
|
104
|
+
@generic_app.a_method_that_does_not_exist
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_extension_loading_for_app_with_extension
|
110
|
+
ensure_application_undefined :Finder
|
111
|
+
assert_application_not_defined :Finder
|
112
|
+
first_mention_of Finder
|
113
|
+
assert_application_defined :Finder
|
114
|
+
assert_equal "applications/finder.rb", Finder.extension_path
|
115
|
+
assert_equal Tornado::Applications::Finder, Finder.extension
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_extension_loading_for_app_without_extension
|
119
|
+
Appscript.stubs(:app).returns(true)
|
120
|
+
assert_application_not_defined :ApplicationWithoutExtension
|
121
|
+
first_mention_of ApplicationWithoutExtension
|
122
|
+
assert_extension_not_defined :ApplicationWithoutExtension
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'helpers/test_helpers'
|
2
|
+
|
3
|
+
$main = self
|
4
|
+
|
5
|
+
class TornadoTest < Test::Unit::TestCase
|
6
|
+
include TestHelpers
|
7
|
+
include Tornado
|
8
|
+
|
9
|
+
def test_tell
|
10
|
+
$main.respond_to?(:tell)
|
11
|
+
Object.new.respond_to?(:tell)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_automatic_application_reference_based_on_const_missing
|
15
|
+
ensure_application_undefined :Terminal
|
16
|
+
assert_application_not_defined :Terminal
|
17
|
+
first_mention_of Terminal
|
18
|
+
assert Terminal.kind_of?(GenericApplication)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_tell
|
22
|
+
fake_app = mock(:nothing => true)
|
23
|
+
tell fake_app do
|
24
|
+
nothing
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/tornado.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{tornado}
|
3
|
+
s.version = "0.0.1"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Jim Garvin"]
|
7
|
+
s.date = %q{2009-01-10}
|
8
|
+
s.description = %q{Ruby DSL for easily launching, arranging and doing other simple tasks with Mac OS X applications.}
|
9
|
+
s.email = %q{jim at thegarvin dot com}
|
10
|
+
s.extra_rdoc_files = ["lib/applications/finder.rb", "lib/applications/firefox.rb", "lib/applications/safari.rb", "lib/applications/terminal.rb", "lib/applications/textmate.rb", "lib/generic_application.rb", "lib/osx.rb", "lib/tornado.rb", "README.markdown"]
|
11
|
+
s.files = ["examples/tornado-cubenot_site.rb", "lib/applications/finder.rb", "lib/applications/firefox.rb", "lib/applications/safari.rb", "lib/applications/terminal.rb", "lib/applications/textmate.rb", "lib/generic_application.rb", "lib/osx.rb", "lib/tornado.rb", "Manifest", "Rakefile", "README.markdown", "test/helpers/test_helpers.rb", "test/test_application_finder.rb", "test/test_application_osx.rb", "test/test_application_terminal.rb", "test/test_generic_application.rb", "test/test_tornado.rb", "tornado.gemspec"]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.homepage = %q{http://github.com/coderifous/tornado}
|
14
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tornado", "--main", "README.markdown"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = %q{tornado}
|
17
|
+
s.rubygems_version = %q{1.2.0}
|
18
|
+
s.summary = %q{Ruby DSL for easily launching, arranging and doing other simple tasks with Mac OS X applications.}
|
19
|
+
s.test_files = ["test/helpers/test_helpers.rb", "test/test_application_finder.rb", "test/test_application_osx.rb", "test/test_application_terminal.rb", "test/test_generic_application.rb", "test/test_tornado.rb"]
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 2
|
24
|
+
|
25
|
+
if current_version >= 3 then
|
26
|
+
s.add_runtime_dependency(%q<rb-appscript>, [">= 0.5.1"])
|
27
|
+
else
|
28
|
+
s.add_dependency(%q<rb-appscript>, [">= 0.5.1"])
|
29
|
+
end
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<rb-appscript>, [">= 0.5.1"])
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: coderifous-tornado
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Garvin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-10 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rb-appscript
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.5.1
|
23
|
+
version:
|
24
|
+
description: Ruby DSL for easily launching, arranging and doing other simple tasks with Mac OS X applications.
|
25
|
+
email: jim at thegarvin dot com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- lib/applications/finder.rb
|
32
|
+
- lib/applications/firefox.rb
|
33
|
+
- lib/applications/safari.rb
|
34
|
+
- lib/applications/terminal.rb
|
35
|
+
- lib/applications/textmate.rb
|
36
|
+
- lib/generic_application.rb
|
37
|
+
- lib/osx.rb
|
38
|
+
- lib/tornado.rb
|
39
|
+
- README.markdown
|
40
|
+
files:
|
41
|
+
- examples/tornado-cubenot_site.rb
|
42
|
+
- lib/applications/finder.rb
|
43
|
+
- lib/applications/firefox.rb
|
44
|
+
- lib/applications/safari.rb
|
45
|
+
- lib/applications/terminal.rb
|
46
|
+
- lib/applications/textmate.rb
|
47
|
+
- lib/generic_application.rb
|
48
|
+
- lib/osx.rb
|
49
|
+
- lib/tornado.rb
|
50
|
+
- Manifest
|
51
|
+
- Rakefile
|
52
|
+
- README.markdown
|
53
|
+
- test/helpers/test_helpers.rb
|
54
|
+
- test/test_application_finder.rb
|
55
|
+
- test/test_application_osx.rb
|
56
|
+
- test/test_application_terminal.rb
|
57
|
+
- test/test_generic_application.rb
|
58
|
+
- test/test_tornado.rb
|
59
|
+
- tornado.gemspec
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: http://github.com/coderifous/tornado
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options:
|
64
|
+
- --line-numbers
|
65
|
+
- --inline-source
|
66
|
+
- --title
|
67
|
+
- Tornado
|
68
|
+
- --main
|
69
|
+
- README.markdown
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "1.2"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project: tornado
|
87
|
+
rubygems_version: 1.2.0
|
88
|
+
signing_key:
|
89
|
+
specification_version: 2
|
90
|
+
summary: Ruby DSL for easily launching, arranging and doing other simple tasks with Mac OS X applications.
|
91
|
+
test_files:
|
92
|
+
- test/helpers/test_helpers.rb
|
93
|
+
- test/test_application_finder.rb
|
94
|
+
- test/test_application_osx.rb
|
95
|
+
- test/test_application_terminal.rb
|
96
|
+
- test/test_generic_application.rb
|
97
|
+
- test/test_tornado.rb
|