mack 0.7.0.1 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +34 -0
  2. data/README +6 -13
  3. data/bin/env_handler.rb +10 -0
  4. data/bin/gem_load_path.rb +25 -0
  5. data/bin/mack +1 -0
  6. data/bin/mackery +22 -0
  7. data/bin/mackery-console +16 -0
  8. data/bin/mackery-server +41 -0
  9. data/lib/mack.rb +7 -1
  10. data/lib/mack/controller/controller.rb +5 -4
  11. data/lib/mack/controller/request.rb +19 -1
  12. data/lib/mack/errors/errors.rb +8 -8
  13. data/lib/mack/generators/controller_generator/controller_generator.rb +1 -1
  14. data/lib/mack/generators/mack_application_generator/templates/app/views/layouts/application.html.erb.template +1 -2
  15. data/lib/mack/generators/mack_application_generator/templates/config/app_config/default.yml.template +2 -0
  16. data/lib/mack/generators/mack_application_generator/templates/config/database.yml.template +6 -6
  17. data/lib/mack/generators/passenger_generator/passenger_generator.rb +2 -0
  18. data/lib/mack/generators/passenger_generator/templates/config.ru.template +5 -0
  19. data/lib/mack/generators/passenger_generator/templates/tmp/README.template +2 -0
  20. data/lib/mack/initialization/application.rb +39 -36
  21. data/lib/mack/initialization/boot_loader.rb +174 -0
  22. data/lib/mack/initialization/configuration.rb +83 -95
  23. data/lib/mack/initialization/console.rb +11 -0
  24. data/lib/mack/initialization/environment.rb +16 -0
  25. data/lib/mack/initialization/helpers.rb +26 -24
  26. data/lib/mack/initialization/logging.rb +81 -81
  27. data/lib/mack/initialization/plugins.rb +14 -11
  28. data/lib/mack/initialization/server/simple_server.rb +3 -3
  29. data/lib/mack/rendering/type/base.rb +1 -1
  30. data/lib/mack/rendering/type/layout.rb +1 -1
  31. data/lib/mack/rendering/type/partial.rb +1 -1
  32. data/lib/mack/rendering/type/public.rb +1 -1
  33. data/lib/mack/rendering/type/template.rb +1 -1
  34. data/lib/mack/runner.rb +2 -2
  35. data/lib/mack/runner_helpers/session.rb +3 -3
  36. data/lib/mack/sessions/cookie_session_store.rb +44 -0
  37. data/lib/mack/{controller → sessions}/session.rb +0 -0
  38. data/lib/mack/sessions/session_store_base.rb +62 -0
  39. data/lib/mack/sessions/test_session_store.rb +38 -0
  40. data/lib/mack/tasks/mack_server_tasks.rake +8 -21
  41. data/lib/mack/tasks/mack_tasks.rake +33 -6
  42. data/lib/mack/tasks/rake_rules.rake +8 -0
  43. data/lib/mack/tasks/test_tasks.rake +39 -15
  44. data/lib/mack/testing/helpers.rb +6 -39
  45. data/lib/mack/utils/content_length_handler.rb +45 -0
  46. data/lib/mack/utils/forgery_detector.rb +152 -0
  47. data/lib/mack/utils/gem_manager.rb +22 -1
  48. data/lib/mack/utils/paths.rb +154 -0
  49. data/lib/mack/utils/server.rb +8 -6
  50. data/lib/mack/version.rb +1 -1
  51. data/lib/mack/view_helpers/asset_helpers.rb +50 -0
  52. data/lib/mack/view_helpers/form_helpers.rb +52 -6
  53. data/lib/mack/view_helpers/link_helpers.rb +6 -3
  54. data/lib/mack_app.rb +12 -14
  55. data/lib/mack_core.rb +35 -14
  56. data/lib/mack_tasks.rb +35 -20
  57. metadata +23 -27
  58. data/lib/mack/tasks/cachetastic_tasks.rake +0 -58
@@ -4,30 +4,17 @@ namespace :mack do
4
4
 
5
5
  desc "Starts the webserver."
6
6
  task :start do |t|
