kawaii-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ module Kawaii
2
+ # Template rendering based on Tilt.
3
+ module RenderMethods
4
+ # Renders a template.
5
+ # @param tmpl [String] file name or path to template, relative to /views in project dir
6
+ # @example Rendering html erb file
7
+ # render('index.html.erb')
8
+ # @todo Layouts.
9
+ def render(tmpl)
10
+ t = Tilt.new(File.join('views', tmpl)) # @todo Caching!
11
+ t.render(self)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ module Kawaii
2
+ # Matching and resolution for a single route.
3
+ class Route
4
+ include MethodChain
5
+
6
+ # Create a {Route} object.
7
+ # @param path [String, Regexp, Matcher] any path specification which can be
8
+ # consumed by {Matcher.compile}
9
+ # @param block [Proc] route handler
10
+ def initialize(scope, path, &block)
11
+ self.parent_scope = scope
12
+ @matcher = Matcher.compile(path, full_match: true)
13
+ @block = block
14
+ end
15
+
16
+ # Tries to match the route against a Rack environment.
17
+ # @param env [Hash] Rack environment
18
+ # @return [RouteHandler] a Rack application creating environment to run
19
+ # the route's handler block in on {RouteHandler#call}. Can be nil if
20
+ # no match found.
21
+ def match(env)
22
+ match = @matcher.match(env[Rack::PATH_INFO])
23
+ # puts "Route#match #{@matcher} #{env[Rack::PATH_INFO]} #{match.inspect}"
24
+ RouteHandler.new(self, match.params, &@block) if match
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ module Kawaii
2
+ # Implementation of nested routes generated via Router#context.
3
+ #
4
+ # @example A simple context
5
+ # context '/foo' do
6
+ # get '/bar' do
7
+ # end
8
+ # end
9
+ #
10
+ # # It is a rough equivalent of:
11
+ #
12
+ # ctx = RouteContext.new('/foo')
13
+ # ctx.get '/bar' do
14
+ # end
15
+ #
16
+ # @private
17
+ class RouteContext
18
+ include RoutingMethods
19
+ include MethodChain
20
+
21
+ # Create a {RouteContext} object.
22
+ # @param path [String, Regexp, Matcher] any path specification which can
23
+ # be consumed by {Matcher.compile}
24
+ def initialize(scope, path)
25
+ self.parent_scope = scope
26
+ super()
27
+ @matcher = Matcher.compile(path, starts_with: true)
28
+ end
29
+
30
+ # Tries to match the context against a Rack environment.
31
+ # @param env [Hash] Rack environment
32
+ # @return [Route] matching route defined inside the context. Can be nil if
33
+ # no match found.
34
+ def match(env)
35
+ m = @matcher.match(env[Rack::PATH_INFO])
36
+ super(env.merge(Rack::PATH_INFO => ensure_slash(m.remaining_path))) if m
37
+ end
38
+
39
+ protected
40
+
41
+ def ensure_slash(path)
42
+ if path.start_with?('/')
43
+ path
44
+ else
45
+ '/' + path
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ module Kawaii
2
+ # Creates context for execution of route handler block provided by the user
3
+ # with {#params} and other objects.
4
+ #
5
+ # @example Route handler block
6
+ # get '/users/:id' do
7
+ # if params[:id] ...
8
+ # end
9
+ class RouteHandler
10
+ include MethodChain
11
+ include RenderMethods
12
+
13
+ # Params based on request visible in the route handler scope.
14
+ attr_reader :params
15
+ # Rack::Request object visible in the route handler scope
16
+ attr_reader :request
17
+
18
+ # Creates a new RouteHandler wrapping a handler block.
19
+ # @param path_params [Hash] named parameters from paths similar to
20
+ # /users/:id
21
+ # @param block [Proc] the actual route handler
22
+ def initialize(scope, path_params, &block)
23
+ self.parent_scope = scope
24
+ @path_params = path_params
25
+ @block = block
26
+ end
27
+
28
+ # Invokes the handler as a normal Rack application.
29
+ # @param env [Hash] Rack environment
30
+ # @return [Array] Rack response array
31
+ def call(env)
32
+ @request = Rack::Request.new(env)
33
+ @params = @path_params.merge(@request.params.symbolize_keys)
34
+ process_response(instance_exec(self, params, request, &@block))
35
+ end
36
+
37
+ protected
38
+
39
+ class ResponseError < RuntimeError; end
40
+
41
+ def process_response(response)
42
+ if response.is_a?(String)
43
+ [200,
44
+ { Rack::CONTENT_TYPE => 'text/html',
45
+ Rack::CONTENT_LENGTH => response.size.to_s },
46
+ [response]]
47
+ elsif response.is_a?(Array)
48
+ response
49
+ else
50
+ fail ResponseError, "Unsupported handler aresponse: #{response.inspect}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ module Kawaii
2
+ # @private
3
+ # Maps route to a handler based on parameters.
4
+ # Supports mapping to blocks and to controller actions.
5
+ class RouteMapping
6
+ def initialize(mapping, &block)
7
+ fail 'Do not provide a block if mapping given' if mapping && block
8
+ fail 'Provide a mapping or a block' unless mapping || block
9
+ @mapping = mapping
10
+ @block = block
11
+ end
12
+
13
+ def resolve
14
+ return @block if @block
15
+ controller_name, method = parse(@mapping)
16
+ controller_class = find_controller(controller_name)
17
+ fail "Cannot find controller: #{controller_name}" if controller_class.nil?
18
+ proc do |& _args|
19
+ controller = controller_class.new(params, request)
20
+ controller.send(method)
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def find_controller(controller_name)
27
+ Object.const_get(controller_name)
28
+ end
29
+
30
+ def parse(mapping)
31
+ controller, method = mapping.split('#')
32
+ [controller.camelcase, method.to_sym]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,120 @@
1
+ module Kawaii
2
+ # Core route-building and matching.
3
+ #
4
+ # These functions can be used both in a class inheriting from {Base}
5
+ # and in file scope.
6
+ #
7
+ # @example Using a class deriving from {Base}
8
+ # class MyApp < Kawaii::Base
9
+ # get '/' do
10
+ # 'Hello, world'
11
+ # end
12
+ # end
13
+ #
14
+ # @example Using top-level (source file scope) route definitions
15
+ # get '/' do
16
+ # 'Hello, world'
17
+ # end
18
+ module RoutingMethods
19
+ # @!macro [attach] add_http_method
20
+ # @method $1(path, &block)
21
+ # Route handler for $1 HTTP method
22
+ # @param path [String, Regexp, Matcher] any path specification which can
23
+ # be consumed by {Matcher.compile}
24
+ # @param block the route handler
25
+ # @yield to the given block
26
+ # @note Supported HTTP verbs based on https://github.com/rack/rack/blob/master/lib/rack.rb#L48
27
+ def self.add_http_method(meth)
28
+ define_method(meth) do |path, mapping = nil, &block|
29
+ handler = RouteMapping.new(mapping, &block).resolve
30
+ add_route!(meth.to_s.upcase, Route.new(self, path, &handler))
31
+ end
32
+ end
33
+
34
+ add_http_method :get
35
+ add_http_method :post
36
+ add_http_method :put
37
+ add_http_method :patch
38
+ add_http_method :delete
39
+ add_http_method :head
40
+ add_http_method :options
41
+ add_http_method :link
42
+ add_http_method :unlink
43
+ add_http_method :trace
44
+ # Note: Have to generate them individually due to yard limitations.
45
+
46
+
47
+ # Insert routes corresponding to REST actions (similar to Rails `resource`).
48
+ # @param path [String] path prefix (e.g. "/users/")
49
+ # @param controller [String] snakecase controller name (e.g. "hello_world"
50
+ # corresponds to HelloWorld).
51
+ # @example REST resource routes
52
+ # route '/users/', 'hello_world'
53
+ #
54
+ # # Will insert routes corresponding to:
55
+ # # GET /users/? -> Controller#index
56
+ # # GET /users/:id/? -> Controller#show
57
+ # # POST /users/? -> Controller#create
58
+ # # PATCH /users/:id/? -> Controller#update
59
+ # # DELETE /users/:id/? -> Controller#destroy
60
+ def route(path, controller)
61
+ get(File.join(path, '?'), "#{controller}#index")
62
+ get(File.join(path, '/:id/?'), "#{controller}#show")
63
+ post(File.join(path, '?'), "#{controller}#create")
64
+ patch(File.join(path, '/:id/?'), "#{controller}#update")
65
+ delete(File.join(path, '/:id/?'), "#{controller}#destroy")
66
+ end
67
+
68
+ # Create a context for route nesting.
69
+ #
70
+ # @param path [String, Regexp, Matcher] any path specification which can
71
+ # be consumed by {Matcher.compile}
72
+ # @param block the route handler
73
+ # @yield to the given block
74
+ #
75
+ # @example A simple context
76
+ # context '/foo' do
77
+ # get '/bar' do
78
+ # end
79
+ # end
80
+ def context(path, &block)
81
+ ctx = RouteContext.new(self, path)
82
+ # @todo Is there a better way to keep ordering of routes?
83
+ # An alternative would be to enter each route in a context only once
84
+ # (with 'prefix' based on containing contexts).
85
+ # On the other hand, we're only doing that when compiling routes, further
86
+ # processing is faster this way.
87
+ ctx.instance_eval(&block)
88
+ ctx.methods_used.each do |meth|
89
+ add_route!(meth, ctx)
90
+ end
91
+ end
92
+
93
+ # Tries to match against a Rack environment.
94
+ # @param env [Hash] Rack environment
95
+ # @return [Route] matching route. Can be nil if no match found.
96
+ def match(env)
97
+ routes[env[Rack::REQUEST_METHOD]]
98
+ .lazy # Lazy to avoid unnecessary calls to #match.
99
+ .map { |r| r.match(env) }
100
+ .find { |r| !r.nil? }
101
+ end
102
+
103
+ # Returns a list of HTTP methods used by the routes (incl. nested routes).
104
+ # @return [Array<String>] example ["GET", "POST"]
105
+ def methods_used
106
+ routes.keys
107
+ end
108
+
109
+ protected
110
+
111
+ def routes
112
+ @routes ||= Hash.new { |h, k| h[k] = [] }
113
+ end
114
+
115
+ def add_route!(method, route)
116
+ # puts "add_route! #{method} #{route.inspect}"
117
+ routes[method] << route
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ module Kawaii
2
+ # Mixins for starting a self-contained server. To be included in a class
3
+ # derived from {Base}.
4
+ # @note At the moment hard-coded to use WEBrick.
5
+ module ServerMethods
6
+ WEBRICK = 'WEBrick'
7
+
8
+ # Starts serving the app.
9
+ # @param port [Fixnum] port number to bind to
10
+ def start!(port) # @todo Support other handlers http://www.rubydoc.info/github/rack/rack/Rack/Handler
11
+ Rack::Handler.get(WEBRICK).run(self, Port: port) do |s|
12
+ @server = s
13
+ at_exit { stop! }
14
+ [:INT, :TERM].each do |signal|
15
+ old = trap(signal) do
16
+ stop!
17
+ old.call if old.respond_to?(:call)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ # Stops serving the app.
24
+ def stop!
25
+ @server.stop if @server # NOTE: WEBrick-specific
26
+ end
27
+
28
+ # Returns true if the server is running.
29
+ def running?
30
+ !@server.nil?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ module Kawaii
2
+ # Class used to implement a standalone Kawaii app generated with top-level
3
+ # route helpers (e.g. monkey-patched onto the `main` object).
4
+ #
5
+ # This lets you create a .rb file containing just route definitions and run it
6
+ # with `ruby` command.
7
+ #
8
+ # @example test.rb
9
+ # require 'kawaii'
10
+ #
11
+ # get '/' do
12
+ # 'Hello, world'
13
+ # end
14
+ #
15
+ # @example Running from command line
16
+ # ruby -r kawaii test.rb
17
+ # ...
18
+ #
19
+ class SingletonApp < Base
20
+ class << self
21
+ def maybe_start!(port)
22
+ # Give routes a chance to install and app to initialize.
23
+ at_exit { start!(port) unless $ERROR_INFO } if !running? && run_direct?
24
+ end
25
+
26
+ protected
27
+
28
+ def run_direct?
29
+ c = caller_locations.map(&:path).find { |path| !skip_caller?(path) }
30
+ File.identical?($PROGRAM_NAME, c)
31
+ end
32
+
33
+ def skip_caller?(path)
34
+ File.identical?(path, __FILE__) ||
35
+ path[%r{rubygems/core_ext/kernel_require\.rb$}] ||
36
+ path[%r{/kawaii.rb$}]
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # Helpers you use directly in a .rb file without using a class
43
+ # inheriting from {Base}.
44
+ #
45
+ # @example hello_world.rb
46
+ # get '/' do
47
+ # 'Hello, world'
48
+ # end
49
+ class << self
50
+ include Kawaii::RoutingMethods
51
+
52
+ def routes
53
+ Kawaii::SingletonApp.routes # Add route handlers to {SingletonApp}
54
+ end
55
+ end
56
+
57
+ # For self-contained execution without config.ru. See {SingletonApp} above.
58
+ Kawaii::SingletonApp.maybe_start!(8088) # @todo Hard-coded port number.
@@ -0,0 +1,4 @@
1
+ # Gem version.
2
+ module Kawaii
3
+ VERSION = '0.1.0'
4
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kawaii-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marcin Bilski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tilt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack-test
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.6'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.8'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.8'
125
+ description: Kawaii is a basic but extensible web framework based on Rack
126
+ email:
127
+ - gyamtso@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".travis.yml"
135
+ - Gemfile
136
+ - Guardfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - bin/console
141
+ - bin/setup
142
+ - examples/controller.ru
143
+ - examples/hello_world.rb
144
+ - examples/hello_world.ru
145
+ - examples/modular.ru
146
+ - examples/modular/first_app.rb
147
+ - examples/modular/second_app.rb
148
+ - examples/nested_routes.rb
149
+ - examples/views.ru
150
+ - examples/views/index.html.erb
151
+ - kawaii-core.gemspec
152
+ - lib/kawaii.rb
153
+ - lib/kawaii/base.rb
154
+ - lib/kawaii/controller.rb
155
+ - lib/kawaii/core_ext/hash.rb
156
+ - lib/kawaii/core_ext/string.rb
157
+ - lib/kawaii/matchers.rb
158
+ - lib/kawaii/method_chain.rb
159
+ - lib/kawaii/render_methods.rb
160
+ - lib/kawaii/route.rb
161
+ - lib/kawaii/route_context.rb
162
+ - lib/kawaii/route_handler.rb
163
+ - lib/kawaii/route_mapping.rb
164
+ - lib/kawaii/routing_methods.rb
165
+ - lib/kawaii/server_methods.rb
166
+ - lib/kawaii/singleton_app.rb
167
+ - lib/kawaii/version.rb
168
+ homepage: https://github.com/bilus/kawaii
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.2.2
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Kawaii is a simple web framework based on Rack
192
+ test_files: []
193
+ has_rdoc: