merb 0.3.4 → 0.3.7

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 (62) hide show
  1. data/README +206 -197
  2. data/Rakefile +12 -21
  3. data/bin/merb +1 -1
  4. data/examples/skeleton/Rakefile +6 -20
  5. data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
  6. data/examples/skeleton/dist/conf/database.yml +23 -0
  7. data/examples/skeleton/dist/conf/environments/development.rb +1 -0
  8. data/examples/skeleton/dist/conf/environments/production.rb +1 -0
  9. data/examples/skeleton/dist/conf/environments/test.rb +1 -0
  10. data/examples/skeleton/dist/conf/merb.yml +32 -28
  11. data/examples/skeleton/dist/conf/merb_init.rb +16 -13
  12. data/examples/skeleton/dist/conf/router.rb +9 -9
  13. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
  14. data/lib/merb.rb +23 -18
  15. data/lib/merb/caching/fragment_cache.rb +3 -7
  16. data/lib/merb/caching/store/memcache.rb +20 -0
  17. data/lib/merb/core_ext/merb_array.rb +0 -0
  18. data/lib/merb/core_ext/merb_class.rb +44 -4
  19. data/lib/merb/core_ext/merb_enumerable.rb +43 -1
  20. data/lib/merb/core_ext/merb_hash.rb +200 -122
  21. data/lib/merb/core_ext/merb_kernel.rb +2 -0
  22. data/lib/merb/core_ext/merb_module.rb +41 -0
  23. data/lib/merb/core_ext/merb_numeric.rb +57 -5
  24. data/lib/merb/core_ext/merb_object.rb +172 -6
  25. data/lib/merb/generators/merb_app/merb_app.rb +15 -9
  26. data/lib/merb/merb_abstract_controller.rb +193 -0
  27. data/lib/merb/merb_constants.rb +26 -1
  28. data/lib/merb/merb_controller.rb +143 -234
  29. data/lib/merb/merb_dispatcher.rb +28 -20
  30. data/lib/merb/merb_drb_server.rb +2 -3
  31. data/lib/merb/merb_exceptions.rb +194 -49
  32. data/lib/merb/merb_handler.rb +34 -26
  33. data/lib/merb/merb_mail_controller.rb +200 -0
  34. data/lib/merb/merb_mailer.rb +33 -13
  35. data/lib/merb/merb_part_controller.rb +42 -0
  36. data/lib/merb/merb_plugins.rb +293 -0
  37. data/lib/merb/merb_request.rb +6 -4
  38. data/lib/merb/merb_router.rb +99 -65
  39. data/lib/merb/merb_server.rb +65 -21
  40. data/lib/merb/merb_upload_handler.rb +2 -1
  41. data/lib/merb/merb_view_context.rb +36 -15
  42. data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
  43. data/lib/merb/mixins/controller_mixin.rb +67 -28
  44. data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
  45. data/lib/merb/mixins/form_control_mixin.rb +280 -42
  46. data/lib/merb/mixins/render_mixin.rb +127 -45
  47. data/lib/merb/mixins/responder_mixin.rb +5 -7
  48. data/lib/merb/mixins/view_context_mixin.rb +260 -94
  49. data/lib/merb/session.rb +23 -0
  50. data/lib/merb/session/merb_ar_session.rb +28 -16
  51. data/lib/merb/session/merb_mem_cache_session.rb +108 -0
  52. data/lib/merb/session/merb_memory_session.rb +65 -20
  53. data/lib/merb/template/erubis.rb +22 -13
  54. data/lib/merb/template/haml.rb +5 -16
  55. data/lib/merb/template/markaby.rb +5 -3
  56. data/lib/merb/template/xml_builder.rb +17 -5
  57. data/lib/merb/test/merb_fake_request.rb +63 -0
  58. data/lib/merb/test/merb_multipart.rb +58 -0
  59. data/lib/tasks/db.rake +2 -0
  60. data/lib/tasks/merb.rake +20 -8
  61. metadata +24 -25
  62. data/examples/skeleton.tar +0 -0
@@ -0,0 +1,200 @@
1
+ require File.dirname(__FILE__)+'/merb_mailer'
2
+
3
+ module Merb
4
+ class MailController < AbstractController
5
+
6
+ self._template_root = File.expand_path(DIST_ROOT / "app/mailers/views")
7
+ class_inheritable_accessor :_mailer_klass
8
+ self._mailer_klass = Merb::Mailer
9
+
10
+ attr_accessor :params, :mailer, :mail
11
+ attr_reader :session, :base_controller
12
+
13
+ # You can initialize a MailController with a series of parameters that can
14
+ # be used by methods in the class. You can also pass in a controller
15
+ # object, which will be available to the MailController methods as
16
+ # base_controller.
17
+ def initialize(params = {}, controller = nil)
18
+ @params = params
19
+ @base_controller = controller
20
+ @session = (controller && controller.session) || {}
21
+ end
22
+
23
+ def filters_halted
24
+ end
25
+
26
+ # Allows you to render various types of things into the text and HTML parts
27
+ # of an email If you include just text, the email will be sent as
28
+ # plain-text. If you include HTML, the email will be sent as a multi-part
29
+ # email.
30
+ #
31
+ # There are a lot of ways to use render_mail, but it works similarly to the
32
+ # default Merb render method.
33
+ #
34
+ # First of all, you'll need to store email files in your
35
+ # dist/app/mailers/views directory. They should be under a directory that
36
+ # matches the name of your mailer (e.g. TestMailer's views would be stored
37
+ # under test_mailer).
38
+ #
39
+ # The files themselves should be named action_name.mime_type.extension. For
40
+ # example, a herb template that should be the HTML part of the email, and
41
+ # rendered from the "foo" action would be named foo.html.herb.
42
+ #
43
+ # The only mime-types currently supported are "html" and "text", which
44
+ # correspond to text/html and text/plain respectively. All template systems
45
+ # supported by your app are available to MailController, and the extensions
46
+ # are the same as they are throughout the rest of Merb.
47
+ #
48
+ # render_mail can take any of the following option patterns:
49
+ #
50
+ # render_mail
51
+ #
52
+ # will attempt to render the current action. If the current action is
53
+ # "foo", this is identical to render_mail :foo.
54
+ #
55
+ # render_mail :foo
56
+ #
57
+ # checks for foo.html.ext and foo.text.ext and applies them as appropriate.
58
+ #
59
+ # render_mail :action => {:html => :foo, :text => :bar}
60
+ #
61
+ # checks for foo.html.ext and bar.text.ext in the view directory of the
62
+ # current controller and adds them to the mail object if found
63
+ #
64
+ # render_mail :template => {:html => "foo/bar", :text => "foo/baz"}
65
+ #
66
+ # checks for bar.html.ext and baz.text.ext in the foo directory and adds
67
+ # them to the mail object if found.
68
+ #
69
+ # render_mail :html => :foo, :text => :bar
70
+ #
71
+ # the same as render_mail :action => {html => :foo, :text => :bar }
72
+ #
73
+ # render_mail :html => "FOO", :text => "BAR"
74
+ #
75
+ # adds the text "FOO" as the html part of the email and the text "BAR" as
76
+ # the text part of the email. The difference between the last two examples
77
+ # is that symbols represent actions to render, while string represent the
78
+ # literal text to render. Note that you can use regular render methods
79
+ # instead of literal strings here, like:
80
+ #
81
+ # render_mail :html => render(:action => :foo)
82
+ #
83
+ # but you're probably better off just using render_mail :action at that
84
+ # point.
85
+ #
86
+ # You can also mix and match:
87
+ #
88
+ # render_mail :action => {:html => :foo}, :text => "BAR"
89
+ #
90
+ # which would be identical to:
91
+ #
92
+ # render_mail :html => :foo, :text => "BAR"
93
+ def render_mail(options = @method)
94
+ # If the options are not a hash, normalize to an action hash
95
+ options = {:action => {:html => options, :text => options}} if !options.is_a?(Hash)
96
+ # Normalize :html => :sym and :text => :sym to an action hash
97
+ options[:action] ||= {}
98
+ options[:action][:html] = options.delete(:html) if options[:html].is_a?(Symbol)
99
+ options[:action][:text] = options.delete(:text) if options[:text].is_a?(Symbol)
100
+ options.delete(:action) if options[:action].empty?
101
+
102
+ # set type to action or template, if the hash includes :action or :template
103
+ type = options.keys.find {|x| x.to_s =~ /(action|template)/}
104
+ # normalize :action/:template => ... to :action/template => {:html => ..., :text => ...}
105
+ options[type] = {:html => options[type], :text => options[type]} if type && !options[type].is_a?(Hash)
106
+ for mime_type in [:html, :text]
107
+ # if action or template, get the template to render. If not, get the string to render. If none
108
+ # are available, use the default params[:action]
109
+ renderer = (type && options[type][mime_type] ? options[type][mime_type] : options[mime_type]).to_s
110
+ # action or template
111
+ if type && options[type][mime_type]
112
+ # build the template to render
113
+ mime_renderer = renderer + ".#{mime_type}" if !(renderer =~ /.#{mime_type}$/)
114
+ if find_template(type => mime_renderer)
115
+ @mail.send((mime_type == :html ? "rawhtml=" : "text="),
116
+ render(type => mime_renderer, :layout => mimed_layout(mime_type, options[:layout])))
117
+ elsif find_template(type => renderer) && mime_type == :text
118
+ @mail.text = render(type => renderer, :layout => options[:layout])
119
+ end
120
+ # string to render
121
+ else
122
+ @mail.send((mime_type == :html ? "rawhtml=" : "text="), renderer)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Attaches a file or multiple files to an email. You call this from a
128
+ # method in your MailController (including a before filter).
129
+ #
130
+ # attach File.open("foo")
131
+ # attach [File.open("foo"), File.open("bar")]
132
+ #
133
+ # You can also include the filename, mime-type, or headers in the
134
+ # subsequent parameters.
135
+ #
136
+ # If you are passing an array of files, you should use an array of the
137
+ # allowed parameters:
138
+ #
139
+ # attach [[File.open("foo"), "bar", "text/html"], [File.open("baz"),
140
+ # "bat", "text/css"]
141
+ #
142
+ # which would attach two files ("foo" and "baz" in the filesystem) as
143
+ # "bar" and "bat" respectively. It would also set the mime-type as
144
+ # "text/html" and "text/css" respectively.
145
+ def attach( file_or_files, filename = file_or_files.is_a?(File) ? File.basename(file_or_files.path) : nil,
146
+ type = nil, headers = nil)
147
+ @mail.attach(file_or_files, filename, type, headers)
148
+ end
149
+
150
+ # take a method name to dispatch to and mail parameters for the MailFactory object.
151
+ #
152
+ # Available mail parameters include:
153
+ # to
154
+ # from
155
+ # replyto
156
+ # subject
157
+ # body
158
+ # cc
159
+ #
160
+ # Other parameters passed in will be interpreted as email headers, with _'s converted
161
+ # to -'s.
162
+ def dispatch_and_deliver(method, mail_params)
163
+ @mailer = self.class._mailer_klass.new(mail_params)
164
+ @mail = @mailer.mail
165
+ @method = method
166
+ params[:action] = method
167
+ body = dispatch method
168
+ if !@mail.html.blank? || !@mail.text.blank?
169
+ @mailer.deliver!
170
+ MERB_LOGGER.info "#{method} sent to #{@mail.to} about #{@mail.subject}"
171
+ else
172
+ MERB_LOGGER.info "#{method} was not sent because nothing was rendered for it"
173
+ end
174
+ end
175
+
176
+ # A convenience method that creates a blank copy of the MailController and runs
177
+ # dispatch_and_deliver on it.
178
+ def self.dispatch_and_deliver(method, mail_params)
179
+ new({}).dispatch_and_deliver method, mail_params
180
+ end
181
+
182
+ private
183
+ def mimed_layout(mime_type, layout = nil)
184
+ if layout && layout != :application
185
+ layout_choice = find_template(:layout => layout + ".#{mime_type}") ||
186
+ find_template(:layout => layout)
187
+ elsif name = find_template(:layout => self.class.name.snake_case + ".#{mime_type}")
188
+ layout_choice = name
189
+ elsif name = find_template(:layout => self.class.name.snake_case)
190
+ layout_choice = name
191
+ elsif name = find_template(:layout => "application.#{mime_type}")
192
+ layout_choice = name
193
+ else
194
+ layout_choice = find_template(:layout => "application")
195
+ end
196
+ layout_choice = layout_choice.split("/").last.split(".")[0...-1].join(".")
197
+ end
198
+
199
+ end
200
+ end
@@ -1,5 +1,3 @@
1
-
2
-
3
1
  begin
4
2
  require 'mailfactory'
5
3
  require 'net/smtp'
@@ -8,10 +6,16 @@ rescue LoadError
8
6
  MERB_LOGGER.warn "You need to install the mailfactory gem to use Merb::Mailer"
9
7
  end
10
8
 
9
+ class MailFactory
10
+ attr_reader :html, :text
11
+ end
12
+
11
13
  module Merb
12
14
  class Mailer
13
15
 
14
- class_inheritable_accessor :config
16
+ class_inheritable_accessor :config, :delivery_method, :deliveries
17
+ attr_accessor :mail
18
+ self.deliveries = []
15
19
 
16
20
  def sendmail
17
21
  sendmail = IO.popen("sendmail #{@mail.to}", 'w+')
@@ -22,28 +26,40 @@ module Merb
22
26
  # :plain, :login, or :cram_md5
23
27
  def net_smtp
24
28
  Net::SMTP.start(config[:host], config[:port].to_i, config[:domain],
25
- config[:user], config[:pass], (config[:auth].intern||:plain)) { |smtp|
26
- smtp.send_message(@mail.to_s, @mail.from, @mail.to)
27
- }
29
+ config[:user], config[:pass], (config[:auth].to_sym||:plain)) { |smtp|
30
+ smtp.send_message(@mail.to_s, @mail.from.first, @mail.to)
31
+ }
32
+ end
33
+
34
+ def test_send
35
+ deliveries << @mail
28
36
  end
29
37
 
30
38
  def deliver!
31
- config == :sendmail ? sendmail : net_smtp
39
+ send(delivery_method || :net_smtp)
40
+ end
41
+
42
+ def attach(file_or_files, filename = file_or_files.is_a?(File) ? File.basename(file_or_files.path) : nil,
43
+ type = nil, headers = nil)
44
+ if file_or_files.is_a?(Array)
45
+ file_or_files.each {|k,v| @mail.add_attachment_as k, *v}
46
+ else
47
+ raise ArgumentError, "You did not pass in a file. Instead, you sent a #{file_or_files.class}" if !file_or_files.is_a?(File)
48
+ @mail.add_attachment_as(file_or_files, filename, type, headers)
49
+ end
32
50
  end
33
51
 
34
52
  def initialize(o={})
35
53
  self.config = :sendmail if config.nil?
54
+ o[:rawhtml] = o.delete(:html)
36
55
  @mail = returning MailFactory.new() do |m|
37
- m.to = o[:to]
38
- m.from = o[:from]
39
- m.subject = o[:subject]
40
- m.text = o[:body]
41
- m.html = o[:html] if o[:html]
56
+ o.each { |k,v| m.send "#{k}=", v }
42
57
  end
43
- end
58
+ end
44
59
 
45
60
  end
