spacedocs 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,266 @@
1
+
2
+ /*!
3
+ * Dox
4
+ * Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
5
+ * MIT Licensed
6
+ */
7
+
8
+ /**
9
+ * Module dependencies.
10
+ */
11
+
12
+ var markdown = require('github-flavored-markdown').parse;
13
+
14
+ /**
15
+ * Library version.
16
+ */
17
+
18
+ exports.version = '0.0.5';
19
+
20
+ /**
21
+ * Parse comments in the given string of `js`.
22
+ *
23
+ * @param {String} js
24
+ * @return {Array}
25
+ * @see exports.parseComment
26
+ * @api public
27
+ */
28
+
29
+ exports.parseComments = function(js){
30
+ var comments = []
31
+ , comment
32
+ , buf = ''
33
+ , ignore
34
+ , within
35
+ , code;
36
+
37
+ for (var i = 0, len = js.length; i < len; ++i) {
38
+ // start comment
39
+ if ('/' == js[i] && '*' == js[i+1]) {
40
+ // code following previous comment
41
+ if (buf.trim().length) {
42
+ comment = comments[comments.length - 1];
43
+ comment.code = code = buf.trim();
44
+ comment.ctx = exports.parseCodeContext(code);
45
+ buf = '';
46
+ }
47
+ i += 2;
48
+ within = true;
49
+ ignore = '!' == js[i];
50
+ // end comment
51
+ } else if ('*' == js[i] && '/' == js[i+1]) {
52
+ i += 2;
53
+ buf = buf.replace(/^ *\* ?/gm, '');
54
+ var comment = exports.parseComment(buf);
55
+ comment.ignore = ignore;
56
+ comments.push(comment);
57
+ within = ignore = false;
58
+ buf = '';
59
+ // buffer comment or code
60
+ } else {
61
+ buf += js[i];
62
+ }
63
+ }
64
+
65
+ // trailing code
66
+ if (buf.trim().length) {
67
+ comment = comments[comments.length - 1];
68
+ code = buf.trim();
69
+ comment.code = code;
70
+ }
71
+
72
+ return comments;
73
+ };
74
+
75
+ /**
76
+ * Parse the given comment `str`.
77
+ *
78
+ * The comment object returned contains the following:
79
+ *
80
+ * - `tags` array of tag objects
81
+ * - `description` the first line of the comment
82
+ * - `body` lines following the description
83
+ * - `content` both the description and the body
84
+ * - `isPrivate` true when "@api private" is used
85
+ *
86
+ * @param {String} str
87
+ * @return {Object}
88
+ * @see exports.parseTag
89
+ * @api public
90
+ */
91
+
92
+ exports.parseComment = function(str) {
93
+ str = str.trim();
94
+ var comment = { tags: [] }
95
+ , description = {};
96
+
97
+ // parse comment body
98
+ description.full = str.split('@')[0].replace(/^([\w ]+):/gm, '## $1');
99
+ description.summary = description.full.split('\n\n')[0];
100
+ description.body = description.full.split('\n\n').slice(1).join('\n\n');
101
+ comment.description = description;
102
+
103
+ // parse tags
104
+ if (~str.indexOf('@')) {
105
+ var tags = '@' + str.split('@').slice(1).join('@');
106
+ comment.tags = tags.split('\n').map(exports.parseTag);
107
+ comment.isPrivate = comment.tags.some(function(tag){
108
+ return 'api' == tag.type && 'private' == tag.visibility;
109
+ })
110
+ }
111
+
112
+ // markdown
113
+ description.full = markdown(escape(description.full));
114
+ description.summary = markdown(escape(description.summary));
115
+ description.body = markdown(escape(description.body));
116
+
117
+ return comment;
118
+ }
119
+
120
+ /**
121
+ * Parse tag string "@param {Array} name description" etc.
122
+ *
123
+ * @param {String}
124
+ * @return {Object}
125
+ * @api public
126
+ */
127
+
128
+ exports.parseTag = function(str) {
129
+ var tag = {}
130
+ , parts = str.split(/ +/)
131
+ , type = tag.type = parts.shift().replace('@', '');
132
+
133
+ /* shouldn't fail */
134
+ switch (type) { // asfasdfasdf /** /////
135
+ case 'param':
136
+ tag.types = exports.parseTagTypes(parts.shift());
137
+ tag.name = parts.shift() || '';
138
+ tag.description = parts.join(' ');
139
+ break;
140
+ case 'return':
141
+ tag.types = exports.parseTagTypes(parts.shift());
142
+ tag.description = parts.join(' ');
143
+ break;
144
+ case 'see':
145
+ if (~str.indexOf('http')) {
146
+ tag.title = parts.length > 1
147
+ ? parts.shift()
148
+ : '';
149
+ tag.url = parts.join(' ');
150
+ } else {
151
+ tag.local = parts.join(' ');
152
+ }
153
+ case 'api':
154
+ tag.visibility = parts.shift();
155
+ break;
156
+ case 'type':
157
+ tag.types = exports.parseTagTypes(parts.shift());
158
+ break;
159
+ }
160
+
161
+ return tag;
162
+ }
163
+
164
+ /**
165
+ * Parse tag type string "{Array|Object}" etc.
166
+ *
167
+ * @param {String} str
168
+ * @return {Array}
169
+ * @api public
170
+ */
171
+
172
+ exports.parseTagTypes = function(str) {
173
+ return str
174
+ .replace(/[{}]/g, '')
175
+ .split(/ *[|,\/] */);
176
+ };
177
+
178
+ /**
179
+ * Parse the context from the given `str` of js.
180
+ *
181
+ * This method attempts to discover the context
182
+ * for the comment based on it's code. Currently
183
+ * supports:
184
+ *
185
+ * - function statements
186
+ * - function expressions
187
+ * - prototype methods
188
+ * - prototype properties
189
+ * - methods
190
+ * - properties
191
+ * - declarations
192
+ *
193
+ * @param {String} str
194
+ * @return {Object}
195
+ * @api public
196
+ */
197
+
198
+ exports.parseCodeContext = function(str){
199
+ var str = str.split('\n')[0];
200
+
201
+ // function statement
202
+ if (/^function (\w+)\(/.exec(str)) {
203
+ return {
204
+ type: 'function'
205
+ , name: RegExp.$1
206
+ };
207
+ // function expression
208
+ } else if (/^var *(\w+) *= *function/.exec(str)) {
209
+ return {
210
+ type: 'function'
211
+ , name: RegExp.$1
212
+ };
213
+ // prototype method
214
+ } else if (/^(\w+)\.prototype\.(\w+) *= *function/.exec(str)) {
215
+ return {
216
+ type: 'method'
217
+ , constructor: RegExp.$1
218
+ , name: RegExp.$2
219
+ };
220
+ // prototype property
221
+ } else if (/^(\w+)\.prototype\.(\w+) *= *([^\n;]+)/.exec(str)) {
222
+ return {
223
+ type: 'property'
224
+ , constructor: RegExp.$1
225
+ , name: RegExp.$2
226
+ , value: RegExp.$3
227
+ };
228
+ // method
229
+ } else if (/^(\w+)\.(\w+) *= *function/.exec(str)) {
230
+ return {
231
+ type: 'method'
232
+ , receiver: RegExp.$1
233
+ , name: RegExp.$2
234
+ };
235
+ // property
236
+ } else if (/^(\w+)\.(\w+) *= *([^\n;]+)/.exec(str)) {
237
+ return {
238
+ type: 'property'
239
+ , receiver: RegExp.$1
240
+ , name: RegExp.$2
241
+ , value: RegExp.$3
242
+ };
243
+ // declaration
244
+ } else if (/^var +(\w+) *= *([^\n;]+)/.exec(str)) {
245
+ return {
246
+ type: 'declaration'
247
+ , name: RegExp.$1
248
+ , value: RegExp.$2
249
+ };
250
+ }
251
+ };
252
+
253
+ /**
254
+ * Escape the given `html`.
255
+ *
256
+ * @param {String} html
257
+ * @return {String}
258
+ * @api private
259
+ */
260
+
261
+ function escape(html){
262
+ return String(html)
263
+ .replace(/&(?!\w+;)/g, '&amp;')
264
+ .replace(/</g, '&lt;')
265
+ .replace(/>/g, '&gt;');
266
+ }
@@ -0,0 +1,15 @@
1
+
2
+ /**
3
+ * Parse tag type string "{Array|Object}" etc.
4
+ *
5
+ * @name is arbitrary
6
+ * @param {String} str
7
+ * @return {Array}
8
+ * @api public
9
+ */
10
+
11
+ exports.parseTagTypes = function(str) {
12
+ return str
13
+ .replace(/[{}]/g, '')
14
+ .split(/ *[|,\/] */);
15
+ };
@@ -0,0 +1,14 @@
1
+
2
+ /**
3
+ * Description.
4
+ *
5
+ * Some examples:
6
+ *
7
+ * foo
8
+ *
9
+ * Some longer thing
10
+ * for example:
11
+ *
12
+ * bar
13
+ *
14
+ */
@@ -0,0 +1,155 @@
1
+ @import compass/css3
2
+
3
+ $width: 600px
4
+
5
+ html
6
+ color: #222
7
+ height: 100%
8
+
9
+ body
10
+ +background-image(linear-gradient(135deg, rgba(0, 0, 0, 0.05) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.05) 50%, rgba(0, 0, 0, 0.05) 75%, transparent 75%, transparent))
11
+ -webkit-background-size: 4px 4px
12
+
13
+ font-family: 'Lucida Grande', Arial, sans-serif
14
+ font-size: 13px
15
+ height: 100%
16
+ margin: 0
17
+
18
+ .method_list
19
+ +border-radius(5px)
20
+ +box-shadow(rgba(0,0,0,0.15) 0 0 15px)
21
+
22
+ position: fixed
23
+ top: 20px
24
+ right: 20px
25
+ background-color: white
26
+ border: 1px solid rgba(0, 0, 0, 0.3)
27
+ max-width: 210px
28
+ text-align: center
29
+ margin: 0
30
+ padding: 0.25em 0.5em
31
+
32
+ h3
33
+ text-align: center
34
+ margin: 0
35
+ margin-top: 0.5em
36
+
37
+ a
38
+ color: #222
39
+ display: inline-block
40
+ padding: 0.4em 0.5em
41
+ text-decoration: none
42
+
43
+ nav
44
+ +background-image(linear-gradient(45deg, rgba(0, 0, 0, 0.05) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.05) 50%, rgba(0, 0, 0, 0.05) 75%, transparent 75%, transparent))
45
+ +box-shadow(rgba(0, 0, 0, 0.15) -1px 0 4px inset)
46
+ -webkit-background-size: 12px 12px
47
+ display: inline-block
48
+ width: 130px
49
+ height: 100%
50
+ border-right: 1px solid rgba(0, 0, 0, 0.2)
51
+ background-color: rgba(0, 0, 0, 0.15)
52
+ padding: 0.5em 0
53
+ position: fixed
54
+ left: 0
55
+ overflow: auto
56
+ top: 0
57
+
58
+ a
59
+ +box-sizing(border-box)
60
+
61
+ border: 1px solid transparent
62
+ color: #222
63
+ display: block
64
+ text-decoration: none
65
+ padding: 0.5em 0 0.5em 1em
66
+
67
+ &.active
68
+ +border-left-radius(5px)
69
+ +box-shadow(rgba(0, 0, 0, 0.2) 0 0 4px)
70
+
71
+ background-image: -webkit-linear-gradient(135deg, rgba(0, 0, 0, 0.05) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.05) 50%, rgba(0, 0, 0, 0.05) 75%, transparent 75%, transparent)
72
+ background-color: white
73
+ background-size: 4px 4px
74
+ padding: 0.5em 0 0.5em 0.5em
75
+ margin-left: 0.5em
76
+ border-right: 1px white
77
+ border: 1px solid rgba(0, 0, 0, 0.3)
78
+ border-right: 1px solid rgba(0, 0, 0, 0.15)
79
+
80
+ .documentation
81
+ display: inline-block
82
+ max-width: $width
83
+ padding-left: 145px
84
+ vertical-align: top
85
+
86
+ a, a:active, a:visited, a:hover
87
+ color: #307EB6
88
+
89
+ h1, h2, h3, h4, h5
90
+ display: inline-block
91
+ margin: 0
92
+
93
+ h1
94
+ font-size: 1.5em
95
+
96
+ p
97
+ margin: 0.5em 0
98
+
99
+ ul
100
+ margin: 0
101
+ padding-left: 1em
102
+ list-style: none
103
+
104
+ li
105
+ padding-top: 0.5em
106
+
107
+ .returns
108
+ p
109
+ margin-left: 1em
110
+
111
+ section
112
+ margin-bottom: 0.75em
113
+
114
+ .source pre
115
+ display: none
116
+
117
+ .toggle_source
118
+ cursor: pointer
119
+
120
+ pre
121
+ margin: 0.5em 0
122
+
123
+ .param_name
124
+ font-style: italic
125
+
126
+ .description
127
+ margin-left: 1em
128
+
129
+ p code
130
+ +border-radius(4px)
131
+ +box-shadow(rgba(0, 0, 0, 0.15) 0 0 4px inset)
132
+
133
+ background-color: #EEE
134
+ border: 1px solid rgba(0, 0, 0, 0.2)
135
+ padding: 0 0.3em
136
+
137
+ .description, .source
138
+ pre
139
+ +border-radius(4px)
140
+ +box-shadow(rgba(0, 0, 0, 0.15) 0 0 4px inset)
141
+ +box-sizing(border-box)
142
+
143
+ border: 1px solid rgba(0, 0, 0, 0.2)
144
+ background-color: #EEE
145
+ max-width: $width
146
+ padding: 0.5em 1em
147
+
148
+ hr
149
+ margin-left: 1px
150
+ margin-top: 1em
151
+ margin-bottom: 1em
152
+ max-width: $width
153
+
154
+ &:last-child
155
+ display: none
@@ -0,0 +1,6 @@
1
+ module Spacedocs
2
+ class Engine < Rails::Engine
3
+ # tells rails to add the project's app/assets, lib/assets,
4
+ # and vendor/assets to the asset load path.
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module Spacedocs
2
+ VERSION = "0.0.2"
3
+ end
data/lib/spacedocs.rb ADDED
@@ -0,0 +1,215 @@
1
+ require 'tilt'
2
+ require 'haml'
3
+ require 'json'
4
+ require 'fileutils'
5
+
6
+ module Spacedocs
7
+ class << self
8
+ def doc(project_dir, file)
9
+ tilt_path = File.dirname(__FILE__)
10
+
11
+ #TODO Dangerous
12
+ json = %x[dox < "#{File.join project_dir, file}"]
13
+
14
+ doc_json = JSON.parse json
15
+
16
+ processed_data = process_data doc_json
17
+
18
+ template = Tilt.new(File.join tilt_path, "../source/class.html.haml")
19
+ index_template = Tilt.new(File.join tilt_path, "../source/index.html.haml")
20
+
21
+ files = {}
22
+
23
+ class_data = processed_data[:docs_data]
24
+
25
+ class_data.each_key do |namespace|
26
+ files[namespace] = true
27
+ end
28
+
29
+ docs_dir = File.join(project_dir, 'docs')
30
+
31
+ FileUtils.rm_rf docs_dir
32
+ FileUtils.mkdir_p docs_dir
33
+
34
+ if __FILE__ == $0
35
+ File.open("source/index.html", 'w') do |f|
36
+ f.write(index_template.render self, { class_names: files.keys, dev: true })
37
+ end
38
+
39
+ files.each_key do |file_name|
40
+ methods = class_data[file_name]['methods']
41
+
42
+ File.open("source/#{file_name}.html", 'w') do |f|
43
+ f.write(template.render self, {
44
+ class_name: file_name,
45
+ method_list: methods.keys,
46
+ methods: methods,
47
+ class_names: files.keys,
48
+ class_summary: class_data[file_name]['summary'],
49
+ dev: true
50
+ })
51
+ end
52
+ end
53
+ else
54
+ File.open(File.join(project_dir, "docs/index.html"), 'w') do |f|
55
+ f.write(index_template.render self, { class_names: files.keys })
56
+ end
57
+
58
+ files.each_key do |file_name|
59
+ methods = class_data[file_name]['methods']
60
+
61
+ File.open(File.join(project_dir, "docs/#{file_name}.html"), 'w') do |f|
62
+ f.write(template.render self, {
63
+ class_name: file_name,
64
+ method_list: methods.keys,
65
+ methods: methods,
66
+ class_names: files.keys,
67
+ class_summary: class_data[file_name]['summary'],
68
+ dev: false
69
+ })
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+ def tags_named(name, tags)
77
+ tags.map do |tag|
78
+ tag['string'] if tag['type'] == name
79
+ end.compact
80
+ end
81
+
82
+ def method_of_data(tags)
83
+ tags_named('methodOf', tags)
84
+ end
85
+
86
+ def name_data(tags)
87
+ tags_named('name', tags)
88
+ end
89
+
90
+ def format_class_name(name)
91
+ if name.end_with?('#')
92
+ name[0...-1]
93
+ else
94
+ name
95
+ end
96
+ end
97
+
98
+ def params_data(tags)
99
+ tags.each_with_object({}) do |tag, hash|
100
+ if tag['type'] == 'param'
101
+ hash["#{tag['name'].gsub(/[\[\]]/, '')}"] = {
102
+ "type" => tag['types'].join(', '),
103
+ "description" => tag['description'],
104
+ "optional" => tag['name'].start_with?('[')
105
+ }
106
+ end
107
+ end
108
+ end
109
+
110
+ def returns_data(tags)
111
+ tags.map do |tag|
112
+ if tag['type'] == 'returns'
113
+ description = tag['string'].split ' '
114
+
115
+ type = description.shift.gsub(/[{}]/, '')
116
+ remaining = description.join ' '
117
+
118
+ { "type" => type, "description" => remaining }
119
+ end
120
+ end.compact
121
+ end
122
+
123
+ def see_data(tags)
124
+ tags_named('see', tags)
125
+ end
126
+
127
+ def class_name_data(tags)
128
+ class_names = []
129
+
130
+ tags.each do |tag|
131
+ class_names << format_class_name(tag['string']) if tag['type'] == 'methodOf'
132
+
133
+ if ['constructor', 'namespace'].include? tag['type']
134
+ tags.each do |tag|
135
+ class_names << tag['string'] if tag['type'] == 'name'
136
+ end
137
+ end
138
+ end
139
+
140
+ class_names.first
141
+ end
142
+
143
+ def process_data(json)
144
+ class_names = []
145
+ tags_list = []
146
+ docs_data = {}
147
+ module_map = {}
148
+ class_summaries = {}
149
+
150
+ json.each do |item|
151
+ tags = item['tags']
152
+
153
+ tags.each do |tag|
154
+ if ['constructor', 'namespace'].include? tag['type']
155
+ class_summaries[class_name_data(tags)] ||= {}
156
+ class_summaries[class_name_data(tags)]['description'] ||= {}
157
+
158
+ class_summaries[class_name_data(tags)]['description']['summary'] = item['description']['summary'] || ""
159
+ class_summaries[class_name_data(tags)]['description']['body'] = item['description']['body'] || ""
160
+ end
161
+ end
162
+
163
+ tags_list << tags
164
+
165
+ name = name_data(tags).first
166
+ returns = returns_data(tags).first || {}
167
+ see = see_data(tags).first || ""
168
+ method_of = method_of_data(tags).first
169
+ params = params_data(tags)
170
+
171
+ class_names << class_name_data(tags)
172
+
173
+ if name && method_of
174
+ if method_of.end_with?('#')
175
+ name = "##{name}"
176
+ method_of = method_of[0...-1]
177
+ else
178
+ name = ".#{name}"
179
+ end
180
+
181
+ module_map[method_of] ||= {}
182
+ module_map[method_of][name] = {
183
+ "summary" => item['description']['summary'],
184
+ "code_sample" => item['description']['body'],
185
+ "source" => item['code'],
186
+ "parameters" => params,
187
+ "returns" => returns,
188
+ "see" => see
189
+ }
190
+ end
191
+ end
192
+
193
+ class_names = class_names.compact.flatten.uniq.sort
194
+
195
+ class_names.each do |source_class|
196
+ method_data = module_map[source_class] || {}
197
+
198
+ docs_data[source_class] = {
199
+ 'summary' => class_summaries[source_class],
200
+ 'methods' => method_data
201
+ }
202
+ end
203
+
204
+ # File.open("source/sanity.json", 'w') do |f|
205
+ # f.write(JSON.pretty_generate(docs_data))
206
+ # end
207
+
208
+ return { docs_data: docs_data, class_names: class_names }
209
+ end
210
+ end
211
+ end
212
+
213
+ if __FILE__ == $0
214
+ Spacedocs.doc 'projects/6', 'game.js'
215
+ end