kawaii-core 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,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: