rucola 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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