mack 0.7.0.1 → 0.7.1

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 (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