pakyow-core 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-core/CHANGELOG.md +23 -0
  3. data/pakyow-core/lib/pakyow/core/app.rb +81 -0
  4. data/pakyow-core/lib/{core → pakyow/core}/app_context.rb +0 -0
  5. data/pakyow-core/lib/pakyow/core/base.rb +61 -0
  6. data/pakyow-core/lib/pakyow/core/call_context.rb +171 -0
  7. data/pakyow-core/lib/{core → pakyow/core}/config/app.rb +10 -30
  8. data/pakyow-core/lib/pakyow/core/config/cookies.rb +4 -0
  9. data/pakyow-core/lib/{core → pakyow/core}/config/logger.rb +11 -16
  10. data/pakyow-core/lib/pakyow/core/config/reloader.rb +10 -0
  11. data/pakyow-core/lib/{core → pakyow/core}/config/server.rb +2 -4
  12. data/pakyow-core/lib/pakyow/core/config/session.rb +41 -0
  13. data/pakyow-core/lib/{core → pakyow/core}/config.rb +2 -0
  14. data/pakyow-core/lib/{core → pakyow/core}/errors.rb +0 -0
  15. data/pakyow-core/lib/pakyow/core/helpers/configuring.rb +142 -0
  16. data/pakyow-core/lib/pakyow/core/helpers/hooks.rb +106 -0
  17. data/pakyow-core/lib/pakyow/core/helpers/running.rb +124 -0
  18. data/pakyow-core/lib/{core → pakyow/core}/helpers.rb +21 -5
  19. data/pakyow-core/lib/{core → pakyow/core}/loader.rb +1 -1
  20. data/pakyow-core/lib/{core → pakyow/core}/middleware/logger.rb +8 -2
  21. data/pakyow-core/lib/pakyow/core/middleware/override.rb +3 -0
  22. data/pakyow-core/lib/pakyow/core/middleware/reloader.rb +23 -0
  23. data/pakyow-core/lib/pakyow/core/middleware/req_path_normalizer.rb +49 -0
  24. data/pakyow-core/lib/pakyow/core/middleware/session.rb +5 -0
  25. data/pakyow-core/lib/pakyow/core/middleware/static.rb +76 -0
  26. data/pakyow-core/lib/{core → pakyow/core}/multilog.rb +0 -0
  27. data/pakyow-core/lib/{core → pakyow/core}/request.rb +7 -3
  28. data/pakyow-core/lib/{core → pakyow/core}/response.rb +4 -2
  29. data/pakyow-core/lib/{core → pakyow/core}/route_eval.rb +0 -0
  30. data/pakyow-core/lib/{core → pakyow/core}/route_expansion_eval.rb +1 -1
  31. data/pakyow-core/lib/{core → pakyow/core}/route_lookup.rb +0 -0
  32. data/pakyow-core/lib/{core → pakyow/core}/route_merger.rb +0 -0
  33. data/pakyow-core/lib/{core → pakyow/core}/route_module.rb +0 -0
  34. data/pakyow-core/lib/{core → pakyow/core}/route_set.rb +2 -2
  35. data/pakyow-core/lib/{core → pakyow/core}/route_template_defaults.rb +0 -0
  36. data/pakyow-core/lib/{core → pakyow/core}/route_template_eval.rb +0 -0
  37. data/pakyow-core/lib/{core → pakyow/core}/router.rb +4 -0
  38. data/pakyow-core/lib/pakyow/core.rb +8 -0
  39. data/pakyow-core/lib/pakyow-core.rb +1 -11
  40. metadata +41 -34
  41. data/pakyow-core/lib/core/app.rb +0 -469
  42. data/pakyow-core/lib/core/base.rb +0 -56
  43. data/pakyow-core/lib/core/config/cookies.rb +0 -4
  44. data/pakyow-core/lib/core/middleware/reloader.rb +0 -14
  45. data/pakyow-core/lib/core/middleware/static.rb +0 -40
  46. data/pakyow-core/lib/views/errors/404.html +0 -13
  47. data/pakyow-core/lib/views/errors/500.html +0 -15
@@ -0,0 +1,106 @@
1
+ module Pakyow
2
+ module Helpers
3
+ # Methods to register and call hooks before and after particular triggers.
4
+ #
5
+ # @api public
6
+ module Hooks
7
+ TYPES = %i(before after)
8
+ TRIGGERS = %i(init load process route match error configure reload)
9
+
10
+ module InstanceMethods
11
+ protected
12
+
13
+ def hook_around(trigger)
14
+ call_hooks :before, trigger
15
+ yield
16
+ call_hooks :after, trigger
17
+ end
18
+
19
+ def call_hooks(type, trigger)
20
+ self.class.hook(type, trigger).each do |block|
21
+ instance_exec(&block)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.extended(object)
27
+ object.send(:include, InstanceMethods)
28
+ end
29
+
30
+ # Registers a before hook for a particular trigger.
31
+ #
32
+ # @api public
33
+ def before(trigger, &block)
34
+ register_hook(:before, trigger, block)
35
+ end
36
+
37
+ # Registers an after hook for a particular trigger.
38
+ #
39
+ # @api public
40
+ def after(trigger, &block)
41
+ register_hook(:after, trigger, block)
42
+ end
43
+
44
+ # Fetches a hook by type (before | after) and trigger.
45
+ #
46
+ # @api private
47
+ def hook(type, trigger)
48
+ check_hook_type(type)
49
+ check_trigger(trigger)
50
+
51
+ hooks[type.to_sym][trigger.to_sym]
52
+ end
53
+
54
+ protected
55
+
56
+ def hooks
57
+ return @hooks unless @hooks.nil?
58
+
59
+ @hooks = {
60
+ before: {},
61
+ after: {}
62
+ }
63
+
64
+ TRIGGERS.each do |name|
65
+ @hooks[:before][name.to_sym] = []
66
+ @hooks[:after][name.to_sym] = []
67
+ end
68
+
69
+ @hooks
70
+ end
71
+
72
+ def register_hook(type, trigger, block)
73
+ raise ArgumentError, 'Expected a block' if block.nil?
74
+
75
+ trigger = trigger.to_sym
76
+
77
+ check_trigger(trigger)
78
+ check_hook_type(type)
79
+
80
+ hooks[type][trigger] << block
81
+ end
82
+
83
+ def hook_around(trigger)
84
+ call_hooks :before, trigger
85
+ yield
86
+ call_hooks :after, trigger
87
+ end
88
+
89
+ def call_hooks(type, trigger)
90
+ hook(type, trigger).each do |block|
91
+ instance_exec(&block)
92
+ end
93
+ end
94
+
95
+ def check_trigger(trigger)
96
+ return true if TRIGGERS.include?(trigger)
97
+ raise ArgumentError, "Hook trigger #{trigger} doesn't exist"
98
+ end
99
+
100
+ def check_hook_type(type)
101
+ return true if TYPES.include?(type)
102
+ raise ArgumentError, "Hook type #{type} doesn't exist"
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,124 @@
1
+ require 'rack/builder'
2
+ require 'rack/handler'
3
+
4
+ module Pakyow
5
+ module Helpers
6
+ # Methods run running an app.
7
+ #
8
+ # @api public
9
+ module Running
10
+ STOP_METHODS = ['stop!', 'stop']
11
+ HANDLERS = ['puma', 'thin', 'mongrel', 'webrick']
12
+ SIGNALS = [:INT, :TERM]
13
+
14
+ # Prepares the app for being staged in one or more environments by
15
+ # loading config(s), middleware, and setting the load path.
16
+ #
17
+ # @api public
18
+ def prepare(*env_or_envs)
19
+ return true if prepared?
20
+
21
+ # load config for one or more environments
22
+ load_env_config(*env_or_envs)
23
+
24
+ # load each block from middleware stack
25
+ load_middleware
26
+
27
+ # add app/lib to load path
28
+ $LOAD_PATH.unshift config.app.src_dir
29
+
30
+ @prepared = true
31
+ end
32
+
33
+ # Stages the app by preparing and returning an instance. This is
34
+ # essentially everything short of running it.
35
+ #
36
+ # @api public
37
+ def stage(*env_or_envs)
38
+ prepare(*env_or_envs)
39
+ self.new
40
+ end
41
+
42
+ # Runs the staged app.
43
+ #
44
+ # @api public
45
+ def run(*env_or_envs)
46
+ return true if running?
47
+ @running = true
48
+ builder.run(stage(*env_or_envs))
49
+ detect_handler.run(builder, Host: config.server.host, Port: config.server.port) do |server|
50
+ SIGNALS.each do |signal|
51
+ trap(signal) { stop(server) }
52
+ end
53
+ end
54
+ end
55
+
56
+ # Returns true if the application is prepared.
57
+ #
58
+ # @api public
59
+ def prepared?
60
+ @prepared == true
61
+ end
62
+
63
+ # Returns true if the application is running.
64
+ #
65
+ # @api public
66
+ def running?
67
+ @running == true
68
+ end
69
+
70
+ # Returns true if the application is staged.
71
+ #
72
+ # @api public
73
+ def staged?
74
+ !Pakyow.app.nil?
75
+ end
76
+
77
+ # Returns a rack builder instance.
78
+ #
79
+ # @api public
80
+ def builder
81
+ @builder ||= Rack::Builder.new
82
+ end
83
+
84
+ # Returns an instance of the rack handler.
85
+ #
86
+ # @api private
87
+ def detect_handler
88
+ if config.server.handler
89
+ HANDLERS.unshift(config.server.handler).uniq!
90
+ end
91
+
92
+ HANDLERS.each do |handler_name|
93
+ begin
94
+ handler = Rack::Handler.get(handler_name)
95
+ return handler unless handler.nil?
96
+ rescue LoadError
97
+ rescue NameError
98
+ end
99
+ end
100
+
101
+ raise 'No handler found'
102
+ end
103
+
104
+ protected
105
+
106
+ def stop(server)
107
+ STOP_METHODS.each do |method|
108
+ if server.respond_to?(method)
109
+ return server.send(method)
110
+ end
111
+ end
112
+
113
+ # exit ungracefully
114
+ Process.exit!
115
+ end
116
+
117
+ def load_middleware
118
+ middleware.each do |block|
119
+ instance_exec(builder, &block)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -1,12 +1,14 @@
1
1
  module Pakyow
