rack-tail 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +13 -0
- data/lib/rack/tail.rb +8 -165
- data/lib/rack/tail/app.rb +28 -0
- data/lib/rack/tail/request_handler.rb +154 -0
- data/lib/rack/tail/version.rb +2 -2
- data/spec/fixtures/test +5 -0
- data/spec/lib/rack/tail_spec.rb +84 -27
- metadata +8 -3
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Do this to generate your change history
|
2
|
+
|
3
|
+
git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
|
+
|
5
|
+
### 1.0.0 (9 April 2014)
|
6
|
+
|
7
|
+
* 4e008e1 - Adding more tests around headers (bethesque, Wed Apr 9 09:25:07 2014 +1000)
|
8
|
+
* 9e3c82e - Refactored request handling to use an object with class methods (bethesque, Wed Apr 9 09:12:07 2014 +1000)
|
9
|
+
* 203214f - Adding more tests (bethesque, Wed Apr 9 08:44:47 2014 +1000)
|
10
|
+
* 530ee19 - Renamed Rack::TailFile to Rack::Tail (bethesque, Tue Apr 8 12:47:52 2014 +1000)
|
11
|
+
* 1414ef9 - Removing realpath from root calculations (bethesque, Tue Apr 8 07:45:56 2014 +1000)
|
12
|
+
* d015a68 - Added a 403 response when trying to access a file outside of the root (bethesque, Tue Apr 8 07:43:12 2014 +1000)
|
13
|
+
* 80e128d - Initial commit (bethesque, Mon Apr 7 15:01:17 2014 +1000)
|
data/lib/rack/tail.rb
CHANGED
@@ -1,176 +1,19 @@
|
|
1
1
|
require "rack/tail/version"
|
2
|
-
|
3
|
-
require 'elif'
|
4
|
-
require 'time'
|
5
|
-
require 'rack/utils'
|
6
|
-
require 'rack/mime'
|
2
|
+
require "rack/tail/app"
|
7
3
|
|
8
4
|
module Rack
|
9
|
-
|
5
|
+
|
6
|
+
# Rack::Tail is shamelessly ripped off Rake::File
|
7
|
+
|
8
|
+
# Rack::Tail serves files below the +root+ directory given, according to the
|
10
9
|
# path info of the Rack request.
|
11
10
|
# e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
|
12
11
|
# as http://localhost:9292/passwd
|
13
|
-
#
|
14
|
-
# Handlers can detect if bodies are a Rack::File, and use mechanisms
|
15
|
-
# like sendfile on the +path+.
|
16
|
-
|
17
|
-
class Tail
|
18
|
-
|
19
|
-
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
20
|
-
ALLOWED_VERBS = %w[GET HEAD]
|
21
|
-
|
22
|
-
attr_accessor :root
|
23
|
-
attr_accessor :path
|
24
|
-
attr_accessor :cache_control
|
25
|
-
|
26
|
-
alias :to_path :path
|
27
|
-
|
28
|
-
def initialize(root, headers={}, default_mime = 'text/plain')
|
29
|
-
@root = root
|
30
|
-
@headers = headers
|
31
|
-
@default_mime = default_mime
|
32
|
-
end
|
33
|
-
|
34
|
-
def call(env)
|
35
|
-
dup._call(env)
|
36
|
-
end
|
37
|
-
|
38
|
-
F = ::File
|
39
|
-
|
40
|
-
def _call(env)
|
41
|
-
return fail(405, "Method Not Allowed") unless method_allowed?(env)
|
42
|
-
return fail(403, "Forbidden") unless path_is_within_root?(env)
|
43
|
-
|
44
|
-
@path = file_path(env)
|
45
|
-
|
46
|
-
if available?
|
47
|
-
serving(env)
|
48
|
-
else
|
49
|
-
fail(404, "File not found: #{path_info_for(env)}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def method_allowed? env
|
54
|
-
ALLOWED_VERBS.include? env["REQUEST_METHOD"]
|
55
|
-
end
|
56
|
-
|
57
|
-
def available?
|
58
|
-
begin
|
59
|
-
F.file?(@path) && F.readable?(@path)
|
60
|
-
rescue SystemCallError
|
61
|
-
false
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def path_info_for env
|
66
|
-
Utils.unescape(env["PATH_INFO"])
|
67
|
-
end
|
68
|
-
|
69
|
-
def path_is_within_root? env
|
70
|
-
root = root env
|
71
|
-
target = target_file env
|
72
|
-
!target.relative_path_from(root).to_s.split(SEPS).any?{|p| p == ".."}
|
73
|
-
end
|
74
|
-
|
75
|
-
def target_file env
|
76
|
-
path_info = Pathname.new("").join(*path_info_for(env).split(SEPS))
|
77
|
-
root = root env
|
78
|
-
root.join(path_info)
|
79
|
-
end
|
80
|
-
|
81
|
-
def root env
|
82
|
-
Pathname.new(@root)
|
83
|
-
end
|
84
|
-
|
85
|
-
def file_path(env)
|
86
|
-
target_file env
|
87
|
-
end
|
88
|
-
|
89
|
-
def serving(env)
|
90
|
-
last_modified = F.mtime(@path).httpdate
|
91
|
-
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
|
92
|
-
|
93
|
-
headers = { "Last-Modified" => last_modified }
|
94
|
-
mime = Mime.mime_type(F.extname(@path), @default_mime)
|
95
|
-
headers["Content-Type"] = mime if mime
|
96
|
-
|
97
|
-
# Set custom headers
|
98
|
-
@headers.each { |field, content| headers[field] = content } if @headers
|
99
|
-
|
100
|
-
response = [ 200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : self ]
|
101
|
-
response[1]["Content-Length"] = requested_size(env, response).to_s
|
102
|
-
response
|
103
|
-
end
|
104
|
-
|
105
|
-
def requested_lines_size(env)
|
106
|
-
(env.fetch("QUERY_STRING")[/\d+/] || 50).to_i
|
107
|
-
end
|
108
|
-
|
109
|
-
def tail_size_for line_count
|
110
|
-
elif = Elif.new(@path)
|
111
|
-
tail_size = 0
|
112
|
-
line_count.times do
|
113
|
-
begin
|
114
|
-
tail_size += Rack::Utils.bytesize(elif.readline)
|
115
|
-
rescue EOFError
|
116
|
-
return tail_size
|
117
|
-
end
|
118
|
-
end
|
119
|
-
tail_size - 1 # Don't include the first \n
|
120
|
-
end
|
121
|
-
|
122
|
-
|
123
|
-
def requested_size(env, response)
|
124
|
-
# NOTE:
|
125
|
-
# We check via File::size? whether this file provides size info
|
126
|
-
# via stat (e.g. /proc files often don't), otherwise we have to
|
127
|
-
# figure it out by reading the whole file into memory.
|
128
|
-
size = F.size?(@path) || Utils.bytesize(F.read(@path))
|
129
|
-
|
130
|
-
#TODO handle invalid lines
|
131
|
-
tail_size = tail_size_for requested_lines_size(env)
|
132
|
-
|
133
|
-
if tail_size == size
|
134
|
-
response[0] = 200
|
135
|
-
@range = 0..size-1
|
136
|
-
else
|
137
|
-
start_byte = size - tail_size - 1
|
138
|
-
@range = start_byte..size-1
|
139
|
-
response[0] = 206
|
140
|
-
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
|
141
|
-
size = @range.end - @range.begin + 1
|
142
|
-
end
|
143
|
-
|
144
|
-
size
|
145
|
-
end
|
146
|
-
|
147
|
-
def each
|
148
|
-
F.open(@path, "rb") do |file|
|
149
|
-
file.seek(@range.begin)
|
150
|
-
remaining_len = @range.end-@range.begin+1
|
151
|
-
while remaining_len > 0
|
152
|
-
part = file.read([8192, remaining_len].min)
|
153
|
-
break unless part
|
154
|
-
remaining_len -= part.length
|
155
|
-
|
156
|
-
yield part
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
12
|
|
161
|
-
|
13
|
+
module Tail
|
162
14
|
|
163
|
-
def
|
164
|
-
|
165
|
-
[
|
166
|
-
status,
|
167
|
-
{
|
168
|
-
"Content-Type" => "text/plain",
|
169
|
-
"Content-Length" => body.size.to_s,
|
170
|
-
"X-Cascade" => "pass"
|
171
|
-
},
|
172
|
-
[body]
|
173
|
-
]
|
15
|
+
def self.new(root, headers={}, default_mime = 'text/plain', default_lines = 50)
|
16
|
+
App.new(root, headers, default_mime, default_lines)
|
174
17
|
end
|
175
18
|
|
176
19
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "rack/tail/request_handler"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
module Tail
|
6
|
+
|
7
|
+
class App
|
8
|
+
|
9
|
+
attr_accessor :cache_control
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
def initialize(root, headers, default_mime, default_lines)
|
13
|
+
@root = root
|
14
|
+
@headers = headers
|
15
|
+
@default_mime = default_mime
|
16
|
+
@default_lines = default_lines
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
RequestHandler.new(env, @root, @headers, @default_mime, @default_lines).call
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'elif'
|
2
|
+
require 'time'
|
3
|
+
require 'rack/utils'
|
4
|
+
require 'rack/mime'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Tail
|
8
|
+
|
9
|
+
class RequestHandler
|
10
|
+
|
11
|
+
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
12
|
+
ALLOWED_VERBS = %w[GET HEAD]
|
13
|
+
|
14
|
+
attr_accessor :env
|
15
|
+
attr_accessor :root
|
16
|
+
attr_accessor :range
|
17
|
+
attr_accessor :path
|
18
|
+
attr_accessor :default_lines
|
19
|
+
|
20
|
+
def initialize(env, root, headers, default_mime, default_lines)
|
21
|
+
@root = Pathname.new(root)
|
22
|
+
@headers = headers
|
23
|
+
@default_mime = default_mime
|
24
|
+
@default_lines = default_lines
|
25
|
+
@env = env
|
26
|
+
end
|
27
|
+
|
28
|
+
F = ::File
|
29
|
+
|
30
|
+
def call
|
31
|
+
return fail(405, "Method Not Allowed") unless method_allowed?
|
32
|
+
return fail(403, "Forbidden") unless path_is_within_root?
|
33
|
+
|
34
|
+
if available?
|
35
|
+
serving
|
36
|
+
else
|
37
|
+
fail(404, "File not found: #{path_info}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_allowed?
|
42
|
+
ALLOWED_VERBS.include? env["REQUEST_METHOD"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def available?
|
46
|
+
begin
|
47
|
+
F.file?(path) && F.readable?(path)
|
48
|
+
rescue SystemCallError
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def path_info
|
54
|
+
Utils.unescape(env["PATH_INFO"])
|
55
|
+
end
|
56
|
+
|
57
|
+
def path_is_within_root?
|
58
|
+
!path.relative_path_from(root).to_s.split(SEPS).any?{|p| p == ".."}
|
59
|
+
end
|
60
|
+
|
61
|
+
def path
|
62
|
+
@path ||= root.join(Pathname.new("").join(*path_info.split(SEPS)))
|
63
|
+
end
|
64
|
+
|
65
|
+
def serving
|
66
|
+
last_modified = F.mtime(path).httpdate
|
67
|
+
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
|
68
|
+
|
69
|
+
headers = { "Last-Modified" => last_modified }
|
70
|
+
mime = Mime.mime_type(F.extname(path), @default_mime)
|
71
|
+
headers["Content-Type"] = mime if mime
|
72
|
+
|
73
|
+
# Set custom headers
|
74
|
+
@headers.each { |field, content| headers[field] = content } if @headers
|
75
|
+
|
76
|
+
response = [ 200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : self ]
|
77
|
+
response[1]["Content-Length"] = requested_size(response).to_s
|
78
|
+
response
|
79
|
+
end
|
80
|
+
|
81
|
+
def requested_lines_size
|
82
|
+
(env.fetch("QUERY_STRING")[/\d+/] || default_lines).to_i
|
83
|
+
end
|
84
|
+
|
85
|
+
def tail_size_for line_count
|
86
|
+
elif = Elif.new(path)
|
87
|
+
tail_size = 0
|
88
|
+
line_count.times do
|
89
|
+
begin
|
90
|
+
tail_size += Rack::Utils.bytesize(elif.readline)
|
91
|
+
rescue EOFError
|
92
|
+
return tail_size
|
93
|
+
end
|
94
|
+
end
|
95
|
+
tail_size - 1 # Don't include the first \n
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def requested_size(response)
|
100
|
+
# NOTE:
|
101
|
+
# We check via File::size? whether this file provides size info
|
102
|
+
# via stat (e.g. /proc files often don't), otherwise we have to
|
103
|
+
# figure it out by reading the whole file into memory.
|
104
|
+
size = F.size?(path) || Utils.bytesize(F.read(path))
|
105
|
+
|
106
|
+
# TODO handle invalid lines eg. lines=-3
|
107
|
+
tail_size = tail_size_for requested_lines_size
|
108
|
+
|
109
|
+
if tail_size == size
|
110
|
+
response[0] = 200
|
111
|
+
@range = 0..size-1
|
112
|
+
else
|
113
|
+
start_byte = size - tail_size - 1
|
114
|
+
@range = start_byte..size-1
|
115
|
+
response[0] = 206
|
116
|
+
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
|
117
|
+
size = @range.end - @range.begin + 1
|
118
|
+
end
|
119
|
+
|
120
|
+
size
|
121
|
+
end
|
122
|
+
|
123
|
+
def each
|
124
|
+
F.open(path, "rb") do |file|
|
125
|
+
file.seek(range.begin)
|
126
|
+
remaining_len = range.end-range.begin+1
|
127
|
+
while remaining_len > 0
|
128
|
+
part = file.read([8192, remaining_len].min)
|
129
|
+
break unless part
|
130
|
+
remaining_len -= part.length
|
131
|
+
|
132
|
+
yield part
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def fail(status, body)
|
140
|
+
body += "\n"
|
141
|
+
[
|
142
|
+
status,
|
143
|
+
{
|
144
|
+
"Content-Type" => "text/plain",
|
145
|
+
"Content-Length" => body.size.to_s,
|
146
|
+
"X-Cascade" => "pass"
|
147
|
+
},
|
148
|
+
[body]
|
149
|
+
]
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/rack/tail/version.rb
CHANGED
data/spec/lib/rack/tail_spec.rb
CHANGED
@@ -1,63 +1,111 @@
|
|
1
1
|
require 'rack/tail'
|
2
2
|
require 'rack/test'
|
3
|
+
|
3
4
|
describe Rack::Tail do
|
4
5
|
|
5
6
|
include Rack::Test::Methods
|
6
7
|
|
7
|
-
|
8
|
-
Rack::Tail.new root
|
9
|
-
end
|
8
|
+
let(:app) { Rack::Tail.new root, headers, mime_type, default_lines }
|
10
9
|
|
11
10
|
let(:root) { './spec/fixtures' }
|
12
11
|
let(:file_path) { root + file_name }
|
13
12
|
let(:file_name) { "/test.txt" }
|
14
|
-
let(:file_contents) { File.
|
13
|
+
let(:file_contents) { File.readlines(file_path).last(default_lines).join}
|
14
|
+
let(:default_lines) { File.readlines(file_path).count + 1 }
|
15
|
+
let(:mime_type) { 'content-type' }
|
16
|
+
let(:headers) { {"X-Custom-Header" => "blah"} }
|
17
|
+
|
18
|
+
|
15
19
|
|
16
20
|
describe "GET" do
|
17
21
|
|
18
|
-
|
22
|
+
before do
|
23
|
+
subject
|
24
|
+
end
|
25
|
+
|
26
|
+
subject { get file_name }
|
27
|
+
|
28
|
+
|
29
|
+
it "adds the configured headers" do
|
30
|
+
expect(last_response.headers["X-Custom-Header"]).to eq "blah"
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when the content type of the file can be determined" do
|
34
|
+
it "sets the content type" do
|
35
|
+
expect(last_response.headers['Content-Type']).to eq 'text/plain'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when the content type can't be determined" do
|
40
|
+
let(:file_name) { "/test" }
|
19
41
|
|
20
|
-
|
42
|
+
context "when the default mime type is not specified" do
|
43
|
+
|
44
|
+
let(:app) { Rack::Tail.new root }
|
45
|
+
|
46
|
+
it "sets the content type to text/plain" do
|
47
|
+
expect(last_response.headers['Content-Type']).to eq 'text/plain'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when the default mime type is specified" do
|
52
|
+
|
53
|
+
it "uses the default mime type" do
|
54
|
+
expect(last_response.headers['Content-Type']).to eq mime_type
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the number of lines required is not specified and the default lines is less than the entire file" do
|
61
|
+
|
62
|
+
let(:default_lines) { 3 }
|
63
|
+
|
64
|
+
it "returns the default number of lines" do
|
65
|
+
expect(last_response.body).to eq(file_contents)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "sets a status of 206" do
|
69
|
+
expect(last_response.status).to eq 206
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when the number of lines required is not specified and the default lines is more than the number of lines in the file" do
|
21
74
|
|
22
75
|
it "returns the entire file" do
|
23
|
-
subject
|
24
76
|
expect(last_response.body).to eq(file_contents)
|
25
77
|
end
|
26
78
|
|
27
79
|
it "sets a status of 200" do
|
28
|
-
subject
|
29
80
|
expect(last_response.status).to eq 200
|
30
81
|
end
|
31
82
|
end
|
32
83
|
|
33
|
-
context "when a number of lines is specified" do
|
84
|
+
context "when a number of lines is specified that is less than the number of lines in the file" do
|
34
85
|
let(:file_contents) { File.readlines(file_path).last(2).join }
|
35
86
|
|
36
87
|
subject { get file_name, "lines" => "2" }
|
37
88
|
|
38
89
|
it "returns the last lines" do
|
39
|
-
subject
|
40
90
|
expect(last_response.body).to eq(file_contents)
|
41
91
|
end
|
42
92
|
|
43
93
|
it "sets a status of 206" do
|
44
|
-
subject
|
45
94
|
expect(last_response.status).to eq 206
|
46
95
|
end
|
47
96
|
end
|
48
97
|
|
49
98
|
context "when a number of lines is specified that is more than the number of lines in the file" do
|
50
99
|
|
51
|
-
subject { get file_name, "lines" => "
|
100
|
+
subject { get file_name, "lines" => "40" }
|
52
101
|
let(:file_contents) { File.read(file_path )}
|
102
|
+
let(:default_lines) { 1 }
|
53
103
|
|
54
104
|
it "returns the entire file" do
|
55
|
-
subject
|
56
105
|
expect(last_response.body).to eq(file_contents)
|
57
106
|
end
|
58
107
|
|
59
108
|
it "sets a status of 200" do
|
60
|
-
subject
|
61
109
|
expect(last_response.status).to eq 200
|
62
110
|
end
|
63
111
|
end
|
@@ -66,7 +114,6 @@ describe Rack::Tail do
|
|
66
114
|
subject { get "something.txt" }
|
67
115
|
|
68
116
|
it "sets a status of 404" do
|
69
|
-
subject
|
70
117
|
expect(last_response.status).to eq 404
|
71
118
|
end
|
72
119
|
end
|
@@ -75,12 +122,10 @@ describe Rack::Tail do
|
|
75
122
|
subject { get "../lib/rack/tail_file_spec.rb" }
|
76
123
|
|
77
124
|
it "returns a 403 Forbidden" do
|
78
|
-
subject
|
79
125
|
expect(last_response.status).to eq 403
|
80
126
|
end
|
81
127
|
|
82
128
|
it "does not return the file" do
|
83
|
-
subject
|
84
129
|
expect(last_response.body).to_not eq(file_contents)
|
85
130
|
end
|
86
131
|
end
|
@@ -90,44 +135,56 @@ describe Rack::Tail do
|
|
90
135
|
subject { get "../fixtures/blah/..#{file_name}" }
|
91
136
|
|
92
137
|
it "returns a 200 Success" do
|
93
|
-
|
94
|
-
expect(last_response.status).to eq 200
|
138
|
+
expect(last_response).to be_successful
|
95
139
|
end
|
96
140
|
end
|
97
141
|
end
|
98
142
|
|
99
143
|
describe "HEAD" do
|
100
144
|
|
145
|
+
before do
|
146
|
+
subject
|
147
|
+
end
|
148
|
+
|
101
149
|
subject { head file_name }
|
102
150
|
|
103
|
-
context "when the number of lines required is not specified" do
|
104
|
-
|
151
|
+
context "when the number of lines required is not specified and the default is less than the number of lines in the file" do
|
152
|
+
|
153
|
+
it "returns an empty body" do
|
154
|
+
expect(last_response.body.size).to eq 0
|
155
|
+
end
|
156
|
+
|
157
|
+
xit "sets a status of 206" do
|
158
|
+
expect(last_response.status).to eq 206
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when the number of lines required is not specified and the default is more than the number of lines in the file" do
|
163
|
+
|
164
|
+
let(:default_lines) { File.readlines(file_path).count + 1 }
|
105
165
|
|
106
166
|
it "returns an empty body" do
|
107
|
-
subject
|
108
167
|
expect(last_response.body.size).to eq 0
|
109
168
|
end
|
110
169
|
|
111
170
|
it "sets a status of 200" do
|
112
|
-
subject
|
113
171
|
expect(last_response.status).to eq 200
|
114
172
|
end
|
115
173
|
end
|
116
174
|
|
117
|
-
context "when a number of lines is specified" do
|
118
|
-
let(:file_contents) { File.readlines(file_path).last(2).join }
|
175
|
+
context "when a number of lines is specified that is less than the number of lines in the file" do
|
119
176
|
|
120
177
|
subject { head file_name, "lines" => "2" }
|
178
|
+
let(:default_lines) { File.readlines(file_path).count + 1 }
|
121
179
|
|
122
180
|
it "returns an empty body" do
|
123
|
-
subject
|
124
181
|
expect(last_response.body.size).to eq 0
|
125
182
|
end
|
126
183
|
|
127
184
|
xit "sets a status of 206" do
|
128
|
-
subject
|
129
185
|
expect(last_response.status).to eq 206
|
130
186
|
end
|
187
|
+
|
131
188
|
end
|
132
189
|
|
133
190
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-tail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -116,13 +116,17 @@ extra_rdoc_files: []
|
|
116
116
|
files:
|
117
117
|
- .gitignore
|
118
118
|
- .rspec
|
119
|
+
- CHANGELOG.md
|
119
120
|
- Gemfile
|
120
121
|
- LICENSE.txt
|
121
122
|
- README.md
|
122
123
|
- Rakefile
|
123
124
|
- lib/rack/tail.rb
|
125
|
+
- lib/rack/tail/app.rb
|
126
|
+
- lib/rack/tail/request_handler.rb
|
124
127
|
- lib/rack/tail/version.rb
|
125
128
|
- rack-tail_file.gemspec
|
129
|
+
- spec/fixtures/test
|
126
130
|
- spec/fixtures/test.txt
|
127
131
|
- spec/lib/rack/tail_spec.rb
|
128
132
|
homepage: ''
|
@@ -140,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
144
|
version: '0'
|
141
145
|
segments:
|
142
146
|
- 0
|
143
|
-
hash: -
|
147
|
+
hash: -4478541751073078281
|
144
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
149
|
none: false
|
146
150
|
requirements:
|
@@ -149,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
153
|
version: '0'
|
150
154
|
segments:
|
151
155
|
- 0
|
152
|
-
hash: -
|
156
|
+
hash: -4478541751073078281
|
153
157
|
requirements: []
|
154
158
|
rubyforge_project:
|
155
159
|
rubygems_version: 1.8.23
|
@@ -157,5 +161,6 @@ signing_key:
|
|
157
161
|
specification_version: 3
|
158
162
|
summary: A rack app that serves the last lines of a file
|
159
163
|
test_files:
|
164
|
+
- spec/fixtures/test
|
160
165
|
- spec/fixtures/test.txt
|
161
166
|
- spec/lib/rack/tail_spec.rb
|