jsduck 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/jsduck/merger.rb CHANGED
@@ -103,7 +103,7 @@ module JsDuck
103
103
  end
104
104
  end
105
105
 
106
- if tag[:tagname] == :xtype
106
+ if tag[:tagname] == :alias
107
107
  groups[:class] << tag
108
108
  elsif group_name == :cfg
109
109
  groups[:cfg].last << tag
@@ -123,8 +123,7 @@ module JsDuck
123
123
  :extends => detect_extends(doc_map, code),
124
124
  :mixins => detect_list(:mixins, doc_map, code),
125
125
  :alternateClassNames => detect_list(:alternateClassNames, doc_map, code),
126
- :xtypes => detect_xtypes(doc_map, code),
127
- :meta => detect_meta(doc_map),
126
+ :aliases => detect_aliases(doc_map, code),
128
127
  :singleton => detect_singleton(doc_map, code),
129
128
  :requires => detect_list(:requires, doc_map, code),
130
129
  :uses => detect_list(:uses, doc_map, code),
@@ -227,7 +226,8 @@ module JsDuck
227
226
  :static => !!doc_map[:static],
228
227
  :inheritable => !!doc_map[:inheritable],
229
228
  :deprecated => detect_deprecated(doc_map),
230
- :alias => doc_map[:alias] ? doc_map[:alias].first : nil,
229
+ :inheritdoc => doc_map[:inheritdoc] ? doc_map[:inheritdoc].first : nil,
230
+ :meta => detect_meta(doc_map),
231
231
  })
232
232
  hash[:id] = create_member_id(hash)
233
233
  return hash
@@ -330,31 +330,35 @@ module JsDuck
330
330
  end
331
331
  end
332
332
 
333
- def detect_xtypes(doc_map, code)
334
- if doc_map[:xtype]
335
- {"widget" => doc_map[:xtype].map {|tag| tag[:name] } }
333
+ def detect_aliases(doc_map, code)
334
+ if doc_map[:alias]
335
+ build_aliases_hash(doc_map[:alias].map {|tag| tag[:name] })
336
336
  elsif code[:xtype] || code[:alias]
337
- xtypes = {}
338
- (code[:xtype] || []).each do |a|
339
- if xtypes["widget"]
340
- xtypes["widget"] << a
337
+ hash = {}
338
+ build_aliases_hash(code[:xtype].map {|xtype| "widget."+xtype }, hash) if code[:xtype]
339
+ build_aliases_hash(code[:alias], hash) if code[:alias]
340
+ hash
341
+ else
342
+ {}
343
+ end
344
+ end
345
+
346
+ # Given array of full alias names like "foo.bar", "foo.baz"
347
+ # build hash like {"foo" => ["bar", "baz"]}
348
+ #
349
+ # When hash given as second argument, then merges the aliases into
350
+ # it instead of creating a new hash.
351
+ def build_aliases_hash(aliases, hash={})
352
+ aliases.each do |a|
353
+ if a =~ /^([\w.]+)\.(\w+)$/
354
+ if hash[$1]
355
+ hash[$1] << $2
341
356
  else
342
- xtypes["widget"] = [a]
343
- end
344
- end
345
- (code[:alias] || []).each do |a|
346
- if a =~ /^([\w.]+)\.(\w+)$/
347
- if xtypes[$1]
348
- xtypes[$1] << $2
349
- else
350
- xtypes[$1] = [$2]
351
- end
357
+ hash[$1] = [$2]
352
358
  end
353
359
  end
354
- xtypes
355
- else
356
- {}
357
360
  end
361
+ hash
358
362
  end
359
363
 
360
364
  def detect_meta(doc_map)
@@ -437,7 +441,7 @@ module JsDuck
437
441
  parent[:properties] = [] unless parent[:properties]
438
442
  parent[:properties] << it
439
443
  else
440
- Logger.instance.warn("Ignoring subproperty #{$1}.#{$2}, no parent found with name '#{$1}'.", @filename, @linenr)
444
+ Logger.instance.warn(:subproperty, "Ignoring subproperty #{$1}.#{$2}, no parent found with name '#{$1}'.", @filename, @linenr)
441
445
  end
442
446
  else
443
447
  items << it
