siresta 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9217dfe568d7730704e4e45843b624407d854749
4
+ data.tar.gz: 5f9e29a33a17aa6ea78a085b5d3aa27e64ef5cad
5
+ SHA512:
6
+ metadata.gz: 362d5b662cdedb546d5df3a9601b0339139ad9900fc2bd8d67a003bcba464f17d9f9a342bb466c0a440449a281191550d289809b77cf3d87715f195acf8889c2
7
+ data.tar.gz: 70d5e0b4ecca360daf376dd9adcece4d0bae1dda08299a975ec0164ef1af0722ae4a2e87f55ef5ec92c51d1e87666b817d3e2bc2d18d27e4804ba120c04154e9
@@ -0,0 +1 @@
1
+ --markup markdown
@@ -0,0 +1,144 @@
1
+ []: {{{1
2
+
3
+ File : README.md
4
+ Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ Date : 2014-06-18
6
+
7
+ Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ Version : v0.0.2
9
+
10
+ []: }}}1
11
+
12
+ [![Gem Version](https://badge.fury.io/rb/siresta.png)](https://badge.fury.io/rb/siresta)
13
+
14
+ ## Description
15
+ []: {{{1
16
+
17
+ siRESTa - declarative REST APIs
18
+
19
+ siRESTa is a DSL for declarative REST APIs. It can generate a ruby
20
+ API (w/ sinatra [1]) and Client (w/ excon [2]) for you, based on a
21
+ YAML file. Processing requests is done using a monad.
22
+
23
+ More documentation is underway. For now, see `example/` and
24
+ `features/`.
25
+
26
+ ...
27
+
28
+ []: }}}1
29
+
30
+ ## Examples
31
+ []: {{{1
32
+
33
+ ```yaml
34
+ name: FooBarBaz
35
+ version: v1
36
+ request_formats: [json, xml]
37
+ response_formats: [json, xml]
38
+ api:
39
+ - resource: foos
40
+ contains:
41
+ - desc: Gets foos
42
+ get: get_foos
43
+ - post: create_foo
44
+ - resource: :foo_id
45
+ contains:
46
+ - desc: Get a foo
47
+ get: get_foo
48
+ - put: create_foo
49
+ - delete: delete_foo
50
+ - resource: bars
51
+ contains:
52
+ - get: get_bars
53
+ # ...
54
+ - resource: baz
55
+ contains:
56
+ - get: get_baz
57
+ # ...
58
+ ```
59
+
60
+ ```ruby
61
+ require 'siresta'
62
+ API = Siresta.api file: 'config/api.yml'
63
+ class API
64
+ data :foos, []
65
+
66
+ handle :get_foos do |m, h, p, b|
67
+ m.get_data(:foos) { |foos| m.ok foos }
68
+ end
69
+
70
+ # ...
71
+ end
72
+ API.run!
73
+ ```
74
+
75
+ ```
76
+ GET /foos
77
+ POST /foos
78
+ GET /foos/:foo_id
79
+ PUT /foos/:foo_id
80
+ DELETE /foos/:foo_id
81
+ GET /foos/:foo_id/bars
82
+ GET /baz
83
+ ...
84
+ ```
85
+
86
+ ```ruby
87
+ require 'siresta'
88
+ Client = Siresta.client
89
+ c = Client.new 'http://localhost:4567'
90
+
91
+ c.foos.get
92
+ c.foos.post headers: { 'Content-Type' => 'foo/bar' }
93
+ c.foos[some_foo_id].get query: { foo: 'bar' }
94
+ c.foos[some_foo_id].put
95
+ c.foos[some_foo_id].delete
96
+ c.foos[some_foo_id].bars.get
97
+ c.baz.get
98
+ ```
99
+
100
+ ```ruby
101
+ require 'siresta'
102
+ Siresta.routes
103
+ # => [["GET", "/foos", "Gets foos" ],
104
+ # ["POST", "/foos", nil ],
105
+ # ["GET", "/foos/:foo_id", "Get a foo" ],
106
+ # ["PUT", "/foos/:foo_id", nil ],
107
+ # ["DELETE", "/foos/:foo_id", nil ],
108
+ # ["GET", "/foos/:foo_id/bars", nil ],
109
+ # ["GET", "/baz", nil ]]
110
+ ```
111
+
112
+ []: }}}1
113
+
114
+ ## Specs & Docs
115
+
116
+ ```bash
117
+ $ rake cuke
118
+ $ rake docs
119
+ ```
120
+
121
+ ## TODO
122
+
123
+ * finish monad, api
124
+ * specs
125
+ * docs
126
+ * authorization?
127
+ * authentication?
128
+
129
+ ## License
130
+
131
+ LGPLv3+ [3].
132
+
133
+ ## References
134
+
135
+ [1] Sinatra
136
+ --- http://www.sinatrarb.com
137
+
138
+ [2] Excon
139
+ --- https://github.com/excon/excon
140
+
141
+ [3] GNU Lesser General Public License, version 3
142
+ --- http://www.gnu.org/licenses/lgpl-3.0.html
143
+
144
+ []: ! ( vim: set tw=70 sw=2 sts=2 et fdm=marker : )
@@ -0,0 +1,63 @@
1
+ cuke = ENV['CUKE']
2
+
3
+ desc 'Run cucumber'
4
+ task :cuke do
5
+ sh "cucumber -fprogress #{cuke}"
6
+ end
7
+
8
+ desc 'Run cucumber strictly'
9
+ task 'cuke:strict' do
10
+ sh "cucumber -fprogress -S #{cuke}"
11
+ end
12
+
13
+ desc 'Run cucumber verbosely'
14
+ task 'cuke:verbose' do
15
+ sh "cucumber #{cuke}"
16
+ end
17
+
18
+ desc 'Run cucumber verbosely, view w/ less'
19
+ task 'cuke:less' do
20
+ sh "cucumber -c #{cuke} | less -R"
21
+ end
22
+
23
+ desc 'Cucumber step defs'
24
+ task 'cuke:steps' do
25
+ sh 'cucumber -c -fstepdefs | less -R'
26
+ end
27
+
28
+
29
+ desc 'Check for warnings'
30
+ task :warn do
31
+ sh 'ruby -w -I lib -r siresta -e ""' # TODO
32
+ end
33
+
34
+
35
+ desc 'Generate docs'
36
+ task :docs do
37
+ sh 'yardoc | cat'
38
+ end
39
+
40
+ desc 'List undocumented objects'
41
+ task 'docs:undoc' do
42
+ sh 'yard stats --list-undoc'
43
+ end
44
+
45
+
46
+ desc 'Cleanup'
47
+ task :clean do
48
+ sh 'rm -rf .yardoc/ doc/ *.gem examples/*/db/*.sqlite3'
49
+ end
50
+
51
+
52
+ desc 'Build SNAPSHOT gem'
53
+ task :snapshot do
54
+ v = Time.new.strftime '%Y%m%d%H%M%S'
55
+ f = 'lib/siresta/version.rb'
56
+ sh "sed -ri~ 's!(SNAPSHOT)!\\1.#{v}!' #{f}"
57
+ sh 'gem build siresta.gemspec'
58
+ end
59
+
60
+ desc 'Undo SNAPSHOT gem'
61
+ task 'snapshot:undo' do
62
+ sh 'git checkout -- lib/siresta/version.rb'
63
+ end
@@ -0,0 +1,5 @@
1
+ require 'siresta/api'
2
+ require 'siresta/client'
3
+ require 'siresta/env'
4
+ require 'siresta/routes'
5
+ require 'siresta/version'
@@ -0,0 +1,190 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/api.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-17
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'json'
13
+ require 'obfusk/atom'
14
+ require 'sinatra/base'
15
+ require 'siresta/response'
16
+ require 'siresta/spec'
17
+
18
+ module Siresta
19
+ module API
20
+ # handle request for generated route
21
+ def _handle_request(meth, path, formats, pipe, handler)
22
+ b = method handler
23
+ r = Response
24
+ fs = [] # TODO
25
+
26
+ fs << r.choose_request_format(handler, formats) \
27
+ unless formats[:request].empty?
28
+ fs << r.get
29
+ fs << -> s { b[r, s.request.headers, s.request.params, s.request.body] }
30
+ fs << r.choose_response_format(handler, formats) \
31
+ unless formats[:response].empty?
32
+
33
+ x = r.pipeline(r.return(nil), *fs)
34
+ s = x.exec _begin_state
35
+
36
+ # TODO
37
+ if s.status.is_a? r::ResponseError
38
+ [500, s.status.message]
39
+ elsif s.response.data.is_a? r::ResponseBody
40
+ [s.response.status, s.response.headers, s.response.data.data]
41
+ else
42
+ raise 'OOPS' # TODO
43
+ end
44
+ end
45
+
46
+ # begin state for Response monad
47
+ def _begin_state
48
+ r = Response
49
+ r.ResponseState(
50
+ r.RequestData(
51
+ {}, # TODO: headers
52
+ request.body.read, params
53
+ ),
54
+ r.ResponseInfo(nil, {}, r.ResponseEmpty()),
55
+ r.ResponseContinue(), self
56
+ )
57
+ end
58
+
59
+ # preferred formats
60
+ def preferred_formats(formats)
61
+ @preferred_formats ||= {}
62
+ return @preferred_formats[formats] if @preferred_formats[formats]
63
+ fmts = formats[:request] | formats[:response]
64
+ m2f = Hash[fmts.map { |x| [mime_type(x), x] }]
65
+ f_in = m2f[request.content_type] || formats[:request].first
66
+ f_out = m2f[request.preferred_type m2f.keys]
67
+ @preferred_formats[formats] = [f_in, f_out]
68
+ end
69
+
70
+ # convert from preferred format
71
+ def convert_from(handler, formats, body)
72
+ f_in, f_out = preferred_formats formats
73
+ ss = settings.siresta[:convert_from][f_in] || {}
74
+ f = ss[handler] || ss[:__all__]
75
+ f ? f[body] : raise('OOPS') # TODO
76
+ end
77
+
78
+ # convert to preferred format
79
+ def convert_to(handler, formats, body)
80
+ f_in, f_out = preferred_formats formats
81
+ ss = settings.siresta[:convert_to][f_out] || {}
82
+ f = ss[handler] || ss[:__all__]
83
+ f ? f[body] : raise('OOPS') # TODO
84
+ end
85
+
86
+ # atoms
87
+ def data
88
+ settings.siresta[:data]
89
+ end
90
+
91
+ def self.included(base)
92
+ base.extend ClassMethods
93
+ end
94
+
95
+ module ClassMethods
96
+ # generate route
97
+ def _gen_route(meth, path, formats, pipe, handler)
98
+ send(meth, path) do
99
+ _handle_request meth, path, formats, pipe, handler
100
+ end
101
+ end
102
+
103
+ # named handler
104
+ def handle(name, &b)
105
+ define_method name, &b
106
+ end
107
+
108
+ # declare data (atom)
109
+ def data(k, v)
110
+ settings.siresta[:data][k] = Obfusk.atom v
111
+ end
112
+
113
+ # handle authorization
114
+ def to_authorize(handler, &b)
115
+ settings.siresta[:authorize][handler] = b
116
+ end
117
+
118
+ # convert body from format
119
+ def to_convert_from(format, handler = :__all__, &b)
120
+ (settings.siresta[:convert_from][format] ||= {})[handler] = b
121
+ end
122
+
123
+ # convert params
124
+ def to_convert_params(handler, &b)
125
+ settings.siresta[:convert_params][handler] = b
126
+ end
127
+
128
+ # convert body to format
129
+ def to_convert_to(format, handler = :__all__, &b)
130
+ (settings.siresta[:convert_to][format] ||= {})[handler] = b
131
+ end
132
+
133
+ # validate body
134
+ def to_validate_body(handler, &b)
135
+ settings.siresta[:validate_body][handler] = b
136
+ end
137
+
138
+ # validate params
139
+ def to_validate_params(handler, &b)
140
+ settings.siresta[:validate_params][handler] = b
141
+ end
142
+ end
143
+ end
144
+
145
+ class ApiBase < Sinatra::Base
146
+ include Siresta::API
147
+
148
+ set :siresta, { data: {}, authorize: {}, convert_from: {},
149
+ convert_params: {}, convert_to: {},
150
+ validate_body: {}, validate_params: {} }
151
+
152
+ to_convert_from :json do |body|
153
+ body.empty? ? nil : JSON.parse(body)
154
+ end
155
+
156
+ to_convert_to :json do |data|
157
+ data.to_json
158
+ end
159
+ end
160
+
161
+ # generate an API (Sinatra::Base subclass) based on a YAML
162
+ # description
163
+ def self.api(opts = {})
164
+ api = Class.new ApiBase
165
+ Spec.walk api_spec(opts), {
166
+ root: -> (info) {
167
+ api.class_eval do
168
+ enable :sessions if info[:sessions] # TODO
169
+ set :name , info[:name]
170
+ set :version, info[:version]
171
+ end
172
+ api
173
+ },
174
+ resource: -> (info) {
175
+ api.class_eval do
176
+ info[:methods].each do |m|
177
+ formats = info[:specs][m][:formats]
178
+ symfmts = Hash[formats.map { |k,v| [k,v.map(&:to_sym)] }]
179
+ _gen_route m.to_sym, info[:path], symfmts,
180
+ info[:specs][m]['pipeline'], info[:specs][m][m].to_sym
181
+ end
182
+ end
183
+ nil
184
+ },
185
+ subresource: -> (_) {}, parametrized_subresource: -> (_) {},
186
+ }
187
+ end
188
+ end
189
+
190
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,71 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/client.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-17
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'excon'
13
+ require 'siresta/spec'
14
+
15
+ module Siresta
16
+ module Client
17
+ # (sub)resource: wraps url, gets extendes as-needed w/ `.post`,
18
+ # `.get`, `.put`, `.delete`, `.some_resource`, `[some_param]` etc.
19
+ # Route methods take the same arguments as Excon's.
20
+ class Resource
21
+ attr_reader :url
22
+ def initialize(url, *path)
23
+ @url = ([url]+path)*'/'
24
+ end
25
+ end
26
+ end
27
+
28
+ # generate a client (Excon wrapper) based on a YAML description
29
+ def self.client(opts = {})
30
+ opts_ = opts.dup
31
+ http_client = opts_.delete(:http_client) || Excon
32
+ Spec.walk api_spec(opts_), {
33
+ root: -> (info) {
34
+ info[:res].class_eval do
35
+ define_method(:name) { info[:name] }
36
+ define_method(:version) { info[:version] }
37
+ end
38
+ info[:res]
39
+ },
40
+ resource: -> (info) {
41
+ res = Class.new Client::Resource
42
+ res.class_eval do
43
+ info[:methods].map(&:to_sym).each do |m|
44
+ # resource.{get,post,put,delete}
45
+ define_method(m) do |*params, &b|
46
+ # TODO: quoting
47
+ http_client.send m, url, *params, &b
48
+ end
49
+ end
50
+ end
51
+ res
52
+ },
53
+ subresource: -> (info) {
54
+ info[:res].class_eval do
55
+ # resource.some_route
56
+ define_method(info[:route].to_sym) do
57
+ info[:sub].new url, info[:route]
58
+ end
59
+ end
60
+ },
61
+ parametrized_subresource: -> (info) {
62
+ info[:res].class_eval do
63
+ # resource[some_param]
64
+ define_method(:[]) { |param| info[:sub].new url, param }
65
+ end
66
+ },
67
+ }
68
+ end
69
+ end
70
+
71
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,6 @@
1
+ module Siresta
2
+ # environment
3
+ def self.env
4
+ ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
5
+ end
6
+ end
@@ -0,0 +1,176 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/response.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-17
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'obfusk/adt'
13
+ require 'obfusk/monads'
14
+
15
+ module Siresta
16
+ class Response < Obfusk::Monads::State
17
+ class ResponseState
18
+ include Obfusk::ADT
19
+ constructor :ResponseState,
20
+ :request, :response, :status, :api_obj
21
+ end
22
+
23
+ class RequestData
24
+ include Obfusk::ADT
25
+ constructor :RequestData, :headers, :body, :params # ...
26
+ end
27
+
28
+ class ResponseInfo
29
+ include Obfusk::ADT
30
+ constructor :ResponseInfo, :status, :headers, :data
31
+ end
32
+
33
+ class ResponseData
34
+ include Obfusk::ADT
35
+ constructor :ResponseEmpty
36
+ constructor :ResponseBody, :data
37
+ constructor :ResponseStream, :handle
38
+ end
39
+
40
+ class ResponseStatus
41
+ include Obfusk::ADT
42
+ constructor :ResponseContinue
43
+ constructor :ResponseDone
44
+ constructor :ResponseError, :message
45
+ end
46
+
47
+ [ ResponseState, RequestData, ResponseInfo ] \
48
+ .each { |x| x.import_constructors self, false }
49
+
50
+ [ ResponseData, ResponseStatus ] \
51
+ .each { |x| x.import_constructors self }
52
+
53
+ # -- constructor helpers --
54
+
55
+ # add error to state
56
+ def self._error(s, msg)
57
+ s.clone(status: ResponseError(msg))
58
+ end
59
+
60
+ # call block with state if error (to short-circuit)
61
+ def self._on_error(s, &b)
62
+ b[s] if s.status.is_a? ResponseError
63
+ end
64
+
65
+ # call block with error state if done
66
+ def self._on_done(s, &b)
67
+ b[_error(s, 'cannot continue when done')] \
68
+ if s.status.is_a? ResponseDone
69
+ end
70
+
71
+ # call block with error state if empty
72
+ def self._on_empty(s, &b)
73
+ b[_error(s, 'cannot use empty body')] \
74
+ if s.response.data.is_a? ResponseEmpty
75
+ end
76
+
77
+ # call block with error state if stream
78
+ def self._on_stream(s, &b)
79
+ b[_error(s, 'cannot use body response')] \
80
+ if s.response.data.is_a? ResponseStream
81
+ end
82
+
83
+ # -- constructors --
84
+
85
+ # everything ok
86
+ def self.ok(body, headers = {}, status = 200)
87
+ modify -> s {
88
+ _on_error(s) { |t| return t }
89
+ _on_done(s) { |t| return t }
90
+ _on_stream(s) { |t| return t }
91
+ s.clone(
92
+ response: s.response.clone(
93
+ status: status, headers: s.response.headers.merge(headers),
94
+ data: ResponseBody(body)
95
+ ),
96
+ status: ResponseContinue()
97
+ )
98
+ }
99
+ end
100
+
101
+ # everyting ok, stream data
102
+ def self.stream(data, headers = {}, status = 200)
103
+ # TODO
104
+ end
105
+
106
+ # everything ok, stream data (keep open)
107
+ def self.stream_keep_open
108
+ # TODO
109
+ end
110
+
111
+ # error!
112
+ def self.error(msg)
113
+ modify { |s| _error s, msg }
114
+ end
115
+
116
+ # ...
117
+
118
+ # -- helpers --
119
+
120
+ # get atom data
121
+ def self.get_data(k, &b)
122
+ x = get >> -> s { mreturn s.api_obj.data[k]._ }; b ? x >> b : x
123
+ end
124
+
125
+ # set atom data
126
+ def self.set_data(k, v)
127
+ get >> -> s { s.api_obj.data[k].swap! { |_| v }; mreturn nil }
128
+ end
129
+
130
+ # authorization
131
+ def self.authorize(handler)
132
+ # TODO
133
+ end
134
+
135
+ # convert parameters
136
+ def self.convert_params(handler)
137
+ # TODO
138
+ end
139
+
140
+ # validate body
141
+ def self.validate_body(handler)
142
+ # TODO
143
+ end
144
+
145
+ # validate parameters
146
+ def self.validate_params(handler)
147
+ # TODO
148
+ end
149
+
150
+ # -- formats --
151
+
152
+ # convert request body from appropriate format
153
+ def self.choose_request_format(handler, formats)
154
+ modify -> s {
155
+ _on_error(s) { |t| return t }
156
+ _on_done(s) { |t| return t }
157
+ b = s.api_obj.convert_from(handler, formats, s.request.body)
158
+ s.clone(request: s.request.clone(body: b))
159
+ }
160
+ end
161
+
162
+ # convert response body to appropriate format
163
+ def self.choose_response_format(handler, formats)
164
+ modify -> s {
165
+ _on_error(s) { |t| return t }
166
+ _on_done(s) { |t| return t }
167
+ _on_empty(s) { |t| return t }
168
+ _on_stream(s) { |t| return t }
169
+ b = s.api_obj.convert_to(handler, formats, s.response.data.data)
170
+ s.clone(response: s.response.clone(data: ResponseBody(b)))
171
+ }
172
+ end
173
+ end
174
+ end
175
+
176
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,32 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/routes.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-13
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'siresta/spec'
13
+
14
+ module Siresta
15
+ # get routes from YAML description
16
+ def self.routes(opts = {})
17
+ routes = []
18
+ Spec.walk api_spec(opts), {
19
+ resource: -> (info) {
20
+ info[:methods].each do |m|
21
+ routes << [m.upcase, info[:path], info[:specs][m]['desc']]
22
+ end
23
+ nil
24
+ },
25
+ root: -> (_) {}, subresource: -> (_) {},
26
+ parametrized_subresource: -> (_) {},
27
+ }
28
+ routes
29
+ end
30
+ end
31
+
32
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,79 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/spec.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-17
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'yaml'
13
+
14
+ module Siresta
15
+ DEFAULT_API_YAML = 'config/api.yml'
16
+
17
+ module Spec
18
+ METHODS = %w{ post get put delete }
19
+
20
+ # walk spec
21
+ def self.walk(spec, opts)
22
+ name = spec['name']
23
+ version = spec['version']
24
+ sessions = spec['sessions']
25
+ formats = { request: spec['request_formats'] || [],
26
+ response: spec['response_formats'] || [] }
27
+ res = walk_resource spec['api'], '/', opts, formats
28
+ opts[:root][{
29
+ res: res, name: name, version: version, sessions: sessions
30
+ }]
31
+ end
32
+
33
+ # process resource when walking spec
34
+ def self.walk_resource(specs, path, opts, formats)
35
+ ms, ss = specs.inject([[],{}]) do |(ms,ss), spec|
36
+ chs = formats.merge({ request: spec['request_formats'],
37
+ response: spec['response_formats'] }
38
+ .reject { |k,v| !v })
39
+ (m = (METHODS & spec.keys).first) ?
40
+ [ms + [m], ss.merge(m => spec.merge(formats: chs))] : [ms,ss]
41
+ end
42
+ opts[:resource][{ methods: ms, specs: ss, path: path }] \
43
+ .tap { |res| walk_subresources res, specs, path, opts, formats }
44
+ end
45
+
46
+ # process subresources when walking spec
47
+ def self.walk_subresources(res, specs, path, opts, formats)
48
+ specs.each do |spec|
49
+ if (r = spec['resource'])
50
+ r_s = (p = Symbol === r) ? ":#{r}" : r
51
+ chs = formats.merge({ request: spec['request_formats'],
52
+ response: spec['response_formats'] }
53
+ .reject { |k,v| !v })
54
+ pth = (path == '/' ? '' : path) + '/' + r_s
55
+ sub = walk_resource spec['contains'], pth, opts, chs
56
+ opts[p ? :parametrized_subresource : :subresource][
57
+ { res: res, sub: sub, parametrized: p, route: r,
58
+ route_s: r_s, path: path }
59
+ ]
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ # get (cached) API spec
66
+ # @param [Hash] opts options
67
+ # @option opts [String] :data YAML data, or:
68
+ # @option opts [String] :file file name
69
+ def self.api_spec(opts = {})
70
+ if opts[:data]
71
+ YAML.load opts[:data]
72
+ else
73
+ file = opts[:file] || DEFAULT_API_YAML
74
+ (@api_spec ||= {})[file] ||= api_spec data: File.read(file)
75
+ end
76
+ end
77
+ end
78
+
79
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,4 @@
1
+ module Siresta
2
+ VERSION = '0.0.2'
3
+ DATE = '2014-06-18'
4
+ end
@@ -0,0 +1,72 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : siresta/xml.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2014-06-13
6
+ #
7
+ # Copyright : Copyright (C) 2014 Felix C. Stegerman
8
+ # Licence : LGPLv3+
9
+ #
10
+ # -- ; }}}1
11
+
12
+ # TODO: move to separate lib
13
+
14
+ require 'ox'
15
+
16
+ module Siresta
17
+ module XML
18
+ # parse XML
19
+ #
20
+ # ```ruby
21
+ # Siresta::XML.parse '<foo><bar id="99">hi!</bar></foo>'
22
+ # # => { tag: 'foo', attrs: {}, contents: [
23
+ # # { tag: 'bar', attrs: { id: 99 }, contents: ['hi!'] }
24
+ # # ] }
25
+ # ```
26
+ def self.parse(xml)
27
+ ox_elem = Ox.parse xml
28
+ _parse Ox::Document === ox_elem ? ox_elem.root : ox_elem
29
+ end
30
+
31
+ def self._parse(ox_elem)
32
+ if String === ox_elem
33
+ ox_elem
34
+ else
35
+ contents = ox_elem.nodes.map { |n| _parse n }
36
+ { tag: ox_elem.name, attrs: ox_elem.attributes, contents: contents }
37
+ end
38
+ end
39
+
40
+ # emit XML
41
+ #
42
+ # ```ruby
43
+ # puts Siresta::XML.emit(
44
+ # { tag: 'foo', attrs: {}, contents: [
45
+ # { tag: 'bar', attrs: { id: 99 }, contents: ['hi!'] }
46
+ # ] }
47
+ # )
48
+ # # <?xml version="1.0"?>
49
+ # # <foo>
50
+ # # <bar id="99">hi!</bar>
51
+ # # </foo>
52
+ # ```
53
+ def self.emit(elem, opts = {})
54
+ ox_doc = Ox::Document.new version: '1.0'
55
+ _emit ox_doc, elem
56
+ Ox.dump ox_doc, { with_xml: true }.merge(opts)
57
+ end
58
+
59
+ def self._emit(ox_doc, elem)
60
+ if String === elem
61
+ ox_doc << elem
62
+ else
63
+ ox_elem = Ox::Element.new elem[:tag]
64
+ elem[:attrs].each_pair { |k,v| ox_elem[k] = v }
65
+ ox_doc << ox_elem
66
+ elem[:contents].each { |e| _emit ox_elem, e }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,36 @@
1
+ require File.expand_path('../lib/siresta/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'siresta'
5
+ s.homepage = 'https://github.com/obfusk/siresta'
6
+ s.summary = 'siRESTa - declarative REST APIs'
7
+
8
+ s.description = <<-END.gsub(/^ {4}/, '')
9
+ siRESTa is a DSL for declarative REST APIs. It can generate a
10
+ ruby API (w/ sinatra) and Client (w/ excon) for you, based on a
11
+ YAML file. Processing requests is done using a monad.
12
+ END
13
+
14
+ s.version = Siresta::VERSION
15
+ s.date = Siresta::DATE
16
+
17
+ s.authors = [ 'Felix C. Stegerman' ]
18
+ s.email = %w{ flx@obfusk.net }
19
+
20
+ s.licenses = %w{ LGPLv3+ }
21
+
22
+ s.files = %w{ .yardopts README.md Rakefile siresta.gemspec } \
23
+ + Dir['lib/**/*.rb']
24
+
25
+ s.add_runtime_dependency 'excon'
26
+ # s.add_runtime_dependency 'hashie'
27
+ s.add_runtime_dependency 'obfusk', '>= 0.1.1'
28
+ s.add_runtime_dependency 'ox'
29
+ s.add_runtime_dependency 'sinatra'
30
+
31
+ s.add_development_dependency 'cucumber'
32
+ s.add_development_dependency 'rake'
33
+ s.add_development_dependency 'yard'
34
+
35
+ s.required_ruby_version = '>= 1.9.1'
36
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: siresta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Felix C. Stegerman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: excon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: obfusk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: ox
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cucumber
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: |
112
+ siRESTa is a DSL for declarative REST APIs. It can generate a
113
+ ruby API (w/ sinatra) and Client (w/ excon) for you, based on a
114
+ YAML file. Processing requests is done using a monad.
115
+ email:
116
+ - flx@obfusk.net
117
+ executables: []
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".yardopts"
122
+ - README.md
123
+ - Rakefile
124
+ - lib/siresta.rb
125
+ - lib/siresta/api.rb
126
+ - lib/siresta/client.rb
127
+ - lib/siresta/env.rb
128
+ - lib/siresta/response.rb
129
+ - lib/siresta/routes.rb
130
+ - lib/siresta/spec.rb
131
+ - lib/siresta/version.rb
132
+ - lib/siresta/xml.rb
133
+ - siresta.gemspec
134
+ homepage: https://github.com/obfusk/siresta
135
+ licenses:
136
+ - LGPLv3+
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 1.9.1
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.2.2
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: siRESTa - declarative REST APIs
158
+ test_files: []
159
+ has_rdoc: