validate-website 1.9.0 → 1.11.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 +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>
|