@@ -0,0 +1,19 @@
1
+ module JsDuck
2
+ # A class that does nothing.
3
+ # Responds to all methods by returning self, unless a hash passed to
4
+ # constructor.
5
+ # See: http://en.wikipedia.org/wiki/Null_Object_pattern
6
+ class NullObject
7
+ # Optionally takes a hash of method_name => return_value pairs,
8
+ # making it return those values for those methods, sort of like
9
+ # OpenStruct, but for all other methods self is still returned and
10
+ # any number of arguments is accepted.
11
+ def initialize(methods={})
12
+ @methods = methods
13
+ end
14
+
15
+ def method_missing(meth, *args, &block)
16
+ @methods.has_key?(meth) ? @methods[meth] : self
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
  require 'jsduck/meta_tag_loader'
3
+ require 'jsduck/logger'
3
4
 
4
5
  module JsDuck
5
6
 
@@ -11,8 +12,6 @@ module JsDuck
11
12
  attr_accessor :ignore_global
12
13
  attr_accessor :external_classes
13
14
  attr_accessor :meta_tags
14
- attr_accessor :warnings
15
- attr_accessor :verbose
16
15
 
17
16
  # Customizing output
18
17
  attr_accessor :title
@@ -24,6 +23,7 @@ module JsDuck
24
23
  attr_accessor :guides
25
24
  attr_accessor :videos
26
25
  attr_accessor :examples
26
+ attr_accessor :stats
27
27
  attr_accessor :categories_path
28
28
  attr_accessor :pretty_json
29
29
  attr_accessor :images
@@ -73,9 +73,7 @@ module JsDuck
73
73
  @meta_tags = []
74
74
  @meta_tag_paths = []
75
75
 
76
- @warnings = true
77
- @verbose = false
78
- @version = "3.0.1"
76
+ @version = "3.1.0"
79
77
 
80
78
  # Customizing output
81
79
  @title = "Sencha Docs - Ext JS"
@@ -87,6 +85,7 @@ module JsDuck
87
85
  @guides = nil
88
86
  @videos = nil
89
87
  @examples = nil
88
+ @stats = false
90
89
  @categories_path = nil
91
90
  @pretty_json = false
92
91
  @images = []
@@ -130,8 +129,9 @@ module JsDuck
130
129
 
131
130
  opts.on('-o', '--output=PATH',
132
131
  "Directory to output all this amazing documentation.",
133
- "This option MUST be specified (unless --stdout).", " ") do |path|
134
- @output_dir = canonical(path)
132
+ "This option MUST be specified (unless --stdout).",
133
+ "Use dash '-' to write docs to STDOUT (only export).", " ") do |path|
134
+ @output_dir = path == "-" ? :stdout : canonical(path)
135
135
  end
136
136
 
137
137
  opts.on('--ignore-global', "Turns off the creation of global class.", " ") do
@@ -152,16 +152,17 @@ module JsDuck
152
152
 
153
153
  opts.on('--meta-tags=PATH',
154
154
  "Path to Ruby file or directory with custom",
155
- "meta-tag implementations. Experimantal!", " ") do |path|
156
- @meta_tag_paths << path
155
+ "meta-tag implementations.", " ") do |path|
156
+ @meta_tag_paths << canonical(path)
157
157
  end
158
158
 
159
- opts.on('--no-warnings', "Turns off warnings.", " ") do
160
- @warnings = false
159
+ opts.on('--no-warnings',
160
+ "Turns off all warnings. Same as --warnings=-all", " ") do
161
+ Logger.instance.set_warning(:all, false)
161
162
  end
162
163
 
163
164
  opts.on('-v', '--verbose', "This will fill up your console.", " ") do
164
- @verbose = true
165
+ Logger.instance.verbose = true
165
166
  end
166
167
 
167
168
  opts.separator "Customizing output:"
@@ -211,6 +212,11 @@ module JsDuck
211
212
  @examples = canonical(path)
212
213
  end
213
214
 
215
+ opts.on('--stats',
216
+ "Creates page with all kinds of statistics. Experimental!", " ") do
217
+ @stats = true
218
+ end
219
+
214
220
  opts.on('--categories=PATH',
215
221
  "Path to JSON file which defines categories for classes.", " ") do |path|
216
222
  @categories_path = canonical(path)
@@ -249,12 +255,11 @@ module JsDuck
249
255
  @img_tpl = tpl
250
256
  end
251
257
 
252
- opts.on('--json', "Produces JSON export instead of HTML documentation.", " ") do
253
- @export = :json
254
- end
255
-
256
- opts.on('--stdout', "Writes JSON export to STDOUT instead of writing to the filesystem", " ") do
257
- @export = :stdout
258
+ opts.on('--export=TYPE',
259
+ "Exports docs in JSON. TYPE is one of:",
260
+ "* full - full class docs.",
261
+ "* api - only class- and member names.", " ") do |format|
262
+ @export = format.to_sym
258
263
  end
