zero 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +59 -0
- data/Guardfile +15 -0
- data/README.md +60 -0
- data/Thorfile +6 -0
- data/lib/zero.rb +7 -0
- data/lib/zero/controller.rb +46 -0
- data/lib/zero/rack_request.rb +44 -0
- data/lib/zero/renderer.rb +139 -0
- data/lib/zero/request.rb +100 -0
- data/lib/zero/request/accept.rb +28 -0
- data/lib/zero/request/accept_type.rb +58 -0
- data/lib/zero/request/client.rb +31 -0
- data/lib/zero/request/parameter.rb +101 -0
- data/lib/zero/request/server.rb +41 -0
- data/lib/zero/response.rb +80 -0
- data/lib/zero/router.rb +63 -0
- data/lib/zero/version.rb +3 -0
- data/spec/fixtures/templates/index.html.erb +1 -0
- data/spec/fixtures/templates/index.json.erb +1 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/unit/controller/call_spec.rb +22 -0
- data/spec/unit/controller/renderer_spec.rb +11 -0
- data/spec/unit/renderer/read_template_path_spec.rb +53 -0
- data/spec/unit/renderer/render_spec.rb +50 -0
- data/spec/unit/renderer/template_path.rb +8 -0
- data/spec/unit/renderer/type_map_spec.rb +9 -0
- data/spec/unit/request/accept/encoding_spec.rb +9 -0
- data/spec/unit/request/accept/language_spec.rb +9 -0
- data/spec/unit/request/accept/types_spec.rb +9 -0
- data/spec/unit/request/accept_spec.rb +7 -0
- data/spec/unit/request/accepttype/each_spec.rb +10 -0
- data/spec/unit/request/accepttype/preferred_spec.rb +35 -0
- data/spec/unit/request/client/address_spec.rb +9 -0
- data/spec/unit/request/client/hostname_spec.rb +9 -0
- data/spec/unit/request/client/user_agent_spec.rb +9 -0
- data/spec/unit/request/client_spec.rb +8 -0
- data/spec/unit/request/content_type_spec.rb +16 -0
- data/spec/unit/request/create_spec.rb +21 -0
- data/spec/unit/request/delete_spec.rb +15 -0
- data/spec/unit/request/get_spec.rb +15 -0
- data/spec/unit/request/head_spec.rb +15 -0
- data/spec/unit/request/method_spec.rb +8 -0
- data/spec/unit/request/parameter/[]_spec.rb +56 -0
- data/spec/unit/request/parameter/custom_spec.rb +18 -0
- data/spec/unit/request/parameter/initialize_spec.rb +12 -0
- data/spec/unit/request/parameter/payload_spec.rb +33 -0
- data/spec/unit/request/parameter/query_spec.rb +25 -0
- data/spec/unit/request/params_spec.rb +8 -0
- data/spec/unit/request/patch_spec.rb +15 -0
- data/spec/unit/request/path_spec.rb +9 -0
- data/spec/unit/request/post_spec.rb +15 -0
- data/spec/unit/request/put_spec.rb +15 -0
- data/spec/unit/request/server/hostname_spec.rb +9 -0
- data/spec/unit/request/server/port_spec.rb +7 -0
- data/spec/unit/request/server/protocol_spec.rb +9 -0
- data/spec/unit/request/server/software_spec.rb +9 -0
- data/spec/unit/request/server_spec.rb +8 -0
- data/spec/unit/response/response_spec.rb +146 -0
- data/spec/unit/router/call_spec.rb +55 -0
- data/zero.gemspec +27 -0
- metadata +345 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
zero (0.0.1)
|
5
|
+
tilt
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
coderay (1.0.8)
|
11
|
+
diff-lcs (1.1.3)
|
12
|
+
guard (1.5.4)
|
13
|
+
listen (>= 0.4.2)
|
14
|
+
lumberjack (>= 1.0.2)
|
15
|
+
pry (>= 0.9.10)
|
16
|
+
thor (>= 0.14.6)
|
17
|
+
guard-bundler (1.0.0)
|
18
|
+
bundler (~> 1.0)
|
19
|
+
guard (~> 1.1)
|
20
|
+
guard-rspec (2.3.0)
|
21
|
+
guard (>= 1.1)
|
22
|
+
rspec (~> 2.11)
|
23
|
+
listen (0.6.0)
|
24
|
+
lumberjack (1.0.2)
|
25
|
+
method_source (0.8.1)
|
26
|
+
multi_json (1.3.7)
|
27
|
+
pry (0.9.10)
|
28
|
+
coderay (~> 1.0.5)
|
29
|
+
method_source (~> 0.8)
|
30
|
+
slop (~> 3.3.1)
|
31
|
+
rack (1.4.1)
|
32
|
+
rspec (2.12.0)
|
33
|
+
rspec-core (~> 2.12.0)
|
34
|
+
rspec-expectations (~> 2.12.0)
|
35
|
+
rspec-mocks (~> 2.12.0)
|
36
|
+
rspec-core (2.12.0)
|
37
|
+
rspec-expectations (2.12.0)
|
38
|
+
diff-lcs (~> 1.1.3)
|
39
|
+
rspec-mocks (2.12.0)
|
40
|
+
simplecov (0.7.1)
|
41
|
+
multi_json (~> 1.0)
|
42
|
+
simplecov-html (~> 0.7.1)
|
43
|
+
simplecov-html (0.7.1)
|
44
|
+
slop (3.3.3)
|
45
|
+
thor (0.16.0)
|
46
|
+
tilt (1.3.3)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
guard
|
53
|
+
guard-bundler
|
54
|
+
guard-rspec
|
55
|
+
rack
|
56
|
+
rspec
|
57
|
+
simplecov
|
58
|
+
thor
|
59
|
+
zero!
|
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
7
|
+
watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'rspec' do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
end
|
15
|
+
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
zero - a toolkit for web services
|
2
|
+
=================================
|
3
|
+
|
4
|
+
The focus of this project is to provide small helpers for building web services
|
5
|
+
without the need of learning a complete new web stack from the bottom to the
|
6
|
+
top.
|
7
|
+
|
8
|
+
As this project is still at the beginning, some things may change or may not be
|
9
|
+
as good as other parts. But we are working on these parts to make them easier
|
10
|
+
to use and stable enough.
|
11
|
+
|
12
|
+
The following is a small list of things, the project already provides and at the
|
13
|
+
end you can find a small sample on what is already possible.
|
14
|
+
|
15
|
+
parts of the toolkit
|
16
|
+
--------------------
|
17
|
+
|
18
|
+
### Request
|
19
|
+
|
20
|
+
A new request implementation is provided to make it easier for accessing all the
|
21
|
+
various elements of a request. It provides an interface for all access headers
|
22
|
+
through the method `#access`. It also provides a method `#params`, which returns
|
23
|
+
an object with parameters seperated between *query* and *payload* parameters.
|
24
|
+
|
25
|
+
### Response
|
26
|
+
|
27
|
+
A new response is also provided. which is just some lines of code at the moment,
|
28
|
+
but will grow in functionality. We aim at a response which will take care of all
|
29
|
+
the http specific stuff itself, to make it easier for you.
|
30
|
+
|
31
|
+
### Router
|
32
|
+
|
33
|
+
A small router with variables is also provided. It can take static urls, but
|
34
|
+
also dynamic routes and push them to the appropiate controller. It is very
|
35
|
+
lightweight and can therefore be used in other projects, where a Rack::Router
|
36
|
+
is needed but can't do as much and other implementations are just to big.
|
37
|
+
|
38
|
+
### Renderer
|
39
|
+
|
40
|
+
This part should do the work for you to decide, which template to use. You can
|
41
|
+
define a template directory and a mapping of simple shortcuts of types to all
|
42
|
+
types the template should be rendered with. With these two information the
|
43
|
+
renderer will take care of selecting the correct template, so that you can
|
44
|
+
concentrate on the hard parts.
|
45
|
+
|
46
|
+
### Controller
|
47
|
+
|
48
|
+
This is in a very rough state at the moment, but already shows the potential in
|
49
|
+
which direction this hole project should go. It defines a very simple interface
|
50
|
+
for your own controllers, in that it only wants `#render` to be implemented, but
|
51
|
+
also calls `#process` to seperate the processing and rendering. It uses the
|
52
|
+
Zero tools at the moment, but later should be able to use other libs too.
|
53
|
+
|
54
|
+
example
|
55
|
+
-------
|
56
|
+
|
57
|
+
To give you an impression on what these parts can already do for you, you can
|
58
|
+
take a look at the sample application
|
59
|
+
[here](https://github.com/Gibheer/zero-examples) and try it out. It does not
|
60
|
+
take much.
|
data/Thorfile
ADDED
data/lib/zero.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Zero
|
2
|
+
# abstract class to make creation of controllers easier
|
3
|
+
#
|
4
|
+
# This abstract class creates an interface to make it easier to write
|
5
|
+
# rack compatible controllers. It catches #call and creates a new instance
|
6
|
+
# with the environment and calls #render on it.
|
7
|
+
class Controller
|
8
|
+
# initialize a new instance of the controller and call response on it
|
9
|
+
def self.call(env)
|
10
|
+
new(Zero::Request.create(env)).response
|
11
|
+
end
|
12
|
+
|
13
|
+
# set the renderer to use in the controller
|
14
|
+
def self.renderer=(renderer)
|
15
|
+
@@renderer = renderer
|
16
|
+
end
|
17
|
+
|
18
|
+
# get the renderer set in the controller
|
19
|
+
def self.renderer
|
20
|
+
@@renderer
|
21
|
+
end
|
22
|
+
|
23
|
+
# a small helper to get the actual renderer
|
24
|
+
def renderer
|
25
|
+
self.class.renderer
|
26
|
+
end
|
27
|
+
|
28
|
+
# initialize the controller
|
29
|
+
# @param request [Request] a request object
|
30
|
+
def initialize(request)
|
31
|
+
@request = request
|
32
|
+
@response = Zero::Response.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# build the response and return it
|
36
|
+
#
|
37
|
+
# This method calls #process if it was defined so make it easier to process
|
38
|
+
# the request before rendering stuff.
|
39
|
+
# @return Response a rack conform response
|
40
|
+
def response
|
41
|
+
process if respond_to?(:process)
|
42
|
+
render
|
43
|
+
@response.to_a
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# as we need #update_param we patch it it into the request
|
2
|
+
# this is directly from thr request.rb of the rack repository, so this
|
3
|
+
# can be deleted, when rack was released
|
4
|
+
r = Rack::Request.new(Rack::MockRequest.env_for('foo'))
|
5
|
+
if not r.respond_to?('update_param') then
|
6
|
+
class Rack::Request
|
7
|
+
# Destructively update a parameter, whether it's in GET and/or POST.
|
8
|
+
# Returns nil.
|
9
|
+
#
|
10
|
+
# The parameter is updated wherever it was previous defined, so
|
11
|
+
# GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
12
|
+
#
|
13
|
+
# env['rack.input'] is not touched.
|
14
|
+
def update_param(k, v)
|
15
|
+
found = false
|
16
|
+
if self.GET.has_key?(k)
|
17
|
+
found = true
|
18
|
+
self.GET[k] = v
|
19
|
+
end
|
20
|
+
if self.POST.has_key?(k)
|
21
|
+
found = true
|
22
|
+
self.POST[k] = v
|
23
|
+
end
|
24
|
+
unless found
|
25
|
+
self.GET[k] = v
|
26
|
+
end
|
27
|
+
@params = nil
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Destructively delete a parameter, whether it's in GET or POST.
|
32
|
+
# Returns the value of the deleted parameter.
|
33
|
+
#
|
34
|
+
# If the parameter is in both GET and POST, the POST value takes
|
35
|
+
# precedence since that's how #params works.
|
36
|
+
#
|
37
|
+
# env['rack.input'] is not touched.
|
38
|
+
def delete_param(k)
|
39
|
+
v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first
|
40
|
+
@params = nil
|
41
|
+
v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Zero
|
2
|
+
# the base renderer for getting render containers
|
3
|
+
#
|
4
|
+
# This class handles templates and render coontainers, which can be used for
|
5
|
+
# the actual rendering.
|
6
|
+
#
|
7
|
+
# To use this renderer you have to give it a template path and optionally
|
8
|
+
# a map of shorthand type descriptions to fully types. This will then be used
|
9
|
+
# to extend the internal map of templates to possible formats in a way, that
|
10
|
+
# you will be able to answer xhtml and html requests with the same template.
|
11
|
+
#
|
12
|
+
# When the object is initialized and you are sure, everything is loaded, call
|
13
|
+
# #read_template_path! and the template tree will be built. Without this step,
|
14
|
+
# you will probably don't get any output.
|
15
|
+
#
|
16
|
+
# After the setup, the renderer can be used to build render containers, which
|
17
|
+
# then can be used to actually render something.
|
18
|
+
class Renderer
|
19
|
+
# initializes a new Renderer
|
20
|
+
#
|
21
|
+
# This method takes a path to the base template directory and a type map.
|
22
|
+
# This type map is used to extend the possible renderings for different
|
23
|
+
# types, which the clients sends.
|
24
|
+
#
|
25
|
+
# @example create a simple renderer
|
26
|
+
# Renderer.new('app/templates')
|
27
|
+
#
|
28
|
+
# @example create a renderer with a small map
|
29
|
+
# Renderer.new('app', {
|
30
|
+
# 'html' => ['text/html', 'application/html+xml'],
|
31
|
+
# 'json' => ['application/json', 'application/aweomse+json']
|
32
|
+
# })
|
33
|
+
#
|
34
|
+
# @param template_path [String] a string to templates
|
35
|
+
# @param type_map [Hash] a map of simple types to complex ones
|
36
|
+
def initialize(template_path, type_map = {})
|
37
|
+
@template_path = template_path + '/'
|
38
|
+
@type_map = type_map
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns the hash of type conversions
|
42
|
+
# @return [Hash] type conversion
|
43
|
+
attr_reader :type_map
|
44
|
+
# get the path to the templates
|
45
|
+
# @return [String] the base template path
|
46
|
+
attr_reader :template_path
|
47
|
+
# get the tree of templates
|
48
|
+
# @api private
|
49
|
+
# @return [Hash] the template tree
|
50
|
+
attr_reader :templates
|
51
|
+
|
52
|
+
# load the template tree
|
53
|
+
#
|
54
|
+
# This method gets all templates in the `template_path` and builds an
|
55
|
+
# internal tree structure, where templates and types direct the request to
|
56
|
+
# the wanted template.
|
57
|
+
# @return [Self] returns the object
|
58
|
+
def read_template_path!
|
59
|
+
# TODO clean up later
|
60
|
+
@templates = {}
|
61
|
+
search_files.each do |file|
|
62
|
+
parts = file.gsub(/#{template_path}/, '').split('.')
|
63
|
+
@templates[parts[0]] ||= {}
|
64
|
+
|
65
|
+
# Set default value
|
66
|
+
types = 'default'
|
67
|
+
# Overwrite default value, if it's set in template path
|
68
|
+
if parts.count > 2 then
|
69
|
+
types = parts[1]
|
70
|
+
end
|
71
|
+
|
72
|
+
read_type(types).each do |type|
|
73
|
+
@templates[parts[0]][type] = file
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# render a template
|
79
|
+
#
|
80
|
+
# This method will render the given template, based on the type in the given
|
81
|
+
# context.
|
82
|
+
# @param name [String] the name of the template
|
83
|
+
# @param type [Array] a list of accept types used to find the template
|
84
|
+
# @param context [Object] the context in which to evaluate the template
|
85
|
+
# @return [String] the rendered content
|
86
|
+
def render(name, type, context)
|
87
|
+
template(name, type).render(context)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# search in `template_path` for templates beginning with `template_name`
|
93
|
+
# @api private
|
94
|
+
# @param template_name [String] the name of the template
|
95
|
+
# @return [#each] a list of all templates found
|
96
|
+
def search_files
|
97
|
+
Dir[template_path + '**/*.*']
|
98
|
+
end
|
99
|
+
|
100
|
+
# gets the type information from a file and converts it to an array of
|
101
|
+
# possible matching types
|
102
|
+
# @api private
|
103
|
+
# @param short_notation [String] a short notation of a type, like `html`
|
104
|
+
# @return [Array] a list of matching types, like `text/html`
|
105
|
+
def read_type(short_notation)
|
106
|
+
to_type_list(type_map[short_notation] || short_notation)
|
107
|
+
end
|
108
|
+
|
109
|
+
# convert a map to an array if it is not one
|
110
|
+
# @api private
|
111
|
+
# @param original_map [Object] the type(s) to convert
|
112
|
+
# @return [Array] a list of objects
|
113
|
+
def to_type_list(original_map)
|
114
|
+
return original_map if original_map.respond_to?(:each)
|
115
|
+
[original_map]
|
116
|
+
end
|
117
|
+
|
118
|
+
# get the prepared template for the name and type
|
119
|
+
# @api private
|
120
|
+
# @param name [String] the name of the template
|
121
|
+
# @param types [Array] the types for the template
|
122
|
+
# @return [Tilt::Template] a prepared tilt template
|
123
|
+
def template(name, types)
|
124
|
+
if templates.has_key? name
|
125
|
+
types.each do |type|
|
126
|
+
template = templates[name][type]
|
127
|
+
unless template.nil?
|
128
|
+
return template if template.kind_of?(Tilt::Template)
|
129
|
+
return Tilt.new(template)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
raise ArgumentError.new(
|
133
|
+
"No template found for any of this types #{types.join ', '}!"
|
134
|
+
)
|
135
|
+
end
|
136
|
+
raise ArgumentError.new "No template found for '#{name}'!"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/zero/request.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative 'request/accept'
|
2
|
+
require_relative 'request/client'
|
3
|
+
require_relative 'request/parameter'
|
4
|
+
require_relative 'request/server'
|
5
|
+
|
6
|
+
module Zero
|
7
|
+
# This class wraps around a rack environment for easier access to all data.
|
8
|
+
class Request
|
9
|
+
def self.create(environment)
|
10
|
+
return environment['zero.request'] if environment.has_key?('zero.request')
|
11
|
+
new(environment)
|
12
|
+
end
|
13
|
+
|
14
|
+
# create a new request object
|
15
|
+
def initialize(env)
|
16
|
+
@env = env
|
17
|
+
@env['zero.request'] = self
|
18
|
+
end
|
19
|
+
|
20
|
+
# get the environment
|
21
|
+
# @return [Hash] the environment hash
|
22
|
+
attr_reader :env
|
23
|
+
|
24
|
+
# get the requested path
|
25
|
+
# @return [String] returns the requested path
|
26
|
+
def path
|
27
|
+
@path ||= @env[CONST_PATH_INFO]
|
28
|
+
end
|
29
|
+
|
30
|
+
# return all information about the client
|
31
|
+
# @return [Client] an information object about the client
|
32
|
+
def client
|
33
|
+
@client ||= Request::Client.new(@env)
|
34
|
+
end
|
35
|
+
|
36
|
+
# get the information on the server
|
37
|
+
# @return [Server] information on the running server
|
38
|
+
def server
|
39
|
+
@server ||= Request::Server.new(@env)
|
40
|
+
end
|
41
|
+
|
42
|
+
# get an object representing the parameters of the request
|
43
|
+
# @return [Parameter] object having all parameters
|
44
|
+
def params
|
45
|
+
@params ||= Request::Parameter.new(@env)
|
46
|
+
end
|
47
|
+
|
48
|
+
# return the content type of the request
|
49
|
+
# TODO change into its own object?
|
50
|
+
# @return [String] returns the content type of the request
|
51
|
+
def content_type
|
52
|
+
@env[CONST_CONTENT_TYPE] if @env.has_key?(CONST_CONTENT_TYPE)
|
53
|
+
end
|
54
|
+
|
55
|
+
# get the media types
|
56
|
+
# @return [Accept] on Accept object managing all types and their order
|
57
|
+
def accept
|
58
|
+
@accept ||= Request::Accept.new(@env)
|
59
|
+
end
|
60
|
+
|
61
|
+
# get the method of the request
|
62
|
+
# @return [Symbol] the symbol representation of the method
|
63
|
+
def method
|
64
|
+
@method ||= @env[CONST_REQUEST_METHOD].downcase.to_sym
|
65
|
+
end
|
66
|
+
# is the method 'GET'?
|
67
|
+
# @return [Boolean] true if this is a get request
|
68
|
+
def get?; method == :get; end
|
69
|
+
# is the method 'POST'?
|
70
|
+
# @return [Boolean] true if this is a post request
|
71
|
+
def post?; method == :post; end
|
72
|
+
# is the method 'PUT'?
|
73
|
+
# @return [Boolean] true if this is a put request
|
74
|
+
def put?; method == :put; end
|
75
|
+
# is the method 'DELETE'?
|
76
|
+
# @return [Boolean] true if this is a delete request
|
77
|
+
def delete?; method == :delete; end
|
78
|
+
# is the method 'HEAD'?
|
79
|
+
# @return [Boolean] true if this is a head request
|
80
|
+
def head?; method == :head; end
|
81
|
+
# is the method 'PATCH'?
|
82
|
+
# @return [Boolean] true if this is a patch request
|
83
|
+
def patch?; method == :patch; end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# constant for the content type key
|
88
|
+
# @api private
|
89
|
+
CONST_CONTENT_TYPE = 'CONTENT_TYPE'
|
90
|
+
# constant for the http accept key
|
91
|
+
# @api private
|
92
|
+
CONST_HTTP_ACCEPT = 'HTTP_ACCEPT'
|
93
|
+
# constant for the path info key
|
94
|
+
# @api private
|
95
|
+
CONST_PATH_INFO = 'PATH_INFO'
|
96
|
+
# constant for the request method key
|
97
|
+
# @api private
|
98
|
+
CONST_REQUEST_METHOD = 'REQUEST_METHOD'
|
99
|
+
end
|
100
|
+
end
|