7
+ puts %{
8
+ This task has been removed. Please use the 'mackery' command to start/stop the server:
7
9
 
8
- require 'rubygems'
9
- require 'optparse'
10
- require 'optparse/time'
11
- require 'ostruct'
12
- require 'fileutils'
10
+ $ mackery server
13
11
 
14
- require 'thin'
12
+ The environment can be set like this:
15
13
 
16
- options = OpenStruct.new
17
- options.port = (ENV["PORT"] ||= "3000") # Does NOT work with Thin!! You must edit the thin.yml file!
18
- options.handler = (ENV["HANDLER"] ||= "thin")
19
-
20
-
21
- # require File.join(Mack.root, "config", "boot.rb")
22
- require 'mack'
23
-
24
- if options.handler == "thin"
25
- # thin_opts = ["start", "-r", "config/thin.ru"]
26
- thin_opts = ["start"]
27
- Thin::Runner.new(thin_opts.flatten).run!
28
- else
29
- Mack::SimpleServer.run(options)
30
- end
14
+ $ mackery server -e test
15
+
16
+ $ mackery server -p 8080 -e production # etc...
17
+ }
31
18
  end # start
32
19
 
33
20
  end # server
@@ -1,18 +1,45 @@
1
+ require 'fileutils'
1
2
  namespace :mack do
2
3
 
3
4
  desc "Loads the Mack environment. Default is development."
4
5
  task :environment do
5
- require File.join(File.dirname(__FILE__), '..', "..", 'mack')
6
+ require File.join(File.dirname(__FILE__), '..', '..', 'mack')
7
+ Mack::Environment.load
6
8
  end # environment
7
9
 
8
- desc "Loads an irb console allow you full access to the application w/o a browser."
10
+ # desc "Loads an irb console allow you full access to the application w/o a browser."
9
11
  task :console do
10
- libs = []
11
- libs << "-r irb/completion"
12
- libs << "-r #{File.join(File.dirname(__FILE__), '..', 'initialization', 'console')}"
13
- system "irb #{libs.join(" ")} --simple-prompt"
12
+ puts %{
13
+ This task has been removed. Please use the 'mackery' command to access the console:
14
+
15
+ $ mackery console
16
+
17
+ The environment can be set like this:
18
+
19
+ $ mackery console -e test
20
+ }
14
21
  end # console
15
22
 
23
+ namespace :freeze do
24
+
25
+ desc "Freezes the Edge Mack code into your vendor/framework folder"
26
+ task :edge do
27
+ f_dir = File.join(FileUtils.pwd, 'vendor', 'framework')
28
+ FileUtils.mkdir_p(f_dir)
29
+ %w{mack mack-more}.each do |proj|
30
+ proj_dir = File.join(f_dir, proj)
31
+ if File.exists?(proj_dir)
32
+ FileUtils.cd proj_dir
33
+ system 'git pull'
34
+ else
35
+ FileUtils.cd f_dir
36
+ system "git clone git://github.com/#{ENV["USERNAME"] || 'markbates'}/#{proj}.git"
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
16
43
  end # mack
17
44
 
18
45
  alias_task :console, "mack:console"
@@ -17,6 +17,14 @@ rule /^cachetastic:/ do |t|
17
17
  Rake::Task["cachetastic:manipulate_caches"].invoke
18
18
  end
19
19
 
20
+ rule /^generate:.+:desc/ do |t|
21
+ klass = t.name.gsub("generate:", '')
22
+ klass.gsub!(":desc", '')
23
+ Rake::Task["environment"].invoke
24
+ klass = "#{klass.camelcase}Generator"
25
+ puts klass.constantize.describe
26
+ end
27
+
20
28
  rule /^generate:/ do |t|
21
29
  klass = t.name.gsub("generate:", '')
22
30
  Rake::Task["environment"].invoke
@@ -10,24 +10,25 @@ namespace :test do
10
10
 
11
11
  desc "Run test code."
12
12
  Rake::TestTask.new(:test_case) do |t|
13
+ require File.join(File.dirname(__FILE__), '..', 'initialization', 'configuration')
14
+ Mack::BootLoader.run(:configuration)
13
15
  t.libs << "test"
14
- t.pattern = 'test/**/*_test.rb'
16
+ t.pattern = app_config.mack.send("#{app_config.mack.testing_framework}_file_pattern")
15
17
  t.verbose = true
16
18
  end
17
19
 
18
20
  desc 'Run specifications'
19
21
  Spec::Rake::SpecTask.new(:rspec) do |t|
22
+ require File.join(File.dirname(__FILE__), '..', 'initialization', 'configuration')
23
+ Mack::BootLoader.run(:configuration)
20
24
  t.spec_opts << '--options' << 'test/spec.opts' if File.exists?('test/spec.opts')
21
- t.spec_files = Dir.glob('test/**/*_spec.rb')
25
+ t.spec_files = Dir.glob(app_config.mack.send("#{app_config.mack.testing_framework}_file_pattern"))
22
26
  end
23
27
 
24
28
  desc "Report code statistics (KLOCs, etc) from the application. Requires the rcov gem."
25
29
  task :stats do |t|
26
- ENV["MACK_ENV"] = "test"
27
- Rake::Task["mack:environment"].invoke
28
- Rake::Task["test:setup"].invoke
29
- x = `rcov test/**/*_#{app_config.mack.testing_framework == "rspec" ? "spec" : "test"}.rb -T --no-html -x Rakefile,config\/,tasks\/`
30
- x.each do |line|
30
+ res = common_coverage
31
+ res.each do |line|
31
32
  case line
32
33
  when /^\+[\+\-]*\+$/, /^\|.*\|$/, /\d+\sLines\s+\d+\sLOC/
33
34
  puts line
@@ -37,11 +38,14 @@ namespace :test do
37
38
 
38
39
  desc "Generates test coverage from the application. Requires the rcov gem."
39
40
  task :coverage do |t|
40
- ENV["MACK_ENV"] = "test"
41
- Rake::Task["mack:environment"].invoke
42
- Rake::Task["test:setup"].invoke
43
- `rcov test/**/*_#{app_config.mack.testing_framework == "rspec" ? "spec" : "test"}.rb -x Rakefile,config\/,tasks\/`
44
- `open coverage/index.html`
41
+ common_coverage
42
+
43
+ unless PLATFORM['i386-mswin32']
44
+ system("open coverage/index.html") if PLATFORM['darwin']
45
+ else
46
+ system("\"C:/Program Files/Mozilla Firefox/firefox.exe\" " +
47
+ "coverage/index.html")
48
+ end
45
49
  end
46
50
 
47
51
  task :empty do |t|
@@ -52,12 +56,32 @@ namespace :test do
52
56
  raise "Oh No!"
53
57
  end
54
58
 
59
+ private
60
+ def common_coverage
61
+ ENV["MACK_ENV"] = "test"
62
+ Rake::Task["mack:environment"].invoke
63
+ Rake::Task["test:setup"].invoke
64
+
65
+ rm_f Mack::Paths.root("coverage")
66
+ rm_f Mack::Paths.root("coverage.data")
67
+ unless PLATFORM['i386-mswin32']
68
+ rcov = "rcov --sort coverage --rails --aggregate coverage.data " +
69
+ "--text-summary -Ilib -T -x gems/*,rcov*,lib/tasks/*,Rakefile"
70
+ else
71
+ rcov = "rcov.cmd --sort coverage --rails --aggregate coverage.data " +
72
+ "--text-summary -Ilib -T"
73
+ end
74
+
75
+ puts "Generating... please wait..."
76
+ res = `#{rcov} --html #{app_config.mack.send("#{app_config.mack.testing_framework}_file_pattern")}`
77
+ res
78
+ end
79
+
55
80
  end
56
81
 
57
82
  task :default do
58
- require 'application_configuration'
59
- app_config.load_file(File.join(FileUtils.pwd, "config", "app_config", "default.yml"))
60
- app_config.load_file(File.join(FileUtils.pwd, "config", "app_config", "test.yml"))
83
+ require File.join(File.dirname(__FILE__), '..', 'initialization', 'configuration')
84
+ Mack::BootLoader.run(:configuration)
61
85
  tf = "rspec"
62
86
  begin
63
87
  tf = app_config.mack.testing_framework
@@ -19,41 +19,6 @@ module Mack
19
19
  module Testing # :nodoc:
20
20
  module Helpers
21
21
 
22
- # Runs the given rake task. Takes an optional hash that mimics command line parameters.
23
- def rake_task(name, env = {}, tasks = [])
24
- # set up the Rake application
25
- rake = Rake::Application.new
26
- Rake.application = rake
27
-
28
- [File.join(File.dirname(__FILE__), "..", "..", "mack_tasks.rb"), tasks].flatten.each do |task|
29
- load(task)
30
- end
31
-
32
- # save the old ENV so we can revert it
33
- old_env = ENV.to_hash
34
- # add in the new ENV stuff
35
- env.each_pair {|k,v| ENV[k.to_s] = v}
36
-
37
- begin
38
- # run the rake task
39
- rake[name].invoke
40
-
41
- # yield for the tests
42
- yield if block_given?
43
-
44
- rescue Exception => e
45
- raise e
46
- ensure
47
- # empty out the ENV
48
- ENV.clear
49
- # revert to the ENV before the test started
50
- old_env.to_hash.each_pair {|k,v| ENV[k] = v}
51
-
52
- # get rid of the Rake application
53
- Rake.application = nil
54
- end
55
- end
56
-
57
22
  # Temporarily changes the application configuration. Changes are reverted after
58
23
  # the yield returns.
59
24
  def temp_app_config(options = {})
@@ -125,14 +90,16 @@ module Mack
125
90
 
126
91
  # Returns a Mack::Session from the request.
127
92
  def session # :nodoc:
128
- Cachetastic::Caches::MackSessionCache.get($current_session_id) do
93
+ sess = Mack::SessionStore.get($current_session_id, nil, nil, nil)
94
+ if sess.nil?
129
95
  id = String.randomize(40).downcase
130
96
  set_cookie(app_config.mack.session_id, id)
131
97
  sess = Mack::Session.new(id)
132
- Cachetastic::Caches::MackSessionCache.set(id, sess)
98
+ Mack::SessionStore.store.direct_set(id, sess)
133
99
  $current_session_id = id
134
- sess
100
+ sess
135
101
  end
102
+ sess
136
103
  end
137
104
 
138
105
  # Used to create a 'session' around a block of code. This is great of 'integration' tests.
@@ -148,7 +115,7 @@ module Mack
148
115
 
149
116
  # Clears all the sessions.
150
117
  def clear_session
151
- Cachetastic::Caches::MackSessionCache.expire_all
118
+ Mack::SessionStore.expire_all
152
119
  end
153
120
 
154
121
  # Returns a Hash of cookies from the response.
