sitediff 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.
- checksums.yaml +5 -5
- data/bin/sitediff +10 -4
- data/lib/sitediff.rb +179 -91
- data/lib/sitediff/cache.rb +106 -0
- data/lib/sitediff/cli.rb +391 -60
- data/lib/sitediff/config.rb +383 -37
- data/lib/sitediff/config/creator.rb +114 -0
- data/lib/sitediff/config/preset.rb +75 -0
- data/lib/sitediff/crawler.rb +131 -0
- data/lib/sitediff/diff.rb +57 -12
- data/lib/sitediff/exception.rb +5 -0
- data/lib/sitediff/fetch.rb +76 -0
- data/lib/sitediff/files/diff.html.erb +20 -2
- data/lib/sitediff/files/jquery.min.js +2 -0
- data/lib/sitediff/files/normalize.css +349 -0
- data/lib/sitediff/files/report.html.erb +144 -0
- data/lib/sitediff/files/sidebyside.html.erb +16 -0
- data/lib/sitediff/files/sitediff.css +236 -29
- data/lib/sitediff/files/sitediff.js +176 -0
- data/lib/sitediff/report.rb +238 -0
- data/lib/sitediff/result.rb +63 -26
- data/lib/sitediff/sanitize.rb +160 -141
- data/lib/sitediff/sanitize/dom_transform.rb +130 -0
- data/lib/sitediff/sanitize/regexp.rb +82 -0
- data/lib/sitediff/uriwrapper.rb +114 -35
- data/lib/sitediff/webserver.rb +94 -0
- data/lib/sitediff/webserver/resultserver.rb +134 -0
- metadata +103 -43
- data/lib/sitediff/files/html_report.html.erb +0 -47
- data/lib/sitediff/util/cache.rb +0 -32
- data/lib/sitediff/util/webserver.rb +0 -77
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sitediff'
|
4
|
+
require 'sitediff/report'
|
5
|
+
require 'sitediff/webserver'
|
6
|
+
require 'erb'
|
7
|
+
|
8
|
+
class SiteDiff
|
9
|
+
class Webserver
|
10
|
+
# SiteDiff Result Server.
|
11
|
+
class ResultServer < Webserver
|
12
|
+
# Display a page from the cache
|
13
|
+
class CacheServlet < WEBrick::HTTPServlet::AbstractServlet
|
14
|
+
##
|
15
|
+
# Creates a Cache Servlet.
|
16
|
+
def initialize(_server, cache)
|
17
|
+
@cache = cache
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Performs a GET request.
|
22
|
+
def do_GET(req, res)
|
23
|
+
path = req.path_info
|
24
|
+
(md = %r{^/([^/]+)(/.*)$}.match(path)) ||
|
25
|
+
raise(WEBrick::HTTPStatus::NotFound)
|
26
|
+
tag, path = *md.captures
|
27
|
+
(r = @cache.get(tag.to_sym, path)) ||
|
28
|
+
raise(WEBrick::HTTPStatus::NotFound)
|
29
|
+
|
30
|
+
raise WEBrick::HTTPStatus[r.error_code] if r.error_code
|
31
|
+
raise WEBrick::HTTPStatus::InternalServerError, r.error if r.error
|
32
|
+
|
33
|
+
res['content-type'] = 'text/html'
|
34
|
+
res.body = r.content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Display two pages side by side.
|
40
|
+
class SideBySideServlet < WEBrick::HTTPServlet::AbstractServlet
|
41
|
+
##
|
42
|
+
# Creates a Side By Side Servlet.
|
43
|
+
def initialize(_server, cache, settings)
|
44
|
+
@cache = cache
|
45
|
+
@settings = settings
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Generates URLs for a given path.
|
50
|
+
def urls(path)
|
51
|
+
%w[before after].map do |tag|
|
52
|
+
base = @settings[tag]
|
53
|
+
base = "/cache/#{tag}" if @settings['cached'].include? tag
|
54
|
+
base + path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Performs a GET request.
|
60
|
+
def do_GET(req, res)
|
61
|
+
path = req.path_info
|
62
|
+
before, after = *urls(path)
|
63
|
+
|
64
|
+
res['content-type'] = 'text/html'
|
65
|
+
erb = File.join(SiteDiff::FILES_DIR, 'sidebyside.html.erb')
|
66
|
+
res.body = ERB.new(File.read(erb)).result(binding)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Creates a Result Server.
|
72
|
+
def initialize(port, dir, opts = {})
|
73
|
+
unless File.exist?(File.join(dir, Report::SETTINGS_FILE))
|
74
|
+
raise SiteDiffException,
|
75
|
+
"Please run 'sitediff diff' before running 'sitediff serve'"
|
76
|
+
end
|
77
|
+
|
78
|
+
@settings = YAML.load_file(File.join(dir, Report::SETTINGS_FILE))
|
79
|
+
puts @settings
|
80
|
+
@cache = opts[:cache]
|
81
|
+
super(port, [dir], opts)
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# TODO: Document what this method does.
|
86
|
+
def server(opts)
|
87
|
+
dir = opts.delete(:DocumentRoot)
|
88
|
+
srv = super(opts)
|
89
|
+
srv.mount_proc('/') do |req, res|
|
90
|
+
if req.path == '/'
|
91
|
+
res.set_redirect(WEBrick::HTTPStatus::Found,
|
92
|
+
"/files/#{Report::REPORT_FILE_HTML}")
|
93
|
+
else
|
94
|
+
res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect,
|
95
|
+
"#{@settings['after']}#{req.path}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
srv.mount('/files', WEBrick::HTTPServlet::FileHandler, dir, true)
|
100
|
+
srv.mount('/cache', CacheServlet, @cache)
|
101
|
+
srv.mount('/sidebyside', SideBySideServlet, @cache, @settings)
|
102
|
+
srv
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Sets up the server.
|
107
|
+
def setup
|
108
|
+
super
|
109
|
+
root = uris.first
|
110
|
+
puts "Serving at #{root}"
|
111
|
+
open_in_browser(root) if @opts[:browse]
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Opens a URL in a browser.
|
116
|
+
def open_in_browser(url)
|
117
|
+
commands = %w[xdg-open open]
|
118
|
+
cmd = commands.find { |c| which(c) }
|
119
|
+
system(cmd, url) if cmd
|
120
|
+
cmd
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# TODO: Document what this method does.
|
125
|
+
def which(cmd)
|
126
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
127
|
+
file = File.join(path, cmd)
|
128
|
+
return file if File.executable?(file)
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sitediff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Dergachev
|
@@ -10,121 +10,181 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2020-06-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: pkg-config
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
21
|
+
version: '1.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- -
|
26
|
+
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: '
|
28
|
+
version: '1.1'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
|
-
name:
|
30
|
+
name: minitar
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
|
-
- -
|
33
|
+
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '0'
|
35
|
+
version: '0.9'
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
|
-
- -
|
40
|
+
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '0'
|
42
|
+
version: '0.9'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
|
-
name:
|
44
|
+
name: thor
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- -
|
47
|
+
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
49
|
+
version: 0.20.0
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
56
|
+
version: 0.20.0
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: typhoeus
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
|
-
- -
|
61
|
+
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: '0'
|
63
|
+
version: '1.0'
|
64
64
|
type: :runtime
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
|
-
- -
|
68
|
+
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: '0'
|
70
|
+
version: '1.0'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: rainbow
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
74
74
|
requirements:
|
75
|
-
- -
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 3.0.0
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 3.0.0
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: nokogiri
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
76
90
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
91
|
+
version: 1.10.4
|
78
92
|
type: :runtime
|
79
93
|
prerelease: false
|
80
94
|
version_requirements: !ruby/object:Gem::Requirement
|
81
95
|
requirements:
|
82
|
-
- -
|
96
|
+
- - ">="
|
83
97
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
85
|
-
|
86
|
-
|
98
|
+
version: 1.10.4
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: addressable
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 2.5.2
|
106
|
+
type: :runtime
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 2.5.2
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: diffy
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 3.3.0
|
120
|
+
type: :runtime
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 3.3.0
|
127
|
+
description: " SiteDiff makes it easy to see differences between two versions of
|
128
|
+
a website. It accepts a set of paths to compare two versions of the site together
|
129
|
+
with potential normalization/sanitization rules. From the provided paths and configuration
|
130
|
+
SiteDiff generates an HTML report of all the status of HTML comparison between the
|
131
|
+
given paths together with a readable diff-like HTML for each specified path containing
|
132
|
+
the differences between the two versions of the site. It is useful tool for QAing
|
133
|
+
re-deployments, site upgrades, etc.\n"
|
87
134
|
email: alex@evolvingweb.ca
|
88
135
|
executables:
|
89
136
|
- sitediff
|
90
137
|
extensions: []
|
91
138
|
extra_rdoc_files: []
|
92
139
|
files:
|
140
|
+
- bin/sitediff
|
141
|
+
- lib/sitediff.rb
|
142
|
+
- lib/sitediff/cache.rb
|
93
143
|
- lib/sitediff/cli.rb
|
94
144
|
- lib/sitediff/config.rb
|
145
|
+
- lib/sitediff/config/creator.rb
|
146
|
+
- lib/sitediff/config/preset.rb
|
147
|
+
- lib/sitediff/crawler.rb
|
95
148
|
- lib/sitediff/diff.rb
|
96
|
-
- lib/sitediff/
|
97
|
-
- lib/sitediff/
|
98
|
-
- lib/sitediff/uriwrapper.rb
|
99
|
-
- lib/sitediff/util/cache.rb
|
100
|
-
- lib/sitediff/util/webserver.rb
|
101
|
-
- lib/sitediff.rb
|
149
|
+
- lib/sitediff/exception.rb
|
150
|
+
- lib/sitediff/fetch.rb
|
102
151
|
- lib/sitediff/files/diff.html.erb
|
103
|
-
- lib/sitediff/files/
|
152
|
+
- lib/sitediff/files/jquery.min.js
|
153
|
+
- lib/sitediff/files/normalize.css
|
104
154
|
- lib/sitediff/files/pretty_print.xsl
|
155
|
+
- lib/sitediff/files/report.html.erb
|
156
|
+
- lib/sitediff/files/sidebyside.html.erb
|
105
157
|
- lib/sitediff/files/sitediff.css
|
106
|
-
-
|
107
|
-
|
158
|
+
- lib/sitediff/files/sitediff.js
|
159
|
+
- lib/sitediff/report.rb
|
160
|
+
- lib/sitediff/result.rb
|
161
|
+
- lib/sitediff/sanitize.rb
|
162
|
+
- lib/sitediff/sanitize/dom_transform.rb
|
163
|
+
- lib/sitediff/sanitize/regexp.rb
|
164
|
+
- lib/sitediff/uriwrapper.rb
|
165
|
+
- lib/sitediff/webserver.rb
|
166
|
+
- lib/sitediff/webserver/resultserver.rb
|
167
|
+
homepage: https://sitediff.io/
|
108
168
|
licenses:
|
109
|
-
- GPL-2
|
110
|
-
metadata:
|
169
|
+
- GPL-2.0
|
170
|
+
metadata:
|
171
|
+
source_code_uri: https://github.com/evolvingweb/sitediff
|
111
172
|
post_install_message:
|
112
173
|
rdoc_options: []
|
113
174
|
require_paths:
|
114
175
|
- lib
|
115
176
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
177
|
requirements:
|
117
|
-
- -
|
178
|
+
- - ">="
|
118
179
|
- !ruby/object:Gem::Version
|
119
|
-
version:
|
180
|
+
version: '2.4'
|
120
181
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
182
|
requirements:
|
122
|
-
- -
|
183
|
+
- - ">="
|
123
184
|
- !ruby/object:Gem::Version
|
124
185
|
version: '0'
|
125
186
|
requirements: []
|
126
|
-
|
127
|
-
rubygems_version: 2.0.14
|
187
|
+
rubygems_version: 3.1.2
|
128
188
|
signing_key:
|
129
189
|
specification_version: 4
|
130
190
|
summary: Compare two versions of a site with ease!
|
@@ -1,47 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html>
|
3
|
-
<head>
|
4
|
-
<!-- important: otherwise chrome will choke on non-ascii characters -->
|
5
|
-
<meta charset="utf-8" />
|
6
|
-
<style>
|
7
|
-
<%= SiteDiff::Diff.css %>
|
8
|
-
</style>
|
9
|
-
<title> SiteDiff Report </title>
|
10
|
-
</head>
|
11
|
-
<body>
|
12
|
-
<div class="sitediff">
|
13
|
-
<div class="legend">
|
14
|
-
<strong>before</strong> (base url): <a href="<%=before%>"><%=before%></a> |
|
15
|
-
<strong>after </strong> (base url): <a href="<%=after%>" ><%=after %></a>
|
16
|
-
</div>
|
17
|
-
<table class="results">
|
18
|
-
|
19
|
-
<colgroup>
|
20
|
-
<col class="before-col">
|
21
|
-
<col class="after-col">
|
22
|
-
<col class="path-col">
|
23
|
-
<col class="diff-stat-col">
|
24
|
-
</colgroup>
|
25
|
-
|
26
|
-
<thead>
|
27
|
-
<tr>
|
28
|
-
<th> Before </th>
|
29
|
-
<th> After </th>
|
30
|
-
<th> Path </th>
|
31
|
-
<th> Status </th>
|
32
|
-
</tr>
|
33
|
-
</thead>
|
34
|
-
|
35
|
-
<% results.each do |result| %>
|
36
|
-
<tr class="<%= result.status_text %>">
|
37
|
-
<td class="before"><a href="<%= result.url(before) %>">[before]</a></td>
|
38
|
-
<td class="after"><a href="<%= result.url(after) %>">[after]</a></td>
|
39
|
-
<td class="path"><%= result.path %></td>
|
40
|
-
<td class="status"><%= result.link %></td>
|
41
|
-
</tr>
|
42
|
-
<% end %>
|
43
|
-
|
44
|
-
</table>
|
45
|
-
</div>
|
46
|
-
</body>
|
47
|
-
</html>
|
data/lib/sitediff/util/cache.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
class SiteDiff
|
2
|
-
module Util
|
3
|
-
# A typhoeus cache, backed by DBM
|
4
|
-
class Cache
|
5
|
-
def initialize(file)
|
6
|
-
# Default to GDBM, if we have it, we don't want pag/dir files
|
7
|
-
begin
|
8
|
-
require 'gdbm'
|
9
|
-
@dbm = GDBM.new(file)
|
10
|
-
rescue LoadError
|
11
|
-
require 'dbm'
|
12
|
-
@dbm = DBM.new(file)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# Older Typhoeus doesn't have cache_key
|
17
|
-
def cache_key(req)
|
18
|
-
return req.cache_key if req.respond_to?(:cache_key)
|
19
|
-
return Marshal.dump([req.base_url, req.options])
|
20
|
-
end
|
21
|
-
|
22
|
-
def get(req)
|
23
|
-
resp = @dbm[cache_key(req)] or return nil
|
24
|
-
Marshal.load(resp)
|
25
|
-
end
|
26
|
-
|
27
|
-
def set(req, resp)
|
28
|
-
@dbm[cache_key(req)] = Marshal.dump(resp)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'webrick'
|
2
|
-
|
3
|
-
class SiteDiff
|
4
|
-
module Util
|
5
|
-
# Simple webserver for testing purposes
|
6
|
-
class Webserver
|
7
|
-
DEFAULT_PORT = 13080
|
8
|
-
|
9
|
-
attr_accessor :ports
|
10
|
-
|
11
|
-
# Serve a list of directories
|
12
|
-
def initialize(start_port, dirs, params = {})
|
13
|
-
start_port ||= DEFAULT_PORT
|
14
|
-
@ports = (start_port...(start_port + dirs.size)).to_a
|
15
|
-
|
16
|
-
if params[:announce]
|
17
|
-
puts "Serving at #{uris.join(", ")}"
|
18
|
-
end
|
19
|
-
|
20
|
-
opts = {}
|
21
|
-
if params[:quiet]
|
22
|
-
opts[:Logger] = WEBrick::Log.new(IO::NULL)
|
23
|
-
opts[:AccessLog] = []
|
24
|
-
end
|
25
|
-
|
26
|
-
@threads = []
|
27
|
-
dirs.each_with_index do |dir, idx|
|
28
|
-
opts[:Port] = @ports[idx]
|
29
|
-
opts[:DocumentRoot] = dir
|
30
|
-
server = WEBrick::HTTPServer.new(opts)
|
31
|
-
@threads << Thread.new { server.start }
|
32
|
-
end
|
33
|
-
|
34
|
-
if block_given?
|
35
|
-
yield self
|
36
|
-
kill
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def kill
|
41
|
-
@threads.each { |t| t.kill }
|
42
|
-
end
|
43
|
-
|
44
|
-
def wait
|
45
|
-
@threads.each { |t| t.join }
|
46
|
-
end
|
47
|
-
|
48
|
-
def uris
|
49
|
-
ports.map { |p| "http://localhost:#{p}" }
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
# Helper to serve one dir
|
54
|
-
def self.serve(port, dir, params = {})
|
55
|
-
new(port, [dir], params)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class FixtureServer < Webserver
|
60
|
-
PORT = DEFAULT_PORT + 1
|
61
|
-
BASE = 'spec/fixtures/ruby-doc.org'
|
62
|
-
NAMES = %w[core-1.9.3 core-2.0]
|
63
|
-
|
64
|
-
def initialize(port = PORT, base = BASE, names = NAMES)
|
65
|
-
dirs = names.map { |n| File.join(base, n) }
|
66
|
-
super(port, dirs, :quiet => true)
|
67
|
-
end
|
68
|
-
|
69
|
-
def before
|
70
|
-
uris.first
|
71
|
-
end
|
72
|
-
def after
|
73
|
-
uris.last
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|