neorack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8a5de5634584563b6e83b7e14a1cb7764f8cd5f45f7728f279c4044b0e7076d
4
+ data.tar.gz: d550f93859ddd7d803d1631d194d3e7d05fa248752403238bcf1cc9be39d1d15
5
+ SHA512:
6
+ metadata.gz: c30238d35ed24d54c7de16bf66a1e0386f27b469dfdc395f4a5d370d7b0afee8a10c8c3815a4bacb587bc52f41a618acf68d4cb60ffdd7afa04647df439a3081
7
+ data.tar.gz: 4318adb128db67a97446ee4b2f4d54d21db13a0bd22890103a702806140afc7d1be2aa1301c23c005982e2c09f66274ac42aaf0a89d98a3c41d633c81f6694b3
@@ -0,0 +1,3 @@
1
+ # Specify filepatterns you want git to ignore.
2
+ pkg/
3
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ # NeoRack Change Log
2
+
3
+ **Note**: changes will be logged after (and relative to) the first developer release (version 0.1.0).
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at bo@bowild.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in neorack.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Boaz Segev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,58 @@
1
+ # NeoRack
2
+ [![NeoRack logo](neorack_logo.png)](SPEC.md)
3
+
4
+ [Rack](https://github.com/rack/rack) is great and I love all it's done for Ruby. It made us all stronger, gave us a unified platform and saved us countless developer hours and time.
5
+
6
+ However, Rack's design is showing its age... its CGI model has shortcomings that we can all sort of mitigate and code around, but cost us developer hours and often leave us with degraded performance.
7
+
8
+ The NeoRack specification is designed to offer a solution for these shortcomings by:
9
+
10
+ * Making long-polling, streaming and long requests first class citizens.
11
+
12
+ * Supporting server feature testing during startup and application buildup (in addition to during response execution).
13
+
14
+ * Supporting server extensions that can be implemented by either the server or external gems.
15
+
16
+ * Supporting (optional) backwards compatibility with Rack.
17
+
18
+ My hope is that one day NeoRack and Rack could be merged in a way that makes developers happy and advanced web applications easy to author.
19
+
20
+ Please read the [NeoRack specifications](SPEC.md) for details.
21
+
22
+ ---
23
+
24
+ # The NeoRack Gem
25
+
26
+ Currently the gem is mostly a placeholder for a gem that will slowly fill up with common helpers.
27
+
28
+ ## Installation
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ ```ruby
33
+ gem 'neorack'
34
+ ```
35
+
36
+ And then execute:
37
+
38
+ $ bundle install
39
+
40
+ Or install it yourself as:
41
+
42
+ $ gem install neorack
43
+
44
+ ## Usage
45
+
46
+ The only available module at the moment is the `NeoRack::Builder` that loads script files, offering them the DSL listed in the [NeoRack specifications](SPEC.md#neorack-application-scripts).
47
+
48
+ ## Contributing
49
+
50
+ Bug reports and pull requests are welcome on GitHub at https://github.com/boazsegev/neorack. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/boazsegev/neorack/blob/master/CODE_OF_CONDUCT.md).
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
55
+
56
+ ## Code of Conduct
57
+
58
+ Everyone interacting in the NeoRack project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/boazsegev/neorack/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/SPEC.md ADDED
@@ -0,0 +1,650 @@
1
+ # NeoRack Specification
2
+
3
+ NeoRack define a protocol / API for web application servers and applications to communicate with each other.
4
+
5
+ The following are the NeoRack Application, Server and Extension specifications.
6
+
7
+ NeoRack Applications, Servers and Extensions **MUST** follow in order to be considered conforming.
8
+
9
+ Note that NeoRack Applications, Servers and Extensions are **NOT** required to include any specific NeoRack related gem in order to conform to these specifications.
10
+
11
+ ## NeoRack Applications
12
+
13
+ A NeoRack application is any Ruby object that responds to the method `call` where the `call` method accepts two arguments (`request`, `response`). i.e.,
14
+
15
+ ```ruby
16
+ APP = Proc.new {|request, response| response << "Hello World" }
17
+ ```
18
+
19
+ For backwards compatibility reasons, a NeoRack Application that returns while both `response.streaming?` and `response.finished?` would return `false` **SHOULD NOT** return an `Array` instance object as its final value - otherwise the returned value might be processed.
20
+
21
+ If both `response.streaming?` and `response.finished?` return `false` after the Application returns, the Server **MUST** call `response.finish`.
22
+
23
+ i.e., in pseudo code:
24
+
25
+ ```ruby
26
+ r = APP.call(request, response)
27
+ unless response.streaming? || response.finished?
28
+ process_backwards_compatible_response(r) if r.is_a?(Array)
29
+ response.finish
30
+ end
31
+ ```
32
+
33
+ ## The NeoRack Request Object
34
+
35
+ The NeoRack Request Object (`request`) **SHOULD** be an instance of a class that inherits from `Hash` (but is **NOT** an instance of `Hash`) and **MUST** implement the following "Hash-like" methods: `[]`, `[]=`, `each`, `size`, `has_key?` and `merge!` in the same way they are implemented by `Hash`.
36
+
37
+ The `request` object **MAY** be used by Applications and/or Extensions to store and/or communicate additional data relevant to the request.
38
+
39
+ **Note**: during Server initialization, an application might `extend` or `include` its own modules into the Server's Request class. For this reason it is better if that class is not the Hash class itself.
40
+
41
+ ### Request methods
42
+
43
+ The `request` object **MUST** respond to the following methods:
44
+
45
+ #### `server`
46
+
47
+ Returns the Server class / object that called the Application object.
48
+
49
+ ### Request key-value pairs
50
+
51
+ The Server **MUST** set following key-value pairs in the NeoRack Request object. All keys are Symbols (not Strings).
52
+
53
+ #### `:VERSION_SPEC`
54
+
55
+ The version for this specification as a three member Array of Numbers, currently `[0,0,1]`.
56
+
57
+ #### `:VERSION`
58
+
59
+ The HTTP version String as reported by the client. For HTTP/2 and HTTP/3 (QUIC) use `'HTTP/2'` and `'HTTP/3'` respectively.
60
+
61
+ #### `:secure`
62
+
63
+ **MUST** be set to false unless the server itself handles TLS/SSL encryption for this request / connection.
64
+
65
+ #### `:scheme`
66
+
67
+ Depending on the request URL and environment, usually `http`, `ws`, `https` or `wss`.
68
+
69
+ #### `:method`
70
+
71
+ The HTTP method used for the request, set as a String instance (`'HEAD'`, `'GET'`, `'POST'`, `'UPDATE'`, etc').
72
+
73
+ #### `:query`
74
+
75
+ The portion of the request URL that follows the `'?'`, if any. **MUST** be a String (even if empty).
76
+
77
+ #### `:path`
78
+
79
+ The portion of the request URL before the query and after the host name and optional authentication details.
80
+
81
+ **MUST** be a String. **MUST** start with the `'/'` character (even if the original path was an empty String).
82
+
83
+ Servers that support multiple Application objects **MAY** change this value in a documented manner.
84
+
85
+ #### `:path_root`
86
+
87
+ The portion of the request URL path that was extracted from the beginning of the `:path` String or an empty String.
88
+
89
+ **MUST** be a String (even if empty).
90
+
91
+ **MUST NOT** end with `'/'`.
92
+
93
+ Unless empty, **MUST** start with a `'/'`.
94
+
95
+ Servers that support multiple Application objects **SHOULD** document how `path_root` is used.
96
+
97
+ **Note**: it **MUST** be possible to construct a valid URL that routes to the same Application object using:
98
+
99
+ ```ruby
100
+ url = "#{request[:scheme]}://#{request['host']}#{request[:path_root]}#{request[:path]}?#{request[:query]}"
101
+ ```
102
+
103
+ #### `:body`
104
+
105
+ An HTTP payload (body) object (see details further on).
106
+
107
+ If a payload exists, this value **MUST** be set. Otherwise this **MAY** be set to `nil`, left unset or point to an empty body object.
108
+
109
+ ### HTTP Request Headers
110
+
111
+ All HTTP headers, **MUST** be set as key-value pairs in the `request` where the key is a **String** and the value is either a String or an Array of Strings.
112
+
113
+ Header names **MUST** be converted into their lower case equivalent before the key-value pair is set (i.e., `"Content-Length"` MUST be converted to `"content-length"`).
114
+
115
+ Unless the Server implements the `:backwards_compatible` extension (see further on), HTTP headers **MUST** be the **ONLY** String keys in the `request` set by the Server or by any of its Extension. Otherwise, it might make it impossible to implemented a `:backwards_compatible` extension.
116
+
117
+ When a header arrives only once, it's value **MUST** be set as a String instance object.
118
+
119
+ Headers that arrive more than once **SHOULD** be set as an Array of Strings, ordered by header value arrival (i.e., `headers['cache-control'] = ['no-cache', 'no-store']`)
120
+
121
+ **ONLY IF** [the HTTP protocols allows for this variation](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2), Servers **MAY** also merge header values into a single comma separated value String instance or separate a single header into an Array of Strings.
122
+
123
+ NeoRack Applications and Extensions **MUST** be prepared to handle both variations (Array / String).
124
+
125
+ ### HTTP Request Body
126
+
127
+ The NeoRack Request Body (`request[:body]`) is an IO-like object which contains the raw HTTP POST data (payload / body). It is backwards compatible with the original Rack specification with the addition of the methods `length`.
128
+
129
+ When no HTTP payload / body was received, `request[:body]` **SHOULD** be `nil`, but **MAY** be a Request Body Object that maps to an empty String.
130
+
131
+ The `request[:body]` object (if set) **MUST** respond to IO style methods `gets`, `each`, `read`, `rewind` and `length`. These methods **MUST** behave the same way as described by [the Ruby IO documentation](https://ruby-doc.org/core-2.7.1/IO.html). The following clarifications were pretty much copied from [the original Rack specification](https://github.com/rack/rack/blob/master/SPEC.rdoc):
132
+
133
+ #### `gets`
134
+
135
+ **MUST** be called without arguments and Reads the next "line" from the body object. It returns either a String (on success), or `nil` (on `EOF`).
136
+
137
+ #### `read(length = nil, buffer = nil)`
138
+
139
+ If given, `length` **MUST** be a non-negative Integer (>= 0) or `nil`,
140
+
141
+ If `length` is given and not `nil`, then this method reads at most `length` bytes from the body object.
142
+
143
+ If `length` is not given or `nil`, then this method reads all data until `EOF`.
144
+
145
+ When `EOF` is reached, this method returns `nil` if `length` is given and not `nil`, or `""` if `length` is not given or is `nil`.
146
+
147
+ If given, `buffer` **MUST** be a String.
148
+
149
+ If `buffer` is given, then the read data will be placed into `buffer` instead of a newly created String object.
150
+
151
+ #### `each`
152
+
153
+ **MUST** be called without arguments and only yield one String instance object per iteration.
154
+
155
+ #### `rewind`
156
+
157
+ **MUST** be called without arguments. It rewinds the body object back to the beginning. It **MUST NOT** raise `Errno::ESPIPE`: that is, it may not be a pipe or a socket. Therefore, developers **MUST** buffer the input data into some rewindable object if the underlying body object is not rewindable.
158
+
159
+ #### `length`
160
+
161
+ Returns the length of the data in the body object.
162
+
163
+ **Note**: Applications **SHOULD NOT** access the `'content-length'` header using the request object and **SHOULD** prefer to access this data using the `request[:body]` object (`request[:body].length`).
164
+
165
+ #### `close`
166
+
167
+ **MUST NOT** be called on the body object except by the Server.
168
+
169
+ ## The NeoRack Response Object
170
+
171
+ The NeoRack Response Object (`response`) manages the response data, including the response status, header data and body (payload).
172
+
173
+ ### NeoRack Response Methods
174
+
175
+ The NeoRack Response Object **MUST** respond to the following methods:
176
+
177
+ #### `status`
178
+
179
+ Returns the response status.
180
+
181
+ #### `status=`
182
+
183
+ Sets the response status.
184
+
185
+ **SHOULD** raise an exception if either `streaming?` or `finished?` would have returned `true`.
186
+
187
+ #### `add_header(name, value)`
188
+
189
+ Adds a header to the response. If the header exists, the header will be sent multiple times (i.e., `'set-cookie'`).
190
+
191
+ If either `name` or `value` are `nil`, does nothing (returns, no exception is raised).
192
+
193
+ `name` and `value` **MUST** be either `nil`, a String object or a Number. Otherwise an `ArgumentError` exception **MUST** be raised.
194
+
195
+ The header `name` **SHOULD** be lowercase, but Servers **SHOULD** expect application to be inconsistent in this regard.
196
+
197
+ The Server **MAY** perform any additional action, such as sending an *Early Hints* responses or invoking an HTTP/2 *push promise* request for `'link'` headers, attempt to auto-correct or rewrite header data, etc'. Such behavior **SHOULD** be documented.
198
+
199
+ **MUST** raise an exception if either `streaming?` or `finished?` would have returned `true`.
200
+
201
+ #### `reset_header(name, value = nil)`
202
+
203
+ If `name` is `nil`, does nothing (returns, no exception is raised).
204
+
205
+ Deletes the header from the response if the header exists.
206
+
207
+ If `value` is set, calls `add_header(name, value)`
208
+
209
+ **MUST** raise an exception if either `streaming?` or `finished?` would have returned `true`.
210
+
211
+ #### `set_cookie(name, value, options={})`
212
+
213
+ If `name` is `nil`, does nothing (returns, no exception is raised).
214
+
215
+ If `value` is `nil`, sets the cookie to be deleted.
216
+
217
+ If both `name` and `value` are set, adds a cookie to the response. If the cookie exists, it is overwritten.
218
+
219
+ **MUST** raise an exception if either `streaming?` or `finished?` would have returned `true`.
220
+
221
+ Servers **SHOULD NOT** encode cookie data (names / value). Encoding the data is the Application's responsibility and choice.
222
+
223
+ `name` and `value` **MUST** be either `nil` or a String object. Otherwise an `ArgumentError` exception **MUST** be raised. **Note**: empty String objects are valid, but **SHOULD** be avoided by the Applications.
224
+
225
+ If `name` is an invalid cookie name or `value` is an invalid cookie value, behavior is undefined (see [here](https://tools.ietf.org/html/rfc6265) and [here](https://stackoverflow.com/questions/1969232/what-are-allowed-characters-in-cookies)). The Server **MAY** attempt to auto-correct the issue, raise and exception, quietly fail, drop the connection, etc'.
226
+
227
+ The `option` hash **MUST** recognize the following Symbols for setting cookies (see also the [`Set-Cookie` header details](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)):
228
+
229
+ * `:path` - (String) A path that must exist in the requested URL, or the browser won't send the Cookie header.
230
+
231
+ * `:domain` - (String) The domain for which this cookie applies. Valid a specific domain String object (i.e. `'example.com'`). Note that subdomains are always allowed.
232
+
233
+ * `:same_site` - (Boolean) If set, **MUST** be either `:strict`, `:lax` or `:none`. [Read details here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
234
+
235
+ * `:secure` - (Boolean) A secure cookie is only sent to the server when a request is made with the `https` scheme.
236
+
237
+ * `:http_only` - (Boolean) If `true`, forbids JavaScript from accessing the cookie through a script (i.e, using `document.cookie`).
238
+
239
+ **Note**: The cookie is still sent with JavaScript originated requests.
240
+
241
+ * `:max_age` - (Number) The number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately.
242
+
243
+ * `:expires` - (Number / Time) **SHOULD** be considered deprecated by Application developers. It is only provided for compatibility purposes.
244
+
245
+ `:expires` is the maximum "due date" lifetime for the cookie as either a number (Unix style time stamp) or a Ruby Time instance object. If provided, servers **MUST** translate the time stamp into an HTTP Date format.
246
+
247
+ If both `:max_age` and `:expires` are set, `:max_age` **MUST** be preferred and `:expires` **SHOULD** be ignored. Servers **MAY** send both values (`Max-Age` always has precedence).
248
+
249
+ If only `:expires` is set, the Server **MAY** convert `:expires` to `:max_age` values.
250
+
251
+ **Note**: if both `:max_age` and `:expires` are missing, the cookie will be considered a session cookie by the client.
252
+
253
+ #### `write(data, offset = 0, length = nil)`
254
+
255
+ **MUST** raise an exception if `finished?` would have returned `true`.
256
+
257
+ If `streaming?` is `false`, the Server **MUST**:
258
+
259
+ * Add the data to be sent to the response payload (body), unless `request[:method] == 'HEAD'`.
260
+
261
+ If `streaming?` is `true`, the Server **MUST**:
262
+
263
+ * Unless previously performed:
264
+
265
+ * Update the `'transfer-encoding'` header as required by the protocol, unless the `'content-length'` header was previously set or the server knows the final length of the data to be sent.
266
+
267
+ * Validate and send the status and header data.
268
+
269
+ * If `request[:method] == 'HEAD'`, return at this point without further processing.
270
+
271
+ * Add the data to be sent to the response payload (body).
272
+
273
+ * Send any pending data in the response payload / body (or schedule to do so) using the proper encoding scheme (streaming responses my need to be `chunked`).
274
+
275
+ **Note**: depending on their design, Servers **MAY** block to until the data was actually sent through the socket.
276
+
277
+ Returns `self` (the `response` object).
278
+
279
+ `data` **MUST** either:
280
+
281
+ * Be a String instance object.
282
+
283
+ * Respond to both `to_path` and `close`, allowing the Server to `close` the object and send it from disk (possibly in that order).
284
+
285
+ * Respond to both `fileno` and `close`, allowing the Server to take ownership of the IO device and stream it.
286
+
287
+ If `offset` is set, the first `offset` bytes in the `data` object are ignored (not sent). `offset` **MUST** be a Number.
288
+
289
+ If `length` is set, up to `length` bytes from the `data` object will be sent. `length` **MUST** be either `nil` or a Number.
290
+
291
+ Servers **MUST** test the `length` value for overflow whenever possible (may not be always possible). If `length` overflows it is **NOT** an error, but it could cause network errors that the Server **MUST** properly handle or require that the response be streamed.
292
+
293
+ #### `<<`
294
+
295
+ The `<<` method is an alias to `write`.
296
+
297
+ #### `stream`
298
+
299
+ **MUST** raise an exception if `finished?` would have returned `true`.
300
+
301
+ Sets the response to streaming.
302
+
303
+ Returns `self` (the `response` object).
304
+
305
+ If `write` was previously called, **MUST** perform all necessary operations as if the call to `write` was performed after the call to `stream` - i.e., update the proper `'transfer-encoding'` header, validate and send the status and header data, etc'.
306
+
307
+ #### `streaming?`
308
+
309
+ **MUST** return `false` unless both `stream` was previously called and `finished?` is `false`.
310
+
311
+ Otherwise, **MUST** return `true`.
312
+
313
+ #### `finish(data = nil, offset = 0, length = nil)`
314
+
315
+ **SHOULD** raise an exception if `finished?` would have returned `true`, but **MAY** quietly fail without doing anything.
316
+
317
+ The Server **MUST**:
318
+
319
+ * Call `write` if `data` isn't `nil`.
320
+
321
+ **Note**: if `stream` was previously called but no data was previously sent (`write` wasn't called previously), the Server **SHOULD** behave as if the `stream` method was never called (i.e., set `'content-length'` rather than stream, if possible).
322
+
323
+ * Unless previously performed, validate and send the status and header data.
324
+
325
+ * Unless `request[:method] == 'HEAD'`, send any pending data in the response payload / body (or schedule to do so) using the proper encoding scheme.
326
+
327
+ * Call (or schedule) all the `run_after` callbacks.
328
+
329
+ #### `finished?`
330
+
331
+ **MUST** return `false` **unless** either `finish` or `cancel` were called.
332
+
333
+ Otherwise, **MUST** return `true`.
334
+
335
+ #### `cancel(error_code)`
336
+
337
+ Cancels the response object, returning the response management to the Server.
338
+
339
+ If possible, the Server **MUST** send an appropriate error response.
340
+
341
+ **SHOULD** raise an exception if `error_code` is less than 400 or grater than 599 (`< 400 || >= 600`).
342
+
343
+ **MUST** raise an exception if either `streaming?` or `finished?` would have returned `true`.
344
+
345
+ ## NeoRack Servers
346
+
347
+ A NeoRack Server **MUST**, at the very least, handle network and HTTP protocol details to the minimal level required to implement the NeoRack `request` and NeoRack `response` objects and call a NeoRack Application object.
348
+
349
+ A NeoRack Server **MUST** accept at least one NeoRack Applications and forward a `request` and `response` object to the Application.
350
+
351
+ If a NeoRack Server accepts more than a single NeoRack Application, the Server **SHOULD** document how it selects the Application object.
352
+
353
+ Servers **SHOULD** implement the `hijack` extension if possible. It **SHOULD NOT** be used by applications, but it often is.
354
+
355
+ ### Server methods
356
+
357
+ A NeoRack Server **MUST** implement the following methods in the Server object returned by a call to `request.server`:
358
+
359
+ #### `forking?`
360
+
361
+ **MUST** be set to `true` **ONLY IF** the Server's concurrency model expects to use `fork` (i.e., workers processes or a process-per-connection). If this is not the case, returns `false`.
362
+
363
+ This allows Extensions and Applications know if some features require IPC (Inter Process Communication).
364
+
365
+ #### `blocking?`
366
+
367
+ **MUST** be set to `true` **ONLY IF** the Server's concurrency model allows execution to block without negative side-effects, i.e., process/thread/fiber-per-connection design patterns.
368
+
369
+ #### `extensions`
370
+
371
+ Returns a Hash object that maps extension name Symbols to version number arrays. **MAY** be an empty Hash.
372
+
373
+ Version number arrays **MUST** be frozen Array who's first three members are Numbers that follow [semantic versioning](https://semver.org).
374
+
375
+ i.e.: `request[:SERVER].extensions[:rack_compatible] # => [1,3,0]`
376
+
377
+ #### `classes`
378
+
379
+ Returns a Hash object where Symbol keys are mapped to Class objects (not Class instances).
380
+
381
+ The Server MUST set the following key-value pairs:
382
+
383
+ * `:request` **MUST** map to the Class used for the Server's `request` objects.
384
+
385
+ * `:response` **MUST** map to the Class used for the Server's `response` objects.
386
+
387
+ * `:concurrency` **MUST** map to either `Thread` or `Fiber`, allowing the Server to hint at the preferred way for the Application to handle concurrent processing. Servers **SHOULD** set this value to `Thread` unless they are `Fiber` scheduling aware.
388
+
389
+ This **MAY** be used to test for and implement extensions. i.e.:
390
+
391
+ ```ruby
392
+ # an Application could call this or a similar line using the DSL (see later) to add features to the Response object
393
+ server.classes(:response).include MyNeoRackHelpers unless server.extensions[:common_helpers] && server.extensions[:common_helpers][0] == 1
394
+ ```
395
+
396
+ Extensions to this specification **MAY** require additional names to be added as long as these names are unique.
397
+
398
+ ## NeoRack Extensions
399
+
400
+ NeoRack Servers **MAY** be extended either internally, through the Server supporting an extension, or externally, through setting up middleware and/or adding mixins to the published classes and updating the Server's `extensions` and `classes`.
401
+
402
+ Extensions registered in the NeoRack repository will have their specifications published in the `extensions` folder and **MUST** specify a unique name for the Servers `extensions` Hash, specify a [semantic versioning](https://semver.org) compliant 3 numbered version array and be mature.
403
+
404
+ Developers may also ask to register extension drafts to be placed in the `extensions/drafts` folder and request community feedback.
405
+
406
+ ### External Extensions
407
+
408
+ External extensions **SHOULD** implement a `register` method that accepts at least one argument - the `self` object of the DSL execution environment.
409
+
410
+ The following is a made-up logging extension implementation example that should be considered as pseudo code (and is actually "middleware" in nature):
411
+
412
+ ```ruby
413
+ # Place module somewhere
414
+ module MyLoggingExtension
415
+ # registers the extension from the DLS
416
+ def self.register(dsl)
417
+ return if server.extensions[:logging_example]
418
+ dsl.server.extensions[:logging_example] = [0,0,1]
419
+ dsl.run_before(self.method :on_start)
420
+ dsl.run_after(self.method :on_finish)
421
+ end
422
+ # Marks time
423
+ def self.on_start(request, response)
424
+ request[:STARTED_AT] = Time.now
425
+ end
426
+ # Prints log
427
+ def self.on_finish(request)
428
+ delta = Time.now - request[:STARTED_AT]
429
+ puts "HTTP request %s%s took %.4fs"%(request[:path_root], request[:path], delta)
430
+ end
431
+ end
432
+
433
+ # in the DSL, require the module and perform:
434
+ MyLoggingExtension.register(self)
435
+ ```
436
+
437
+ ## Starting up a NeoRack Server and Application
438
+
439
+ A NeoRack Server **MUST** document its startup and shutdown procedures.
440
+
441
+ A NeoRack Server **MAY** offer a Ruby API for setting up and/or running and/or managing the Server from within Ruby.
442
+
443
+ A NeoRack Server **SHOULD** implement a CLI (Command Line Interface) and **SHOULD** expose as many options through its CLI, minimizing the need for server specific setup files / code.
444
+
445
+ ### Common CLI Arguments
446
+
447
+ If provided, the CLI **MUST** be able to load a Ruby script as detailed in the **NeoRack Application Scripts** section. The default script name is `'config.ru'`.
448
+
449
+ It is **RECOMMENDED** that Servers automatically test for the `ADDRESS` and `PORT` environment variables if those aren't set by the command line.
450
+
451
+ It is **RECOMMENDED** that Servers recognize the first unnamed CLI option as a the script name for the NeoRack application.
452
+
453
+ The following common CLI option names are **RECOMMENDED** (but to each their own):
454
+
455
+ * `-b` - the address to listen to.
456
+
457
+ * `-p` - the port number to listen to.
458
+
459
+ * `-D` - log Debug messages, if any.
460
+
461
+ * `-url` - possible alternative to the `-b` and `-p`, allowing a URL type address for the address and port binding.
462
+
463
+ * `-l` - log HTTP requests once the response was sent (finished), if supported.
464
+
465
+ * `-k` - sets HTTP keep-alive timeout in seconds.
466
+
467
+ * `-maxbd` - sets the approximate HTTP upload limit in Mb.
468
+
469
+ * `-maxhd` - sets the approximate total header length limit per HTTP request in Kb.
470
+
471
+ * `-t` - if multi-threaded mode is supported, sets the number of threads to be used.
472
+
473
+ * `-w` - if a process worker pool is supported, sets the number of worker processes to be used.
474
+
475
+ ### NeoRack Application Scripts
476
+
477
+ NeoRack Servers **SHOULD** support loading NeoRack Applications using a Ruby script (i.e., `'config.ru'`).
478
+
479
+ When loading such a script, the following methods **MUST** be made available to the script as if they were global methods (a DSL):
480
+
481
+ #### `server`
482
+
483
+ Returns the Server object, allowing access to the Server methods.
484
+
485
+ #### `run(application)`
486
+
487
+ Sets the NeoRack Application object for the current Script.
488
+
489
+ #### `run_before(proc_obj = nil, &block)`
490
+
491
+ This may be used for pre-request logic, such as authentication, database connection checkout, etc'.
492
+
493
+ Either the `proc_obj` or `block` **MUST** respond to `call(request, response)`.
494
+
495
+ Only on of these objects (`proc_obj` or `block`) will be used. `proc_obj` has precedence.
496
+
497
+ The NeoRack Server **MUST** call the object **before** calling the Application.
498
+
499
+ The NeoRack Server **MUST** call the callbacks in order of insertion.
500
+
501
+ If `response.finished?` is `true` after the Server called the object, the server **MUST** stop processing the request.
502
+
503
+ #### `run_after(proc_obj = nil, &block)`
504
+
505
+ This may be used for cleanup logic, such as removing database connections from the `request` Hash, logging, etc'.
506
+
507
+ Either the `proc_obj` or `block` **MUST** respond to `call(request)`.
508
+
509
+ Only on of these objects (`proc_obj` or `block`) will be used. `proc_obj` has precedence.
510
+
511
+ The NeoRack Server **MUST** call the object (or schedule it) when `response.finish` is called.
512
+
513
+ The NeoRack Server **MUST** call the callbacks in **reverse** order of insertion.
514
+
515
+ #### `use(middleware, *args, &block)`
516
+
517
+ Provided for for backwards compatibility with a nested middleware design pattern.
518
+
519
+ #### `warmup(proc_obj = nil, &block)`
520
+
521
+ Provided for for backwards compatibility, takes a block and/or Proc object that will be called with the final Application object.
522
+
523
+ #### Example Application Script Loader
524
+
525
+ An example application script loader is available in the NeoRack `'/gem'` folder, [in the file `'builder.rb'`](./gem/lib/neorack/builder).
526
+
527
+ Servers **MAY** roll their own, copy the code or require the `neorack` gem at their discretion.
528
+
529
+ ## NeoRack Compatibility with Rack
530
+
531
+ NeoRack backwards compatibility with [the CGI style Rack specifications](https://github.com/rack/rack/blob/master/SPEC.rdoc) is considered an Extension and uses the reserved extension name `:backwards_compatible`.
532
+
533
+ Backwards compatibility extensions **MUST**:
534
+
535
+ * Set all `env` values as set in the [CGI style Rack applications](https://github.com/rack/rack/blob/master/SPEC.rdoc).
536
+
537
+ * Set the Server's `extensions[:backwards_compatible]` values to the Rack protocol version they support (i.e., `[1,3,0].freeze`).
538
+
539
+ * Set `env['neorack.request']` to the `request` object.
540
+
541
+ * Set `env['neorack.response']` to the `response` object.
542
+
543
+ Backwards compatibility extensions **SHOULD** use the `request` object for implementing the Rack style `env`.
544
+
545
+ The following is an example for an external backwards compatibility extension, it is untested, might not work and should be considered pseudo code:
546
+
547
+ ```ruby
548
+ # place this extension code somewhere
549
+ module NeoRack
550
+ class BackwardsCompatibility
551
+ def self.register(dsl)
552
+ unless dsl.server.extensions[:backwards_compatible] && dsl.server.extensions[:backwards_compatible][0] == 1
553
+ dsl.server.extensions[:backwards_compatible] = [1,3,0].freeze
554
+ dsl.use(self)
555
+ end
556
+ end
557
+ def initialize(app)
558
+ @app = app
559
+ end
560
+
561
+ def call(request, response)
562
+ add_old_env_values_to_request(request, response)
563
+ old = @app.call(request)
564
+ process_old_return_value(old, response)
565
+ end
566
+
567
+ # example implementation
568
+ def self.add_old_env_values_to_request(request, response)
569
+ new_headers = {}
570
+ request.each do |k,v|
571
+ if (k.is_a?(String) && k[0].ord >= 'a'.ord && k[0].ord <= 'a'.ord)
572
+ if k == 'content-length' || k == 'content-type'
573
+ new_headers[k.swapcase.gsub!('-', '_')] ||= v
574
+ else
575
+ new_headers["#{HTTP_}#{k.swapcase.gsub('-', '_')}"] ||= v
576
+ end
577
+ end
578
+ end
579
+ request.merge! new_headers
580
+ # set whatever Rack requires... i.e.:
581
+ request['REQUEST_METHOD'] = request[:method]
582
+ request['rack.url_scheme'] = request[:scheme]
583
+ request['SERVER_NAME'] = request['host']
584
+ request['SCRIPT_NAME'] = request[:path_root]
585
+ request['PATH_INFO'] = request[:path]
586
+ request['QUERY_STRING'] = request[:query]
587
+ request['rack.version'] = [1,3,0]
588
+ request['rack.errors'] = STDERR
589
+ if request[:body]
590
+ request['rack.input'] = request[:body]
591
+ request['CONTENT_LENGTH'] = request[:body].length.to_s
592
+ request['CONTENT_TYPE'] = request[:body].type if request[:body].type
593
+ else
594
+ request['rack.input'] = StringIO.new
595
+ end
596
+ # allow NeoRack aware apps access to these objects
597
+ request['neorack.request'] = request
598
+ request['neorack.response'] = response
599
+ # support hijack if supported
600
+ if(request.server.extensions[:hijack])
601
+ request['rack.hijack'] = Proc.new { request['rack.hijack_io'] = response.hijack(false) }
602
+ request['rack.hijack?'] = true
603
+ end
604
+ end
605
+
606
+ # example implementation
607
+ def self.process_old_return_value(old, response)
608
+ # do nothing if it was already done.
609
+ return nil if(response.finished? || response.streaming?)
610
+ raise "unexpected return value from application" unless (old.is_a?(Array) && old.length == 3)
611
+ # set status
612
+ response.status = old[0].to_i
613
+ # copy headers to new response object
614
+ hijacked = old[2].delete('rack.hijack')
615
+ old[1].each {|name, val| val = val.split("\n"); val.each {|v| response.add_header(name, v)} }
616
+ # handle hijacking or send body
617
+ if(hijacked && request.server.extensions[:hijack])
618
+ hijacked.call(response.hijack(true))
619
+ else
620
+ case old[2].class
621
+ when String
622
+ response << str
623
+ old[2].close if old[2].respond_to?(:close)
624
+ when Array
625
+ old[2].each {|str| response << str }
626
+ old[2].close if old[2].respond_to?(:close)
627
+ else
628
+ # start streaming the response
629
+ response.stream
630
+ # perform `each` in a new thread / fiber, as it may block the server
631
+ if(old['neorack.request'][:SERVER].blocking?)
632
+ old[2].each {|str| response << str }
633
+ response.finish
634
+ else
635
+ old['neorack.request'][:SERVER].classes[:concurrency].new do
636
+ old[2].each {|str| response << str }
637
+ response.finish
638
+ end
639
+ end
640
+ end
641
+ end
642
+ end
643
+ end
644
+ end
645
+
646
+ # run this code in the DSL
647
+ NeoRack::BackwardsCompatibility.register(self)
648
+ APP = Proc.new {|env| [200, {}, ["Hello World"]] }
649
+ run APP
650
+ ```
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "neorack"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # Hijacking Extension
2
+
3
+ This NeoRack extension describes a connection hijacking API that allows for backwards compatibility.
4
+
5
+ ## Extension Name
6
+
7
+ NeoRack Servers that implement this extension **MUST** set `extensions[:hijack]` so it is equal to `[1,0,0].freeze`.
8
+
9
+ NeoRack Servers that implement this extension **MUST** set `classes[:hijack]` to the class of the object returned by `response.hijack`.
10
+
11
+ ## Response Methods
12
+
13
+ NeoRack Servers that implement this extension **MUST** implement the following methods in the `response` object.
14
+
15
+ #### `hijack(send_headers = false)`
16
+
17
+ **SHOULD** raise an exception if `response.streaming?` would have returned `true`.
18
+
19
+ **MUST** raise an exception if `response.finished?` would have returned `true`.
20
+
21
+ **MUST** cause future calls to `response.finished?` to return `true`.
22
+
23
+ Returns an IO like object that responds to the following methods in the same was a Ruby IO or Socket object would have responded: `fileno`, `read`, `write`, `close`, `read_nonblock`, `write_nonblock`, `flush`, `close_read`, `close_write`, `closed?`.
24
+
25
+ The `fileno` method is provided to allow implementations to `poll` the IO device and this feature **SHOULD** be supported. However, NeoRack Applications and other Extensions **MUST NOT** use `fileno` for anything else, as this might break some implementations. i.e., when the server supports HTTP/2 tunneling, writing or reading from the `fileno` will break the HTTP/2 protocol.
26
+
27
+ If `send_headers` is `true`, the Server **MUST** send the status and headers before returning the IO object.
28
+
29
+ **Note**: unless the server handles the IO object calls to `write` and `write_nonblock`, the status and header data must be completely written to the system's IO buffer before the `hijack` method can return.
30
+
31
+ ## Recommendations
32
+
33
+ It is **RECOMMENDED** that NeoRack Applications and Extensions consider `hijack` as a last result solution.
34
+
35
+ It is **RECOMMENDED** that NeoRack Servers don't pass an actual IO object, but instead wrap the IO object in a container that handles as many IO concerns as possible.
36
+
37
+ Servers **SHOULD** do they're best to handle all network and protocol concerns and support extensions that will allow applications to remain blissfully unaware of network details.
@@ -0,0 +1,7 @@
1
+ require "neorack/version"
2
+ require "neorack/builder"
3
+
4
+ module NeoRack
5
+ class Error < StandardError; end
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,102 @@
1
+ module NeoRack
2
+ # A single application NeoRack application DSL script handler.
3
+ #
4
+ # Use the {NeoRack::Builder.load} methods for loading scripts.
5
+ class Builder
6
+ # NOTE: Internal names end with `___` to minimize possible namespace concerns.
7
+
8
+ # Initializes a new builder object and run the script in filename
9
+ def initialize(server, script, filename)
10
+ @server___, @app___, @warmup___, @app___= server, nil, nil, nil
11
+ @stack_pre___, @stack___, @stack_post___ = [].dup, [].dup, [].dup
12
+ # run script in context of the object, enabling the DLS
13
+ instance_eval(script, filename)
14
+ end
15
+
16
+ # DSL method - access the Server object and its methods
17
+ def server
18
+ @server___
19
+ end
20
+
21
+ # DSL method - set the application to be used by the Script
22
+ def run(application)
23
+ # add middleware to a middleware stack
24
+ @app___ = application
25
+ self
26
+ end
27
+
28
+ # DSL method - runs `.call(request, response)` before the application handles the response.
29
+ #
30
+ # Used pre-request logic, such as authentication, database connection checkout, etc'.
31
+ def run_before(prc = nil, &block)
32
+ prc ||= block
33
+ raise(ArgumentError, "this method requires an object that responds to `call(request, response)`") unless(prc.respond_to?(:call))
34
+ @stack_pre___ << prc
35
+ self
36
+ end
37
+
38
+ # DSL method - runs `.call(request)` after the response ended (when steaming, this is delayed until streaming ends).
39
+ #
40
+ # Used for cleanup logic, such as removing database connections from the `request` Hash, logging, etc'.
41
+ def run_after(prc = nil, &block)
42
+ prc ||= block
43
+ raise(ArgumentError, "this method requires an object that responds to `call(request, response)`") unless(prc.respond_to?(:call))
44
+ @stack_pre___ << prc
45
+ self
46
+ end
47
+
48
+ # DSL method for backwards compatibility
49
+ def use(middleware, *args, &block)
50
+ # add middleware to a middleware stack
51
+ @stack___ << [middleware, args, block]
52
+ self
53
+ end
54
+
55
+ # DSL method for backwards compatibility
56
+ def warmup(prc = nil, &block)
57
+ @warmup___ ||= prc || block
58
+ end
59
+
60
+ # Internal use: returns the setup callback stack, the application object and the cleanup callback stack.
61
+ def build_stack___
62
+ raise "Application object missing!" unless @app___
63
+ @stack___ << @app___
64
+ app = @stack___.pop
65
+ tmp = nil
66
+ while((tmp = @stack___.pop))
67
+ if tmp[3]
68
+ app = tmp[0].new(app, *tmp[1], &tmp[2])
69
+ else
70
+ app = tmp[0].new(app, *tmp[1])
71
+ end
72
+ end
73
+ @app___ = app
74
+ @stack_post___.reverse!
75
+ @warmup___.call(@app___) if @warmup___
76
+ [@stack_pre___, @app___, @stack_post___]
77
+ end
78
+
79
+ # Returns a three member Array containing the setup callback stack (Array), the application object and the cleanup callback stack (Array).
80
+ #
81
+ # On script loading failure (i.e., file name doesn't exist), returns `nil`.
82
+ #
83
+ # Note: may raise an exception if the script itself raises an exception.
84
+ #
85
+ # Use:
86
+ #
87
+ # pre_request, app, post_request = *NeoRack::Builder.load(MyServerClass, 'config.ru')
88
+ # raise "MyServer couldn't find 'config.ru'" unless app && pre_request && post_request
89
+ #
90
+ def self.load(server_klass, filename = 'config.ru')
91
+ # try to load the file
92
+ script = ::File.read(filename) rescue nil
93
+ return `nil` unless script
94
+ # remove UTF-8 BOM, see: https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
95
+ script.slice!(0..2) if script.encoding == Encoding::UTF_8 && script.start_with?('\xef\xbb\xbf')
96
+ # start a new builder
97
+ instance = NeoRack::Builder.new(server_klass, script, filename)
98
+ # build stack and return
99
+ instance.build_stack___
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module NeoRack
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'lib/neorack/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "neorack"
5
+ spec.version = NeoRack::VERSION
6
+ spec.authors = ["Bo"]
7
+ spec.email = ["bo@facil.io"]
8
+
9
+ spec.summary = %q{Common helpers and external extensions for NeoRack.}
10
+ spec.description = %q{This gem is optional. NeoRack is a specification, not a gem... but to save developers time in (re)writing the DSL builder and other common tasks, this gem is provided.}
11
+ spec.homepage = "https://github.com/boazsegev/neorack"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/boazsegev/neorack"
19
+ spec.metadata["changelog_uri"] = "https://github.com/boazsegev/neorack/gem/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+ end
Binary file
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: neorack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-09-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This gem is optional. NeoRack is a specification, not a gem... but to
14
+ save developers time in (re)writing the DSL builder and other common tasks, this
15
+ gem is provided.
16
+ email:
17
+ - bo@facil.io
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".gitignore"
23
+ - CHANGELOG.md
24
+ - CODE_OF_CONDUCT.md
25
+ - Gemfile
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - SPEC.md
30
+ - bin/console
31
+ - bin/setup
32
+ - extensions/hijack.md
33
+ - lib/neorack.rb
34
+ - lib/neorack/builder.rb
35
+ - lib/neorack/version.rb
36
+ - neorack.gemspec
37
+ - neorack_logo.png
38
+ homepage: https://github.com/boazsegev/neorack
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ allowed_push_host: https://rubygems.org
43
+ homepage_uri: https://github.com/boazsegev/neorack
44
+ source_code_uri: https://github.com/boazsegev/neorack
45
+ changelog_uri: https://github.com/boazsegev/neorack/gem/CHANGELOG.md
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.3.0
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.1.2
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Common helpers and external extensions for NeoRack.
65
+ test_files: []