validate-website 1.9.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +39 -0
- data/LICENSE +1 -1
- data/README.md +27 -26
- data/Rakefile +3 -2
- data/bin/validate-website +1 -0
- data/bin/validate-website-static +1 -0
- data/lib/validate_website.rb +2 -0
- data/lib/validate_website/colorful_messages.rb +3 -0
- data/lib/validate_website/core.rb +15 -6
- data/lib/validate_website/crawl.rb +9 -2
- data/lib/validate_website/option_parser.rb +4 -0
- data/lib/validate_website/runner.rb +3 -1
- data/lib/validate_website/static.rb +12 -2
- data/lib/validate_website/static_link.rb +5 -3
- data/lib/validate_website/utils.rb +3 -0
- data/lib/validate_website/validator.rb +23 -26
- data/lib/validate_website/validator_class_methods.rb +3 -0
- data/lib/validate_website/version.rb +1 -1
- data/man/man1/validate-website-static.1 +17 -8
- data/man/man1/validate-website.1 +17 -8
- data/test/core_test.rb +3 -1
- data/test/crawler_test.rb +35 -16
- data/test/data/html5-fail.html +0 -337
- data/test/static_test.rb +25 -5
- data/test/test_helper.rb +4 -5
- data/test/validator_test.rb +25 -23
- data/test/webmock_helper.rb +4 -2
- metadata +18 -19
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Base module ValidateWebsite
|
2
4
|
module ValidateWebsite
|
3
5
|
# Utils class for CSS helpers
|
@@ -10,6 +12,7 @@ module ValidateWebsite
|
|
10
12
|
def self.extract_urls_from_css(page)
|
11
13
|
return Set[] unless page
|
12
14
|
return Set[] if page.body.nil?
|
15
|
+
|
13
16
|
nodes = Crass::Parser.parse_stylesheet(page.body)
|
14
17
|
extract_urls_from_nodes nodes, page
|
15
18
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
|
3
5
|
require 'nokogiri'
|
@@ -10,27 +12,25 @@ module ValidateWebsite
|
|
10
12
|
class Validator
|
11
13
|
extend ValidatorClassMethods
|
12
14
|
|
13
|
-
@html5_validator_service_url = 'https://
|
15
|
+
@html5_validator_service_url = 'https://validator.nu/'
|
16
|
+
XHTML_SCHEMA_PATH = File.expand_path('../../data/schemas', __dir__)
|
17
|
+
@mutex = Mutex.new
|
18
|
+
|
14
19
|
class << self
|
15
20
|
attr_accessor :html5_validator_service_url
|
16
|
-
end
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
schema_name = File.basename(schema, '.xsd')
|
27
|
-
schema_content = File.read(File.basename(schema))
|
28
|
-
begin
|
29
|
-
@xsd_schemas[schema_name] = Nokogiri::XML::Schema(schema_content)
|
30
|
-
rescue Nokogiri::XML::SyntaxError
|
31
|
-
STDERR.puts "XSD SCHEMA: #{schema} cannot be loaded"
|
22
|
+
# http://www.w3.org/TR/xhtml1-schema/
|
23
|
+
def schema(namespace)
|
24
|
+
@mutex.synchronize do
|
25
|
+
Dir.chdir(XHTML_SCHEMA_PATH) do
|
26
|
+
if File.exist?("#{namespace}.xsd")
|
27
|
+
Nokogiri::XML::Schema(File.read("#{namespace}.xsd"))
|
28
|
+
end
|
29
|
+
end
|
32
30
|
end
|
33
31
|
end
|
32
|
+
|
33
|
+
alias xsd schema
|
34
34
|
end
|
35
35
|
|
36
36
|
attr_reader :original_doc, :body, :dtd, :doc, :namespace, :html5_validator
|
@@ -65,25 +65,22 @@ module ValidateWebsite
|
|
65
65
|
@ignore ? @errors.reject { |e| @ignore =~ e } : @errors
|
66
66
|
end
|
67
67
|
|
68
|
-
# http://www.w3.org/TR/xhtml1-schema/
|
69
|
-
def self.xsd(namespace)
|
70
|
-
return unless namespace
|
71
|
-
@xsd_schemas[namespace] if @xsd_schemas.key? namespace
|
72
|
-
end
|
73
|
-
|
74
68
|
private
|
75
69
|
|
76
70
|
# http://www.w3.org/TR/xhtml1/#dtds
|
77
71
|
def find_namespace(dtd)
|
78
72
|
return unless dtd.system_id
|
73
|
+
|
79
74
|
dtd_uri = URI.parse(dtd.system_id)
|
80
75
|
return unless dtd_uri.path
|
76
|
+
|
81
77
|
@dtd_uri = dtd_uri
|
82
78
|
File.basename(@dtd_uri.path, '.dtd')
|
83
79
|
end
|
84
80
|
|
85
81
|
def document
|
86
82
|
return @document if @document
|
83
|
+
|
87
84
|
@document = if @dtd_uri && @body.match(@dtd_uri.to_s)
|
88
85
|
@body.sub(@dtd_uri.to_s, @namespace + '.dtd')
|
89
86
|
else
|
@@ -95,11 +92,11 @@ module ValidateWebsite
|
|
95
92
|
def validate
|
96
93
|
if document =~ /^\<!DOCTYPE html\>/i
|
97
94
|
html5_validate
|
98
|
-
elsif self.class.
|
99
|
-
self.class.
|
95
|
+
elsif self.class.schema(@namespace)
|
96
|
+
self.class.schema(@namespace).validate(xhtml_doc)
|
100
97
|
else
|
101
98
|
# dont have xsd fall back to dtd
|
102
|
-
Dir.chdir(
|
99
|
+
Dir.chdir(XHTML_SCHEMA_PATH) do
|
103
100
|
Nokogiri::HTML.parse(document)
|
104
101
|
end.errors
|
105
102
|
end
|
@@ -138,7 +135,7 @@ module ValidateWebsite
|
|
138
135
|
end
|
139
136
|
|
140
137
|
def xhtml_doc
|
141
|
-
Dir.chdir(
|
138
|
+
Dir.chdir(XHTML_SCHEMA_PATH) do
|
142
139
|
Nokogiri::XML(document) { |cfg| cfg.nonoent.dtdload.dtdvalid.nonet }
|
143
140
|
end
|
144
141
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'tidy_ffi'
|
2
4
|
|
3
5
|
# Validator Class Methods
|
@@ -9,6 +11,7 @@ module ValidatorClassMethods
|
|
9
11
|
|
10
12
|
def tidy
|
11
13
|
return @tidy if defined?(@tidy)
|
14
|
+
|
12
15
|
@lib_tidy = TidyFFI::LibTidy
|
13
16
|
@tidy = TidyFFI::Tidy
|
14
17
|
rescue TidyFFI::LibTidyNotInstalled
|
@@ -1,23 +1,32 @@
|
|
1
1
|
'\" t
|
2
2
|
.\" Title: validate-website-static
|
3
|
-
.\" Author: [see the "
|
4
|
-
.\" Generator: Asciidoctor 1.5.
|
5
|
-
.\" Date:
|
3
|
+
.\" Author: [see the "AUTHOR(S)" section]
|
4
|
+
.\" Generator: Asciidoctor 1.5.8
|
5
|
+
.\" Date: 2019-01-09
|
6
6
|
.\" Manual: \ \&
|
7
7
|
.\" Source: \ \&
|
8
8
|
.\" Language: English
|
9
9
|
.\"
|
10
|
-
.TH "VALIDATE\-WEBSITE\-STATIC" "1" "
|
10
|
+
.TH "VALIDATE\-WEBSITE\-STATIC" "1" "2019-01-09" "\ \&" "\ \&"
|
11
11
|
.ie \n(.g .ds Aq \(aq
|
12
12
|
.el .ds Aq '
|
13
13
|
.ss \n[.ss] 0
|
14
14
|
.nh
|
15
15
|
.ad l
|
16
16
|
.de URL
|
17
|
-
\\$2
|
17
|
+
\fI\\$2\fP <\\$1>\\$3
|
18
18
|
..
|
19
|
-
.
|
20
|
-
.
|
19
|
+
.als MTO URL
|
20
|
+
.if \n[.g] \{\
|
21
|
+
. mso www.tmac
|
22
|
+
. am URL
|
23
|
+
. ad l
|
24
|
+
. .
|
25
|
+
. am MTO
|
26
|
+
. ad l
|
27
|
+
. .
|
28
|
+
. LINKSTYLE blue R < >
|
29
|
+
.\}
|
21
30
|
.SH "NAME"
|
22
31
|
validate\-website\-static \- check the validity of your documents
|
23
32
|
.SH "SYNOPSIS"
|
@@ -119,5 +128,5 @@ Laurent Arnoud \c
|
|
119
128
|
.sp
|
120
129
|
The MIT License
|
121
130
|
.sp
|
122
|
-
Copyright (c) 2009\-
|
131
|
+
Copyright (c) 2009\-2019 \c
|
123
132
|
.MTO "laurent\(atspkdev.net" "Laurent Arnoud" ""
|
data/man/man1/validate-website.1
CHANGED
@@ -1,23 +1,32 @@
|
|
1
1
|
'\" t
|
2
2
|
.\" Title: validate-website
|
3
|
-
.\" Author: [see the "
|
4
|
-
.\" Generator: Asciidoctor 1.5.
|
5
|
-
.\" Date:
|
3
|
+
.\" Author: [see the "AUTHOR(S)" section]
|
4
|
+
.\" Generator: Asciidoctor 1.5.8
|
5
|
+
.\" Date: 2019-01-09
|
6
6
|
.\" Manual: \ \&
|
7
7
|
.\" Source: \ \&
|
8
8
|
.\" Language: English
|
9
9
|
.\"
|
10
|
-
.TH "VALIDATE\-WEBSITE" "1" "
|
10
|
+
.TH "VALIDATE\-WEBSITE" "1" "2019-01-09" "\ \&" "\ \&"
|
11
11
|
.ie \n(.g .ds Aq \(aq
|
12
12
|
.el .ds Aq '
|
13
13
|
.ss \n[.ss] 0
|
14
14
|
.nh
|
15
15
|
.ad l
|
16
16
|
.de URL
|
17
|
-
\\$2
|
17
|
+
\fI\\$2\fP <\\$1>\\$3
|
18
18
|
..
|
19
|
-
.
|
20
|
-
.
|
19
|
+
.als MTO URL
|
20
|
+
.if \n[.g] \{\
|
21
|
+
. mso www.tmac
|
22
|
+
. am URL
|
23
|
+
. ad l
|
24
|
+
. .
|
25
|
+
. am MTO
|
26
|
+
. ad l
|
27
|
+
. .
|
28
|
+
. LINKSTYLE blue R < >
|
29
|
+
.\}
|
21
30
|
.SH "NAME"
|
22
31
|
validate\-website \- Web crawler for checking the validity of your documents
|
23
32
|
.SH "SYNOPSIS"
|
@@ -124,5 +133,5 @@ Laurent Arnoud \c
|
|
124
133
|
.sp
|
125
134
|
The MIT License
|
126
135
|
.sp
|
127
|
-
Copyright (c) 2009\-
|
136
|
+
Copyright (c) 2009\-2019 \c
|
128
137
|
.MTO "laurent\(atspkdev.net" "Laurent Arnoud" ""
|
data/test/core_test.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('test_helper', __dir__)
|
2
4
|
|
3
5
|
describe ValidateWebsite::Core do
|
4
6
|
describe 'invalid options' do
|
5
7
|
it 'raise ArgumentError on wrong validation_type' do
|
6
|
-
proc { ValidateWebsite::Core.new({ color: false }, :fail) }
|
8
|
+
_(proc { ValidateWebsite::Core.new({ color: false }, :fail) })
|
7
9
|
.must_raise ArgumentError
|
8
10
|
end
|
9
11
|
end
|
data/test/crawler_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('test_helper', __dir__)
|
2
4
|
|
3
5
|
# rubocop:disable Metrics/BlockLength
|
@@ -21,7 +23,7 @@ describe ValidateWebsite::Crawl do
|
|
21
23
|
_out, _err = capture_io do
|
22
24
|
v = ValidateWebsite::Crawl.new(site: TEST_DOMAIN, user_agent: ua)
|
23
25
|
v.crawl
|
24
|
-
v.crawler.user_agent.must_equal ua
|
26
|
+
_(v.crawler.user_agent).must_equal ua
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
@@ -31,7 +33,7 @@ describe ValidateWebsite::Crawl do
|
|
31
33
|
_out, _err = capture_io do
|
32
34
|
ValidateWebsite::Crawl.new(site: TEST_DOMAIN,
|
33
35
|
html5_validator_service_url: new)
|
34
|
-
validator.html5_validator_service_url.must_equal new
|
36
|
+
_(validator.html5_validator_service_url).must_equal new
|
35
37
|
validator.html5_validator_service_url = original
|
36
38
|
end
|
37
39
|
end
|
@@ -43,7 +45,8 @@ describe ValidateWebsite::Crawl do
|
|
43
45
|
_out, _err = capture_io do
|
44
46
|
v = ValidateWebsite::Crawl.new(site: TEST_DOMAIN, cookies: cookies)
|
45
47
|
v.crawl
|
46
|
-
v.crawler.cookies.cookies_for_host(v.host)
|
48
|
+
_(v.crawler.cookies.cookies_for_host(v.host))
|
49
|
+
.must_equal v.default_cookies
|
47
50
|
end
|
48
51
|
end
|
49
52
|
end
|
@@ -53,37 +56,53 @@ describe ValidateWebsite::Crawl do
|
|
53
56
|
name = 'xhtml1-strict'
|
54
57
|
file = File.join('test', 'data', "#{name}.html")
|
55
58
|
page = FakePage.new(name,
|
56
|
-
body: open(file).read,
|
59
|
+
body: File.open(file).read,
|
57
60
|
content_type: 'text/html')
|
58
61
|
@validate_website.site = page.url
|
59
62
|
_out, _err = capture_io do
|
60
63
|
@validate_website.crawl
|
61
64
|
end
|
62
|
-
@validate_website.history_count.must_equal 5
|
65
|
+
_(@validate_website.history_count).must_equal 5
|
63
66
|
end
|
64
67
|
|
65
68
|
it 'extract link' do
|
66
69
|
name = 'html4-strict'
|
67
70
|
file = File.join('test', 'data', "#{name}.html")
|
68
71
|
page = FakePage.new(name,
|
69
|
-
body: open(file).read,
|
72
|
+
body: File.open(file).read,
|
70
73
|
content_type: 'text/html')
|
71
74
|
@validate_website.site = page.url
|
72
75
|
_out, _err = capture_io do
|
73
76
|
@validate_website.crawl
|
74
77
|
end
|
75
|
-
@validate_website.history_count.must_equal 98
|
78
|
+
_(@validate_website.history_count).must_equal 98
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'can change validator' do
|
82
|
+
name = 'html5-fail'
|
83
|
+
file = File.join('test', 'data', "#{name}.html")
|
84
|
+
page = FakePage.new(name,
|
85
|
+
body: File.open(file).read,
|
86
|
+
content_type: 'text/html')
|
87
|
+
validator_res = File.join('test', 'data', 'validator.nu-failure.json')
|
88
|
+
stub_request(:any, /#{validator.html5_validator_service_url}/)
|
89
|
+
.to_return(body: File.open(validator_res).read)
|
90
|
+
@validate_website.site = page.url
|
91
|
+
_out, _err = capture_io do
|
92
|
+
@validate_website.crawl(html5_validator: :nu, ignore: /Warning/)
|
93
|
+
end
|
94
|
+
_(@validate_website.errors_count).must_equal 1
|
76
95
|
end
|
77
96
|
|
78
97
|
it 'crawl when URLs are not ascii only' do
|
79
98
|
name = 'cozy-community'
|
80
99
|
file = File.join('test', 'data', "#{name}.html")
|
81
100
|
page = FakePage.new(name,
|
82
|
-
body: open(file).read,
|
101
|
+
body: File.open(file).read,
|
83
102
|
content_type: 'text/html')
|
84
103
|
validator_res = File.join('test', 'data', 'validator.nu-failure.json')
|
85
104
|
stub_request(:any, /#{validator.html5_validator_service_url}/)
|
86
|
-
.to_return(body: open(validator_res).read)
|
105
|
+
.to_return(body: File.open(validator_res).read)
|
87
106
|
@validate_website.site = page.url
|
88
107
|
_out, _err = capture_io do
|
89
108
|
@validate_website.crawl
|
@@ -119,7 +138,7 @@ describe ValidateWebsite::Crawl do
|
|
119
138
|
_out, _err = capture_io do
|
120
139
|
@validate_website.crawl
|
121
140
|
end
|
122
|
-
@validate_website.history_count.must_equal 5
|
141
|
+
_(@validate_website.history_count).must_equal 5
|
123
142
|
end
|
124
143
|
|
125
144
|
it 'should extract url with single quote' do
|
@@ -130,7 +149,7 @@ describe ValidateWebsite::Crawl do
|
|
130
149
|
_out, _err = capture_io do
|
131
150
|
@validate_website.crawl
|
132
151
|
end
|
133
|
-
@validate_website.history_count.must_equal 2
|
152
|
+
_(@validate_website.history_count).must_equal 2
|
134
153
|
end
|
135
154
|
|
136
155
|
it 'should extract url with double quote' do
|
@@ -141,7 +160,7 @@ describe ValidateWebsite::Crawl do
|
|
141
160
|
_out, _err = capture_io do
|
142
161
|
@validate_website.crawl
|
143
162
|
end
|
144
|
-
@validate_website.history_count.must_equal 2
|
163
|
+
_(@validate_website.history_count).must_equal 2
|
145
164
|
end
|
146
165
|
|
147
166
|
it 'should extract url with params' do
|
@@ -152,7 +171,7 @@ describe ValidateWebsite::Crawl do
|
|
152
171
|
_out, _err = capture_io do
|
153
172
|
@validate_website.crawl
|
154
173
|
end
|
155
|
-
@validate_website.history_count.must_equal 2
|
174
|
+
_(@validate_website.history_count).must_equal 2
|
156
175
|
end
|
157
176
|
|
158
177
|
it 'should not extract invalid urls' do
|
@@ -163,7 +182,7 @@ describe ValidateWebsite::Crawl do
|
|
163
182
|
_out, _err = capture_io do
|
164
183
|
@validate_website.crawl
|
165
184
|
end
|
166
|
-
@validate_website.history_count.must_equal 1
|
185
|
+
_(@validate_website.history_count).must_equal 1
|
167
186
|
end
|
168
187
|
end
|
169
188
|
|
@@ -182,7 +201,7 @@ describe ValidateWebsite::Crawl do
|
|
182
201
|
_out, _err = capture_io do
|
183
202
|
@validate_website.crawl
|
184
203
|
end
|
185
|
-
@validate_website.errors_count.must_equal 1
|
204
|
+
_(@validate_website.errors_count).must_equal 1
|
186
205
|
end
|
187
206
|
|
188
207
|
it 'should be invalid with syntax error' do
|
@@ -193,7 +212,7 @@ describe ValidateWebsite::Crawl do
|
|
193
212
|
_out, _err = capture_io do
|
194
213
|
@validate_website.crawl
|
195
214
|
end
|
196
|
-
@validate_website.errors_count.must_equal 1
|
215
|
+
_(@validate_website.errors_count).must_equal 1
|
197
216
|
end
|
198
217
|
end
|
199
218
|
end
|
data/test/data/html5-fail.html
CHANGED
@@ -118,333 +118,6 @@
|
|
118
118
|
</div>
|
119
119
|
</div>
|
120
120
|
|
121
|
-
<script type="text/javascript">
|
122
|
-
include = function() {
|
123
|
-
|
124
|
-
// save references to save a few bytes
|
125
|
-
var args = arguments;
|
126
|
-
var doc = document;
|
127
|
-
|
128
|
-
var toLoad = args.length; // load this many scripts
|
129
|
-
var lastArgument = args[toLoad - 1];
|
130
|
-
var hasCallback = lastArgument.call; // is the last arg a callback?
|
131
|
-
if (hasCallback) {
|
132
|
-
toLoad--;
|
133
|
-
}
|
134
|
-
|
135
|
-
function onScriptLoaded() {
|
136
|
-
var readyState = this.readyState; // we test for "complete" or "loaded" if on IE
|
137
|
-
if (!readyState || /ded|te/.test(readyState)) {
|
138
|
-
toLoad--;
|
139
|
-
if (!toLoad && hasCallback) {
|
140
|
-
lastArgument();
|
141
|
-
}
|
142
|
-
}
|
143
|
-
}
|
144
|
-
|
145
|
-
for (var i = 0; i < toLoad; i++) {
|
146
|
-
|
147
|
-
var script = doc.createElement('script');
|
148
|
-
script.src = arguments[i];
|
149
|
-
script.async = true;
|
150
|
-
script.onload = script.onerror = script.onreadystatechange = onScriptLoaded;
|
151
|
-
(
|
152
|
-
doc.head ||
|
153
|
-
doc.getElementsByTagName('head')[0]
|
154
|
-
).appendChild(script);
|
155
|
-
|
156
|
-
}
|
157
|
-
|
158
|
-
};
|
159
|
-
|
160
|
-
|
161
|
-
include("https://cdn.jsdelivr.net/ace/1.1.3/noconflict/ace.js", function () {
|
162
|
-
include("https://cdn.jsdelivr.net/ace/1.1.3/noconflict/mode-rust.js", function () {
|
163
|
-
(function () {
|
164
|
-
"use strict";
|
165
|
-
// ECMAScript 6 Backwards compatability
|
166
|
-
if (typeof String.prototype.startsWith !== 'function') {
|
167
|
-
String.prototype.startsWith = function(str) {
|
168
|
-
return this.slice(0, str.length) === str;
|
169
|
-
};
|
170
|
-
}
|
171
|
-
|
172
|
-
// Regex for finding new lines
|
173
|
-
var newLineRegex = /(?:\r\n|\r|\n)/g;
|
174
|
-
|
175
|
-
// Fetching DOM items
|
176
|
-
var activeCode = document.getElementById("active-code");
|
177
|
-
var editorDiv = document.getElementById("editor");
|
178
|
-
var staticCode = document.getElementById("static-code");
|
179
|
-
var runButton = document.getElementById("run-code");
|
180
|
-
var resultDiv = document.getElementById("result");
|
181
|
-
var playLink = document.getElementById("playlink");
|
182
|
-
|
183
|
-
// Background colors for program result on success/error
|
184
|
-
var successColor = "#E2EEF6";
|
185
|
-
var errorColor = "#F6E2E2";
|
186
|
-
var warningColor = "#FFFBCB";
|
187
|
-
|
188
|
-
// Message to show when the program is running
|
189
|
-
var runningMsg = resultDiv.getAttribute("data-msg-running") || "Running...";
|
190
|
-
|
191
|
-
// Error message to return when there's a server failure
|
192
|
-
var errMsg = "The server encountered an error while running the program.";
|
193
|
-
|
194
|
-
// Stores ACE editor markers (highights) for errors
|
195
|
-
var markers = [];
|
196
|
-
|
197
|
-
// Status codes, because there are no enums in Javascript
|
198
|
-
var SUCCESS = 0;
|
199
|
-
var ERROR = 1;
|
200
|
-
var WARNING = 2;
|
201
|
-
|
202
|
-
// JS exists, display ACE editor
|
203
|
-
staticCode.style.display = "none";
|
204
|
-
activeCode.style.display = "block";
|
205
|
-
|
206
|
-
// Setting up ace editor
|
207
|
-
var editor = ace.edit("editor");
|
208
|
-
var Range = ace.require('ace/range').Range;
|
209
|
-
editor.setTheme("ace/theme/chrome");
|
210
|
-
editor.getSession().setMode("ace/mode/rust");
|
211
|
-
editor.setShowPrintMargin(false);
|
212
|
-
editor.renderer.setShowGutter(false);
|
213
|
-
editor.setHighlightActiveLine(false);
|
214
|
-
|
215
|
-
// Changes the height of the editor to match its contents
|
216
|
-
function updateEditorHeight() {
|
217
|
-
// https://stackoverflow.com/questions/11584061/
|
218
|
-
var newHeight = editor.getSession().getScreenLength()
|
219
|
-
* editor.renderer.lineHeight
|
220
|
-
+ editor.renderer.scrollBar.getWidth();
|
221
|
-
|
222
|
-
editorDiv.style.height = Math.ceil(newHeight).toString() + "px";
|
223
|
-
editor.resize();
|
224
|
-
}
|
225
|
-
|
226
|
-
// Set initial size to match initial content
|
227
|
-
updateEditorHeight();
|
228
|
-
|
229
|
-
// Safely remove all content from the result div
|
230
|
-
function clearResultDiv() {
|
231
|
-
// Clearing the result div will break our reference to
|
232
|
-
// the playlink icon, so let's save it if it exists
|
233
|
-
var newPlayLink = document.getElementById("playlink");
|
234
|
-
if (newPlayLink) {
|
235
|
-
playLink = resultDiv.removeChild(newPlayLink);
|
236
|
-
}
|
237
|
-
resultDiv.innerHTML = "";
|
238
|
-
}
|
239
|
-
|
240
|
-
function escapeHTML(unsafe) {
|
241
|
-
return unsafe
|
242
|
-
.replace(/&/g, "&")
|
243
|
-
.replace(/</g, "<")
|
244
|
-
.replace(/>/g, ">")
|
245
|
-
.replace(/"/g, """)
|
246
|
-
.replace(/'/g, "'")
|
247
|
-
.replace(newLineRegex, '<br />');
|
248
|
-
}
|
249
|
-
|
250
|
-
// Dispatches a XMLHttpRequest to the Rust playpen, running the program, and
|
251
|
-
// issues a callback to `callback` with the result (or null on error)
|
252
|
-
function runProgram(program, callback) {
|
253
|
-
var req = new XMLHttpRequest();
|
254
|
-
var data = JSON.stringify({
|
255
|
-
version: "beta",
|
256
|
-
optimize: "0",
|
257
|
-
code: program
|
258
|
-
});
|
259
|
-
|
260
|
-
req.timeout = 6000;
|
261
|
-
|
262
|
-
// console.log("Sending", data);
|
263
|
-
req.open('POST', "https://play.rust-lang.org/evaluate.json", true);
|
264
|
-
req.onload = function(e) {
|
265
|
-
var statusCode = false;
|
266
|
-
var result = null;
|
267
|
-
|
268
|
-
if (req.readyState === 4 && req.status === 200) {
|
269
|
-
result = JSON.parse(req.response);
|
270
|
-
|
271
|
-
// handle application errors from playpen
|
272
|
-
if (typeof result['error'] === 'string') {
|
273
|
-
statusCode = ERROR;
|
274
|
-
result = 'Playpen Error: ' + result['error'];
|
275
|
-
} else if (typeof result['result'] === 'string') {
|
276
|
-
statusCode = SUCCESS;
|
277
|
-
result = result['result'];
|
278
|
-
|
279
|
-
// handle rustc errors/warnings
|
280
|
-
// Need server support to get an accurate version of this.
|
281
|
-
if (result.indexOf("error:") !== -1) {
|
282
|
-
statusCode = ERROR;
|
283
|
-
} else if (result.indexOf("warning:") !== -1) {
|
284
|
-
statusCode = WARNING;
|
285
|
-
}
|
286
|
-
}
|
287
|
-
}
|
288
|
-
|
289
|
-
callback(statusCode, result);
|
290
|
-
};
|
291
|
-
|
292
|
-
req.onerror = function(e) {
|
293
|
-
callback(false, null);
|
294
|
-
};
|
295
|
-
|
296
|
-
req.ontimeout = function(e) {
|
297
|
-
var statusCode = ERROR;
|
298
|
-
var result = "play.rust-lang.org not responding, please check back later";
|
299
|
-
|
300
|
-
callback(statusCode, result);
|
301
|
-
}
|
302
|
-
|
303
|
-
req.setRequestHeader("Content-Type", "application/json");
|
304
|
-
req.send(data);
|
305
|
-
}
|
306
|
-
|
307
|
-
// The callback to runProgram
|
308
|
-
function handleResult(statusCode, message) {
|
309
|
-
// Dispatch depending on result type
|
310
|
-
if (result == null) {
|
311
|
-
clearResultDiv();
|
312
|
-
resultDiv.style.backgroundColor = errorColor;
|
313
|
-
resultDiv.innerHTML = errMsg;
|
314
|
-
} else if (statusCode === SUCCESS) {
|
315
|
-
handleSuccess(message);
|
316
|
-
} else if (statusCode === WARNING) {
|
317
|
-
handleWarning(message);
|
318
|
-
} else {
|
319
|
-
handleError(message);
|
320
|
-
}
|
321
|
-
}
|
322
|
-
|
323
|
-
// Called on successful program run: display output and playground icon
|
324
|
-
function handleSuccess(message) {
|
325
|
-
resultDiv.style.backgroundColor = successColor;
|
326
|
-
displayOutput(escapeHTML(message), editor.getValue());
|
327
|
-
}
|
328
|
-
|
329
|
-
// Called when program run results in warning(s)
|
330
|
-
function handleWarning(message) {
|
331
|
-
resultDiv.style.backgroundColor = warningColor;
|
332
|
-
handleProblem(message, "warning");
|
333
|
-
}
|
334
|
-
|
335
|
-
// Called when program run results in error(s)
|
336
|
-
function handleError(message) {
|
337
|
-
resultDiv.style.backgroundColor = errorColor;
|
338
|
-
handleProblem(message, "error");
|
339
|
-
}
|
340
|
-
|
341
|
-
// Called on unsuccessful program run. Detects and prints problems (either
|
342
|
-
// warnings or errors) in program output and highlights relevant lines and text
|
343
|
-
// in the code.
|
344
|
-
function handleProblem(message, problem) {
|
345
|
-
// Getting list of ranges with problems
|
346
|
-
var lines = message.split(newLineRegex);
|
347
|
-
|
348
|
-
// Cleaning up the message: keeps only relevant problem output.
|
349
|
-
var cleanMessage = lines.filter(function(line) {
|
350
|
-
return !line.trim().startsWith("--> <anon>")
|
351
|
-
&& !line.startsWith("playpen:")
|
352
|
-
&& !line.trim().startsWith("error: aborting");
|
353
|
-
}).map(function(line) {
|
354
|
-
return escapeHTML(line);
|
355
|
-
}).filter(function(line) {
|
356
|
-
return line != "";
|
357
|
-
}).map(function(line) {
|
358
|
-
return line.replace(/ /g, '\u00a0\u00a0');
|
359
|
-
}).join("<br />");
|
360
|
-
|
361
|
-
// Get all of the row:col in the message.
|
362
|
-
var errorLines = lines.filter(function(line) {
|
363
|
-
return line.indexOf("--> <anon>") !== -1;
|
364
|
-
}).map(function(line) {
|
365
|
-
var lineIndex = line.indexOf(":");
|
366
|
-
if (lineIndex !== -1) {
|
367
|
-
return line.slice(lineIndex);
|
368
|
-
}
|
369
|
-
|
370
|
-
return "";
|
371
|
-
}).filter(function(line) {
|
372
|
-
return line != "";
|
373
|
-
});
|
374
|
-
|
375
|
-
// Setting message
|
376
|
-
displayOutput(cleanMessage, editor.getValue());
|
377
|
-
|
378
|
-
// Highlighting the lines
|
379
|
-
var ranges = parseProblems(errorLines);
|
380
|
-
markers = ranges.map(function(range) {
|
381
|
-
return editor.getSession().addMarker(range, "ace-" + problem + "-line",
|
382
|
-
"fullLine", false);
|
383
|
-
});
|
384
|
-
|
385
|
-
// Highlighting the specific text
|
386
|
-
markers = markers.concat(ranges.map(function(range) {
|
387
|
-
return editor.getSession().addMarker(range, "ace-" + problem + "-text",
|
388
|
-
"text", false);
|
389
|
-
}));
|
390
|
-
}
|
391
|
-
|
392
|
-
// Parses a problem message returning a list of ranges (row:col, row:col) where
|
393
|
-
// problems in the code have occured.
|
394
|
-
function parseProblems(lines) {
|
395
|
-
var ranges = [];
|
396
|
-
for (var i in lines) {
|
397
|
-
var line = lines[i];
|
398
|
-
var parts = line.split(/:\s?|\s+/, 5).slice(1, 5);
|
399
|
-
var ip = parts.map(function(p) { return parseInt(p, 10) - 1; });
|
400
|
-
console.log("line:", line, parts, ip);
|
401
|
-
ranges.push(new Range(ip[0], ip[1], ip[2], ip[3]));
|
402
|
-
}
|
403
|
-
|
404
|
-
return ranges;
|
405
|
-
}
|
406
|
-
|
407
|
-
// Registering handler for run button click
|
408
|
-
runButton.addEventListener("click", function(ev) {
|
409
|
-
resultDiv.style.display = "block";
|
410
|
-
clearResultDiv();
|
411
|
-
resultDiv.innerHTML = runningMsg;
|
412
|
-
|
413
|
-
// clear previous markers, if any
|
414
|
-
markers.map(function(id) { editor.getSession().removeMarker(id); });
|
415
|
-
|
416
|
-
// Get the code, run the program
|
417
|
-
var program = editor.getValue();
|
418
|
-
runProgram(program, handleResult);
|
419
|
-
});
|
420
|
-
|
421
|
-
// Display an output message and a link to the Rust playground
|
422
|
-
function displayOutput(message, program) {
|
423
|
-
var programUrl = "https://play.rust-lang.org/?code=" +
|
424
|
-
encodeURIComponent(program) + "&run=1";
|
425
|
-
playLink.href = programUrl;
|
426
|
-
|
427
|
-
clearResultDiv(); // clear resultDiv, then add
|
428
|
-
resultDiv.appendChild(playLink); // playLink icon and message
|
429
|
-
resultDiv.innerHTML += message;
|
430
|
-
}
|
431
|
-
|
432
|
-
// Highlight active line when focused
|
433
|
-
editor.on('focus', function() {
|
434
|
-
editor.setHighlightActiveLine(true);
|
435
|
-
});
|
436
|
-
|
437
|
-
// Don't when not
|
438
|
-
editor.on('blur', function() {
|
439
|
-
editor.setHighlightActiveLine(false);
|
440
|
-
});
|
441
|
-
}());
|
442
|
-
|
443
|
-
});
|
444
|
-
});
|
445
|
-
</script>
|
446
|
-
|
447
|
-
|
448
121
|
<footer>
|
449
122
|
<p>Our site in other languages:
|
450
123
|
<a href="/en-US/">English</a>,
|
@@ -462,15 +135,5 @@
|
|
462
135
|
</p>
|
463
136
|
</footer>
|
464
137
|
|
465
|
-
<script>
|
466
|
-
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
467
|
-
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
468
|
-
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
469
|
-
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
470
|
-
|
471
|
-
ga('create', 'UA-58390457-1', 'auto');
|
472
|
-
ga('send', 'pageview');
|
473
|
-
|
474
|
-
</script>
|
475
138
|
</body>
|
476
139
|
</html>
|