pakyow-core 0.10.2 → 0.11.0

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