spk-anemone 0.3.1 → 0.4.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.rdoc +12 -0
- data/lib/anemone/cookie_store.rb +35 -0
- data/lib/anemone/core.rb +26 -10
- data/lib/anemone/http.rb +42 -17
- data/lib/anemone/page.rb +8 -0
- data/spec/cookie_store_spec.rb +27 -0
- data/spec/core_spec.rb +30 -1
- data/spec/fakeweb_helper.rb +18 -10
- data/spec/http_spec.rb +3 -2
- data/spec/page_spec.rb +5 -0
- metadata +48 -13
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
== 0.4.0 / 2010-04-08
|
2
|
+
|
3
|
+
* Major enchancements
|
4
|
+
|
5
|
+
* Cookies can be accepted and sent with each HTTP request.
|
6
|
+
|
7
|
+
== 0.3.2 / 2010-02-04
|
8
|
+
|
9
|
+
* Bug fixes
|
10
|
+
|
11
|
+
* Fixed issue that allowed following redirects off the original domain
|
12
|
+
|
1
13
|
== 0.3.1 / 2010-01-22
|
2
14
|
|
3
15
|
* Minor enhancements
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'webrick/cookie'
|
3
|
+
|
4
|
+
class WEBrick::Cookie
|
5
|
+
def expired?
|
6
|
+
!!expires && expires < Time.now
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Anemone
|
11
|
+
class CookieStore < DelegateClass(Hash)
|
12
|
+
|
13
|
+
def initialize(cookies = nil)
|
14
|
+
@cookies = {}
|
15
|
+
cookies.each { |name, value| @cookies[name] = WEBrick::Cookie.new(name, value) } if cookies
|
16
|
+
super(@cookies)
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge!(set_cookie_str)
|
20
|
+
begin
|
21
|
+
cookie_hash = WEBrick::Cookie.parse_set_cookies(set_cookie_str).inject({}) do |hash, cookie|
|
22
|
+
hash[cookie.name] = cookie if !!cookie
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
@cookies.merge! cookie_hash
|
26
|
+
rescue
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
@cookies.values.reject { |cookie| cookie.expired? }.map { |cookie| "#{cookie.name}=#{cookie.value}" }.join(';')
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/anemone/core.rb
CHANGED
@@ -7,7 +7,7 @@ require 'anemone/storage'
|
|
7
7
|
|
8
8
|
module Anemone
|
9
9
|
|
10
|
-
VERSION = '0.
|
10
|
+
VERSION = '0.4.0';
|
11
11
|
|
12
12
|
#
|
13
13
|
# Convenience method to start a crawl
|
@@ -42,14 +42,18 @@ module Anemone
|
|
42
42
|
:redirect_limit => 5,
|
43
43
|
# storage engine defaults to Hash in +process_options+ if none specified
|
44
44
|
:storage => nil,
|
45
|
+
# Hash of cookie name => value to send with HTTP requests
|
46
|
+
:cookies => nil,
|
47
|
+
# accept cookies from the server and send them back?
|
48
|
+
:accept_cookies => false,
|
45
49
|
# Authentication
|
46
50
|
:authorization => nil,
|
47
51
|
}
|
48
52
|
|
49
53
|
# Create setter methods for all options to be called from the crawl block
|
50
54
|
DEFAULT_OPTS.keys.each do |key|
|
51
|
-
define_method "#{key}=" do
|
52
|
-
@opts[key.to_sym] =
|
55
|
+
define_method "#{key}=" do |value|
|
56
|
+
@opts[key.to_sym] = value
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
@@ -178,7 +182,7 @@ module Anemone
|
|
178
182
|
end
|
179
183
|
end
|
180
184
|
|
181
|
-
@tentacles.each { |
|
185
|
+
@tentacles.each { |thread| thread.join }
|
182
186
|
do_after_crawl_blocks
|
183
187
|
self
|
184
188
|
end
|
@@ -191,6 +195,18 @@ module Anemone
|
|
191
195
|
@opts[:threads] = 1 if @opts[:delay] > 0
|
192
196
|
@pages = PageStore.new(@opts[:storage] || Anemone::Storage.Hash)
|
193
197
|
@robots = Robots.new(@opts[:user_agent]) if @opts[:obey_robots_txt]
|
198
|
+
|
199
|
+
freeze_options
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
# Freeze the opts Hash so that no options can be modified
|
204
|
+
# once the crawl begins
|
205
|
+
#
|
206
|
+
def freeze_options
|
207
|
+
@opts.freeze
|
208
|
+
@opts.each_key { |key| @opts[key].freeze }
|
209
|
+
@opts[:cookies].each_key { |key| @opts[:cookies][key].freeze } rescue nil
|
194
210
|
end
|
195
211
|
|
196
212
|
# Generate Authorization string and set authorization opts
|
@@ -213,19 +229,19 @@ module Anemone
|
|
213
229
|
# Execute the after_crawl blocks
|
214
230
|
#
|
215
231
|
def do_after_crawl_blocks
|
216
|
-
@after_crawl_blocks.each { |
|
232
|
+
@after_crawl_blocks.each { |block| block.call(@pages) }
|
217
233
|
end
|
218
234
|
|
219
235
|
#
|
220
236
|
# Execute the on_every_page blocks for *page*
|
221
237
|
#
|
222
238
|
def do_page_blocks(page)
|
223
|
-
@on_every_page_blocks.each do |
|
224
|
-
|
239
|
+
@on_every_page_blocks.each do |block|
|
240
|
+
block.call(page)
|
225
241
|
end
|
226
242
|
|
227
|
-
@on_pages_like_blocks.each do |pattern,
|
228
|
-
|
243
|
+
@on_pages_like_blocks.each do |pattern, blocks|
|
244
|
+
blocks.each { |block| block.call(page) } if page.url.to_s =~ pattern
|
229
245
|
end
|
230
246
|
end
|
231
247
|
|
@@ -263,7 +279,7 @@ module Anemone
|
|
263
279
|
# its URL matches a skip_link pattern.
|
264
280
|
#
|
265
281
|
def skip_link?(link)
|
266
|
-
@skip_link_patterns.any? { |
|
282
|
+
@skip_link_patterns.any? { |pattern| link.path =~ pattern }
|
267
283
|
end
|
268
284
|
|
269
285
|
end
|
data/lib/anemone/http.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
1
|
require 'net/https'
|
2
2
|
require 'anemone/page'
|
3
|
+
require 'anemone/cookie_store'
|
3
4
|
|
4
5
|
module Anemone
|
5
6
|
class HTTP
|
6
7
|
# Maximum number of redirects to follow on each get_response
|
7
8
|
REDIRECT_LIMIT = 5
|
8
9
|
|
10
|
+
# CookieStore for this HTTP client
|
11
|
+
attr_reader :cookie_store
|
12
|
+
|
9
13
|
def initialize(opts = {})
|
10
14
|
@connections = {}
|
11
15
|
@opts = opts
|
16
|
+
@cookie_store = CookieStore.new(@opts[:cookies])
|
12
17
|
end
|
13
18
|
|
14
19
|
#
|
@@ -47,6 +52,28 @@ module Anemone
|
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
55
|
+
#
|
56
|
+
# The maximum number of redirects to follow
|
57
|
+
#
|
58
|
+
def redirect_limit
|
59
|
+
@opts[:redirect_limit] || REDIRECT_LIMIT
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# The user-agent string which will be sent with each request,
|
64
|
+
# or nil if no such option is set
|
65
|
+
#
|
66
|
+
def user_agent
|
67
|
+
@opts[:user_agent]
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Does this HTTP client accept cookies from the server?
|
72
|
+
#
|
73
|
+
def accept_cookies?
|
74
|
+
@opts[:accept_cookies]
|
75
|
+
end
|
76
|
+
|
50
77
|
private
|
51
78
|
|
52
79
|
#
|
@@ -55,22 +82,19 @@ module Anemone
|
|
55
82
|
# for each response.
|
56
83
|
#
|
57
84
|
def get(url, referer = nil)
|
58
|
-
response, response_time = get_response(url, referer)
|
59
|
-
code = Integer(response.code)
|
60
|
-
loc = url
|
61
|
-
redirect_to = response.is_a?(Net::HTTPRedirection) ? URI(response['location']) : nil
|
62
|
-
yield response, code, loc, redirect_to, response_time
|
63
|
-
|
64
85
|
limit = redirect_limit
|
65
|
-
|
66
|
-
|
86
|
+
loc = url
|
87
|
+
begin
|
88
|
+
# if redirected to a relative url, merge it with the host of the original
|
89
|
+
# request url
|
67
90
|
loc = url.merge(loc) if loc.relative?
|
91
|
+
|
68
92
|
response, response_time = get_response(loc, referer)
|
69
93
|
code = Integer(response.code)
|
70
94
|
redirect_to = response.is_a?(Net::HTTPRedirection) ? URI(response['location']) : nil
|
71
95
|
yield response, code, loc, redirect_to, response_time
|
72
96
|
limit -= 1
|
73
|
-
end
|
97
|
+
end while (loc = redirect_to) && allowed?(redirect_to, url) && limit > 0
|
74
98
|
end
|
75
99
|
|
76
100
|
#
|
@@ -82,6 +106,7 @@ module Anemone
|
|
82
106
|
opts = {}
|
83
107
|
opts['User-Agent'] = user_agent if user_agent
|
84
108
|
opts['Referer'] = referer.to_s if referer
|
109
|
+
opts['Cookie'] = @cookie_store.to_s unless @cookie_store.empty? || (!accept_cookies? && @opts[:cookies].nil?)
|
85
110
|
opts['Authorization'] = authorization if authorization
|
86
111
|
|
87
112
|
retries = 0
|
@@ -90,6 +115,7 @@ module Anemone
|
|
90
115
|
response = connection(url).get(full_path, opts)
|
91
116
|
finish = Time.now()
|
92
117
|
response_time = ((finish - start) * 1000).round
|
118
|
+
@cookie_store.merge!(response['Set-Cookie']) if accept_cookies?
|
93
119
|
return response, response_time
|
94
120
|
rescue EOFError
|
95
121
|
refresh_connection(url)
|
@@ -117,18 +143,17 @@ module Anemone
|
|
117
143
|
@connections[url.host][url.port] = http.start
|
118
144
|
end
|
119
145
|
|
120
|
-
def redirect_limit
|
121
|
-
@opts[:redirect_limit] || REDIRECT_LIMIT
|
122
|
-
end
|
123
|
-
|
124
|
-
def user_agent
|
125
|
-
@opts[:user_agent]
|
126
|
-
end
|
127
|
-
|
128
146
|
def verbose?
|
129
147
|
@opts[:verbose]
|
130
148
|
end
|
131
149
|
|
150
|
+
#
|
151
|
+
# Allowed to connect to the requested url?
|
152
|
+
#
|
153
|
+
def allowed?(to_url, from_url)
|
154
|
+
to_url.host.nil? || (to_url.host == from_url.host)
|
155
|
+
end
|
156
|
+
|
132
157
|
def authorization
|
133
158
|
@opts[:authorization]
|
134
159
|
end
|
data/lib/anemone/page.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'ostruct'
|
3
|
+
require 'webrick/cookie'
|
3
4
|
|
4
5
|
module Anemone
|
5
6
|
class Page
|
@@ -94,6 +95,13 @@ module Anemone
|
|
94
95
|
@fetched
|
95
96
|
end
|
96
97
|
|
98
|
+
#
|
99
|
+
# Array of cookies received with this page as WEBrick::Cookie objects.
|
100
|
+
#
|
101
|
+
def cookies
|
102
|
+
WEBrick::Cookie.parse_set_cookies(@headers['Set-Cookie']) rescue []
|
103
|
+
end
|
104
|
+
|
97
105
|
#
|
98
106
|
# The content-type returned by the HTTP request for this page
|
99
107
|
#
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module Anemone
|
4
|
+
describe CookieStore do
|
5
|
+
|
6
|
+
it "should start out empty if no cookies are specified" do
|
7
|
+
CookieStore.new.empty?.should be true
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should accept a Hash of cookies in the constructor" do
|
11
|
+
CookieStore.new({'test' => 'cookie'})['test'].value.should == 'cookie'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to merge an HTTP cookie string" do
|
15
|
+
cs = CookieStore.new({'a' => 'a', 'b' => 'b'})
|
16
|
+
cs.merge! "a=A; path=/, c=C; path=/"
|
17
|
+
cs['a'].value.should == 'A'
|
18
|
+
cs['b'].value.should == 'b'
|
19
|
+
cs['c'].value.should == 'C'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have a to_s method to turn the cookies into a string for the HTTP Cookie header" do
|
23
|
+
CookieStore.new({'a' => 'a', 'b' => 'b'}).to_s.should == 'a=a;b=b'
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/spec/core_spec.rb
CHANGED
@@ -19,7 +19,7 @@ module Anemone
|
|
19
19
|
Anemone.crawl(pages[0].url, @opts).should have(4).pages
|
20
20
|
end
|
21
21
|
|
22
|
-
it "should not leave the original domain" do
|
22
|
+
it "should not follow links that leave the original domain" do
|
23
23
|
pages = []
|
24
24
|
pages << FakePage.new('0', :links => ['1'], :hrefs => 'http://www.other.com/')
|
25
25
|
pages << FakePage.new('1')
|
@@ -30,6 +30,17 @@ module Anemone
|
|
30
30
|
core.pages.keys.should_not include('http://www.other.com/')
|
31
31
|
end
|
32
32
|
|
33
|
+
it "should not follow redirects that leave the original domain" do
|
34
|
+
pages = []
|
35
|
+
pages << FakePage.new('0', :links => ['1'], :redirect => 'http://www.other.com/')
|
36
|
+
pages << FakePage.new('1')
|
37
|
+
|
38
|
+
core = Anemone.crawl(pages[0].url, @opts)
|
39
|
+
|
40
|
+
core.should have(2).pages
|
41
|
+
core.pages.keys.should_not include('http://www.other.com/')
|
42
|
+
end
|
43
|
+
|
33
44
|
it "should follow http redirects" do
|
34
45
|
pages = []
|
35
46
|
pages << FakePage.new('0', :links => ['1'])
|
@@ -143,6 +154,24 @@ module Anemone
|
|
143
154
|
urls.should_not include(pages[1].url)
|
144
155
|
end
|
145
156
|
|
157
|
+
it "should be able to set cookies to send with HTTP requests" do
|
158
|
+
cookies = {:a => '1', :b => '2'}
|
159
|
+
core = Anemone.crawl(FakePage.new('0').url) do |anemone|
|
160
|
+
anemone.cookies = cookies
|
161
|
+
end
|
162
|
+
core.opts[:cookies].should == cookies
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should freeze the options once the crawl begins" do
|
166
|
+
core = Anemone.crawl(FakePage.new('0').url) do |anemone|
|
167
|
+
anemone.threads = 4
|
168
|
+
anemone.on_every_page do
|
169
|
+
lambda {anemone.threads = 2}.should raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
core.opts[:threads].should == 4
|
173
|
+
end
|
174
|
+
|
146
175
|
describe "many pages" do
|
147
176
|
before(:each) do
|
148
177
|
@pages, size = [], 5
|
data/spec/fakeweb_helper.rb
CHANGED
@@ -9,12 +9,12 @@ FakeWeb.allow_net_connect = false
|
|
9
9
|
|
10
10
|
module Anemone
|
11
11
|
SPEC_DOMAIN = "http://www.example.com/"
|
12
|
-
|
12
|
+
|
13
13
|
class FakePage
|
14
14
|
attr_accessor :links
|
15
15
|
attr_accessor :hrefs
|
16
16
|
attr_accessor :body
|
17
|
-
|
17
|
+
|
18
18
|
def initialize(name = '', options = {})
|
19
19
|
@name = name
|
20
20
|
@links = [options[:links]].flatten if options.has_key?(:links)
|
@@ -22,30 +22,38 @@ module Anemone
|
|
22
22
|
@redirect = options[:redirect] if options.has_key?(:redirect)
|
23
23
|
@content_type = options[:content_type] || "text/html"
|
24
24
|
@body = options[:body]
|
25
|
-
|
25
|
+
|
26
26
|
create_body unless @body
|
27
27
|
add_to_fakeweb
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def url
|
31
31
|
SPEC_DOMAIN + @name
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
private
|
35
|
-
|
35
|
+
|
36
36
|
def create_body
|
37
37
|
@body = "<html><body>"
|
38
38
|
@links.each{|l| @body += "<a href=\"#{SPEC_DOMAIN}#{l}\"></a>"} if @links
|
39
39
|
@hrefs.each{|h| @body += "<a href=\"#{h}\"></a>"} if @hrefs
|
40
40
|
@body += "</body></html>"
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def add_to_fakeweb
|
44
44
|
options = {:body => @body, :content_type => @content_type, :status => [200, "OK"]}
|
45
|
-
|
45
|
+
|
46
46
|
if @redirect
|
47
|
-
options[:status] = [301, "Permanently Moved"]
|
48
|
-
|
47
|
+
options[:status] = [301, "Permanently Moved"]
|
48
|
+
|
49
|
+
# only prepend SPEC_DOMAIN if a relative url (without an http scheme) was specified
|
50
|
+
redirect_url = (@redirect =~ /http/) ? @redirect : SPEC_DOMAIN + @redirect
|
51
|
+
options[:location] = redirect_url
|
52
|
+
|
53
|
+
# register the page this one redirects to
|
54
|
+
FakeWeb.register_uri(:get, redirect_url, {:body => '',
|
55
|
+
:content_type => @content_type,
|
56
|
+
:status => [200, "OK"]})
|
49
57
|
end
|
50
58
|
|
51
59
|
FakeWeb.register_uri(:get, SPEC_DOMAIN + @name, options)
|
data/spec/http_spec.rb
CHANGED
data/spec/page_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spk-anemone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 15
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Chris Kite
|
@@ -9,29 +15,41 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2010-
|
18
|
+
date: 2010-08-18 00:00:00 +02:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: nokogiri
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 5
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 4
|
33
|
+
- 1
|
23
34
|
version: 1.4.1
|
24
|
-
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
38
|
name: robots
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
43
|
- - ">="
|
32
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 7
|
49
|
+
- 2
|
33
50
|
version: 0.7.2
|
34
|
-
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
35
53
|
description:
|
36
54
|
email:
|
37
55
|
executables:
|
@@ -46,6 +64,7 @@ files:
|
|
46
64
|
- README.rdoc
|
47
65
|
- bin/anemone
|
48
66
|
- lib/anemone.rb
|
67
|
+
- lib/anemone/cookie_store.rb
|
49
68
|
- lib/anemone/core.rb
|
50
69
|
- lib/anemone/http.rb
|
51
70
|
- lib/anemone/page.rb
|
@@ -60,6 +79,15 @@ files:
|
|
60
79
|
- lib/anemone/cli/count.rb
|
61
80
|
- lib/anemone/cli/pagedepth.rb
|
62
81
|
- lib/anemone/cli/serialize.rb
|
82
|
+
- spec/anemone_spec.rb
|
83
|
+
- spec/cookie_store_spec.rb
|
84
|
+
- spec/core_spec.rb
|
85
|
+
- spec/page_spec.rb
|
86
|
+
- spec/page_store_spec.rb
|
87
|
+
- spec/http_spec.rb
|
88
|
+
- spec/storage_spec.rb
|
89
|
+
- spec/fakeweb_helper.rb
|
90
|
+
- spec/spec_helper.rb
|
63
91
|
has_rdoc: true
|
64
92
|
homepage: http://anemone.rubyforge.org
|
65
93
|
licenses: []
|
@@ -73,26 +101,33 @@ rdoc_options:
|
|
73
101
|
require_paths:
|
74
102
|
- lib
|
75
103
|
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
76
105
|
requirements:
|
77
106
|
- - ">="
|
78
107
|
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
79
111
|
version: "0"
|
80
|
-
version:
|
81
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
82
114
|
requirements:
|
83
115
|
- - ">="
|
84
116
|
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
85
120
|
version: "0"
|
86
|
-
version:
|
87
121
|
requirements: []
|
88
122
|
|
89
123
|
rubyforge_project: anemone
|
90
|
-
rubygems_version: 1.3.
|
124
|
+
rubygems_version: 1.3.7
|
91
125
|
signing_key:
|
92
126
|
specification_version: 3
|
93
127
|
summary: Anemone web-spider framework
|
94
128
|
test_files:
|
95
129
|
- spec/anemone_spec.rb
|
130
|
+
- spec/cookie_store_spec.rb
|
96
131
|
- spec/core_spec.rb
|
97
132
|
- spec/page_spec.rb
|
98
133
|
- spec/page_store_spec.rb
|