signpost 0.1.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.
@@ -0,0 +1,13 @@
1
+ class Signpost
2
+ class Builder
3
+ class Namespace < Nested
4
+
5
+ private
6
+
7
+ def root_name
8
+ :root
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ class Signpost
2
+ class Builder
3
+ class Nested < self
4
+ attr_reader :builders
5
+
6
+ ##
7
+ # Add middleware to routes
8
+ #
9
+ # Params:
10
+ # - middleware {String|Class} middleware or middleware name
11
+ # - *args middleware arguments which will be used for instantiating
12
+ #
13
+ # Example:
14
+ #
15
+ # use Rack::Errors
16
+ # use AuthMiddleware, 'admin', 'seCrEt'
17
+ #
18
+ def use(middleware, *args, &block)
19
+ @options[:middlewares] << Middleware.new(middleware, args, block)
20
+ end
21
+
22
+ def build
23
+ Router.new(@builders, @options)
24
+ end
25
+
26
+ private
27
+
28
+ def root_name
29
+ nil
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ class Signpost
2
+ module Endpoint
3
+ class Action
4
+
5
+ class Context
6
+
7
+ attr_reader :params, :env, :headers
8
+
9
+ def body(text)
10
+ @body = [text]
11
+ end
12
+
13
+ def status(code)
14
+ @status = code.to_i
15
+ end
16
+
17
+ def expand(path, data={})
18
+ @router.expand(path, data)
19
+ end
20
+
21
+ def result
22
+ [@status, @headers, @body]
23
+ end
24
+
25
+ private
26
+
27
+ def initialize(block, router, env, params)
28
+ @router = router
29
+ @env = env
30
+ @params = params
31
+
32
+ @status = 200
33
+ @headers = {}
34
+ @body = []
35
+
36
+ instance_eval(&block)
37
+ end
38
+ end
39
+
40
+ def call(env)
41
+ Context.new(@block, @router, env, env[@params_key]).result
42
+ end
43
+
44
+ private
45
+
46
+ def initialize(block, router)
47
+ @block = block
48
+ @router = router
49
+ @params_key = router.params_key
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ class Signpost
2
+ module Endpoint
3
+ class Builder
4
+
5
+ ##
6
+ # Build middleware stack
7
+ #
8
+ # Returns: {Proc}
9
+ #
10
+ def build
11
+ @middlewares.reverse_each.inject(@endpoint) { |app, m| m.middleware.new(app, *m.args, &m.block) }
12
+ end
13
+
14
+ private
15
+
16
+ def initialize(endpoint, middlewares)
17
+ @endpoint = endpoint
18
+ @middlewares = middlewares
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ class Signpost
2
+ module Endpoint
3
+ class Dynamic
4
+ def call(env)
5
+ controller, action = env[@params_key].values_at('controller', 'action')
6
+
7
+ resolver = Resolver.new({
8
+ controller: @spec[:controller] || controller,
9
+ action: @spec[:action] || action
10
+ }, @options).resolve
11
+
12
+ resolver.endpoint.call(env)
13
+ end
14
+
15
+ private
16
+
17
+ def initialize(options, spec={})
18
+ @options = options
19
+ @spec = spec
20
+ @params_key = options[:params_key]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ class Signpost
2
+ module Endpoint
3
+ class Redirect
4
+
5
+ class Context
6
+
7
+ attr_reader :params, :env
8
+
9
+ def expand(path, data={})
10
+ @router.expand(path, data)
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(router, env, params)
16
+ @router = router
17
+ @env = env
18
+ @params = params
19
+ end
20
+ end
21
+
22
+ attr_reader :status
23
+
24
+ CODES = {
25
+ permanent: 301,
26
+ temporary: 303
27
+ }.freeze
28
+
29
+ def call(env)
30
+ [status, headers(env), body]
31
+ end
32
+
33
+ private
34
+
35
+ def initialize(to, router, status=303)
36
+ @to = to
37
+ @status = status.is_a?(Fixnum) ? status : CODES[status.to_sym]
38
+ @router = router
39
+ @params_key = router.params_key
40
+ end
41
+
42
+ def headers(env)
43
+ { 'Location' => to(env) }
44
+ end
45
+
46
+ def to(env)
47
+ params = env[@params_key]
48
+
49
+ case @to
50
+ when Proc
51
+ Context.new(@router, env, params).instance_exec(&@to)
52
+ when Symbol
53
+ params = @router.expand(@to, params)
54
+ else
55
+ @to.expand(params)
56
+ end
57
+ end
58
+
59
+ def body
60
+ []
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,144 @@
1
+ class Signpost
2
+ module Endpoint
3
+ UnresolvedError = Class.new(StandardError)
4
+
5
+ class Resolver
6
+ ##
7
+ # Class: result of resolving
8
+ #
9
+ # Here is `#endpoint` is an endpoint class and `#params` is a hash
10
+ # with controller and action names
11
+ #
12
+ Result = Struct.new(:endpoint, :params)
13
+
14
+ ##
15
+ # Resolve endpoint and params
16
+ #
17
+ # Returns: {Signpost::Endpoint::Resolver::Result}
18
+ #
19
+ def resolve
20
+ case spec
21
+ when Hash
22
+ params = resolve_hash(spec[:controller], spec[:action])
23
+ when String
24
+ params = resolve_string
25
+ else
26
+ endpoint = spec
27
+ params = {}
28
+ end
29
+
30
+ unless endpoint
31
+ if params[:action].kind_of?(Class)
32
+ endpoint = params[:action]
33
+ elsif params[:controller]
34
+ endpoint = params[:controller]
35
+ else
36
+ if params[:action]
37
+ # Try namespace as endpoint
38
+ # this must be refactored in new year
39
+ if options[:namespace]
40
+ specc = { action: params[:action], controller: options[:namespace] }
41
+ opts = options.dup
42
+ opts.delete(:namespace)
43
+
44
+ result = self.class.new(specc, opts).resolve
45
+
46
+ return result if result
47
+ end
48
+ end
49
+
50
+ endpoint = Dynamic.new(@options, params)
51
+ end
52
+ end
53
+
54
+ Result.new(endpoint, params)
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :spec, :options
60
+
61
+ ##
62
+ # Constructor:
63
+ #
64
+ # Params:
65
+ # - spec {String|Hash|Proc}
66
+ # - options {Hash}
67
+ # - :namespace {String} namespace
68
+ # - :controller_format {String} string with controller name pattern
69
+ #
70
+ def initialize(spec, options={})
71
+ @spec = spec
72
+ @options = options
73
+ end
74
+
75
+ ##
76
+ # Private: resolve controller and action
77
+ #
78
+ # Params:
79
+ # - controller {String|Class} controller name or constant
80
+ # - action {String} action name
81
+ #
82
+ # Returns: {Hash}
83
+ # - :controller {Module|Class} controller constant
84
+ # - :action {String|Class} action name or constant
85
+ #
86
+ def resolve_hash(controller, action)
87
+ if controller.kind_of?(Module)
88
+ if action
89
+ pattern = "#{controller}::#{Inflecto.camelize(action)}"
90
+ action = Inflecto.constantize(pattern) if Object.const_defined?(pattern)
91
+ end
92
+ elsif controller
93
+ name = controller_name(controller)
94
+
95
+ return resolve_hash(Inflecto.constantize(Inflecto.camelize(name)), action)
96
+ end
97
+
98
+ { controller: controller, action: action }
99
+ rescue NameError
100
+ raise(UnresolvedError)
101
+ end
102
+
103
+ ##
104
+ # Private: resolve controller and action from string
105
+ #
106
+ # Returns: {Hash}
107
+ # - :controller {Module|Class} controller constant
108
+ # - :action {String|Class} action name or constant
109
+ #
110
+ def resolve_string
111
+ controller, action = spec.split('#')
112
+
113
+ resolve_hash(controller, action)
114
+ end
115
+
116
+ ##
117
+ # Private: get controller name from pattern and namespace
118
+ #
119
+ # Params:
120
+ # - controller {String} raw controller name
121
+ #
122
+ # Returns: {String} namespaced controller name from pattern
123
+ #
124
+ def controller_name(controller)
125
+ names = {
126
+ name: controller,
127
+ plural_name: Inflecto.pluralize(controller),
128
+ singular_name: Inflecto.singularize(controller)
129
+ }
130
+
131
+ pattern = options[:controller_format] || '%{name}'
132
+
133
+ name = pattern % names
134
+
135
+ if options[:namespace]
136
+ namespace = Array(options[:namespace]).join('/')
137
+ name = "#{Inflecto.camelize(namespace)}::#{Inflecto.camelize(name)}"
138
+ end
139
+
140
+ name
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,17 @@
1
+ class Signpost
2
+ class Middleware
3
+ attr_reader :middleware
4
+
5
+ attr_reader :args
6
+
7
+ attr_reader :block
8
+
9
+ private
10
+
11
+ def initialize(middleware, args, block)
12
+ @middleware = middleware
13
+ @args = args
14
+ @block = block
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ class Signpost
2
+ class Route
3
+ class Nested
4
+ attr_reader :subpath
5
+
6
+ def match(path, env)
7
+ return unless path.start_with?(subpath)
8
+
9
+ @router.call(env)
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(subpath, router)
15
+ @subpath = subpath
16
+ @router = router
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,89 @@
1
+ class Signpost
2
+ class Route
3
+ class Simple
4
+ ##
5
+ # Route matcher/expander
6
+ #
7
+ # Returns: {Mustermann}
8
+ #
9
+ attr_reader :matcher
10
+
11
+ ##
12
+ # Rack-compatible endpoint
13
+ #
14
+ # Returns: {Proc|Object}
15
+ #
16
+ attr_reader :endpoint
17
+
18
+ ##
19
+ # Specific route params
20
+ #
21
+ # Returns: {Hash}
22
+ #
23
+ attr_reader :params
24
+
25
+ ##
26
+ # Environment constraints
27
+ #
28
+ # Returns: {Array}
29
+ #
30
+ attr_reader :constraints
31
+
32
+ ##
33
+ # Route name
34
+ #
35
+ # Returns: {Symbol}
36
+ #
37
+ attr_reader :name
38
+
39
+ ##
40
+ # Match path and return matched data or nil if path doesn't match
41
+ #
42
+ # Params:
43
+ # - path {String} uri path
44
+ #
45
+ # Example:
46
+ #
47
+ # route.match('/users/42') #=> { 'controller' => 'Users', 'action' => 'Show', 'id' => '42' }
48
+ # route.match('/foo/bar') #=> nil
49
+ #
50
+ # Returns: {Hash|NilClass}
51
+ #
52
+ def match(path, env)
53
+ return unless data = matcher.match(path)
54
+
55
+ return unless constraints.all? { |c| c.call(env) }
56
+
57
+ params.merge(Hash[data.names.zip(data.captures)])
58
+ end
59
+
60
+ ##
61
+ # Generate a string from path pattern
62
+ #
63
+ # Params:
64
+ # - data {Hash} key-values for expanding
65
+ #
66
+ # Example:
67
+ #
68
+ # route.expand({ id: 42 }) #=> '/users/42'
69
+ #
70
+ # Returns: {String}
71
+ #
72
+ def expand(data={})
73
+ matcher.expand(:append, data)
74
+ end
75
+
76
+ private
77
+
78
+ def initialize(matcher, endpoint, params={}, constraints=[], name=nil)
79
+ @matcher = matcher
80
+ @endpoint = endpoint
81
+ @params = params.each_with_object({}) { |(k, v), h| h[k.to_s] = v }.freeze
82
+ @constraints = constraints
83
+ @name = name ? name.to_sym : nil
84
+
85
+ freeze
86
+ end
87
+ end
88
+ end
89
+ end