2
- # For methods that should be accessible anywhere
2
+ # Helpers available anywhere
3
+ #
4
+ # @api public
3
5
  module Helpers
4
6
  def context
5
7
  @context or raise NoContextError
6
8
  end
7
9
 
8
10
  def logger
9
- request.logger
11
+ request.logger || Pakyow.logger
10
12
  end
11
13
 
12
14
  def router
@@ -38,8 +40,22 @@ module Pakyow
38
40
  def config
39
41
  Pakyow::Config
40
42
  end
41
- end
42
43
 
43
- # For methods that should only be accessible through App
44
- module AppHelpers; end
44
+ # Returns the primary app environment.
45
+ #
46
+ # @api public
47
+ def env
48
+ config.env
49
+ end
50
+
51
+ # Helpers for Pakyow::App
52
+ #
53
+ # @api public
54
+ module App; end
55
+
56
+ # Helpers for Pakyow::CallContext
57
+ #
58
+ # @api public
59
+ module Context; end
60
+ end
45
61
  end
@@ -17,7 +17,7 @@ module Pakyow
17
17
  next if FileTest.directory?(path)
18
18
  next if path.split('.')[-1] != 'rb'
19
19
 
20
- if Config.app.auto_reload
20
+ if Config.reloader.enabled
21
21
  if !@times[path] || (@times[path] && File.mtime(path) - @times[path] > 0)
22
22
  load(path)
23
23
  @times[path] = File.mtime(path)
@@ -41,7 +41,7 @@ module Pakyow
41
41
 
42
42
  def <<(msg = nil, severity = :unknown)
43
43
  return if @log.nil?
44
- msg << "\n"
44
+ (msg || "") << "\n"
45
45
 
46
46
  msg = format(msg, severity) if @format
47
47
  @mutex.synchronize do
@@ -83,7 +83,7 @@ module Pakyow
83
83
 
84
84
  def format(msg, level)
85
85
  return msg unless color = level_color(level)
86
- return COLOR_SEQ % (30 + COLOR_TABLE.index(color)) + msg + RESET_SEQ
86
+ return COLOR_SEQ % (30 + COLOR_TABLE.index(color)) + (msg || "") + RESET_SEQ
87
87
  end
88
88
 
89
89
  def level_color(level)
@@ -99,6 +99,12 @@ end
99
99
 
100
100
  module Pakyow
101
101
  module Middleware
102
+ Pakyow::App.middleware do |builder|
103
+ if Pakyow::Config.logger.enabled
104
+ builder.use Pakyow::Middleware::Logger
105
+ end
106
+ end
107
+
102
108
  class Logger
103
109
  # handles logging after an error occurs