@@ -0,0 +1,45 @@
1
+ module Mack
2
+ module Utils
3
+ #
4
+ # The whole purpose of this handler is to intercept the rack application process,
5
+ # calculate the size of the response body, and set the 'Content-Length' header if necessary.
6
+ #
7
+ # From: http://thin.lighthouseapp.com/projects/7212/tickets/74
8
+ # Content-Length should not be automatically added:
9
+ # * When the Status code is 1xx, 204 or 304
10
+ # * When the Transfer-Encoding header is set to chunked
11
+ # * When the body is neither a String or an Array
12
+ #
13
+ class ContentLengthHandler
14
+ attr_reader :response
15
+
16
+ def initialize(app) # :nodoc:
17
+ @app = app
18
+ end
19
+
20
+ def call(env) # :nodoc:
21
+ ret = @app.call(env)
22
+
23
+ status = ret[0]
24
+ hdr = ret[1]
25
+ @response = ret[2]
26
+
27
+ unless self.response.is_a?(Rack::File) or
28
+ (!self.response.body.kind_of?Array and !self.response.body.kind_of?String) or
29
+ hdr["Transfer-Encoding"] == "chunked" or
30
+ (status == 204 or status == 304 or (status >= 100 and status < 200))
31
+ size = 0
32
+
33
+ if self.response.body.respond_to?(:to_str)
34
+ size = self.response.body.to_str.size
35
+ elsif self.response.body.respond_to?(:each)
36
+ self.response.body.each { |part| size += part.to_s.size }
37
+ end
38
+
39
+ hdr["Content-Length"] = size.to_s
40
+ end
41
+ return ret
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,152 @@
1
+ module Mack
2
+ module Utils
3
+
4
+ module ForgeryDetector # :nodoc:
5
+
6
+ module ClassMethods
7
+
8
+ #
9
+ # By default the framework will try to validate incoming
10
+ # HTTP request (other than GET) by validating a secret token
11
+ # stored as hidden field in the posted form.
12
+ #
13
+ # There are 2 ways of disabling this feature:
14
+ # 1. Globally disabling this feature by setting mack::disable_forgery_detector to true in app_config
15
+ # 2. In a controller, call this method (disable_forgery_detector) to disable the detection
16
+ # for a specified set of methods.
17
+ #
18
+ # Supported options:
19
+ # :only => list_of_methods.
20
+ # This directive will tell the framework only disable the detection for the specified list of methods.
21
+ # :except => list_of_methods
22
+ # This directive will tell the framework to disable the detection for all methods except the ones specified.
23
+ #
24
+ # Example:
25
+ # class MyController
26
+ # include Mack::Controller
27
+ # disable_forgery_detector :only => [:test1, :test2]
28
+ #
29
+ # def test1
30
+ # end
31
+ #
32
+ # def test2
33
+ # end
34
+ #
35
+ # Notes:
36
+ # - This method will not work properly if both :only and :except options are passed in
37
+ # - In inherited case, the following behavior is to be expected:
38
+ # * If the super class declare that it wants to disable detection for all methods, and the
39
+ # subclass declare that it wants to disable detection for only a set of methods, then
40
+ # when the subclass is run, the "disable all detection" from the parent class will be overwritten
41
+ # * On the other hand, if the super class declare that it wants to disable a set of methods,
42
+ # and the subclass also declare it wants to disable a set of methods from its own set. Then
43
+ # the "only" list will be the combination of both
44
+ # * The first rule apply in inheritance; this method will not work properly if superclass declear
45
+ # "except" list, and subclass declare "only" list
46
+ #
47
+ def disable_forgery_detector(options = {})
48
+ hash = self.ignored_actions
49
+ hash[:all] = true and return if options.empty?
50
+
51
+ # TODO: should raise error if type is invalid
52
+ type = options[:only] ? :only : (options[:except] ? :except : :unknown)
53
+ list = options[:only] ? [options[:only]].flatten : (options[:except] ? [options[:except]].flatten : [])
54
+ if !list.empty?
55
+ hash[type] ||= []
56
+ hash[type] << list
57
+ hash[type].flatten!
58
+ hash[type].uniq!
59
+ hash[:all] = false
60
+ end
61
+ end
62
+
63
+ def ignored_actions # :nodoc:
64
+ unless @ignored_actions
65
+ @ignored_actions = {}
66
+ sc = self.superclass
67
+ if sc.class_is_a?(Mack::Controller)
68
+ sc_hash = sc.ignored_actions
69
+ if sc_hash[:only]
70
+ @ignored_actions[:only] ||= []
71
+ @ignored_actions[:only] << sc_hash[:only]
72
+ @ignored_actions[:only].flatten!
73
+ elsif sc_hash[:except]
74
+ @ignored_actions[:except] ||= []
75
+ @ignored_actions[:except] << sc_hash[:except]
76
+ @ignored_actions[:except].flatten!
77
+ elsif sc_hash[:all]
78
+ @ignored_actions[:all] = sc_hash[:all]
79
+ end
80
+ end
81
+ end
82
+ return @ignored_actions
83
+ end
84
+ end
85
+
86
+ #
87
+ # This method will be added as "before-filter" for all controllers.
88
+ #
89
+ # This method will filter the incoming request, and raise an exception
90
+ # if it thinks that the incoming request is a forged request.
91
+ #
92
+ # The requirement for a request to be considered a forged:
93
+ # - It must not be a GET request
94
+ # - The forgery detection is not disabled globally
95
+ # - The current action is not part of the "disabled" list
96
+ # - The authenticity token in the request param is valid
97
+ # - All of the above must be true
98
+ #
99
+ def detect_forgery
100
+ valid_request? || raise(Mack::Errors::InvalidAuthenticityToken.new(request.params[:__authenticity_token] || "unknown token"))
101
+ end
102
+
103
+ protected
104
+
105
+ def symbolize_list_elements(list = []) # :nodoc:
106
+ list.collect { |x| x.to_sym }
107
+ end
108
+
109
+ def skip_action? # :nodoc:
110
+ return true if self.class.ignored_actions[:all] and self.class.ignored_actions.empty?
111
+ return false if !self.class.ignored_actions[:all] and self.class.ignored_actions.empty?
112
+
113
+ skip = true
114
+ action = request.params[:action]
115
+ hash = self.class.ignored_actions
116
+ if hash[:only]
117
+ list = [hash[:only]].flatten
118
+ list = symbolize_list_elements(list)
119
+ skip = false if !list.include?action.to_sym
120
+ elsif hash[:except]
121
+ list = [hash[:except]].flatten
122
+ list = symbolize_list_elements(list)
123
+ skip = false if list.include?action.to_sym
124
+ end
125
+ return skip
126
+ end
127
+
128
+ def valid_request? # :nodoc:
129
+ return true if !self.class.ignored_actions.empty? and self.skip_action?
130
+ return app_config.mack.disable_forgery_detector ||
131
+ self.skip_action? ||
132
+ request.params[:method] == "get" ||
133
+ (request.params[:__authenticity_token] == authenticity_token)
134
+ end
135
+
136
+ def authenticity_token # :nodoc:
137
+ Mack::Utils::AuthenticityTokenDispenser.instance.dispense_token(request.session.id)
138
+ end
139
+ end
140
+
141
+ class AuthenticityTokenDispenser # :nodoc:
142
+ include Singleton
143
+
144
+ def dispense_token(key) # :nodoc:
145
+ salt = app_config.request_authenticity_token_salt || "shh, it's a secret"
146
+ salt = "shh, it's a secret" if salt.empty?
147
+ string_to_hash = key.to_s + salt.to_s
148
+ Digest::SHA1.hexdigest(string_to_hash)
149
+ end
150
+ end
151
+ end
152
+ end