-
Notifications
You must be signed in to change notification settings - Fork 119
/
styledocco.js
166 lines (153 loc) · 5.4 KB
/
styledocco.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
'use strict';
var marked = require('marked');
marked.setOptions({ sanitize: false, gfm: true });
// Regular expressions to match comments. We only match comments in
// the beginning of lines.
var commentRegexs = {
single: /^\/\//, // Single line comments for Sass, Less and Stylus
multiStart: /^\/\*/,
multiEnd: /\*\//
};
// Make an URL slug from `str`.
var slugify = function(str) {
return encodeURIComponent(
str.trim().toLowerCase()
.replace(/[^\w ]+/g,'')
.replace(/ +/g,'-')
);
};
// Check if a string is code or a comment (and which type of comment).
var checkType = function(str) {
// Treat multi start and end on same row as a single line comment.
if (str.match(commentRegexs.multiStart) && str.match(commentRegexs.multiEnd)) {
return 'single';
// Checking for multi line comments first to avoid matching single line
// comment symbols inside multi line blocks.
} else if (str.match(commentRegexs.multiStart)) {
return 'multistart';
} else if (str.match(commentRegexs.multiEnd)) {
return 'multiend';
} else if ((commentRegexs.single != null) && str.match(commentRegexs.single)) {
return 'single';
} else {
return 'code';
}
};
var formatDocs = function(str) {
// Filter out comment symbols
for (var key in commentRegexs) {
str = str.replace(commentRegexs[key], '');
}
return str + '\n';
};
var formatCode = function(str) {
// Truncate base64 encoded strings
return str.replace(/(;base64,)[^\)]*/, '$1...') + '\n';
};
// Trim newlines from beginning and end of a multi line string.
var trimNewLines = function(str) {
return str.replace(/^\n*/, '').replace(/\n*$/, '');
};
var htmlEntities = function(str) {
return str.replace(/</g, '<').replace(/>/g, '>');
};
var separate = function(css) {
var lines = css.split('\n');
var docs, code, line, blocks = [];
while (lines.length) {
docs = code = '';
// First check for any single line comments.
while (lines.length && checkType(lines[0]) === 'single') {
docs += formatDocs(lines.shift());
}
// A multi line comment starts here, add lines until comment ends.
if (lines.length && checkType(lines[0]) === 'multistart') {
while (lines.length) {
line = lines.shift();
docs += formatDocs(line);
if (checkType(line) === 'multiend') break;
}
}
while (lines.length && (checkType(lines[0]) === 'code' || checkType(lines[0]) === 'multiend')) {
code += formatCode(lines.shift());
}
blocks.push({ docs: docs, code: code });
}
return blocks;
};
var makeSections = exports.makeSections = function(blocks) {
return blocks
.map(function(block) {
// Run comments through marked.lexer to get Markdown tokens.
block.docs = marked.lexer(block.docs);
return block;
})
.map(function(block) {
// If we encounter code blocks in documentation, add preview HTML.
var newBlock = {
code: block.code,
docs: block.docs.reduce(function(tokens, token) {
if (token.type === 'code' && (token.lang == null || token.lang === 'html')) {
token.type = 'html';
token.pre = true;
token.text = '<textarea class="preview-code" spellcheck="false">' + htmlEntities(token.text) + '</textarea>';
// Add permalink `id`s and some custom properties to headings.
} else if (token.type === 'heading') {
var slug = slugify(token.text);
token.type = 'html';
token._slug = slug;
token._origText = token.text;
// This token should start a new doc section
if (token.depth === 1) token._split = true;
token.text = '<h' + token.depth + ' id="' + slug + '">' +
token.text + '</h' + token.depth + '>\n';
}
tokens.push(token);
return tokens;
}, [])
};
// Keep marked's custom links property on the docs array.
newBlock.docs.links = block.docs.links;
return newBlock;
}, [])
.reduce(function(sections, cur) {
// Split into sections with headings as delimiters.
var docs = cur.docs;
while (docs.length) {
// New or first section, add title/slug properties.
if (docs[0]._split || sections.length === 0) {
var title = docs[0]._origText;
var slug = docs[0]._slug;
sections.push({ docs: [ docs.shift() ], code: '',
title: title, slug: slug });
} else {
// Add the documentation to the last section.
sections[sections.length-1].docs.push(docs.shift());
}
// Keep marked's custom links property on the docs arrays.
sections[sections.length-1].docs.links = docs.links;
}
// No docs in file, just add the CSS.
if (sections.length === 0) {
sections.push(cur);
// Add remaining code to the last section.
} else {
sections[sections.length-1].code += cur.code;
}
return sections;
}, [])
.map(function(section) {
// Run through marked parser to generate HTML.
return {
title: section.title ? section.title.trim() : '',
slug: section.slug || '',
docs: trimNewLines(marked.parser(section.docs)),
code: trimNewLines(section.code)
};
});
};
module.exports = function(css) {
return makeSections(separate(css));
};
module.exports.makeSections = makeSections;
module.exports.separate = separate;