specterjs 0.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/lib/javascript/assertions/assert.coffee +4 -0
- data/lib/javascript/specter.js +79 -0
- data/lib/rails/specter.rb +8 -0
- data/lib/rails/specter/application/app/assets/javascripts/exec.js +32 -0
- data/lib/rails/specter/application/app/assets/javascripts/runner.js +65 -0
- data/lib/rails/specter/application/app/assets/javascripts/test.js +104 -0
- data/lib/rails/specter/application/app/assets/javascripts/vendors/prism.js +724 -0
- data/lib/rails/specter/application/app/assets/stylesheets/application.scss +89 -0
- data/lib/rails/specter/application/app/assets/stylesheets/test.scss +37 -0
- data/lib/rails/specter/application/app/assets/stylesheets/tests.scss +76 -0
- data/lib/rails/specter/application/app/assets/stylesheets/variables.scss +11 -0
- data/lib/rails/specter/application/app/assets/stylesheets/vendors/prism.css +197 -0
- data/lib/rails/specter/application/app/controllers/application_controller.rb +2 -0
- data/lib/rails/specter/application/app/controllers/tests_controller.rb +29 -0
- data/lib/rails/specter/application/app/models/specter/test.rb +26 -0
- data/lib/rails/specter/application/app/views/layouts/application.html.erb +24 -0
- data/lib/rails/specter/application/app/views/tests/index.html.erb +19 -0
- data/lib/rails/specter/application/app/views/tests/run.html.erb +20 -0
- data/lib/rails/specter/application/app/views/tests/show.html.erb +14 -0
- data/lib/rails/specter/application/application.rb +13 -0
- data/lib/rails/specter/application/config.ru +12 -0
- data/lib/rails/specter/engine.rb +14 -0
- data/lib/rails/specter/tasks/specter.rake +47 -0
- data/lib/rails/version.rb +3 -0
- data/specter.gemspec +28 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: de9fb4363dbfc4d222dbec9491d7a624d9063f0b
|
4
|
+
data.tar.gz: ca404510a8ea2942318f8c9bed350546fc660268
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6caba7c26f8e95409d936272cc91ddae951928cddad61429fe43e23365944163766429029d4294b677c2c3c7fcec61e040fd0707ec1c7a9678ca2f04c58d42ea
|
7
|
+
data.tar.gz: faebc4e0230cbd2eefb7b840825199bc6e2cee5034dbefe988c009ac90ab10a8b6d98f0b078d97cd6528c6ae5a39e5058ba756567198137669ae08627e1d8ddc
|
data/.gitignore
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
window.Specter = {}
|
2
|
+
|
3
|
+
Specter.compare = function(real, expected) {
|
4
|
+
Specter.removeBlankNode(real)
|
5
|
+
Specter.removeBlankNode(expected)
|
6
|
+
|
7
|
+
Specter.forceTextNodeOnEmptyNode(real)
|
8
|
+
Specter.forceTextNodeOnEmptyNode(expected)
|
9
|
+
|
10
|
+
Specter.compare.element(real, expected)
|
11
|
+
Specter.compare.attributes(real, expected)
|
12
|
+
Specter.compare.textContent(real, expected)
|
13
|
+
|
14
|
+
for (i = 0; i < real.childNodes.length; i++) {
|
15
|
+
Specter.compare(real.childNodes[i], expected.childNodes[i]);
|
16
|
+
}
|
17
|
+
|
18
|
+
return true;
|
19
|
+
}
|
20
|
+
|
21
|
+
Specter.compare.attributes = function(node, expected) {
|
22
|
+
if ((node.attributes || []).length !== (expected.attributes || []).length) {
|
23
|
+
throw new Specter.Error(node, expected, "Not the same number of attributes expected")
|
24
|
+
}
|
25
|
+
|
26
|
+
Array.prototype.forEach.call(expected.attributes || [], function(attribute) {
|
27
|
+
if (node.getAttribute(attribute.name) !== attribute.value) {
|
28
|
+
throw new Specter.Error(node, expected, "Attributes do not match")
|
29
|
+
}
|
30
|
+
})
|
31
|
+
}
|
32
|
+
|
33
|
+
Specter.compare.element = function(node, expected) {
|
34
|
+
if (node.nodeName !== expected.nodeName) {
|
35
|
+
throw new Specter.Error(node, expected, "Unexpected element")
|
36
|
+
}
|
37
|
+
|
38
|
+
if (node.childNodes.length !== expected.childNodes.length) {
|
39
|
+
throw new Specter.Error(node, expected, "Not same amount of children")
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
Specter.compare.textContent = function(node, expected) {
|
44
|
+
if (node.nodeType === Element.TEXT_NODE) {
|
45
|
+
if (node.textContent.trim() !== expected.textContent.trim()) {
|
46
|
+
throw new Specter.Error(node.parentElement, expected.parentElement, "The text content is not identical")
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
Specter.forceTextNodeOnEmptyNode = function(node) {
|
52
|
+
if (node.nodeType === Element.ELEMENT_NODE && node.childNodes.length === 0) {
|
53
|
+
node.appendChild(document.createTextNode(''))
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
Specter.removeBlankNode = function(node) {
|
58
|
+
Array.prototype.forEach.call(node.childNodes || [], function(node) {
|
59
|
+
if (node.textContent.length === 0 || /^\s*$/.test(node.textContent)) {
|
60
|
+
node.remove();
|
61
|
+
}
|
62
|
+
})
|
63
|
+
}
|
64
|
+
|
65
|
+
Specter.Error = function(node, expected, message) {
|
66
|
+
this.expected = expected
|
67
|
+
|
68
|
+
this.node = function() {
|
69
|
+
return node
|
70
|
+
}
|
71
|
+
|
72
|
+
this.message = function() {
|
73
|
+
return message
|
74
|
+
}
|
75
|
+
|
76
|
+
this.toString = function() {
|
77
|
+
return this.message()
|
78
|
+
}
|
79
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
test.setup()
|
2
|
+
var run = function() {
|
3
|
+
test.execute()
|
4
|
+
|
5
|
+
var wrapper = document.getElementById('SpecterTestWrapper')
|
6
|
+
var real = wrapper.children[0]
|
7
|
+
var expected = wrapper.children[1]
|
8
|
+
expected.remove()
|
9
|
+
|
10
|
+
try {
|
11
|
+
Specter.compare(real, expected)
|
12
|
+
if (window.parent !== undefined) {
|
13
|
+
window.parent.postMessage({
|
14
|
+
status: 'success'
|
15
|
+
}, '*')
|
16
|
+
} else {
|
17
|
+
console.log("Test successfully complete")
|
18
|
+
}
|
19
|
+
} catch(e) {
|
20
|
+
if (window.parent !== undefined) {
|
21
|
+
window.parent.postMessage({
|
22
|
+
status: 'error',
|
23
|
+
error: e.toString(),
|
24
|
+
html: e.node().outerHTML
|
25
|
+
}, '*')
|
26
|
+
} else {
|
27
|
+
console.log(e)
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
window.setTimeout(run, 50)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Runner {
|
2
|
+
constructor(button, tests) {
|
3
|
+
if (!button || tests.length === 0) {
|
4
|
+
return;
|
5
|
+
}
|
6
|
+
|
7
|
+
|
8
|
+
this.button = button
|
9
|
+
this.tests = tests
|
10
|
+
|
11
|
+
this.button.addEventListener('click', this.start.bind(this));
|
12
|
+
window.addEventListener('message', this.received.bind(this), false);
|
13
|
+
}
|
14
|
+
|
15
|
+
start() {
|
16
|
+
if (this.button.dataset.running !== 'false') {
|
17
|
+
return
|
18
|
+
}
|
19
|
+
|
20
|
+
this.button.dataset.running = 'true'
|
21
|
+
this.count = this.tests.length
|
22
|
+
|
23
|
+
for (var i = 0; i < this.count; i++) {
|
24
|
+
var test = this.tests[i]
|
25
|
+
test.querySelector('div.status').textContent = ''
|
26
|
+
test.dataset.status = 'started'
|
27
|
+
var iframe = document.createElement('iframe')
|
28
|
+
iframe.src = test.dataset.run
|
29
|
+
iframe.style.display = 'none'
|
30
|
+
document.body.appendChild(iframe)
|
31
|
+
|
32
|
+
test.iframe = iframe
|
33
|
+
iframe.contentWindow.listElement = test
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
received(message) {
|
38
|
+
var test = message.source.listElement
|
39
|
+
|
40
|
+
if (message.data.status === 'success') {
|
41
|
+
test.dataset.status = 'success'
|
42
|
+
var status = test.querySelector('div.status')
|
43
|
+
status.textContent = status.dataset.success
|
44
|
+
}
|
45
|
+
else {
|
46
|
+
test.dataset.status = 'failed'
|
47
|
+
var status = test.querySelector('div.status')
|
48
|
+
status.textContent = status.dataset.error
|
49
|
+
var span = test.querySelector('div > span')
|
50
|
+
span.textContent = message.data.error
|
51
|
+
}
|
52
|
+
|
53
|
+
this.count -= 1
|
54
|
+
message.source.listElement.iframe.remove()
|
55
|
+
|
56
|
+
if (this.count <= 0) {
|
57
|
+
this.button.dataset.running = 'false'
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
(function(window, document) {
|
63
|
+
window.Runner = new Runner(document.querySelector('#Run'), document.querySelectorAll('.test'))
|
64
|
+
|
65
|
+
})(window, document)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
//= require 'vendors/prism.js'
|
2
|
+
|
3
|
+
(function(window, document) {
|
4
|
+
class Test {
|
5
|
+
constructor(element) {
|
6
|
+
window.addEventListener('message', this.received.bind(this), false);
|
7
|
+
}
|
8
|
+
|
9
|
+
received(message) {
|
10
|
+
var result = this.readableOutput(message.source.document.querySelector('#SpecterTestWrapper > div').innerHTML)
|
11
|
+
var container = document.querySelector('#Test section.result')
|
12
|
+
var header = container.querySelector('header')
|
13
|
+
var code = container.querySelector('pre.real > code')
|
14
|
+
var pres = container.querySelectorAll('pre')
|
15
|
+
var line = this.lineNumber(result, message.data.html)
|
16
|
+
|
17
|
+
code.textContent = result
|
18
|
+
|
19
|
+
if (message.data.status == 'error') {
|
20
|
+
Array.prototype.forEach.call(pres, function(pre) {
|
21
|
+
pre.dataset.line = line
|
22
|
+
}, this)
|
23
|
+
|
24
|
+
header.classList.add('error')
|
25
|
+
header.querySelector('div.status').textContent = "❌"
|
26
|
+
header.querySelector('div.message').textContent = message.data.error
|
27
|
+
}
|
28
|
+
else {
|
29
|
+
header.classList.add('success')
|
30
|
+
header.querySelector('div.status').textContent = "✓"
|
31
|
+
header.querySelector('div.message').textContent = "The test has passed"
|
32
|
+
container.querySelector('pre.expected').remove()
|
33
|
+
}
|
34
|
+
Array.prototype.forEach.call(pres, function(pre) {
|
35
|
+
this.parse(pre)
|
36
|
+
}, this)
|
37
|
+
}
|
38
|
+
|
39
|
+
readableOutput(str) {
|
40
|
+
return str.replace(/></gi, ">\n<").trim()
|
41
|
+
}
|
42
|
+
|
43
|
+
lineNumber(code, str) {
|
44
|
+
var num = 1
|
45
|
+
|
46
|
+
code.split("\n").forEach(function(text, line) {
|
47
|
+
if (str == text) {
|
48
|
+
num = line
|
49
|
+
}
|
50
|
+
})
|
51
|
+
|
52
|
+
return num
|
53
|
+
}
|
54
|
+
|
55
|
+
parse(pre) {
|
56
|
+
var code = pre.querySelector('code')
|
57
|
+
var line = parseInt(pre.dataset.line)
|
58
|
+
|
59
|
+
Prism.highlightElement(code)
|
60
|
+
|
61
|
+
if (line > 0) {
|
62
|
+
this.showErrorOnLineNumber(code, line)
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
showErrorOnLineNumber(code, lineNumber) {
|
67
|
+
var elements = []
|
68
|
+
var error = document.createElement('span')
|
69
|
+
var count = 0
|
70
|
+
|
71
|
+
for (var i = 0; i < code.childNodes.length && count <= lineNumber; i++) {
|
72
|
+
var node = code.childNodes[i]
|
73
|
+
|
74
|
+
if (count == lineNumber) {
|
75
|
+
elements.push(node)
|
76
|
+
}
|
77
|
+
|
78
|
+
if (node.nodeType === node.TEXT_NODE && node.textContent === "\n") {
|
79
|
+
count += 1
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
if (elements.length === 0) {
|
84
|
+
return
|
85
|
+
}
|
86
|
+
|
87
|
+
error.classList.add('error')
|
88
|
+
code.insertBefore(error, elements[0])
|
89
|
+
|
90
|
+
for (var i = 0; i < elements.length; i++) {
|
91
|
+
error.appendChild(elements[i])
|
92
|
+
}
|
93
|
+
|
94
|
+
code.querySelector('span.line-numbers-rows').children[lineNumber].classList.add('error')
|
95
|
+
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
var element = document.getElementById('Test')
|
100
|
+
if (element) {
|
101
|
+
window.Test = new Test(element)
|
102
|
+
}
|
103
|
+
|
104
|
+
})(window, document)
|
@@ -0,0 +1,724 @@
|
|
1
|
+
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript&plugins=line-numbers */
|
2
|
+
var _self = (typeof window !== 'undefined')
|
3
|
+
? window // if in browser
|
4
|
+
: (
|
5
|
+
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
6
|
+
? self // if in worker
|
7
|
+
: {} // if in node js
|
8
|
+
);
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Prism: Lightweight, robust, elegant syntax highlighting
|
12
|
+
* MIT license http://www.opensource.org/licenses/mit-license.php/
|
13
|
+
* @author Lea Verou http://lea.verou.me
|
14
|
+
*/
|
15
|
+
|
16
|
+
var Prism = (function(){
|
17
|
+
|
18
|
+
// Private helper vars
|
19
|
+
var lang = /\blang(?:uage)?-(\w+)\b/i;
|
20
|
+
var uniqueId = 0;
|
21
|
+
|
22
|
+
var _ = _self.Prism = {
|
23
|
+
util: {
|
24
|
+
encode: function (tokens) {
|
25
|
+
if (tokens instanceof Token) {
|
26
|
+
return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
|
27
|
+
} else if (_.util.type(tokens) === 'Array') {
|
28
|
+
return tokens.map(_.util.encode);
|
29
|
+
} else {
|
30
|
+
return tokens.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
31
|
+
}
|
32
|
+
},
|
33
|
+
|
34
|
+
type: function (o) {
|
35
|
+
return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
|
36
|
+
},
|
37
|
+
|
38
|
+
objId: function (obj) {
|
39
|
+
if (!obj['__id']) {
|
40
|
+
Object.defineProperty(obj, '__id', { value: ++uniqueId });
|
41
|
+
}
|
42
|
+
return obj['__id'];
|
43
|
+
},
|
44
|
+
|
45
|
+
// Deep clone a language definition (e.g. to extend it)
|
46
|
+
clone: function (o) {
|
47
|
+
var type = _.util.type(o);
|
48
|
+
|
49
|
+
switch (type) {
|
50
|
+
case 'Object':
|
51
|
+
var clone = {};
|
52
|
+
|
53
|
+
for (var key in o) {
|
54
|
+
if (o.hasOwnProperty(key)) {
|
55
|
+
clone[key] = _.util.clone(o[key]);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
return clone;
|
60
|
+
|
61
|
+
case 'Array':
|
62
|
+
// Check for existence for IE8
|
63
|
+
return o.map && o.map(function(v) { return _.util.clone(v); });
|
64
|
+
}
|
65
|
+
|
66
|
+
return o;
|
67
|
+
}
|
68
|
+
},
|
69
|
+
|
70
|
+
languages: {
|
71
|
+
extend: function (id, redef) {
|
72
|
+
var lang = _.util.clone(_.languages[id]);
|
73
|
+
|
74
|
+
for (var key in redef) {
|
75
|
+
lang[key] = redef[key];
|
76
|
+
}
|
77
|
+
|
78
|
+
return lang;
|
79
|
+
},
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Insert a token before another token in a language literal
|
83
|
+
* As this needs to recreate the object (we cannot actually insert before keys in object literals),
|
84
|
+
* we cannot just provide an object, we need anobject and a key.
|
85
|
+
* @param inside The key (or language id) of the parent
|
86
|
+
* @param before The key to insert before. If not provided, the function appends instead.
|
87
|
+
* @param insert Object with the key/value pairs to insert
|
88
|
+
* @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
|
89
|
+
*/
|
90
|
+
insertBefore: function (inside, before, insert, root) {
|
91
|
+
root = root || _.languages;
|
92
|
+
var grammar = root[inside];
|
93
|
+
|
94
|
+
if (arguments.length == 2) {
|
95
|
+
insert = arguments[1];
|
96
|
+
|
97
|
+
for (var newToken in insert) {
|
98
|
+
if (insert.hasOwnProperty(newToken)) {
|
99
|
+
grammar[newToken] = insert[newToken];
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
return grammar;
|
104
|
+
}
|
105
|
+
|
106
|
+
var ret = {};
|
107
|
+
|
108
|
+
for (var token in grammar) {
|
109
|
+
|
110
|
+
if (grammar.hasOwnProperty(token)) {
|
111
|
+
|
112
|
+
if (token == before) {
|
113
|
+
|
114
|
+
for (var newToken in insert) {
|
115
|
+
|
116
|
+
if (insert.hasOwnProperty(newToken)) {
|
117
|
+
ret[newToken] = insert[newToken];
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
ret[token] = grammar[token];
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
// Update references in other language definitions
|
127
|
+
_.languages.DFS(_.languages, function(key, value) {
|
128
|
+
if (value === root[inside] && key != inside) {
|
129
|
+
this[key] = ret;
|
130
|
+
}
|
131
|
+
});
|
132
|
+
|
133
|
+
return root[inside] = ret;
|
134
|
+
},
|
135
|
+
|
136
|
+
// Traverse a language definition with Depth First Search
|
137
|
+
DFS: function(o, callback, type, visited) {
|
138
|
+
visited = visited || {};
|
139
|
+
for (var i in o) {
|
140
|
+
if (o.hasOwnProperty(i)) {
|
141
|
+
callback.call(o, i, o[i], type || i);
|
142
|
+
|
143
|
+
if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
|
144
|
+
visited[_.util.objId(o[i])] = true;
|
145
|
+
_.languages.DFS(o[i], callback, null, visited);
|
146
|
+
}
|
147
|
+
else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
|
148
|
+
visited[_.util.objId(o[i])] = true;
|
149
|
+
_.languages.DFS(o[i], callback, i, visited);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}
|
154
|
+
},
|
155
|
+
plugins: {},
|
156
|
+
|
157
|
+
highlightAll: function(async, callback) {
|
158
|
+
var env = {
|
159
|
+
callback: callback,
|
160
|
+
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
|
161
|
+
};
|
162
|
+
|
163
|
+
_.hooks.run("before-highlightall", env);
|
164
|
+
|
165
|
+
var elements = env.elements || document.querySelectorAll(env.selector);
|
166
|
+
|
167
|
+
for (var i=0, element; element = elements[i++];) {
|
168
|
+
_.highlightElement(element, async === true, env.callback);
|
169
|
+
}
|
170
|
+
},
|
171
|
+
|
172
|
+
highlightElement: function(element, async, callback) {
|
173
|
+
// Find language
|
174
|
+
var language, grammar, parent = element;
|
175
|
+
|
176
|
+
while (parent && !lang.test(parent.className)) {
|
177
|
+
parent = parent.parentNode;
|
178
|
+
}
|
179
|
+
|
180
|
+
if (parent) {
|
181
|
+
language = (parent.className.match(lang) || [,''])[1];
|
182
|
+
grammar = _.languages[language];
|
183
|
+
}
|
184
|
+
|
185
|
+
// Set language on the element, if not present
|
186
|
+
element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
|
187
|
+
|
188
|
+
// Set language on the parent, for styling
|
189
|
+
parent = element.parentNode;
|
190
|
+
|
191
|
+
if (/pre/i.test(parent.nodeName)) {
|
192
|
+
parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
|
193
|
+
}
|
194
|
+
|
195
|
+
var code = element.textContent;
|
196
|
+
|
197
|
+
var env = {
|
198
|
+
element: element,
|
199
|
+
language: language,
|
200
|
+
grammar: grammar,
|
201
|
+
code: code
|
202
|
+
};
|
203
|
+
|
204
|
+
if (!code || !grammar) {
|
205
|
+
_.hooks.run('complete', env);
|
206
|
+
return;
|
207
|
+
}
|
208
|
+
|
209
|
+
_.hooks.run('before-highlight', env);
|
210
|
+
|
211
|
+
if (async && _self.Worker) {
|
212
|
+
var worker = new Worker(_.filename);
|
213
|
+
|
214
|
+
worker.onmessage = function(evt) {
|
215
|
+
env.highlightedCode = evt.data;
|
216
|
+
|
217
|
+
_.hooks.run('before-insert', env);
|
218
|
+
|
219
|
+
env.element.innerHTML = env.highlightedCode;
|
220
|
+
|
221
|
+
callback && callback.call(env.element);
|
222
|
+
_.hooks.run('after-highlight', env);
|
223
|
+
_.hooks.run('complete', env);
|
224
|
+
};
|
225
|
+
|
226
|
+
worker.postMessage(JSON.stringify({
|
227
|
+
language: env.language,
|
228
|
+
code: env.code,
|
229
|
+
immediateClose: true
|
230
|
+
}));
|
231
|
+
}
|
232
|
+
else {
|
233
|
+
env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
|
234
|
+
|
235
|
+
_.hooks.run('before-insert', env);
|
236
|
+
|
237
|
+
env.element.innerHTML = env.highlightedCode;
|
238
|
+
|
239
|
+
callback && callback.call(element);
|
240
|
+
|
241
|
+
_.hooks.run('after-highlight', env);
|
242
|
+
_.hooks.run('complete', env);
|
243
|
+
}
|
244
|
+
},
|
245
|
+
|
246
|
+
highlight: function (text, grammar, language) {
|
247
|
+
var tokens = _.tokenize(text, grammar);
|
248
|
+
return Token.stringify(_.util.encode(tokens), language);
|
249
|
+
},
|
250
|
+
|
251
|
+
tokenize: function(text, grammar, language) {
|
252
|
+
var Token = _.Token;
|
253
|
+
|
254
|
+
var strarr = [text];
|
255
|
+
|
256
|
+
var rest = grammar.rest;
|
257
|
+
|
258
|
+
if (rest) {
|
259
|
+
for (var token in rest) {
|
260
|
+
grammar[token] = rest[token];
|
261
|
+
}
|
262
|
+
|
263
|
+
delete grammar.rest;
|
264
|
+
}
|
265
|
+
|
266
|
+
tokenloop: for (var token in grammar) {
|
267
|
+
if(!grammar.hasOwnProperty(token) || !grammar[token]) {
|
268
|
+
continue;
|
269
|
+
}
|
270
|
+
|
271
|
+
var patterns = grammar[token];
|
272
|
+
patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
|
273
|
+
|
274
|
+
for (var j = 0; j < patterns.length; ++j) {
|
275
|
+
var pattern = patterns[j],
|
276
|
+
inside = pattern.inside,
|
277
|
+
lookbehind = !!pattern.lookbehind,
|
278
|
+
greedy = !!pattern.greedy,
|
279
|
+
lookbehindLength = 0,
|
280
|
+
alias = pattern.alias;
|
281
|
+
|
282
|
+
pattern = pattern.pattern || pattern;
|
283
|
+
|
284
|
+
for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop
|
285
|
+
|
286
|
+
var str = strarr[i];
|
287
|
+
|
288
|
+
if (strarr.length > text.length) {
|
289
|
+
// Something went terribly wrong, ABORT, ABORT!
|
290
|
+
break tokenloop;
|
291
|
+
}
|
292
|
+
|
293
|
+
if (str instanceof Token) {
|
294
|
+
continue;
|
295
|
+
}
|
296
|
+
|
297
|
+
pattern.lastIndex = 0;
|
298
|
+
|
299
|
+
var match = pattern.exec(str),
|
300
|
+
delNum = 1;
|
301
|
+
|
302
|
+
// Greedy patterns can override/remove up to two previously matched tokens
|
303
|
+
if (!match && greedy && i != strarr.length - 1) {
|
304
|
+
// Reconstruct the original text using the next two tokens
|
305
|
+
var nextToken = strarr[i + 1].matchedStr || strarr[i + 1],
|
306
|
+
combStr = str + nextToken;
|
307
|
+
|
308
|
+
if (i < strarr.length - 2) {
|
309
|
+
combStr += strarr[i + 2].matchedStr || strarr[i + 2];
|
310
|
+
}
|
311
|
+
|
312
|
+
// Try the pattern again on the reconstructed text
|
313
|
+
pattern.lastIndex = 0;
|
314
|
+
match = pattern.exec(combStr);
|
315
|
+
if (!match) {
|
316
|
+
continue;
|
317
|
+
}
|
318
|
+
|
319
|
+
var from = match.index + (lookbehind ? match[1].length : 0);
|
320
|
+
// To be a valid candidate, the new match has to start inside of str
|
321
|
+
if (from >= str.length) {
|
322
|
+
continue;
|
323
|
+
}
|
324
|
+
var to = match.index + match[0].length,
|
325
|
+
len = str.length + nextToken.length;
|
326
|
+
|
327
|
+
// Number of tokens to delete and replace with the new match
|
328
|
+
delNum = 3;
|
329
|
+
|
330
|
+
if (to <= len) {
|
331
|
+
if (strarr[i + 1].greedy) {
|
332
|
+
continue;
|
333
|
+
}
|
334
|
+
delNum = 2;
|
335
|
+
combStr = combStr.slice(0, len);
|
336
|
+
}
|
337
|
+
str = combStr;
|
338
|
+
}
|
339
|
+
|
340
|
+
if (!match) {
|
341
|
+
continue;
|
342
|
+
}
|
343
|
+
|
344
|
+
if(lookbehind) {
|
345
|
+
lookbehindLength = match[1].length;
|
346
|
+
}
|
347
|
+
|
348
|
+
var from = match.index + lookbehindLength,
|
349
|
+
match = match[0].slice(lookbehindLength),
|
350
|
+
to = from + match.length,
|
351
|
+
before = str.slice(0, from),
|
352
|
+
after = str.slice(to);
|
353
|
+
|
354
|
+
var args = [i, delNum];
|
355
|
+
|
356
|
+
if (before) {
|
357
|
+
args.push(before);
|
358
|
+
}
|
359
|
+
|
360
|
+
var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
|
361
|
+
|
362
|
+
args.push(wrapped);
|
363
|
+
|
364
|
+
if (after) {
|
365
|
+
args.push(after);
|
366
|
+
}
|
367
|
+
|
368
|
+
Array.prototype.splice.apply(strarr, args);
|
369
|
+
}
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
return strarr;
|
374
|
+
},
|
375
|
+
|
376
|
+
hooks: {
|
377
|
+
all: {},
|
378
|
+
|
379
|
+
add: function (name, callback) {
|
380
|
+
var hooks = _.hooks.all;
|
381
|
+
|
382
|
+
hooks[name] = hooks[name] || [];
|
383
|
+
|
384
|
+
hooks[name].push(callback);
|
385
|
+
},
|
386
|
+
|
387
|
+
run: function (name, env) {
|
388
|
+
var callbacks = _.hooks.all[name];
|
389
|
+
|
390
|
+
if (!callbacks || !callbacks.length) {
|
391
|
+
return;
|
392
|
+
}
|
393
|
+
|
394
|
+
for (var i=0, callback; callback = callbacks[i++];) {
|
395
|
+
callback(env);
|
396
|
+
}
|
397
|
+
}
|
398
|
+
}
|
399
|
+
};
|
400
|
+
|
401
|
+
var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
|
402
|
+
this.type = type;
|
403
|
+
this.content = content;
|
404
|
+
this.alias = alias;
|
405
|
+
// Copy of the full string this token was created from
|
406
|
+
this.matchedStr = matchedStr || null;
|
407
|
+
this.greedy = !!greedy;
|
408
|
+
};
|
409
|
+
|
410
|
+
Token.stringify = function(o, language, parent) {
|
411
|
+
if (typeof o == 'string') {
|
412
|
+
return o;
|
413
|
+
}
|
414
|
+
|
415
|
+
if (_.util.type(o) === 'Array') {
|
416
|
+
return o.map(function(element) {
|
417
|
+
return Token.stringify(element, language, o);
|
418
|
+
}).join('');
|
419
|
+
}
|
420
|
+
|
421
|
+
var env = {
|
422
|
+
type: o.type,
|
423
|
+
content: Token.stringify(o.content, language, parent),
|
424
|
+
tag: 'span',
|
425
|
+
classes: ['token', o.type],
|
426
|
+
attributes: {},
|
427
|
+
language: language,
|
428
|
+
parent: parent
|
429
|
+
};
|
430
|
+
|
431
|
+
if (env.type == 'comment') {
|
432
|
+
env.attributes['spellcheck'] = 'true';
|
433
|
+
}
|
434
|
+
|
435
|
+
if (o.alias) {
|
436
|
+
var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
|
437
|
+
Array.prototype.push.apply(env.classes, aliases);
|
438
|
+
}
|
439
|
+
|
440
|
+
_.hooks.run('wrap', env);
|
441
|
+
|
442
|
+
var attributes = '';
|
443
|
+
|
444
|
+
for (var name in env.attributes) {
|
445
|
+
attributes += (attributes ? ' ' : '') + name + '="' + (env.attributes[name] || '') + '"';
|
446
|
+
}
|
447
|
+
|
448
|
+
return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>';
|
449
|
+
|
450
|
+
};
|
451
|
+
|
452
|
+
if (!_self.document) {
|
453
|
+
if (!_self.addEventListener) {
|
454
|
+
// in Node.js
|
455
|
+
return _self.Prism;
|
456
|
+
}
|
457
|
+
// In worker
|
458
|
+
_self.addEventListener('message', function(evt) {
|
459
|
+
var message = JSON.parse(evt.data),
|
460
|
+
lang = message.language,
|
461
|
+
code = message.code,
|
462
|
+
immediateClose = message.immediateClose;
|
463
|
+
|
464
|
+
_self.postMessage(_.highlight(code, _.languages[lang], lang));
|
465
|
+
if (immediateClose) {
|
466
|
+
_self.close();
|
467
|
+
}
|
468
|
+
}, false);
|
469
|
+
|
470
|
+
return _self.Prism;
|
471
|
+
}
|
472
|
+
|
473
|
+
//Get current script and highlight
|
474
|
+
var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
|
475
|
+
|
476
|
+
if (script) {
|
477
|
+
_.filename = script.src;
|
478
|
+
|
479
|
+
if (document.addEventListener && !script.hasAttribute('data-manual')) {
|
480
|
+
document.addEventListener('DOMContentLoaded', _.highlightAll);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
|
484
|
+
return _self.Prism;
|
485
|
+
|
486
|
+
})();
|
487
|
+
|
488
|
+
if (typeof module !== 'undefined' && module.exports) {
|
489
|
+
module.exports = Prism;
|
490
|
+
}
|
491
|
+
|
492
|
+
// hack for components to work correctly in node.js
|
493
|
+
if (typeof global !== 'undefined') {
|
494
|
+
global.Prism = Prism;
|
495
|
+
}
|
496
|
+
;
|
497
|
+
Prism.languages.markup = {
|
498
|
+
'comment': /<!--[\w\W]*?-->/,
|
499
|
+
'prolog': /<\?[\w\W]+?\?>/,
|
500
|
+
'doctype': /<!DOCTYPE[\w\W]+?>/,
|
501
|
+
'cdata': /<!\[CDATA\[[\w\W]*?]]>/i,
|
502
|
+
'tag': {
|
503
|
+
pattern: /<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,
|
504
|
+
inside: {
|
505
|
+
'tag': {
|
506
|
+
pattern: /^<\/?[^\s>\/]+/i,
|
507
|
+
inside: {
|
508
|
+
'punctuation': /^<\/?/,
|
509
|
+
'namespace': /^[^\s>\/:]+:/
|
510
|
+
}
|
511
|
+
},
|
512
|
+
'attr-value': {
|
513
|
+
pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,
|
514
|
+
inside: {
|
515
|
+
'punctuation': /[=>"']/
|
516
|
+
}
|
517
|
+
},
|
518
|
+
'punctuation': /\/?>/,
|
519
|
+
'attr-name': {
|
520
|
+
pattern: /[^\s>\/]+/,
|
521
|
+
inside: {
|
522
|
+
'namespace': /^[^\s>\/:]+:/
|
523
|
+
}
|
524
|
+
}
|
525
|
+
|
526
|
+
}
|
527
|
+
},
|
528
|
+
'entity': /&#?[\da-z]{1,8};/i
|
529
|
+
};
|
530
|
+
|
531
|
+
// Plugin to make entity title show the real entity, idea by Roman Komarov
|
532
|
+
Prism.hooks.add('wrap', function(env) {
|
533
|
+
|
534
|
+
if (env.type === 'entity') {
|
535
|
+
env.attributes['title'] = env.content.replace(/&/, '&');
|
536
|
+
}
|
537
|
+
});
|
538
|
+
|
539
|
+
Prism.languages.xml = Prism.languages.markup;
|
540
|
+
Prism.languages.html = Prism.languages.markup;
|
541
|
+
Prism.languages.mathml = Prism.languages.markup;
|
542
|
+
Prism.languages.svg = Prism.languages.markup;
|
543
|
+
|
544
|
+
Prism.languages.css = {
|
545
|
+
'comment': /\/\*[\w\W]*?\*\//,
|
546
|
+
'atrule': {
|
547
|
+
pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i,
|
548
|
+
inside: {
|
549
|
+
'rule': /@[\w-]+/
|
550
|
+
// See rest below
|
551
|
+
}
|
552
|
+
},
|
553
|
+
'url': /url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,
|
554
|
+
'selector': /[^\{\}\s][^\{\};]*?(?=\s*\{)/,
|
555
|
+
'string': /("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,
|
556
|
+
'property': /(\b|\B)[\w-]+(?=\s*:)/i,
|
557
|
+
'important': /\B!important\b/i,
|
558
|
+
'function': /[-a-z0-9]+(?=\()/i,
|
559
|
+
'punctuation': /[(){};:]/
|
560
|
+
};
|
561
|
+
|
562
|
+
Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css);
|
563
|
+
|
564
|
+
if (Prism.languages.markup) {
|
565
|
+
Prism.languages.insertBefore('markup', 'tag', {
|
566
|
+
'style': {
|
567
|
+
pattern: /(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,
|
568
|
+
lookbehind: true,
|
569
|
+
inside: Prism.languages.css,
|
570
|
+
alias: 'language-css'
|
571
|
+
}
|
572
|
+
});
|
573
|
+
|
574
|
+
Prism.languages.insertBefore('inside', 'attr-value', {
|
575
|
+
'style-attr': {
|
576
|
+
pattern: /\s*style=("|').*?\1/i,
|
577
|
+
inside: {
|
578
|
+
'attr-name': {
|
579
|
+
pattern: /^\s*style/i,
|
580
|
+
inside: Prism.languages.markup.tag.inside
|
581
|
+
},
|
582
|
+
'punctuation': /^\s*=\s*['"]|['"]\s*$/,
|
583
|
+
'attr-value': {
|
584
|
+
pattern: /.+/i,
|
585
|
+
inside: Prism.languages.css
|
586
|
+
}
|
587
|
+
},
|
588
|
+
alias: 'language-css'
|
589
|
+
}
|
590
|
+
}, Prism.languages.markup.tag);
|
591
|
+
};
|
592
|
+
Prism.languages.clike = {
|
593
|
+
'comment': [
|
594
|
+
{
|
595
|
+
pattern: /(^|[^\\])\/\*[\w\W]*?\*\//,
|
596
|
+
lookbehind: true
|
597
|
+
},
|
598
|
+
{
|
599
|
+
pattern: /(^|[^\\:])\/\/.*/,
|
600
|
+
lookbehind: true
|
601
|
+
}
|
602
|
+
],
|
603
|
+
'string': {
|
604
|
+
pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
|
605
|
+
greedy: true
|
606
|
+
},
|
607
|
+
'class-name': {
|
608
|
+
pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,
|
609
|
+
lookbehind: true,
|
610
|
+
inside: {
|
611
|
+
punctuation: /(\.|\\)/
|
612
|
+
}
|
613
|
+
},
|
614
|
+
'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
|
615
|
+
'boolean': /\b(true|false)\b/,
|
616
|
+
'function': /[a-z0-9_]+(?=\()/i,
|
617
|
+
'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,
|
618
|
+
'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,
|
619
|
+
'punctuation': /[{}[\];(),.:]/
|
620
|
+
};
|
621
|
+
|
622
|
+
Prism.languages.javascript = Prism.languages.extend('clike', {
|
623
|
+
'keyword': /\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,
|
624
|
+
'number': /\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,
|
625
|
+
// Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444)
|
626
|
+
'function': /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i
|
627
|
+
});
|
628
|
+
|
629
|
+
Prism.languages.insertBefore('javascript', 'keyword', {
|
630
|
+
'regex': {
|
631
|
+
pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,
|
632
|
+
lookbehind: true,
|
633
|
+
greedy: true
|
634
|
+
}
|
635
|
+
});
|
636
|
+
|
637
|
+
Prism.languages.insertBefore('javascript', 'class-name', {
|
638
|
+
'template-string': {
|
639
|
+
pattern: /`(?:\\\\|\\?[^\\])*?`/,
|
640
|
+
greedy: true,
|
641
|
+
inside: {
|
642
|
+
'interpolation': {
|
643
|
+
pattern: /\$\{[^}]+\}/,
|
644
|
+
inside: {
|
645
|
+
'interpolation-punctuation': {
|
646
|
+
pattern: /^\$\{|\}$/,
|
647
|
+
alias: 'punctuation'
|
648
|
+
},
|
649
|
+
rest: Prism.languages.javascript
|
650
|
+
}
|
651
|
+
},
|
652
|
+
'string': /[\s\S]+/
|
653
|
+
}
|
654
|
+
}
|
655
|
+
});
|
656
|
+
|
657
|
+
if (Prism.languages.markup) {
|
658
|
+
Prism.languages.insertBefore('markup', 'tag', {
|
659
|
+
'script': {
|
660
|
+
pattern: /(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,
|
661
|
+
lookbehind: true,
|
662
|
+
inside: Prism.languages.javascript,
|
663
|
+
alias: 'language-javascript'
|
664
|
+
}
|
665
|
+
});
|
666
|
+
}
|
667
|
+
|
668
|
+
Prism.languages.js = Prism.languages.javascript;
|
669
|
+
(function() {
|
670
|
+
|
671
|
+
if (typeof self === 'undefined' || !self.Prism || !self.document) {
|
672
|
+
return;
|
673
|
+
}
|
674
|
+
|
675
|
+
Prism.hooks.add('complete', function (env) {
|
676
|
+
if (!env.code) {
|
677
|
+
return;
|
678
|
+
}
|
679
|
+
|
680
|
+
// works only for <code> wrapped inside <pre> (not inline)
|
681
|
+
var pre = env.element.parentNode;
|
682
|
+
var clsReg = /\s*\bline-numbers\b\s*/;
|
683
|
+
if (
|
684
|
+
!pre || !/pre/i.test(pre.nodeName) ||
|
685
|
+
// Abort only if nor the <pre> nor the <code> have the class
|
686
|
+
(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
|
687
|
+
) {
|
688
|
+
return;
|
689
|
+
}
|
690
|
+
|
691
|
+
if (env.element.querySelector(".line-numbers-rows")) {
|
692
|
+
// Abort if line numbers already exists
|
693
|
+
return;
|
694
|
+
}
|
695
|
+
|
696
|
+
if (clsReg.test(env.element.className)) {
|
697
|
+
// Remove the class "line-numbers" from the <code>
|
698
|
+
env.element.className = env.element.className.replace(clsReg, '');
|
699
|
+
}
|
700
|
+
if (!clsReg.test(pre.className)) {
|
701
|
+
// Add the class "line-numbers" to the <pre>
|
702
|
+
pre.className += ' line-numbers';
|
703
|
+
}
|
704
|
+
|
705
|
+
var match = env.code.match(/\n(?!$)/g);
|
706
|
+
var linesNum = match ? match.length + 1 : 1;
|
707
|
+
var lineNumbersWrapper;
|
708
|
+
|
709
|
+
var lines = new Array(linesNum + 1);
|
710
|
+
lines = lines.join('<span></span>');
|
711
|
+
|
712
|
+
lineNumbersWrapper = document.createElement('span');
|
713
|
+
lineNumbersWrapper.className = 'line-numbers-rows';
|
714
|
+
lineNumbersWrapper.innerHTML = lines;
|
715
|
+
|
716
|
+
if (pre.hasAttribute('data-start')) {
|
717
|
+
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
|
718
|
+
}
|
719
|
+
|
720
|
+
env.element.appendChild(lineNumbersWrapper);
|
721
|
+
|
722
|
+
});
|
723
|
+
|
724
|
+
}());
|