jsduck 3.0.1 → 3.1.0

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