259
264
 
260
265
  opts.on('--seo', "Creates index.php that handles search engine traffic.", " ") do
@@ -270,6 +275,21 @@ module JsDuck
270
275
  opts.separator "Debugging:"
271
276
  opts.separator ""
272
277
 
278
+ opts.on('--warnings=+A,-B,+C', Array,
279
+ "Turns warnings selectively on/off.",
280
+ "+foo turns on a warning, -foo turns it off.",
281
+ "Possible warning types are:",
282
+ " ",
283
+ *Logger.instance.doc_warnings) do |warnings|
284
+ warnings.each do |op|
285
+ if op =~ /^([-+]?)(.*)$/
286
+ enable = !($1 == "-")
287
+ name = $2.to_sym
288
+ Logger.instance.set_warning(name, enable)
289
+ end
290
+ end
291
+ end
292
+
273
293
  # For debugging it's often useful to set --processes=0 to get deterministic results.
274
294
  opts.on('-p', '--processes=COUNT',
275
295
  "The number of parallel processes to use.",
@@ -369,7 +389,7 @@ module JsDuck
369
389
  @input_files << fname
370
390
  end
371
391
  else
372
- $stderr.puts "Warning: File #{fname} not found"
392
+ Logger.instance.warn(nil, "File #{fname} not found")
373
393
  end
374
394
  end
375
395
 
@@ -387,7 +407,13 @@ module JsDuck
387
407
  if @input_files.length == 0 && !@welcome && !@guides && !@videos && !@examples
388
408
  puts "You should specify some input files, otherwise there's nothing I can do :("
389
409
  exit(1)
390
- elsif @export != :stdout
410
+ elsif @output_dir == :stdout && !@export
411
+ puts "Output to STDOUT only works when using --export option."
412
+ exit(1)
413
+ elsif ![nil, :full, :api].include?(@export)
414
+ puts "Unknown export format: #{@export}"
415
+ exit(1)
416
+ elsif @output_dir != :stdout
391
417
  if !@output_dir
392
418
  puts "You should also specify an output directory, where I could write all this amazing documentation."
393
419
  exit(1)
@@ -17,7 +17,7 @@ module JsDuck
17
17
  "<div class='doc-contents'>",
18
18
  render_private_class_notice,
19
19
  @cls[:doc],
20
- render_meta_data,
20
+ render_meta_data(@cls[:meta]),
21
21
  "</div>",
22
22
  "<div class='members'>",
23
23
  render_member_sections,
@@ -35,9 +35,11 @@ module JsDuck
35
35
  ]
36
36
  end
37
37
 
38
- def render_meta_data
38
+ def render_meta_data(meta_data)
39
+ return if meta_data.size == 0
40
+
39
41
  @meta_tags.map do |tag|
40
- contents = @cls[:meta][tag.name]
42
+ contents = meta_data[tag.name]
41
43
  if contents
42
44
  tag.to_html(contents)
43
45
  else
@@ -136,9 +138,9 @@ module JsDuck
136
138
  statics = @cls[:statics][sec[:type]]
137
139
  if members.length > 0 || statics.length > 0