46
61
  end
62
+
47
63
  =begin
48
64
 
49
65
 
@@ -54,10 +70,14 @@ Merb::Mailer.config = {
54
70
  :pass=>'pass',
55
71
  :auth=>:plain # :plain, :login, or :cram_md5, default :plain
56
72
  }
73
+
74
+ Merb::Mailer.delivery_method = :sendmail
75
+
57
76
  m = Merb::Mailer.new :to => 'foo@bar.com',
58
77
  :from => 'bar@foo.com',
59
78
  :subject => 'Welcome to whatever!',
60
79
  :body => partial(:sometemplate)
61
80
  m.deliver!
62
81
 
82
+
63
83
  =end
@@ -0,0 +1,42 @@
1
+ module Merb
2
+ class PartController < AbstractController
3
+ self._template_root = File.expand_path(DIST_ROOT / "app/parts/views")
4
+
5
+ def initialize(web_controller)
6
+ @_benchmarks = {}
7
+ @web_controller = web_controller
8
+ end
9
+
10
+ def dispatch(action=:to_s)
11
+ old_action = params[:action]
12
+ params[:action] = action
13
+ super(action)
14
+ params[:action] = old_action
15
+ @_body
16
+ end
17
+
18
+ def request
19
+ @web_controller.request
20
+ end
21
+
22
+ def params
23
+ @web_controller.params
24
+ end
25
+
26
+ def cookies
27
+ @web_controller.cookies
28
+ end
29
+
30
+ def headers
31
+ @web_controller.headers
32
+ end
33
+
34
+ def session
35
+ @web_controller.session
36
+ end
37
+
38
+ def response
39
+ @web_controller.response
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,293 @@
1
+ # TODO: Find Merb app root so we aren't wantonly copying Rakefiles
2
+ # TODO: Figure out a good way to handle the lack of install/uninstall script
3
+ # TODO: Minimum versions for local gems in install
4
+
5
+ require 'yaml'
6
+ require 'rubygems'
7
+
8
+ module Merb
9
+ class PluginManager
10
+
11
+ def self.gem_name
12
+ @@gem_name
13
+ end
14
+
15
+ def self.gem_spec
16
+ @@gem_spec
17
+ end
18
+
19
+ def self.options
20
+ @@options
21
+ end
22
+
23
+ def self.gem_name=(val)
24
+ @@gem_name = val
25
+ end
26
+
27
+ def self.gem_spec=(val)
28
+ @@gem_spec = val
29
+ end
30
+
31
+ def self.options=(val)
32
+ @@options = val
33
+ end
34
+
35
+
36
+ # Run a particular plugin action
37
+ def self.action(action, gem_name = nil, opts = [])
38
+ @@gem_name = gem_name
39
+ @@options = opts.flatten
40
+
41
+ case action
42
+ when 'install':
43
+ install
44
+ when 'uninstall':
45
+ uninstall
46
+ when 'remove':
47
+ remove_from_manifest
48
+ when 'list':
49
+ list
50
+ when 'help'
51
+ help
52
+ when 'migrate'
53
+ migrate
54
+ when 'update'
55
+ raise "This hasn't been implemented yet!"
56
+ when 'upgrade'
57
+ raise "This hasn't been implemented yet!"
58
+ else
59
+ raise "Invalid action: #{action}"
60
+ end
61
+ end
62
+
63
+ # Install a new plugin named +gem_name+.
64
+ def self.install
65
+ raise "Plugin already installed!" if plugin_installed?
66
+
67
+ self.gem_spec = find_locally or install_gem
68
+
69
+ run_install_script
70
+ copy_rakefile
71
+ add_to_manifest
72
+ rescue Exception => e
73
+ puts "\n!!! Plugin installation FAILED: #{e.message}\n\n"
74
+ end
75
+
76
+ # Check the manifest to see if the plugin is already installed in this application.
77
+ def self.plugin_installed?
78
+ with_manifest do |manifest|
79
+ return manifest.has_key?(gem_name)
80
+ end
81
+ rescue
82
+ false
83
+ end
84
+
85
+ # Search the local Rubygems installation for the gem.
86
+ def self.find_locally
87
+ all_installed_gems = Gem::SourceIndex.from_installed_gems
88
+ latest_gem_version = all_installed_gems.search(gem_name).sort_by {|x| x.version.version }.last
89
+ require 'pp'
90
+
91
+ if latest_gem_version
92
+ puts "Local gem found."
93
+ return latest_gem_version
94
+ else
95
+ puts "Can't find gem...installing."
96
+ return false
97
+ end
98
+ end
99
+
100
+ # Run the installation code in the gem.
101
+ def self.run_install_script
102
+ require "#{gem_name}/install"
103
+ end
104
+
105
+ # Copy the Rakefile from the gem to a directory in our application.
106
+ def self.copy_rakefile
107
+ path = gem_spec.loaded_from.split(/specifications/)[0] + "gems/#{gem_name}-#{gem_spec.version.version}/"
108
+
109
+ merb_rake_file = path + "Rakefile.merb"
110
+
111
+ require 'fileutils'
112
+ Dir.mkdir 'plugins' if !File.exists?('plugins/')
113
+ FileUtils.cp merb_rake_file, "plugins/#{gem_name}.rake"
114
+
115
+ File.open('Rakefile', 'a') do |rakefile|
116
+ rakefile.write("\nrequire 'plugins/#{gem_name}.rake\n")
117
+ end
118
+ end
119
+
120
+ def self.remove_rakefile
121
+ FileUtils.rm "plugins/#{gem_name}.rake"
122
+
123
+ File.open('Rakefile', 'r+') do |rakefile|
124
+ contents = rakefile.read
125
+ contents.gsub!("\nrequire 'plugins/#{gem_name}.rake\n", "")
126
+
127
+ rakefile.pos = 0
128
+ rakefile.write contents
129
+ rakefile.truncate rakefile.pos
130
+ end
131
+ end
132
+
133
+ # Do a gem installation since it wasn't found in the locally installed gems.
134
+ def self.install_gem
135
+ require 'rubygems/installer'
136
+
137
+ options = {
138
+ :domain => :both,
139
+ :generate_rdoc => true,
140
+ :generate_ri => true,
141
+ :force => false,
142
+ :test => false,
143
+ :wrappers => true,
144
+ :version => "> 0",
145
+ :install_dir => Gem.dir,
146
+ :security_policy => nil }
147
+
148
+
149
+
150
+ puts "Attempting local install..."
151
+ local_install = Gem::Installer.new(gem_name, options).install(options[:force], options[:install_dir]) rescue false
152
+
153
+ if !local_install
154
+ # Do remote installation
155
+ require 'rubygems/remote_installer'
156
+
157
+ Gem.configuration = ConfigStub.new
158
+
159
+ puts "Attempting remote installation..."
160
+ installer = Gem::RemoteInstaller.new(options)
161
+
162
+ @@options.each do |opt|
163
+ installer.sources << opt.gsub(/source=/, '') if opt.scan(/^source=/) != []
164
+ options[:version] = "> #{opt.gsub(/version=/, '')}" if opt.scan(/^version=/) != []
165
+ end
166
+
167
+ remote_install = installer.install(gem_name, options[:version], options[:force], options[:install_dir]) rescue false
168
+ end
169
+
170
+ if !remote_install && !local_install
171
+ raise "That plugin can't be found in locally or in a remote repository."
172
+ else
173
+ find_locally
174
+ end
175
+ end
176
+
177
+ # Method to open and load our manifest; yields the manifest data to whatever method calls it.
178
+ def self.with_manifest
179
+ File.open('plugins.manifest', 'r+') do |manifest_file|
180
+ manifest_data = YAML::load(manifest_file.read)
181
+ manifest_data = {} if !manifest_data.is_a?(Hash)
182
+
183
+ yield manifest_data
184
+
185
+ manifest_file.pos = 0
186
+ manifest_file.write manifest_data.to_yaml
187
+ manifest_file.truncate manifest_file.pos
188
+ end
189
+ end
190
+
191
+ # Write our newly installed plugin to the application's manifest.
192
+ def self.add_to_manifest
193
+ with_manifest do |manifest_data|
194
+ manifest_data[gem_name] = {'version' => gem_spec.version.version}
195
+ end
196
+ end
197
+
198
+ def self.uninstall
199
+ raise "That plugin isn't installed!" if !plugin_installed?
200
+
201
+ require "#{gem_name}/uninstall" rescue nil
202
+
203
+ remove_rakefile
204
+ remove_from_manifest
205
+ uninstall_gem
206
+ end
207
+
208
+ def self.remove_from_manifest
209
+ with_manifest do |manifest|
210
+ manifest.delete(gem_name)
211
+ end
212
+ end
213
+
214
+ def self.uninstall_gem
215
+ require 'rubygems/installer'
216
+ require 'rubygems/doc_manager'
217
+
218
+ options = {
219
+ :domain => :both,
220
+ :generate_rdoc => true,
221
+ :generate_ri => true,
222
+ :force => false,
223
+ :test => false,
224
+ :wrappers => true,
225
+ :version => "> 0",
226
+ :install_dir => Gem.dir,
227
+ :security_policy => nil }
228
+
229
+ Gem::Uninstaller.new(gem_name, options).uninstall
230
+ end
231
+
232
+ def self.list
233
+ with_manifest do |manifest_data|
234
+ manifest_data.each do |key, value|
235
+ gem_name = key
236
+ gem_spec = find_locally
237
+ puts "#{key} (#{gem_spec.version.version})"
238
+ puts "\t#{gem_spec.description}\n"
239
+ end
240
+ end
241
+ end
242
+
243
+ def self.migrate
244
+ raise "This hasn't been implemented yet!"
245
+ end
246
+
247
+ def self.help
248
+ puts """
249
+ Merb Plugins
250
+ The -P (--plugin) option fed to Merb allows you to manage plugins.
251
+
252
+ Commands
253
+ --------
254
+ install Installs a new plugin.
255
+ uninstall Uninstalls a plugin, removing its entry from the manifest and uninstalling the gem.
256
+ remove Uninstalls a plugin, removing its entry from the manifest but leaving the gem installed.
257
+ list Lists all installed plugins for this Merb application.
258
+ upgrade Upgrades a plugin to the specified version.
259
+ update Updates a plugin to the latest available version.
260
+ migrate Reads the manifest for this application, installing and updating gems as needed.
261
+ help Shows this help message.
262
+
263
+ """
264
+ end
265
+ end
266
+
267
+ # Class to stub out the verbose flag in Rubygems user interaction lib
268
+ class ConfigStub
269
+ attr_accessor :verbose
270
+
271
+ def initialize
272
+ @verbose = false
273
+ end
274
+ end
275
+ end
276
+
277
+ module Gem
278
+ # Fixes a bug in RubyGems where the SilentProgressReporter class is
279
+ # inaccurately named SilentReporter. Maybe one day we'll monkeypatch this for
280
+ # our own purposes.
281
+ class SilentProgressReporter
282
+ attr_reader :count
283
+
284
+ def initialize(out_stream, size, initial_message)
285
+ end
286
+
287
+ def updated(message)
288
+ end
289
+
290
+ def done
291
+ end
292
+ end
293
+ end