104
110
  Pakyow::App.after(:error) {
@@ -0,0 +1,3 @@
1
+ Pakyow::App.middleware do |builder|
2
+ builder.use Rack::MethodOverride
3
+ end
@@ -0,0 +1,23 @@
1
+ module Pakyow
2
+ module Middleware
3
+ Pakyow::App.middleware do |builder|
4
+ if Pakyow::Config.reloader.enabled
5
+ builder.use Pakyow::Middleware::Reloader
6
+ end
7
+ end
8
+
9
+ # Rack compatible middleware that tells app to reload on each request.
10
+ #
11
+ # @api public
12
+ class Reloader
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ Pakyow.app.reload
19
+ @app.call(env)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,49 @@
1
+ require 'pakyow/core/call_context'
2
+
3
+ module Pakyow
4
+ module Middleware
5
+ Pakyow::App.middleware do |builder|
6
+ builder.use Pakyow::Middleware::ReqPathNormalizer
7
+ end
8
+
9
+ # Rack compatible middleware that normalize the path if contains '//'
10
+ # or has a trailing '/', it replace '//' with '/', remove trailing `/`
11
+ # and issue a 301 redirect to the normalized path.
12
+ #
13
+ # @api public
14
+ class ReqPathNormalizer
15
+ TAIL_SLASH_REPLACE_REGEX = /(\/)+$/
16
+ TAIL_SLASH_REGEX = /(.)+(\/)+$/
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ end
21
+
22
+ def call(env)
23
+ path = env['PATH_INFO']
24
+
25
+ if double_slash?(path) || tail_slash?(path)
26
+ catch :halt do
27
+ CallContext.new(env).redirect(normalize_path(path), 301)
28
+ end
29
+ else
30
+ @app.call(env)
31
+ end
32
+ end
33
+
34
+ def normalize_path(path)
35
+ normalized = path
36
+ .gsub('//', '/')
37
+ .gsub(TAIL_SLASH_REPLACE_REGEX, '')
38
+ end
39
+
40
+ def double_slash?(path)
41
+ path.include?('//')
42
+ end
43
+
44
+ def tail_slash?(path)
45
+ (TAIL_SLASH_REGEX =~ path).nil? ? false : true
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ Pakyow::App.middleware do |builder|
2
+ if Pakyow::Config.session.enabled
3
+ builder.use Pakyow::Config.session.object, Pakyow::Config.session.options
4
+ end
5
+ end
@@ -0,0 +1,76 @@
1
+ require 'pakyow/core/config'
2
+ require 'pakyow/core/call_context'
3
+
4
+ module Pakyow
5
+ module Middleware
6
+ # Rack compatible middleware that serves static files from one or more configured resource stores.
7
+ #
8
+ # @example
9
+ # Pakyow::Config.app.resources = {
10
+ # default: './public'
11
+ # }
12
+ #
13
+ # Pakyow::App.builder.use Pakyow::Middleware::Static
14
+ #
15
+ # # Assuming './public/foo.png' exists, a GET request to '/foo.png' will
16
+ # # result in this middleware responding with the static file.
17
+ #
18
+ # @api public
19
+ class Static
20
+ Pakyow::App.middleware do |builder|
21
+ if Pakyow::Config.app.static
22
+ builder.use Pakyow::Middleware::Static
23
+ end
24
+ end
25
+
26
+ def initialize(app)
27
+ @app = app
28
+ end
29
+
30
+ def call(env)
31
+ static, resource_path = self.class.static?(env)
32
+ return @app.call(env) unless static
33
+
34
+ catch :halt do
35
+ CallContext.new(env).send(File.open(resource_path))
36
+ end
37
+ end
38
+
39
+ class << self
40
+ STATIC_REGEX = /\.(.*)$/
41
+ STATIC_HTTP_METHODS = %w(GET)
42
+
43
+ # Checks if `path` can be found in any configured resource store.
44
+ #
45
+ # @api public
46
+ def static?(env)
47
+ path, method = env.values_at('PATH_INFO', 'REQUEST_METHOD')
48
+
49
+ return false unless STATIC_HTTP_METHODS.include?(method)
50
+ return false unless static_path?(path)
51
+
52
+ resources_contain?(path)
53
+ end
54
+
55
+ protected
56
+
57
+ def static_path?(path)
58
+ path =~ STATIC_REGEX
59
+ end
60
+
61
+ def resources_contain?(path)
62
+ resources.each_pair do |_, resource_path|
63
+ full_path = File.join(resource_path, path)
64
+ return true, full_path if File.exists?(full_path)
65
+ end
66
+
67
+ false
68
+ end
69
+
70
+ def resources
71
+ Config.app.resources
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
File without changes
@@ -1,4 +1,5 @@
1
1
  require 'json'
2
+ require 'rack'
2
3
 
3
4
  module Pakyow
4
5
 
@@ -58,9 +59,12 @@ module Pakyow
58
59
 
59
60
  # Returns indifferent params (see {HashUtils.strhash} for more info on indifferent hashes).
60
61
  def params
61
- @params ||= Hash.strhash(super.merge!(env['pakyow.data'] || {}).merge!(JSON.parse(body.read.to_s)))
62
- rescue JSON::JSONError
63
- @params = Hash.strhash(super)
62
+ return @params unless @params.nil?
63
+
64
+ @params = super
65
+ @params.merge!(env['pakyow.data']) if env['pakyow.data'].is_a?(Hash)
66
+ @params.merge!(JSON.parse(body.read.to_s)) if format == :json
67
+ @params = Hash.strhash(@params)
64
68
  end
65
69
 
66
70
  # Returns array of url components.
@@ -4,6 +4,8 @@ module Pakyow
4
4
  class Response < Rack::Response
5
5
  attr_reader :format
6
6
 
7
+ DEFAULT_CONTENT_TYPE = 'text/html;charset=utf-8'.freeze
8
+
7
9
  STATUS_CODE_NAMES = {
8
10
  100 => 'Continue',
9
11
  101 => 'Switching Protocols',
@@ -117,13 +119,13 @@ module Pakyow
117
119
  def initialize(*args)
118
120
  super
119
121
 
120
- self["Content-Type"] ||= 'text/html'
122
+ self["Content-Type"] ||= DEFAULT_CONTENT_TYPE
121
123
  @format = Rack::Mime::MIME_TYPES.key(type)
122
124
  end
123
125
 
124
126
  def format=(format)
125
127
  @format = format
126
- self["Content-Type"] = Rack::Mime.mime_type(".#{format}")
128
+ self["Content-Type"] = format == :html ? DEFAULT_CONTENT_TYPE : Rack::Mime.mime_type(".#{format}")
127
129
  end
128
130
 
129
131
  def type
File without changes
@@ -31,7 +31,7 @@ module Pakyow
31
31
  all_fns = route[3]
32
32
  all_fns[:fns].unshift(fn) if fn
33
33
 
34
- hooks = merge_hooks(hooks, all_fns[:hooks])
34
+ hooks = merge_hooks(all_fns[:hooks], hooks)
35
35
  route[3] = build_fns(all_fns[:fns], hooks)
36
36
 
37
37
  register_route(route)
@@ -28,7 +28,7 @@ module Pakyow
28
28
  method = method.to_sym
29
29
  method = :get if method == :head
30
30
 
31
- @routes[method].each{|r|
31
+ (@routes[method] || []).each do |r|
32
32
  #TODO can we do this without conditionals? fall-through?
33
33
  case r[0]
34
34
  when Regexp
@@ -40,7 +40,7 @@ module Pakyow
40
40
  return r, nil
41
41
  end
42
42
  end
43
- }
43
+ end
44
44
 
45
45
  nil
46
46
  end
@@ -37,6 +37,10 @@ module Pakyow
37
37
  raise MissingRoute, "Could not find route '#{name}'"
38
38
  end
39
39
 
40
+ def exists?(context)
41
+ !match(context.request).empty?
42
+ end
43
+
40
44
  # Performs the initial routing for a request.
41
45
  #
42
46
  def perform(context, app = Pakyow.app, &after_match)
@@ -0,0 +1,8 @@
1
+ require 'find'
2
+ require 'rack'
3
+ require 'rack/file'
4
+ require 'logger'
5
+ require 'cgi'
6
+
7
+ require 'pakyow/support'
8
+ require 'pakyow/core/base'
@@ -1,11 +1 @@
1
- libdir = File.dirname(__FILE__)
2
- $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
-
4
- # Gems
5
- require 'find'
6
- require 'rack'
7
- require 'rack/file'
8
- require 'logger'
9
- require 'cgi'
10
-
11
- require 'core/base'
1
+ require 'pakyow/core'