138
140
  [
139
- "<div id='m-#{sec[:type]}'>",
141
+ "<div class='members-section'>",
140
142
  statics.length == 0 ? "<div class='definedBy'>Defined By</div>" : "",
141
- "<h3 class='members-title'>#{sec[:title]}</h3>",
143
+ "<h3 class='members-title icon-#{sec[:type]}'>#{sec[:title]}</h3>",
142
144
  render_subsection(members, statics.length > 0 ? "Instance #{sec[:title]}" : nil),
143
145
  render_subsection(statics, "Static #{sec[:title]}"),
144
146
  "</div>",
@@ -165,11 +167,11 @@ module JsDuck
165
167
  first_child = is_first ? "first-child" : ""
166
168
  # shorthand to owner class
167
169
  owner = m[:owner]
168
- # use classname "inherited" when member is not defined in this class
169
- inherited = owner == @cls[:name] ? "not-inherited" : "inherited"
170
+ # is this method inherited from parent?
171
+ inherited = (owner != @cls[:name])
170
172
 
171
173
  return [
172
- "<div id='#{m[:id]}' class='member #{first_child} #{inherited}'>",
174
+ "<div id='#{m[:id]}' class='member #{first_child} #{inherited ? 'inherited' : 'not-inherited'}'>",
173
175
  # leftmost column: expand button
174
176
  "<a href='#' class='side expandable'>",
175
177
  "<span>&nbsp;</span>",
@@ -177,8 +179,10 @@ module JsDuck
177
179
  # member name and type + link to owner class and source
178
180
  "<div class='title'>",
179
181
  "<div class='meta'>",
180
- "<a href='#!/api/#{owner}' rel='#{owner}' class='definedIn docClass'>#{owner}</a><br/>",
181
- "<a href='source/#{m[:files][0][:href]}' target='_blank' class='viewSource'>view source</a>",
182
+ inherited ? "<a href='#!/api/#{owner}' rel='#{owner}' class='defined-in docClass'>#{owner}</a>" :
183
+ "<span class='defined-in' rel='#{owner}'>#{owner}</span>",
184
+ "<br/>",
185
+ "<a href='source/#{m[:files][0][:href]}' target='_blank' class='view-source'>view source</a>",
182
186
  "</div>",
183
187
  # method params signature or property type signature
184
188
  render_signature(m),
@@ -202,7 +206,7 @@ module JsDuck
202
206
  name = m[:name]
203
207
  before = ""
204
208
  if m[:tagname] == :method && m[:name] == "constructor"
205
- before = "<strong class='constructor-signature'>new</strong>"
209
+ before = "<strong class='new-keyword'>new</strong>"
206
210
  name = @cls[:name]
207
211
  end
208
212
 
@@ -218,19 +222,19 @@ module JsDuck
218
222
 
219
223
  after = ""
220
224
  if m[:protected]
221
- after += "<strong class='protected-signature'>protected</strong>"
225
+ after += "<strong class='protected signature'>protected</strong>"
222
226
  end
223
227
  if m[:static]
224
- after += "<strong class='static-signature'>static</strong>"
228
+ after += "<strong class='static signature'>static</strong>"
225
229
  end
226
230
  if m[:deprecated]
227
- after += "<strong class='deprecated-signature'>deprecated</strong>"
231
+ after += "<strong class='deprecated signature'>deprecated</strong>"
228
232
  end
229
233
  if m[:required]
230
- after += "<strong class='required-signature'>required</strong>"
234
+ after += "<strong class='required signature'>required</strong>"
231
235
  end
232
236
  if m[:template]
233
- after += "<strong class='template-signature'>template</strong>"
237
+ after += "<strong class='template signature'>template</strong>"
234
238
  end
235
239
 
236
240
  uri = "#!/api/#{m[:owner]}-#{m[:id]}"
@@ -258,7 +262,7 @@ module JsDuck
258
262
  if m[:deprecated]
259
263
  v = m[:deprecated][:version] ? "since " + m[:deprecated][:version] : ""
260
264
  doc << [
261
- "<div class='deprecated'>",
265
+ "<div class='signature-box deprecated'>",
262
266
  "<p>This #{m[:tagname]} has been <strong>deprecated</strong> #{v}</p>",
263
267
  m[:deprecated][:text],
264
268
  "</div>",
@@ -267,13 +271,15 @@ module JsDuck
267
271
 
268
272
  if m[:template]
269
273
  doc << [
270
- "<div class='template'>",
274
+ "<div class='signature-box template'>",
271
275
  "<p>This is a template method. A hook into the functionality of this class.",
272
276
  "Feel free to override it in child classes.</p>",
273
277
  "</div>",
274
278
  ]
275
279
  end
276
280
 
281
+ doc << render_meta_data(m[:meta])
282
+
277
283
  doc << render_params_and_return(m)
278
284
 
279
285
  doc
@@ -29,8 +29,8 @@ module JsDuck
29
29
  return {
30
30
  :cls => cls.full_name,
31
31
  :member => cls.short_name,
32
- :type => :cls,
33
- :xtypes => cls[:xtypes]
32
+ :type => :class,
33
+ :aliases => cls[:aliases]
34
34
  }
35
35
  end
36
36
 
@@ -1,16 +1,29 @@
1
+ require 'jsduck/logger'
2
+ require 'fileutils'
3
+
1
4
  module JsDuck
2
5
 
3
6
  # Writes HTML JavaScript/CSS source into HTML file.
4
7
  class SourceWriter
5
8
 
9
+ # Writes all source files as HTML files into destination dir.
10
+ #
11
+ # Can't be done in parallel, because file.html_filename= method
12
+ # updates all the doc-objects related to the file
13
+ def self.write_all(files, destination)
14
+ FileUtils.mkdir(destination)
15
+ src = SourceWriter.new(destination)
16
+ files.each do |file|
17
+ html_filename = src.write(file.to_html, file.filename)
18
+ Logger.instance.log("Writing source", html_filename)
19
+ file.html_filename = File.basename(html_filename)
20
+ end
21
+ end
22
+
6
23
  # Initializes SourceFormatter to the directory where
7
24
  # HTML-formatted source files will be placed.
8
- #
9
- # Wrapper can be either :page or nil; with the first one the whole
10
- # HTML page is created, otherwise source is left as is.
11
- def initialize(output_dir, wrapper = :page)
25
+ def initialize(output_dir)
12
26
  @output_dir = output_dir
13
- @wrapper = wrapper
14
27
  end
15
28
 
16
29
  # Writes HTML into file in output directory. It returns the name
@@ -18,7 +31,7 @@ module JsDuck
18
31
  def write(source, filename)
19
32
  fname = uniq_html_filename(filename)
20
33
  File.open(fname, 'w') do |f|
21
- f.write(@wrapper ? wrap_page(source) : source)
34
+ f.write(wrap_page(source))
22
35
  end
23
36
  fname
24
37
  end
@@ -0,0 +1,103 @@
1
+
2
+ module JsDuck
3
+
4
+ # Calculates all kinds of statistics for classes
5
+ class Stats
6
+ # Maps array of classes into array of stats per class
7
+ def create(classes)
8
+ @classes = classes
9
+
10
+ classes.map do |cls|
11
+ local_members = cls.all_local_members
12
+ total_members = cls.all_members
13
+ class_wc = wc(cls[:doc])
14
+ members_wc = members_wc(cls)
15
+
16
+ {
17
+ :name => cls[:name],
18
+
19
+ :local_cfgs => member_count(local_members, :cfg),
20
+ :local_properties => member_count(local_members, :property),
21
+ :local_methods => member_count(local_members, :method),
22
+ :local_events => member_count(local_members, :event),
23
+ :local_members => local_members.length,
24
+
25
+ :total_cfgs => member_count(total_members, :cfg),
26
+ :total_properties => member_count(total_members, :property),
27
+ :total_methods => member_count(total_members, :method),
28
+ :total_events => member_count(total_members, :event),
29
+ :total_members => total_members.length,
30
+
31
+ :fanIn => fan_in(cls),
32
+ :fanOut => fan_out(cls),
33
+
34
+ :class_wc => class_wc,
35
+ :members_wc => members_wc,
36
+ :wc_per_member => local_members.length > 0 ? (members_wc / local_members.length) : 0,
37
+ }
38
+ end
39
+ end
40
+
41
+ def member_count(members, type)
42
+ members.find_all {|m| m[:tagname] == type }.length
43
+ end
44
+
45
+ # How many classes depend on this class
46
+ def fan_in(cls)
47
+ fan_in_table[cls[:name]] || 0
48
+ end
49
+
50
+ # On how many classes this class depends on
51
+ def fan_out(cls)
52
+ dependencies(cls).length
53
+ end
54
+
55
+ # list of class names the class depends on
56
+ def dependencies(cls)
57
+ [
58
+ cls[:extends],
59
+ cls[:mixins],
60
+ cls[:requires],
61
+ cls[:uses],
62
+ ].compact.flatten.sort.uniq
63
+ end
64
+
65
+ # Returns map of class names to its fan-in number.
66
+ def fan_in_table
67
+ return @fi_table if @fi_table
68
+
69
+ @fi_table = {}
70
+ @classes.each do |cls|
71
+ dependencies(cls).each do |d|
72
+ @fi_table[d] = (@fi_table[d] || 0) + 1
73
+ end
74
+ end
75
+ @fi_table
76
+ end
77
+
78
+ # Counts nr of words in documentation of all members of class
79
+ def members_wc(cls)
80
+ cnt = 0
81
+ cls.all_local_members.each do |m|
82
+ cnt += wc(m[:doc])
83
+ (m[:params] || []).each {|p| cnt += property_wc(p) }
84
+ (m[:properties] || []).each {|p| cnt += property_wc(p) }
85
+ cnt += wc(m[:return][:doc]) if m[:return]
86
+ end
87
+ cnt
88
+ end
89
+
90
+ def property_wc(property)
91
+ cnt = wc(property[:doc] || "")
92
+ (property[:properties] || []).each {|p| cnt += property_wc(p) }
93
+ cnt
94
+ end
95
+
96
+ # Strips HTML and counts words in text
97
+ def wc(str)
98
+ str.gsub(/<\/?[^>]*>/, "").scan(/\w+/).length
99
+ end
100
+
101
+ end
102
+
103
+ end