spacedocs 0.0.2

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.
@@ -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