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.
Files changed (49) hide show
  1. data/ChangeLog +301 -0
  2. data/History.txt +51 -0
  3. data/Manifest.txt +12 -0
  4. data/README.txt +6 -7
  5. data/TODO +13 -8
  6. data/app_generators/rucola/rucola_generator.rb +1 -1
  7. data/app_generators/rucola/templates/app/controllers/application_controller.rb +2 -2
  8. data/app_generators/rucola/templates/test/controllers/test_application_controller.rb +22 -5
  9. data/app_generators/rucola/templates/test/test_helper.rb +3 -2
  10. data/config/hoe.rb +2 -2
  11. data/lib/autotest/discover.rb +9 -0
  12. data/lib/autotest/fail.png +0 -0
  13. data/lib/autotest/growl_images.rb +39 -0
  14. data/lib/autotest/pass.png +0 -0
  15. data/lib/autotest/rucola.rb +36 -0
  16. data/lib/autotest/sound.rb +52 -0
  17. data/lib/rucola/info_plist.rb +5 -0
  18. data/lib/rucola/initializer.rb +65 -113
  19. data/lib/rucola/nib.rb +2 -2
  20. data/lib/rucola/plugin.rb +57 -0
  21. data/lib/rucola/rucola_support/initialize_hooks.rb +7 -0
  22. data/lib/rucola/rucola_support/notifications/notifications.rb +67 -27
  23. data/lib/rucola/rucola_support/rc_app.rb +9 -0
  24. data/lib/rucola/tasks/main.rake +8 -4
  25. data/lib/rucola/tasks/xcode.rake +10 -6
  26. data/lib/rucola/test_helper.rb +14 -0
  27. data/lib/rucola/version.rb +1 -1
  28. data/lib/rucola/xcode.rb +11 -6
  29. data/rucola_generators/controller/controller_generator.rb +1 -1
  30. data/rucola_generators/controller/templates/test_controller_template.rb.erb +14 -5
  31. data/rucola_generators/document_model/document_model_generator.rb +3 -3
  32. data/rucola_generators/document_model/templates/test_document_model_template.rb.erb +18 -5
  33. data/rucola_generators/rucola_plugin/USAGE +6 -0
  34. data/rucola_generators/rucola_plugin/rucola_plugin_generator.rb +63 -0
  35. data/rucola_generators/rucola_plugin/templates/init.rb.erb +25 -0
  36. data/rucola_generators/window_controller/templates/test_window_controller_template.rb.erb +21 -5
  37. data/rucola_generators/window_controller/window_controller_generator.rb +1 -1
  38. data/test/test_document_model_generator.rb +8 -2
  39. data/test/test_info_plist.rb +4 -0
  40. data/test/test_initializer.rb +80 -0
  41. data/test/test_nib.rb +9 -0
  42. data/test/test_notifications.rb +38 -19
  43. data/test/test_plugin.rb +48 -0
  44. data/test/test_rc_app.rb +5 -0
  45. data/test/test_rucola_plugin_generator.rb +65 -0
  46. data/test/test_xcode.rb +3 -3
  47. data/website/index.html +49 -7
  48. data/website/index.txt +34 -4
  49. metadata +37 -2
@@ -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' => 'NSObject',
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
- # notify_on :win_did_become_key do |notification|
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
@@ -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
- RUBYCOCOA_ENV = ENV['RUBYCOCOA_ENV'].nil? ? 'debug' : ENV['RUBYCOCOA_ENV']
10
- RUBYCOCOA_ROOT = ENV['RUBYCOCOA_ROOT'].nil? ? SOURCE_ROOT : ENV['RUBYCOCOA_ROOT']
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) == 'main.rake' }
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
@@ -19,14 +19,18 @@ namespace :xcode do
19
19
  task :build do
20
20
  executable = "#{build_root}/Contents/MacOS/#{APPNAME}"
21
21
 
22
- unless File.exists?(executable)
23
- sh "xcodebuild -configuration #{config}"
24
- else
25
- puts "Build already exists, skipping. (Use clean if you really really want a new build.)\n\n"
26
- end
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
@@ -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
@@ -2,7 +2,7 @@ module Rucola #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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::NSDictionary.dictionaryWithContentsOfFile(@project_path_data.to_s)
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 @project_data['objects'].select { |object| object.last['name'] == name }.flatten
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 @project_data['objects'].select { |object| object.last['isa'] == type and object.last['name'] == name }.flatten
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
- @project_data['objects'][object_id].nil? ? nil : [object_id, @project_data['objects'][object_id]]
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
- @project_data['objects'][object_id] = object_values
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,
@@ -58,7 +58,7 @@ class ControllerGenerator < RubiGen::Base
58
58
  protected
59
59
  def banner
60
60
  <<-EOS
61
- Creates a ...
61
+ Creates a controller that inherits from NSObject
62
62
 
63
63
  USAGE: #{$0} #{spec.name} name"
64
64
  EOS
@@ -1,10 +1,19 @@
1
1
  require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
- class Test<%= name.camel_case %>Controller < Test::Unit::TestCase
4
-
5
- def test_<%= name.snake_case %>_controller_initialization
6
- <%= name.snake_case %>_controller = <%= name.camel_case %>Controller.alloc.init
7
- assert_instance_of(<%= name.camel_case %>Controller, <%= name.snake_case %>_controller)
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