zero 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.
Files changed (65) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +59 -0
  6. data/Guardfile +15 -0
  7. data/README.md +60 -0
  8. data/Thorfile +6 -0
  9. data/lib/zero.rb +7 -0
  10. data/lib/zero/controller.rb +46 -0
  11. data/lib/zero/rack_request.rb +44 -0
  12. data/lib/zero/renderer.rb +139 -0
  13. data/lib/zero/request.rb +100 -0
  14. data/lib/zero/request/accept.rb +28 -0
  15. data/lib/zero/request/accept_type.rb +58 -0
  16. data/lib/zero/request/client.rb +31 -0
  17. data/lib/zero/request/parameter.rb +101 -0
  18. data/lib/zero/request/server.rb +41 -0
  19. data/lib/zero/response.rb +80 -0
  20. data/lib/zero/router.rb +63 -0
  21. data/lib/zero/version.rb +3 -0
  22. data/spec/fixtures/templates/index.html.erb +1 -0
  23. data/spec/fixtures/templates/index.json.erb +1 -0
  24. data/spec/spec_helper.rb +65 -0
  25. data/spec/unit/controller/call_spec.rb +22 -0
  26. data/spec/unit/controller/renderer_spec.rb +11 -0
  27. data/spec/unit/renderer/read_template_path_spec.rb +53 -0
  28. data/spec/unit/renderer/render_spec.rb +50 -0
  29. data/spec/unit/renderer/template_path.rb +8 -0
  30. data/spec/unit/renderer/type_map_spec.rb +9 -0
  31. data/spec/unit/request/accept/encoding_spec.rb +9 -0
  32. data/spec/unit/request/accept/language_spec.rb +9 -0
  33. data/spec/unit/request/accept/types_spec.rb +9 -0
  34. data/spec/unit/request/accept_spec.rb +7 -0
  35. data/spec/unit/request/accepttype/each_spec.rb +10 -0
  36. data/spec/unit/request/accepttype/preferred_spec.rb +35 -0
  37. data/spec/unit/request/client/address_spec.rb +9 -0
  38. data/spec/unit/request/client/hostname_spec.rb +9 -0
  39. data/spec/unit/request/client/user_agent_spec.rb +9 -0
  40. data/spec/unit/request/client_spec.rb +8 -0
  41. data/spec/unit/request/content_type_spec.rb +16 -0
  42. data/spec/unit/request/create_spec.rb +21 -0
  43. data/spec/unit/request/delete_spec.rb +15 -0
  44. data/spec/unit/request/get_spec.rb +15 -0
  45. data/spec/unit/request/head_spec.rb +15 -0
  46. data/spec/unit/request/method_spec.rb +8 -0
  47. data/spec/unit/request/parameter/[]_spec.rb +56 -0
  48. data/spec/unit/request/parameter/custom_spec.rb +18 -0
  49. data/spec/unit/request/parameter/initialize_spec.rb +12 -0
  50. data/spec/unit/request/parameter/payload_spec.rb +33 -0
  51. data/spec/unit/request/parameter/query_spec.rb +25 -0
  52. data/spec/unit/request/params_spec.rb +8 -0
  53. data/spec/unit/request/patch_spec.rb +15 -0
  54. data/spec/unit/request/path_spec.rb +9 -0
  55. data/spec/unit/request/post_spec.rb +15 -0
  56. data/spec/unit/request/put_spec.rb +15 -0
  57. data/spec/unit/request/server/hostname_spec.rb +9 -0
  58. data/spec/unit/request/server/port_spec.rb +7 -0
  59. data/spec/unit/request/server/protocol_spec.rb +9 -0
  60. data/spec/unit/request/server/software_spec.rb +9 -0
  61. data/spec/unit/request/server_spec.rb +8 -0
  62. data/spec/unit/response/response_spec.rb +146 -0
  63. data/spec/unit/router/call_spec.rb +55 -0
  64. data/zero.gemspec +27 -0
  65. metadata +345 -0
@@ -0,0 +1,2 @@
1
+ .rbx
2
+ config.ru
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --order random
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-18mode
5
+ - jruby-19mode
6
+ - rbx-18mode
7
+ - rbx-19mode
8
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :guard do
6
+ gem 'guard'
7
+ gem 'guard-bundler'
8
+ gem 'guard-rspec'
9
+ end
10
+
11
+ group :test do
12
+ gem 'thor'
13
+ gem 'simplecov'
14
+ end
@@ -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!
@@ -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
+
@@ -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.
@@ -0,0 +1,6 @@
1
+ class Default < Thor
2
+ desc 'spec', 'run all specs'
3
+ def spec
4
+ exec 'rspec'
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Zero
2
+ require_relative 'zero/controller'
3
+ require_relative 'zero/router'
4
+ require_relative 'zero/renderer'
5
+ require_relative 'zero/request'
6
+ require_relative 'zero/response'
7
+ end
@@ -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
@@ -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