oaf 0.1.12
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.
- data/.travis.yml +3 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.md +106 -0
- data/Rakefile +6 -0
- data/bin/oaf +64 -0
- data/lib/oaf.rb +25 -0
- data/lib/oaf/http.rb +88 -0
- data/lib/oaf/util.rb +200 -0
- data/lib/oaf/version.rb +25 -0
- data/oaf.gemspec +27 -0
- data/spec/oaf/http_spec.rb +122 -0
- data/spec/oaf/util_spec.rb +208 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/webrick-mocks.rb +46 -0
- metadata +121 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MIT LICENSE
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
Oaf
|
2
|
+
---
|
3
|
+
|
4
|
+
Care-free web app prototyping using files and scripts
|
5
|
+
|
6
|
+
[](http://badge.fury.io/rb/oaf)
|
7
|
+
[](https://travis-ci.org/ryanuber/oaf)
|
8
|
+
|
9
|
+
`Oaf` provides stupid-easy way of creating dynamic web applications by setting
|
10
|
+
all best practices and security considerations aside until you are sure that you
|
11
|
+
want to invest your time doing so.
|
12
|
+
|
13
|
+
`Oaf` was created as a weekend project to create a small, simple HTTP server
|
14
|
+
program that uses script execution as its primary mechanism for generating
|
15
|
+
responses.
|
16
|
+
|
17
|
+
Example
|
18
|
+
-------
|
19
|
+
|
20
|
+
Create an executable file named `hello.GET`:
|
21
|
+
```
|
22
|
+
#!/bin/bash
|
23
|
+
echo "Hello, ${USER}!"
|
24
|
+
```
|
25
|
+
|
26
|
+
Start the server by running the `oaf` command, then make a request:
|
27
|
+
```
|
28
|
+
$ curl localhost:9000/hello
|
29
|
+
Hello, ryanuber!
|
30
|
+
```
|
31
|
+
|
32
|
+
### Accepted files
|
33
|
+
`Oaf` will run *ANY* file with the executable bit set, be it shell, Python, Ruby,
|
34
|
+
compiled binary, or whatever else you might have.
|
35
|
+
|
36
|
+
`Oaf` can also use plain text files.
|
37
|
+
|
38
|
+
### How file permissions affect output
|
39
|
+
* If the file in your request is executable, the output of its execution will
|
40
|
+
be used as the return data.
|
41
|
+
* If the file is *NOT* executable, then the contents of the file will be used
|
42
|
+
as the return data.
|
43
|
+
|
44
|
+
### Nested methods
|
45
|
+
You can create nested methods using simple directories. Example:
|
46
|
+
```
|
47
|
+
$ ls ./examples/
|
48
|
+
hello.GET
|
49
|
+
|
50
|
+
$ curl http://localhost:8000/examples/hello
|
51
|
+
Hello, world!
|
52
|
+
```
|
53
|
+
|
54
|
+
### HTTP Methods
|
55
|
+
Files must carry the extension of the HTTP method used to invoke them. Some
|
56
|
+
examples: `hello.GET`, `hello.POST`, `hello.PUT`, `hello.DELETE`
|
57
|
+
|
58
|
+
### Headers and Status
|
59
|
+
You can indicate HTTP headers and status using stdout from your script.
|
60
|
+
|
61
|
+
```
|
62
|
+
#!/bin/bash
|
63
|
+
cat <<EOF
|
64
|
+
Hello, world!
|
65
|
+
---
|
66
|
+
content-type: text/plain
|
67
|
+
200
|
68
|
+
EOF
|
69
|
+
```
|
70
|
+
|
71
|
+
Separated by 3 dashes on a line of their own (`\n---\n`), the very last block
|
72
|
+
of output can contain headers and response status.
|
73
|
+
|
74
|
+
### Getting request headers and body
|
75
|
+
The headers and body of the HTTP request will be passed to the script as
|
76
|
+
arguments. The headers will be passed as $1, and the body as $2.
|
77
|
+
|
78
|
+
### Catch-all methods
|
79
|
+
Catch-all's can be defined by naming a file inside of a directory, beginning and
|
80
|
+
ending with underscores (`_`). So for example, `test/_default_.GET` will match:
|
81
|
+
`GET /test/anything`, `GET /test/blah`, etc.
|
82
|
+
|
83
|
+
If you want to define a top-level method for `/test`, you would do so in the
|
84
|
+
file at `/test.GET`.
|
85
|
+
|
86
|
+
Q&A
|
87
|
+
---
|
88
|
+
**Why are the headers and status at the bottom of the response?**
|
89
|
+
Because it is much easier to echo these last. Since Oaf reads response
|
90
|
+
data directly from stdout/stderr, it is very easy for debug or error messages
|
91
|
+
to interfere with them. By specifying the headers and status last, we minimize
|
92
|
+
the chances of unexpected output damaging the response, as all processing is
|
93
|
+
already complete.
|
94
|
+
|
95
|
+
**Why the name `Oaf`?**
|
96
|
+
It's a bit of a clumsy and "oafish" approach at web application prototyping. I
|
97
|
+
constantly find myself trying to write server parts of programs before I have
|
98
|
+
even completed basic functionality, and sometimes even before I have a clear
|
99
|
+
idea of what it is I want the program to do.
|
100
|
+
|
101
|
+
Acknowledgements
|
102
|
+
----------------
|
103
|
+
|
104
|
+
`Oaf` is inspired by [Stubb](https://github.com/knuton/stubb). A number of ideas
|
105
|
+
and conventions were borrowed from it. Kudos to
|
106
|
+
[@knuton](https://github.com/knuton) for having a great idea.
|
data/Rakefile
ADDED
data/bin/oaf
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# oaf - Care-free web app prototyping using files and scripts
|
3
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
require 'optparse'
|
25
|
+
require 'oaf'
|
26
|
+
|
27
|
+
options = {:port => 9000}
|
28
|
+
|
29
|
+
begin
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
opts.banner = [
|
32
|
+
'oaf - Care-free web app prototyping using files and scripts',
|
33
|
+
'', 'SYNOPSIS:', ' oaf [options] [path]', '', 'OPTIONS:'].join "\n"
|
34
|
+
opts.on('-p', '--port PORT', 'Listening port. Default=9000') do |v|
|
35
|
+
if not v.to_i.to_s == v.to_s
|
36
|
+
puts "Port number must be an integer"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
options[:port] = v.to_s
|
40
|
+
end
|
41
|
+
opts.on('--version', 'Show the version number') do
|
42
|
+
puts Oaf::VERSION
|
43
|
+
exit 0
|
44
|
+
end
|
45
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
46
|
+
puts opts
|
47
|
+
exit 0
|
48
|
+
end
|
49
|
+
end.parse!
|
50
|
+
rescue OptionParser::InvalidOption => e
|
51
|
+
puts e.message
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
|
55
|
+
if ARGV.length == 0
|
56
|
+
options[:path] = Dir.pwd
|
57
|
+
elsif ARGV.length == 1
|
58
|
+
options[:path] = File.expand_path ARGV[0]
|
59
|
+
else
|
60
|
+
puts "Unknown arguments: #{ARGV[1..ARGV.length].join(' ')}"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
Oaf::HTTP.serve options[:path], options[:port]
|
data/lib/oaf.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'oaf/version'
|
24
|
+
require 'oaf/util'
|
25
|
+
require 'oaf/http'
|
data/lib/oaf/http.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'webrick'
|
24
|
+
|
25
|
+
module Oaf
|
26
|
+
|
27
|
+
module HTTP
|
28
|
+
extend Oaf
|
29
|
+
extend self
|
30
|
+
|
31
|
+
# Given output from a script, parse HTTP response details and return them
|
32
|
+
# as a hash, including body, status, and headers.
|
33
|
+
#
|
34
|
+
# == Parameters:
|
35
|
+
# output::
|
36
|
+
# The output text data returned by a script.
|
37
|
+
#
|
38
|
+
# == Returns:
|
39
|
+
# A hash containing the HTTP response details (body, status, and headers).
|
40
|
+
#
|
41
|
+
def parse_response output
|
42
|
+
has_meta = false
|
43
|
+
headers = {'content-type' => 'text/plain'}
|
44
|
+
status = 200
|
45
|
+
headers, status, meta_size = Oaf::Util.parse_http_meta output
|
46
|
+
lines = output.split("\n")
|
47
|
+
body = lines.take(lines.length - meta_size).join("\n")+"\n"
|
48
|
+
[headers, status, body]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Invokes the Webrick web server library to handle incoming requests, and
|
52
|
+
# routes them to the appropriate scripts if they exist on the filesystem.
|
53
|
+
#
|
54
|
+
# == Parameters:
|
55
|
+
# path::
|
56
|
+
# The path in which to search for files
|
57
|
+
#
|
58
|
+
# port::
|
59
|
+
# The TCP port to listen on
|
60
|
+
#
|
61
|
+
def serve path, port
|
62
|
+
server = WEBrick::HTTPServer.new :Port => port
|
63
|
+
server.mount_proc '/' do |req, res|
|
64
|
+
req_body = ''
|
65
|
+
req_headers = Oaf::Util.format_request_headers req.header
|
66
|
+
if ['POST', 'PUT'].member? req.request_method
|
67
|
+
begin
|
68
|
+
req_body = req.body
|
69
|
+
rescue WEBrick::HTTPStatus::LengthRequired
|
70
|
+
true # without this, coverage is not collected.
|
71
|
+
end
|
72
|
+
else
|
73
|
+
req_body = req.query
|
74
|
+
end
|
75
|
+
file = Oaf::Util.get_request_file path, req.path, req.request_method
|
76
|
+
out = Oaf::Util.get_output file, req.header, req_body
|
77
|
+
headers, status, body = Oaf::HTTP.parse_response out
|
78
|
+
headers.each do |name, value|
|
79
|
+
res.header[name] = value
|
80
|
+
end
|
81
|
+
res.status = status
|
82
|
+
res.body = body
|
83
|
+
end
|
84
|
+
trap 'INT' do server.shutdown end
|
85
|
+
server.start
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/oaf/util.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module Oaf
|
24
|
+
|
25
|
+
module Util
|
26
|
+
extend Oaf
|
27
|
+
extend self
|
28
|
+
|
29
|
+
# Determines if a line of output looks anything like an HTTP header
|
30
|
+
# declaration.
|
31
|
+
#
|
32
|
+
# == Parameters:
|
33
|
+
# line::
|
34
|
+
# A line of text to examine
|
35
|
+
#
|
36
|
+
# == Returns:
|
37
|
+
# A boolean, true if it can be read as a header string, else false.
|
38
|
+
#
|
39
|
+
def is_http_header? line
|
40
|
+
line.split(':').length == 2
|
41
|
+
end
|
42
|
+
|
43
|
+
# Retrieves a hash in the form `name` => `value` from a string that
|
44
|
+
# describes an HTTP response header.
|
45
|
+
#
|
46
|
+
# == Parameters:
|
47
|
+
# line::
|
48
|
+
# A line of text to parse
|
49
|
+
#
|
50
|
+
# == Returns
|
51
|
+
# A hash in the form `name` => `value`, or `nil`
|
52
|
+
#
|
53
|
+
def get_http_header line
|
54
|
+
return nil if not is_http_header? line
|
55
|
+
parts = line.split(':')
|
56
|
+
[parts[0].strip, parts[1].strip]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Retrieves the numeric value from a line of text as an HTTP status code.
|
60
|
+
#
|
61
|
+
# == Parameters:
|
62
|
+
# line::
|
63
|
+
# A line of text to parse
|
64
|
+
#
|
65
|
+
# == Returns:
|
66
|
+
# An integer if valid, else `nil`.
|
67
|
+
#
|
68
|
+
def get_http_status line
|
69
|
+
is_http_status?(line) ? line.to_i : nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Determines if an HTTP status code is valid per RFC2616
|
73
|
+
#
|
74
|
+
# == Parameters:
|
75
|
+
# code::
|
76
|
+
# A number to validate
|
77
|
+
#
|
78
|
+
# == Returns
|
79
|
+
# A boolean, true if valid, else false.
|
80
|
+
#
|
81
|
+
def is_http_status? code
|
82
|
+
(200..206).to_a.concat((300..307).to_a).concat((400..417).to_a) \
|
83
|
+
.concat((500..505).to_a).include? code.to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
# Format a hash of request headers in preparation for passing it to an
|
87
|
+
# executable program as an argument.
|
88
|
+
#
|
89
|
+
# == Parameters:
|
90
|
+
# headers::
|
91
|
+
# A hash in the form `name` => `value` containing pairs of headers.
|
92
|
+
#
|
93
|
+
# == Returns:
|
94
|
+
# A comma-delimited, colon-separated list of header names and values. The
|
95
|
+
# return value of this function should be parsed according to RFC2616.
|
96
|
+
#
|
97
|
+
def format_request_headers headers
|
98
|
+
result = ''
|
99
|
+
headers.each do |name, value|
|
100
|
+
result += "#{name}:#{value},"
|
101
|
+
end
|
102
|
+
result.sub!(/,$/, '')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Given an array of text lines, iterate over each of them and determine if
|
106
|
+
# they may be interpreted as headers or status. If they can, add them to
|
107
|
+
# the result.
|
108
|
+
#
|
109
|
+
# == Parameters:
|
110
|
+
# text::
|
111
|
+
# Plain text data to examine
|
112
|
+
#
|
113
|
+
# == Returns:
|
114
|
+
# A 3-item structure containing headers, status, and the number of lines
|
115
|
+
# which the complete metadata (including the "---" delimiter) occupies.
|
116
|
+
#
|
117
|
+
def parse_http_meta text
|
118
|
+
headers = {}
|
119
|
+
status = 200
|
120
|
+
size = 0
|
121
|
+
if text.to_s != ''
|
122
|
+
parts = text.split /^---$/
|
123
|
+
meta = parts.last.split "\n"
|
124
|
+
for part in meta
|
125
|
+
if Oaf::Util.is_http_header? part
|
126
|
+
header, value = Oaf::Util.get_http_header part
|
127
|
+
headers.merge! header => value
|
128
|
+
elsif Oaf::Util.is_http_status? part
|
129
|
+
status = Oaf::Util.get_http_status part
|
130
|
+
else
|
131
|
+
next
|
132
|
+
end
|
133
|
+
size += size == 0 ? 2 : 1 # compensate for delimiter
|
134
|
+
end
|
135
|
+
end
|
136
|
+
[headers, status, size]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return a default response string indicating that nothing could be
|
140
|
+
# done and no response files were found.
|
141
|
+
#
|
142
|
+
# == Returns:
|
143
|
+
# A string with response output for parsing.
|
144
|
+
#
|
145
|
+
def get_default_response
|
146
|
+
"oaf: Not Found\n---\n404"
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns the path to the file to use for the request. If the provided
|
150
|
+
# file path does not exist, this method will search for a file within
|
151
|
+
# the same directory matching the default convention "_*_".
|
152
|
+
#
|
153
|
+
# == Parameters:
|
154
|
+
# root::
|
155
|
+
# The root path to search within.
|
156
|
+
# uri::
|
157
|
+
# The URI of the request
|
158
|
+
# method::
|
159
|
+
# The HTTP method of the request
|
160
|
+
#
|
161
|
+
# == Returns:
|
162
|
+
# The path to a file to use, or `nil` if none is found.
|
163
|
+
#
|
164
|
+
def get_request_file root, uri, method
|
165
|
+
file = File.join root, "#{uri}.#{method}"
|
166
|
+
if not File.exist? file
|
167
|
+
Dir.glob(File.join(File.dirname(file), "_*_.#{method}")).each do |f|
|
168
|
+
file = f
|
169
|
+
break
|
170
|
+
end
|
171
|
+
end
|
172
|
+
File.exist?(file) ? file : nil
|
173
|
+
end
|
174
|
+
|
175
|
+
# Executes a file, or reads its contents if it is not executable, passing
|
176
|
+
# it the request headers and body as arguments, and returns the result.
|
177
|
+
#
|
178
|
+
# == Parameters:
|
179
|
+
# file::
|
180
|
+
# The path to the file to use for output
|
181
|
+
# headers::
|
182
|
+
# The HTTP request headers to pass
|
183
|
+
# body::
|
184
|
+
# The HTTP request body to pass
|
185
|
+
#
|
186
|
+
# == Returns:
|
187
|
+
# The result from the file, or a default result if the file is not found.
|
188
|
+
#
|
189
|
+
def get_output file, headers, body
|
190
|
+
if file.nil?
|
191
|
+
out = Oaf::Util.get_default_response
|
192
|
+
elsif File.executable? file
|
193
|
+
out = %x(#{file} "#{headers}" "#{body}")
|
194
|
+
else
|
195
|
+
out = File.open(file).read
|
196
|
+
end
|
197
|
+
out
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/oaf/version.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module Oaf
|
24
|
+
VERSION = '0.1.12'
|
25
|
+
end
|
data/oaf.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'oaf/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'oaf'
|
7
|
+
s.version = Oaf::VERSION
|
8
|
+
s.summary = 'Web app prototyping'
|
9
|
+
s.description = 'Care-free web app prototyping using files and scripts'
|
10
|
+
s.authors = ["Ryan Uber"]
|
11
|
+
s.email = ['ru@ryanuber.com']
|
12
|
+
s.files = %x(git ls-files).split($/)
|
13
|
+
s.homepage = 'https://github.com/ryanuber/oaf'
|
14
|
+
s.license = 'MIT'
|
15
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
s.test_files = s.files.grep(%r{^spec/})
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.required_ruby_version = '>= 1.9'
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'bundler'
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'simplecov'
|
26
|
+
s.add_development_dependency 'coveralls'
|
27
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'spec_helper'
|
24
|
+
|
25
|
+
module Oaf
|
26
|
+
describe "Returning HTTP Responses" do
|
27
|
+
it "should return safe defaults if output is empty" do
|
28
|
+
headers, status, body = Oaf::HTTP.parse_response ''
|
29
|
+
headers.should eq({})
|
30
|
+
status.should eq(200)
|
31
|
+
body.should eq("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return safe defaults when only body is present" do
|
35
|
+
text = "This is a test\n"
|
36
|
+
headers, status, body = Oaf::HTTP.parse_response text
|
37
|
+
headers.should eq({})
|
38
|
+
status.should eq(200)
|
39
|
+
body.should eq("This is a test\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return headers correctly" do
|
43
|
+
text = "---\nx-powered-by: oaf"
|
44
|
+
headers, status, body = Oaf::HTTP.parse_response text
|
45
|
+
headers.should eq({'x-powered-by' => 'oaf'})
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return status correctly" do
|
49
|
+
text = "---\n201"
|
50
|
+
headers, status, body = Oaf::HTTP.parse_response text
|
51
|
+
status.should eq(201)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return body correctly" do
|
55
|
+
text = "This is a test\n---\n200"
|
56
|
+
headers, status, body = Oaf::HTTP.parse_response text
|
57
|
+
body.should eq("This is a test\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return body correctly when no metadata is present" do
|
61
|
+
text = "This is a test"
|
62
|
+
headers, status, body = Oaf::HTTP.parse_response text
|
63
|
+
body.should eq("This is a test\n")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "Running an HTTP Server" do
|
68
|
+
before(:all) do
|
69
|
+
require 'webrick-mocks'
|
70
|
+
@tempdir1 = Dir.mktmpdir
|
71
|
+
@f1 = Tempfile.new ['oaf', '.GET'], @tempdir1
|
72
|
+
@f1.write "This is a test.\n---\n201\nx-powered-by: oaf"
|
73
|
+
@f1.close
|
74
|
+
@f1request = File.basename(@f1.path).sub!(/\.GET$/, '')
|
75
|
+
|
76
|
+
@f2 = Tempfile.new ['oaf', '.PUT'], @tempdir1
|
77
|
+
@f2.write "Containable Test\n---\n202\nx-powered-by: oaf"
|
78
|
+
@f2.close
|
79
|
+
@f2request = File.basename(@f2.path).sub!(/\.PUT$/, '')
|
80
|
+
end
|
81
|
+
|
82
|
+
after(:all) do
|
83
|
+
@f1.delete
|
84
|
+
@f2.delete
|
85
|
+
Dir.delete @tempdir1
|
86
|
+
end
|
87
|
+
|
88
|
+
before(:each) do
|
89
|
+
@webrick = double()
|
90
|
+
@webrick.should_receive(:start).once.and_return(true)
|
91
|
+
WEBrick::HTTPServer.stub(:new).and_return(@webrick)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should start an HTTP server" do
|
95
|
+
@webrick.should_receive(:mount_proc).with('/').once \
|
96
|
+
.and_yield(Oaf::FakeReq.new, Oaf::FakeRes.new)
|
97
|
+
Oaf::HTTP.serve '/tmp', 9000
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should parse the request properly" do
|
101
|
+
req = Oaf::FakeReq.new :path => @f1request
|
102
|
+
res = Oaf::FakeRes.new
|
103
|
+
@webrick.should_receive(:mount_proc).with('/').once \
|
104
|
+
.and_yield(req, res)
|
105
|
+
Oaf::HTTP.serve @tempdir1, 9000
|
106
|
+
res.body.should eq("This is a test.\n")
|
107
|
+
res.status.should eq(201)
|
108
|
+
res.header.should eq('x-powered-by' => 'oaf')
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should accept containable methods properly" do
|
112
|
+
req = Oaf::FakeReq.new({:path => @f2request, :method => 'PUT'})
|
113
|
+
res = Oaf::FakeRes.new
|
114
|
+
@webrick.should_receive(:mount_proc).with('/').once \
|
115
|
+
.and_yield(req, res)
|
116
|
+
Oaf::HTTP.serve(@tempdir1, 9000)
|
117
|
+
res.body.should eq("Containable Test\n")
|
118
|
+
res.status.should eq(202)
|
119
|
+
res.header.should eq('x-powered-by' => 'oaf')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# oaf - Care-free web app prototyping using files and scripts
|
2
|
+
# Copyright 2013 Ryan Uber <ru@ryanuber.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'spec_helper'
|
24
|
+
|
25
|
+
module Oaf
|
26
|
+
describe "Header Lines" do
|
27
|
+
it "should detect a valid header line" do
|
28
|
+
result = Oaf::Util.is_http_header? 'x-powered-by: oaf'
|
29
|
+
result.should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should detect an invalid header line" do
|
33
|
+
result = Oaf::Util.is_http_header? 'invalid header line'
|
34
|
+
result.should be_false
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should parse a name and value from a header line" do
|
38
|
+
result = Oaf::Util.get_http_header 'x-powered-by: oaf'
|
39
|
+
result.should eq(['x-powered-by', 'oaf'])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should detect an invalid header line during header parsing" do
|
43
|
+
result = Oaf::Util.get_http_header 'invalid header line'
|
44
|
+
result.should be_nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Status Lines" do
|
49
|
+
it "should detect all valid HTTP status codes" do
|
50
|
+
[200, 201, 202, 203, 204, 205, 206,
|
51
|
+
300, 301, 302, 303, 304, 305, 306, 307,
|
52
|
+
400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411,
|
53
|
+
412, 413, 414, 415, 416, 417,
|
54
|
+
500, 501, 502, 503, 504, 505].each do |status|
|
55
|
+
result = Oaf::Util.is_http_status? status
|
56
|
+
result.should be_true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to validate a string http status line" do
|
61
|
+
result = Oaf::Util.is_http_status? '200'
|
62
|
+
result.should be_true
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should detect an invalid http status code" do
|
66
|
+
result1 = Oaf::Util.is_http_status? 199
|
67
|
+
result2 = Oaf::Util.is_http_status? 'not a status'
|
68
|
+
result1.should be_false
|
69
|
+
result2.should be_false
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should retrieve an http status code" do
|
73
|
+
result = Oaf::Util.get_http_status '200'
|
74
|
+
result.should eq(200)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should detect an invalid status code during parsing" do
|
78
|
+
result = Oaf::Util.get_http_status 'not a status'
|
79
|
+
result.should be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "Format Request Headers" do
|
84
|
+
it "should return a single key/value for just one header" do
|
85
|
+
headers = [['x-powered-by', 'oaf']]
|
86
|
+
result = Oaf::Util.format_request_headers headers
|
87
|
+
result.should eq('x-powered-by:oaf')
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return a comma-delimited list for multiple headers" do
|
91
|
+
headers = [['x-powered-by', 'oaf'], ['content-type', 'text/plain']]
|
92
|
+
result = Oaf::Util.format_request_headers headers
|
93
|
+
result.should eq('x-powered-by:oaf,content-type:text/plain')
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return nil if no headers present" do
|
97
|
+
headers = []
|
98
|
+
result = Oaf::Util.format_request_headers headers
|
99
|
+
result.should be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "Parse Request Metadata From Output" do
|
104
|
+
it "should find headers in request metadata" do
|
105
|
+
text = ['---', 'x-powered-by: oaf', '201'].join "\n"
|
106
|
+
headers, status, size = Oaf::Util.parse_http_meta text
|
107
|
+
headers.should eq('x-powered-by' => 'oaf')
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return the number of lines the metadata consumes" do
|
111
|
+
text = ['---', 'x-powered-by: oaf', '200'].join "\n"
|
112
|
+
headers, status, size = Oaf::Util.parse_http_meta text
|
113
|
+
size.should eq(3)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should assume 200 as the default return code" do
|
117
|
+
text = ['---', 'x-powered-by: oaf'].join "\n"
|
118
|
+
headers, status, size = Oaf::Util.parse_http_meta text
|
119
|
+
status.should eq(200)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should assume meta size 0 if no metadata is present" do
|
123
|
+
text = 'this response uses default metadata'
|
124
|
+
headers, status, size = Oaf::Util.parse_http_meta text
|
125
|
+
size.should eq(0)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should return safe defaults if the response is empty" do
|
129
|
+
text = ''
|
130
|
+
headers, status, size = Oaf::Util.parse_http_meta text
|
131
|
+
headers.should eq({})
|
132
|
+
status.should eq(200)
|
133
|
+
size.should eq(0)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "Determine File Paths" do
|
138
|
+
before(:all) do
|
139
|
+
@tempdir1 = Dir.mktmpdir
|
140
|
+
@tempdir2 = Dir.mktmpdir
|
141
|
+
|
142
|
+
@f1 = Tempfile.new ['oaf', '.GET'], @tempdir1
|
143
|
+
@f1.write "This is a test.\n"
|
144
|
+
@f1.close
|
145
|
+
@f1request = File.basename(@f1.path).sub!(/\.GET$/, '')
|
146
|
+
|
147
|
+
@f2 = Tempfile.new ['_', '_.GET'], @tempdir1
|
148
|
+
@f2.write "This is a default file.\n"
|
149
|
+
@f2.close
|
150
|
+
end
|
151
|
+
|
152
|
+
after(:all) do
|
153
|
+
@f1.delete
|
154
|
+
@f2.delete
|
155
|
+
Dir.delete @tempdir1
|
156
|
+
Dir.delete @tempdir2
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should find existing files correctly" do
|
160
|
+
result = Oaf::Util.get_request_file @tempdir1, @f1request, 'GET'
|
161
|
+
result.should eq(@f1.path)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should return the fall-through file if request file doesn't exist" do
|
165
|
+
result = Oaf::Util.get_request_file @tempdir1, 'nonexistent', 'GET'
|
166
|
+
result.should eq(@f2.path)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should return nil if neither the requested or default file exist" do
|
170
|
+
result = Oaf::Util.get_request_file @tempdir2, 'nonexistent', 'GET'
|
171
|
+
result.should be_nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "Executing and Reading Files" do
|
176
|
+
before(:all) do
|
177
|
+
@f1 = Tempfile.new 'oaf'
|
178
|
+
@f1.chmod 0755
|
179
|
+
@f1.write "#!/bin/bash\necho 'This is a test'"
|
180
|
+
@f1.close
|
181
|
+
|
182
|
+
@f2 = Tempfile.new 'oaf'
|
183
|
+
@f2.chmod 0644
|
184
|
+
@f2.write "This is a test\n"
|
185
|
+
@f2.close
|
186
|
+
end
|
187
|
+
|
188
|
+
after(:all) do
|
189
|
+
@f1.delete
|
190
|
+
@f2.delete
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should execute a file if it is executable" do
|
194
|
+
result = Oaf::Util.get_output @f1.path, nil, nil
|
195
|
+
result.should eq("This is a test\n")
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should read file contents if it is not executable" do
|
199
|
+
result = Oaf::Util.get_output @f2.path, nil, nil
|
200
|
+
result.should eq("This is a test\n")
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should assume safe defaults if the file doesnt exist" do
|
204
|
+
result = Oaf::Util.get_output nil, nil, nil
|
205
|
+
result.should eq(Oaf::Util.get_default_response)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
|
3
|
+
module Oaf
|
4
|
+
class FakeReq
|
5
|
+
|
6
|
+
attr_accessor :path, :request_method, :header, :query
|
7
|
+
attr_writer :body
|
8
|
+
|
9
|
+
@body = @path = @request_method = @query = nil
|
10
|
+
@header = Hash.new
|
11
|
+
|
12
|
+
def initialize opts={}
|
13
|
+
@body = opts[:body] ? opts[:body] : nil
|
14
|
+
@path, @query = opts[:path] ? opts[:path].split('?') : ['/']
|
15
|
+
@request_method = opts[:method] ? opts[:method] : 'GET'
|
16
|
+
@header = opts[:header] ? opts[:header] : Hash.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
if ['POST', 'PUT'].member? @request_method
|
21
|
+
# Mock a webrick bug
|
22
|
+
raise WEBrick::HTTPStatus::LengthRequired
|
23
|
+
end
|
24
|
+
@body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class FakeRes
|
29
|
+
|
30
|
+
attr_accessor :body, :status
|
31
|
+
attr_reader :header
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@body = @status = nil
|
35
|
+
@header = Hash.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](field)
|
39
|
+
@header[field]
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(field, value)
|
43
|
+
@header[field] = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oaf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.12
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Uber
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &21830760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *21830760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &21829320 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *21829320
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &21828360 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *21828360
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: simplecov
|
49
|
+
requirement: &21825800 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *21825800
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: coveralls
|
60
|
+
requirement: &21825300 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *21825300
|
69
|
+
description: Care-free web app prototyping using files and scripts
|
70
|
+
email:
|
71
|
+
- ru@ryanuber.com
|
72
|
+
executables:
|
73
|
+
- oaf
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .travis.yml
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/oaf
|
83
|
+
- lib/oaf.rb
|
84
|
+
- lib/oaf/http.rb
|
85
|
+
- lib/oaf/util.rb
|
86
|
+
- lib/oaf/version.rb
|
87
|
+
- oaf.gemspec
|
88
|
+
- spec/oaf/http_spec.rb
|
89
|
+
- spec/oaf/util_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
- spec/webrick-mocks.rb
|
92
|
+
homepage: https://github.com/ryanuber/oaf
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '1.9'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.11
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Web app prototyping
|
117
|
+
test_files:
|
118
|
+
- spec/oaf/http_spec.rb
|
119
|
+
- spec/oaf/util_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
- spec/webrick-mocks.rb
|