rucola 0.0.1 → 0.0.2
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.
- data/ChangeLog +301 -0
- data/History.txt +51 -0
- data/Manifest.txt +12 -0
- data/README.txt +6 -7
- data/TODO +13 -8
- data/app_generators/rucola/rucola_generator.rb +1 -1
- data/app_generators/rucola/templates/app/controllers/application_controller.rb +2 -2
- data/app_generators/rucola/templates/test/controllers/test_application_controller.rb +22 -5
- data/app_generators/rucola/templates/test/test_helper.rb +3 -2
- data/config/hoe.rb +2 -2
- data/lib/autotest/discover.rb +9 -0
- data/lib/autotest/fail.png +0 -0
- data/lib/autotest/growl_images.rb +39 -0
- data/lib/autotest/pass.png +0 -0
- data/lib/autotest/rucola.rb +36 -0
- data/lib/autotest/sound.rb +52 -0
- data/lib/rucola/info_plist.rb +5 -0
- data/lib/rucola/initializer.rb +65 -113
- data/lib/rucola/nib.rb +2 -2
- data/lib/rucola/plugin.rb +57 -0
- data/lib/rucola/rucola_support/initialize_hooks.rb +7 -0
- data/lib/rucola/rucola_support/notifications/notifications.rb +67 -27
- data/lib/rucola/rucola_support/rc_app.rb +9 -0
- data/lib/rucola/tasks/main.rake +8 -4
- data/lib/rucola/tasks/xcode.rake +10 -6
- data/lib/rucola/test_helper.rb +14 -0
- data/lib/rucola/version.rb +1 -1
- data/lib/rucola/xcode.rb +11 -6
- data/rucola_generators/controller/controller_generator.rb +1 -1
- data/rucola_generators/controller/templates/test_controller_template.rb.erb +14 -5
- data/rucola_generators/document_model/document_model_generator.rb +3 -3
- data/rucola_generators/document_model/templates/test_document_model_template.rb.erb +18 -5
- data/rucola_generators/rucola_plugin/USAGE +6 -0
- data/rucola_generators/rucola_plugin/rucola_plugin_generator.rb +63 -0
- data/rucola_generators/rucola_plugin/templates/init.rb.erb +25 -0
- data/rucola_generators/window_controller/templates/test_window_controller_template.rb.erb +21 -5
- data/rucola_generators/window_controller/window_controller_generator.rb +1 -1
- data/test/test_document_model_generator.rb +8 -2
- data/test/test_info_plist.rb +4 -0
- data/test/test_initializer.rb +80 -0
- data/test/test_nib.rb +9 -0
- data/test/test_notifications.rb +38 -19
- data/test/test_plugin.rb +48 -0
- data/test/test_rc_app.rb +5 -0
- data/test/test_rucola_plugin_generator.rb +65 -0
- data/test/test_xcode.rb +3 -3
- data/website/index.html +49 -7
- data/website/index.txt +34 -4
- metadata +37 -2
data/lib/rucola/nib.rb
CHANGED
@@ -34,10 +34,10 @@ module Rucola
|
|
34
34
|
@data['IBClasses']
|
35
35
|
end
|
36
36
|
|
37
|
-
def add_class(class_name)
|
37
|
+
def add_class(class_name, superclass_name = 'NSObject')
|
38
38
|
classes.push({
|
39
39
|
'CLASS' => class_name,
|
40
|
-
'SUPERCLASS' =>
|
40
|
+
'SUPERCLASS' => superclass_name,
|
41
41
|
'LANGUAGE' => 'ObjC'
|
42
42
|
})
|
43
43
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'osx/cocoa'
|
2
|
+
|
3
|
+
module Rucola
|
4
|
+
# Abstract base class for Rucola plugins
|
5
|
+
# override the methods in your plugin to get code run at appropriate times
|
6
|
+
class Plugin
|
7
|
+
@@plugins = []
|
8
|
+
|
9
|
+
def self.plugins
|
10
|
+
@@plugins
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.inherited(subclass)
|
14
|
+
@@plugins << subclass.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.before_boot
|
18
|
+
plugins.each { |p| p.before_boot }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.after_boot
|
22
|
+
plugins.each { |p| p.after_boot }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.before_process(initializer)
|
26
|
+
plugins.each {|p| p.before_process(initializer) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.after_process(initializer)
|
30
|
+
plugins.each {|p| p.after_process(initializer) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.after_launch
|
34
|
+
plugins.each {|p| p.after_launch }
|
35
|
+
end
|
36
|
+
|
37
|
+
def before_boot; end
|
38
|
+
def after_boot; end
|
39
|
+
def before_process(initializer); end
|
40
|
+
def after_process(initializer); end
|
41
|
+
def after_launch; end
|
42
|
+
end
|
43
|
+
|
44
|
+
# This class is used to be able to run hooks when the app has started.
|
45
|
+
class PluginRunner < OSX::NSObject
|
46
|
+
def initialize
|
47
|
+
center = OSX::NSNotificationCenter.defaultCenter
|
48
|
+
center.addObserver_selector_name_object(self, :after_launch, OSX::NSApplicationDidFinishLaunchingNotification, nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
def after_launch(notification)
|
52
|
+
Rucola::Plugin.after_launch
|
53
|
+
end
|
54
|
+
|
55
|
+
@instance = self.alloc.init
|
56
|
+
end
|
57
|
+
end
|
@@ -8,10 +8,17 @@ module Rucola
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
# Calls all the hooks that have been added to the queue.
|
12
|
+
#
|
13
|
+
# It will also call the method +after_init+, but only if you have defined it.
|
14
|
+
# Create this method to do work that you would normally do in +initialize+ or +init+.
|
11
15
|
def initialize
|
12
16
|
# get the hooks, if they exist let them all do their after initialization work.
|
13
17
|
hooks = self.class.instance_variable_get(:@_rucola_initialize_hooks)
|
14
18
|
hooks.each { |hook| self.instance_eval(&hook) } unless hooks.nil?
|
19
|
+
|
20
|
+
# also call after_init for custom initialization code.
|
21
|
+
send :after_init if respond_to? :after_init # TODO: test
|
15
22
|
end
|
16
23
|
|
17
24
|
def self.included(base) # :nodoc
|
@@ -38,7 +38,7 @@ module Rucola
|
|
38
38
|
# # This will make sure that :win_ is expanded to :window_ in the notifications that you register.
|
39
39
|
# notification_prefix :win => :window
|
40
40
|
#
|
41
|
-
#
|
41
|
+
# when :win_did_become_key do |notification|
|
42
42
|
# # code
|
43
43
|
# end
|
44
44
|
# end
|
@@ -48,6 +48,13 @@ module Rucola
|
|
48
48
|
(@_notification_prefixes ||= {}).merge! prefixes
|
49
49
|
end
|
50
50
|
|
51
|
+
# Creates a notification and posts it to the reciever
|
52
|
+
def fire_notification(notification, obj)
|
53
|
+
notification_name = resolve_notification_name(notification)
|
54
|
+
OSX::NSNotificationCenter.defaultCenter.postNotificationName_object(notification_name, obj)
|
55
|
+
end
|
56
|
+
alias_method :post_notification, :fire_notification
|
57
|
+
|
51
58
|
# Registers the object for the given notification and executes the given block when the
|
52
59
|
# notification is posted to the OSX::NSNotificationCenter.defaultCenter.
|
53
60
|
#
|
@@ -68,33 +75,8 @@ module Rucola
|
|
68
75
|
#
|
69
76
|
# You can even register shortcut prefixes. See +notification_prefix+.
|
70
77
|
def notify_on(notification, &block)
|
71
|
-
notification_name = notification
|
72
|
-
|
73
|
-
if notification_name.is_a? Symbol
|
74
|
-
notification_name = notification_name.to_s
|
75
|
-
|
76
|
-
# first check if this notification_name uses a shortcut prefix
|
77
|
-
splitted_notification_name = notification_name.split('_')
|
78
|
-
prefix = splitted_notification_name.first.to_sym
|
79
|
-
if @_notification_prefixes and @_notification_prefixes.has_key? prefix
|
80
|
-
notification_name = @_notification_prefixes[prefix].to_s << '_' << splitted_notification_name[1..-1].join('_')
|
81
|
-
end
|
82
|
-
|
83
|
-
begin
|
84
|
-
# try with only Notification appended
|
85
|
-
notification_name = notification_name.camel_case << 'Notification'
|
86
|
-
OSX.const_get(notification_name)
|
87
|
-
rescue NameError
|
88
|
-
begin
|
89
|
-
# then try with NS prepended
|
90
|
-
notification_name = 'NS' << notification_name
|
91
|
-
OSX.const_get(notification_name)
|
92
|
-
rescue NameError
|
93
|
-
raise NameError, "Unable to find the notification corresponding to :#{notification}"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
78
|
|
79
|
+
notification_name = resolve_notification_name(notification)
|
98
80
|
method_name = "_handle_#{notification_name.snake_case}".to_sym
|
99
81
|
|
100
82
|
# define the handle method
|
@@ -106,6 +88,29 @@ module Rucola
|
|
106
88
|
@_registered_notifications[notification_name.to_s] = method_name
|
107
89
|
end
|
108
90
|
|
91
|
+
# Registers the object for the given notification and executes the given block when the
|
92
|
+
# notification is posted to the OSX::NSNotificationCenter.defaultCenter.
|
93
|
+
#
|
94
|
+
# class FooController < OSX::NSObject
|
95
|
+
#
|
96
|
+
# once OSX::NSApplicationDidFinishLaunchingNotification do |notification|
|
97
|
+
# puts "Application did finish launching."
|
98
|
+
# p notification
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# # code
|
102
|
+
#
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# You can also pass it a symbol as +notification+ in which case it will be exapnded.
|
106
|
+
# It will first check if the name + 'Notification' exists, if not it will prepend 'NS'.
|
107
|
+
# So :application_did_finish_launching becomes 'NSApplicationDidFinishLaunchingNotification'.
|
108
|
+
#
|
109
|
+
# You can even register shortcut prefixes. See +notification_prefix+.
|
110
|
+
#
|
111
|
+
# FIXME: Which is better +once+ or +notify_on+?
|
112
|
+
alias_method :once, :notify_on
|
113
|
+
|
109
114
|
# Register a callback when a notification is posted.
|
110
115
|
#
|
111
116
|
# class FooController < OSX::NSObject
|
@@ -121,6 +126,41 @@ module Rucola
|
|
121
126
|
@_registered_notifications[options[:when]] = method_to_notify
|
122
127
|
end
|
123
128
|
|
129
|
+
protected
|
130
|
+
|
131
|
+
# Given a symbol, attempt to map it to an NSNoficication, otherwise
|
132
|
+
# return the symbol if nothing is found.
|
133
|
+
#
|
134
|
+
# :app_finished_launching => NSApplicationFinishLaunchingNotification
|
135
|
+
def resolve_notification_name(name)
|
136
|
+
return name if name.is_a?(String)
|
137
|
+
notification_name = name.to_s
|
138
|
+
|
139
|
+
# first check if this notification_name uses a shortcut prefix
|
140
|
+
split_notification_name = notification_name.split('_')
|
141
|
+
prefix = split_notification_name.first.to_sym
|
142
|
+
if @_notification_prefixes and @_notification_prefixes.has_key? prefix
|
143
|
+
notification_name = @_notification_prefixes[prefix].to_s << '_' << split_notification_name[1..-1].join('_')
|
144
|
+
end
|
145
|
+
|
146
|
+
begin
|
147
|
+
# try with only Notification appended
|
148
|
+
notification_name = notification_name.camel_case << 'Notification'
|
149
|
+
OSX.const_get(notification_name)
|
150
|
+
return notification_name
|
151
|
+
rescue NameError
|
152
|
+
# then try with NS prepended
|
153
|
+
begin
|
154
|
+
notification_name = 'NS' << notification_name
|
155
|
+
OSX.const_get(notification_name)
|
156
|
+
return notification_name
|
157
|
+
rescue
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
return name
|
162
|
+
end
|
163
|
+
|
124
164
|
end
|
125
165
|
|
126
166
|
def self.included(base) # :nodoc
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rucola/info_plist'
|
2
|
+
|
1
3
|
module Rucola
|
2
4
|
module RCApp
|
3
5
|
# Returns the path to the current source root of the application.
|
@@ -97,5 +99,12 @@ module Rucola
|
|
97
99
|
end
|
98
100
|
module_function :path_for_asset
|
99
101
|
|
102
|
+
# Returns the name of the application as specified in the Info.plist file.
|
103
|
+
#
|
104
|
+
# Rucola::RCApp.app_name #=> 'MyApp'
|
105
|
+
def app_name
|
106
|
+
Rucola::InfoPlist.open((RUBYCOCOA_ROOT + 'config/Info.plist').to_s).app_name
|
107
|
+
end
|
108
|
+
module_function :app_name
|
100
109
|
end
|
101
110
|
end
|
data/lib/rucola/tasks/main.rake
CHANGED
@@ -6,8 +6,11 @@ require 'rucola/nib'
|
|
6
6
|
require 'rucola/rucola_support'
|
7
7
|
|
8
8
|
# set the env, default to debug if we are running a rake task.
|
9
|
-
|
10
|
-
|
9
|
+
ENV['RUBYCOCOA_ENV'] ||= 'debug'
|
10
|
+
ENV['RUBYCOCOA_ROOT'] ||= SOURCE_ROOT
|
11
|
+
|
12
|
+
require 'rucola/initializer'
|
13
|
+
|
11
14
|
puts "RUNNING IN MODE: #{RUBYCOCOA_ENV.upcase}\n\n"
|
12
15
|
|
13
16
|
# FIXME: We also need to check if the user uses a frozen rc framework
|
@@ -16,8 +19,8 @@ RUBYCOCOA_FRAMEWORK = OSX::NSBundle.bundleWithIdentifier('com.apple.rubycocoa').
|
|
16
19
|
# TASKS
|
17
20
|
|
18
21
|
# Get all the tasks
|
19
|
-
Dir["#{File.dirname(__FILE__)}/*.rake"].each {|file| load file unless File.basename(file)
|
20
|
-
|
22
|
+
Dir["#{File.dirname(__FILE__)}/*.rake"].each {|file| load file unless ['main.rake'].include? File.basename(file) }
|
23
|
+
Dir[(ENV['RUBYCOCOA_ROOT'] + '/vendor/plugins/*/tasks/*.rake').to_s].each { |r| load r }
|
21
24
|
task :default => 'xcode:build'
|
22
25
|
|
23
26
|
desc 'Runs all the clean tasks'
|
@@ -25,5 +28,6 @@ task :clean => 'xcode:clean'
|
|
25
28
|
|
26
29
|
Rake::TestTask.new do |t|
|
27
30
|
t.test_files = FileList['test/*/test_*.rb']
|
31
|
+
t.options = '-rr'
|
28
32
|
t.verbose = true
|
29
33
|
end
|
data/lib/rucola/tasks/xcode.rake
CHANGED
@@ -19,14 +19,18 @@ namespace :xcode do
|
|
19
19
|
task :build do
|
20
20
|
executable = "#{build_root}/Contents/MacOS/#{APPNAME}"
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
# For now let's do xcodebuild everytime.
|
23
|
+
# Otherwise nibs that are updated will not be updated in the bundle...
|
24
|
+
sh "xcodebuild -configuration #{config}"
|
25
|
+
|
26
|
+
# unless File.exists?(executable)
|
27
|
+
# sh "xcodebuild -configuration #{config}"
|
28
|
+
# else
|
29
|
+
# puts "Build already exists, skipping. (Use clean if you really really want a new build.)\n\n"
|
30
|
+
# end
|
27
31
|
|
28
32
|
# Make sure the app is brought to the front once launched.
|
29
|
-
Thread.new do
|
33
|
+
Thread.new(executable) do |executable|
|
30
34
|
sleep 0.025 until OSX::NSWorkspace.sharedWorkspace.launchedApplications.any? {|dict| dict['NSApplicationName'] == APPNAME }
|
31
35
|
`osascript -e 'tell application "#{executable}" to activate'`
|
32
36
|
end
|
data/lib/rucola/test_helper.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
require 'osx/cocoa'
|
2
2
|
|
3
|
+
class Object
|
4
|
+
# A mocha helper to get at an instance variable without having to use instance_variable_get.
|
5
|
+
#
|
6
|
+
# obj.ivar(:some_ivar).expects(:foo)
|
7
|
+
def ivar(name)
|
8
|
+
instance_variable_get("@#{name}".to_sym)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
3
12
|
module OSX
|
4
13
|
# Allows methods to be overriden with a different arity.
|
5
14
|
#
|
@@ -9,6 +18,11 @@ module OSX
|
|
9
18
|
def self._ignore_ns_override; true; end
|
10
19
|
|
11
20
|
class NSObject
|
21
|
+
# A mocha helper to get at an outlet (ivar) without having to use instance_variable_get.
|
22
|
+
#
|
23
|
+
# obj.ib_outlet(:some_text_view).expects(:string=).with('foo')
|
24
|
+
alias_method :ib_outlet, :ivar
|
25
|
+
|
12
26
|
# A Mocha helper method which allows to stub alloc.init and return a mock.
|
13
27
|
#
|
14
28
|
# it "should init and return an instance" do
|
data/lib/rucola/version.rb
CHANGED
data/lib/rucola/xcode.rb
CHANGED
@@ -13,7 +13,7 @@ module Rucola
|
|
13
13
|
@project_path = Pathname.new(project_path)
|
14
14
|
@project = @project_path.basename.to_s.sub(/\.xcodeproj$/, '')
|
15
15
|
@project_path_data = @project_path + 'project.pbxproj'
|
16
|
-
@project_data = OSX::
|
16
|
+
@project_data = OSX::NSMutableDictionary.dictionaryWithContentsOfFile(@project_path_data.to_s)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Saves the project data atomically.
|
@@ -41,16 +41,20 @@ module Rucola
|
|
41
41
|
end
|
42
42
|
private :nil_if_empty
|
43
43
|
|
44
|
+
def objects
|
45
|
+
@project_data['objects'] = @project_data['objects'].to_ns
|
46
|
+
end
|
47
|
+
|
44
48
|
# Get's the id & values for a object which name is the one passed to this method.
|
45
49
|
# Returns an array: [id, values]
|
46
50
|
def object_for_name(name)
|
47
|
-
nil_if_empty
|
51
|
+
nil_if_empty objects.select { |object| object.last['name'] == name }.flatten
|
48
52
|
end
|
49
53
|
|
50
54
|
# Get's the id & values for a object which type and name is the one passed to this method.
|
51
55
|
# Returns an array: [id, values]
|
52
56
|
def object_for_type_and_name(type, name)
|
53
|
-
nil_if_empty
|
57
|
+
nil_if_empty objects.select { |object| object.last['isa'] == type and object.last['name'] == name }.flatten
|
54
58
|
end
|
55
59
|
|
56
60
|
# Returns the object that represents this projects target.
|
@@ -62,18 +66,19 @@ module Rucola
|
|
62
66
|
# Returns the object for a given name.
|
63
67
|
# Returns an array: [id, values]
|
64
68
|
def object_for_id(object_id)
|
65
|
-
|
69
|
+
objects[object_id].nil? ? nil : [object_id, objects[object_id]]
|
66
70
|
end
|
67
71
|
|
68
72
|
# Adds an object to the objects.
|
69
73
|
def add_object(object_id, object_values)
|
70
|
-
|
74
|
+
objects[object_id] = object_values
|
71
75
|
end
|
72
76
|
|
73
77
|
# Adds a build phase specified by +object_id+ to the build phases of the project target.
|
74
78
|
def add_build_phase_to_project_target(object_id)
|
75
79
|
# Add the new build phase to the main project target if it doesn't already exist
|
76
80
|
build_target_id, build_target_values = object_for_project_target
|
81
|
+
build_target_values['buildPhases'] = build_target_values['buildPhases'].to_ns
|
77
82
|
build_target_values['buildPhases'].push(object_id) unless build_target_values['buildPhases'].include?(object_id)
|
78
83
|
end
|
79
84
|
|
@@ -89,7 +94,7 @@ module Rucola
|
|
89
94
|
'dstPath' => '',
|
90
95
|
'dstSubfolderSpec' => 10, # TODO: is 10 the number for the location popup choice: Frameworks
|
91
96
|
'runOnlyForDeploymentPostprocessing' => 0,
|
92
|
-
'files' => []
|
97
|
+
'files' => [].to_ns
|
93
98
|
}]
|
94
99
|
# Creates a new framework copy build phase.
|
95
100
|
# It does not add it to the objects nor the build phases,
|
@@ -1,10 +1,19 @@
|
|
1
1
|
require File.expand_path('../../test_helper', __FILE__)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
describe '<%= name.camel_case %>Controller' do
|
4
|
+
before do
|
5
|
+
@controller = <%= name.camel_case %>Controller.alloc.init
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should initialize" do
|
9
|
+
@controller.should.be.an.instance_of <%= name.camel_case %>Controller
|
8
10
|
end
|
9
11
|
|
12
|
+
it "should do stuff at awakeFromNib" do
|
13
|
+
# Some example code of testing your #awakeFromNib.
|
14
|
+
#
|
15
|
+
# @controller.ib_outlet(:some_text_view).expects(:string=).with('foo')
|
16
|
+
|
17
|
+
@controller.awakeFromNib
|
18
|
+
end
|
10
19
|
end
|
@@ -9,7 +9,7 @@ class DocumentModelGenerator < RubiGen::Base
|
|
9
9
|
|
10
10
|
def initialize(runtime_args, runtime_options = {})
|
11
11
|
super
|
12
|
-
usage if args.empty?
|
12
|
+
usage if args.empty? || args.length == 1
|
13
13
|
@name = args.shift
|
14
14
|
@extension = args.shift
|
15
15
|
extract_options
|
@@ -41,9 +41,9 @@ class DocumentModelGenerator < RubiGen::Base
|
|
41
41
|
protected
|
42
42
|
def banner
|
43
43
|
<<-EOS
|
44
|
-
Creates a
|
44
|
+
Creates a model that inherits from NSDocument.
|
45
45
|
|
46
|
-
USAGE: #{$0} #{spec.name} name"
|
46
|
+
USAGE: #{$0} #{spec.name} name extension"
|
47
47
|
EOS
|
48
48
|
end
|
49
49
|
|