jsduck 5.0.0.beta01 → 5.0.0.beta2

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.
Files changed (95) hide show
  1. data/.travis.yml +1 -0
  2. data/README.md +6 -32
  3. data/Rakefile +5 -5
  4. data/bin/jsduck +0 -1
  5. data/js-classes/String.js +3 -5
  6. data/jsduck.gemspec +3 -2
  7. data/lib/jsduck/aggregator.rb +1 -3
  8. data/lib/jsduck/app.rb +2 -2
  9. data/lib/jsduck/categories/file.rb +0 -6
  10. data/lib/jsduck/class.rb +1 -2
  11. data/lib/jsduck/doc/parser.rb +12 -5
  12. data/lib/jsduck/doc/scanner.rb +6 -0
  13. data/lib/jsduck/doc/standard_tag_parser.rb +10 -5
  14. data/lib/jsduck/doc/subproperties.rb +9 -2
  15. data/lib/jsduck/docs_code_comparer.rb +20 -7
  16. data/lib/jsduck/exporter/app.rb +18 -13
  17. data/lib/jsduck/exporter/full.rb +18 -21
  18. data/lib/jsduck/format/doc.rb +0 -1
  19. data/lib/jsduck/format/html_stack.rb +1 -2
  20. data/lib/jsduck/format/subproperties.rb +2 -2
  21. data/lib/jsduck/inline/auto_link.rb +1 -1
  22. data/lib/jsduck/inline/img.rb +1 -1
  23. data/lib/jsduck/inline/link.rb +4 -6
  24. data/lib/jsduck/inline/video.rb +1 -2
  25. data/lib/jsduck/js/ast.rb +1 -1
  26. data/lib/jsduck/js/esprima.rb +24 -9
  27. data/lib/jsduck/logger.rb +50 -12
  28. data/lib/jsduck/members_index.rb +1 -2
  29. data/lib/jsduck/merger.rb +20 -2
  30. data/lib/jsduck/options.rb +125 -24
  31. data/lib/jsduck/process/accessors.rb +21 -8
  32. data/lib/jsduck/process/enums.rb +2 -3
  33. data/lib/jsduck/process/ext4_events.rb +2 -1
  34. data/lib/jsduck/process/global_members.rb +1 -2
  35. data/lib/jsduck/process/importer.rb +2 -6
  36. data/lib/jsduck/process/inherit_class.rb +58 -0
  37. data/lib/jsduck/process/inherit_doc.rb +6 -175
  38. data/lib/jsduck/process/inherit_members.rb +257 -0
  39. data/lib/jsduck/process/lint.rb +18 -7
  40. data/lib/jsduck/process/overrides.rb +1 -2
  41. data/lib/jsduck/render/class.rb +1 -1
  42. data/lib/jsduck/tag/alias.rb +2 -1
  43. data/lib/jsduck/tag/alternate_class_names.rb +1 -0
  44. data/lib/jsduck/tag/aside.rb +3 -3
  45. data/lib/jsduck/tag/author.rb +18 -3
  46. data/lib/jsduck/tag/autodetected.rb +21 -0
  47. data/lib/jsduck/tag/boolean_tag.rb +1 -1
  48. data/lib/jsduck/tag/cfg.rb +7 -3
  49. data/lib/jsduck/tag/class.rb +1 -1
  50. data/lib/jsduck/tag/class_list_tag.rb +1 -1
  51. data/lib/jsduck/tag/constructor.rb +1 -1
  52. data/lib/jsduck/tag/css_var.rb +1 -1
  53. data/lib/jsduck/tag/default.rb +1 -1
  54. data/lib/jsduck/tag/deprecated_tag.rb +1 -1
  55. data/lib/jsduck/tag/docauthor.rb +2 -0
  56. data/lib/jsduck/tag/enum.rb +2 -2
  57. data/lib/jsduck/tag/event.rb +1 -1
  58. data/lib/jsduck/tag/extends.rb +1 -1
  59. data/lib/jsduck/tag/ftype.rb +2 -1
  60. data/lib/jsduck/tag/inheritdoc.rb +1 -1
  61. data/lib/jsduck/tag/localdoc.rb +33 -0
  62. data/lib/jsduck/tag/markdown.rb +1 -1
  63. data/lib/jsduck/tag/member.rb +1 -1
  64. data/lib/jsduck/tag/method.rb +1 -1
  65. data/lib/jsduck/tag/mixins.rb +1 -0
  66. data/lib/jsduck/tag/override.rb +1 -1
  67. data/lib/jsduck/tag/param.rb +16 -5
  68. data/lib/jsduck/tag/preventable.rb +1 -1
  69. data/lib/jsduck/tag/property.rb +7 -3
  70. data/lib/jsduck/tag/ptype.rb +2 -1
  71. data/lib/jsduck/tag/requires.rb +1 -0
  72. data/lib/jsduck/tag/return.rb +2 -1
  73. data/lib/jsduck/tag/since.rb +1 -5
  74. data/lib/jsduck/tag/tag.rb +21 -12
  75. data/lib/jsduck/tag/throws.rb +2 -1
  76. data/lib/jsduck/tag/type.rb +2 -2
  77. data/lib/jsduck/tag/uses.rb +1 -0
  78. data/lib/jsduck/tag/xtype.rb +2 -1
  79. data/lib/jsduck/tag_loader.rb +26 -15
  80. data/lib/jsduck/tag_registry.rb +20 -11
  81. data/lib/jsduck/web/css.rb +22 -0
  82. data/lib/jsduck/web/data.rb +50 -0
  83. data/lib/jsduck/web/icons.rb +31 -0
  84. data/lib/jsduck/web/index_html.rb +88 -0
  85. data/lib/jsduck/web/search.rb +148 -0
  86. data/lib/jsduck/{source/writer.rb → web/source.rb} +2 -2
  87. data/lib/jsduck/web/template.rb +52 -0
  88. data/lib/jsduck/web/writer.rb +84 -0
  89. metadata +513 -488
  90. data/lib/jsduck/app_data.rb +0 -41
  91. data/lib/jsduck/icons.rb +0 -29
  92. data/lib/jsduck/index_html.rb +0 -84
  93. data/lib/jsduck/search_data.rb +0 -146
  94. data/lib/jsduck/template_dir.rb +0 -50
  95. data/lib/jsduck/web_writer.rb +0 -87
@@ -6,40 +6,37 @@ module JsDuck
6
6
  # Exporter for all the class docs.
7
7
  class Full
8
8
  def initialize(relations, opts={})
9
- @relations = relations
10
- # opts parameter is here just for compatibility with other exporters
9
+ # parameters are just for compatibility with other exporters
11
10
  end
12
11
 
13
- # Returns all data in Class object as hash.
12
+ # Returns a hash of class data, with :members field expanded
13
+ # into list of all members (including those inherited from
14
+ # parents and mixins).
14
15
  def export(cls)
15
16
  # Make copy of the internal data structure of a class
16
17
  # so our modifications on it will be safe.
17
18
  h = cls.internal_doc.clone
18
19
 
19
- h[:members] = {}
20
- h[:statics] = {}
21
- TagRegistry.member_type_names.each do |tagname|
22
- h[:members][tagname] = export_members(cls, {:tagname => tagname, :static => false})
23
- h[:statics][tagname] = export_members(cls, {:tagname => tagname, :static => true})
24
- end
25
- h[:component] = cls.inherits_from?("Ext.Component")
26
- h[:superclasses] = cls.superclasses.collect {|c| c[:name] }
27
- h[:subclasses] = @relations.subclasses(cls).collect {|c| c[:name] }.sort
28
- h[:mixedInto] = @relations.mixed_into(cls).collect {|c| c[:name] }.sort
29
- h[:alternateClassNames] = cls[:alternateClassNames].sort if cls[:alternateClassNames]
30
-
31
- h[:mixins] = cls.deps(:mixins).collect {|c| c[:name] }.sort
32
- h[:parentMixins] = cls.parent_deps(:mixins).collect {|c| c[:name] }.sort
33
- h[:requires] = cls.deps(:requires).collect {|c| c[:name] }.sort
34
- h[:uses] = cls.deps(:uses).collect {|c| c[:name] }.sort
20
+ h[:members] = export_members(cls)
35
21
 
36
22
  h
37
23
  end
38
24
 
39
25
  private
40
26
 
41
- # Looks up members, and sorts them so that constructor method is first
42
- def export_members(cls, cfg)
27
+ # Generates flat list of all members
28
+ def export_members(cls)
29
+ groups = []
30
+ TagRegistry.member_type_names.each do |tagname|
31
+ groups << export_members_group(cls, {:tagname => tagname, :static => false})
32
+ groups << export_members_group(cls, {:tagname => tagname, :static => true})
33
+ end
34
+ groups.flatten
35
+ end
36
+
37
+ # Looks up members of given type, and sorts them so that
38
+ # constructor method is first
39
+ def export_members_group(cls, cfg)
43
40
  ms = cls.find_members(cfg)
44
41
  ms.sort! {|a,b| a[:name] <=> b[:name] }
45
42
  cfg[:tagname] == :method ? constructor_first(ms) : ms
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'strscan'
3
2
  require 'rdiscount'
4
3
  require 'jsduck/format/html_stack'
@@ -78,9 +78,8 @@ module JsDuck
78
78
  end
79
79
 
80
80
  def warn(msg, tags)
81
- ctx = @doc_context
82
81
  tag_list = tags.map {|tag| "<#{tag}>" }.join(", ")
83
- Logger.warn(:html, "#{msg}: #{tag_list}", ctx[:filename], ctx[:linenr])
82
+ Logger.warn(:html, "#{msg}: #{tag_list}", @doc_context)
84
83
  end
85
84
 
86
85
  def void?(tag)
@@ -50,9 +50,9 @@ module JsDuck
50
50
  else
51
51
  context = @formatter.doc_context
52
52
  if tp.error == :syntax
53
- Logger.warn(:type_syntax, "Incorrect type syntax #{type}", context[:filename], context[:linenr])
53
+ Logger.warn(:type_syntax, "Incorrect type syntax #{type}", context)
54
54
  else
55
- Logger.warn(:type_name, "Unknown type #{type}", context[:filename], context[:linenr])
55
+ Logger.warn(:type_name, "Unknown type #{type}", context)
56
56
  end
57
57
  Util::HTML.escape(type)
58
58
  end
@@ -97,7 +97,7 @@ module JsDuck
97
97
  end
98
98
 
99
99
  def warn_magic_link(msg)
100
- Logger.warn(:link_auto, msg, @doc_context[:filename], @doc_context[:linenr])
100
+ Logger.warn(:link_auto, msg, @doc_context)
101
101
  end
102
102
 
103
103
  end
@@ -38,7 +38,7 @@ module JsDuck
38
38
  def apply_tpl(url, alt_text)
39
39
  img = @images.get(url)
40
40
  if !img
41
- Logger.warn(:image, "Image #{url} not found.", @doc_context[:filename], @doc_context[:linenr])
41
+ Logger.warn(:image, "Image #{url} not found.", @doc_context)
42
42
  img = {}
43
43
  end
44
44
 
@@ -62,15 +62,13 @@ module JsDuck
62
62
  text = cls
63
63
  end
64
64
 
65
- file = @doc_context[:filename]
66
- line = @doc_context[:linenr]
67
65
  if !@relations[cls]
68
- Logger.warn(:link, "#{full_link} links to non-existing class", file, line)
66
+ Logger.warn(:link, "#{full_link} links to non-existing class", @doc_context)
69
67
  return text
70
68
  elsif member
71
69
  ms = @renderer.find_members(cls, {:name => member, :tagname => type, :static => static})
72
70
  if ms.length == 0
73
- Logger.warn(:link, "#{full_link} links to non-existing member", file, line)
71
+ Logger.warn(:link, "#{full_link} links to non-existing member", @doc_context)
74
72
  return text
75
73
  end
76
74
 
@@ -82,11 +80,11 @@ module JsDuck
82
80
  instance_ms = ms.find_all {|m| !m[:static] }
83
81
  if instance_ms.length > 1
84
82
  alternatives = instance_ms.map {|m| "#{m[:tagname]} in #{m[:owner]}" }.join(", ")
85
- Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, file, line)
83
+ Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, @doc_context)
86
84
  elsif instance_ms.length == 0
87
85
  static_ms = ms.find_all {|m| m[:static] }
88
86
  alternatives = static_ms.map {|m| "static " + m[:tagname].to_s }.join(", ")
89
- Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, file, line)
87
+ Logger.warn(:link_ambiguous, "#{full_link} is ambiguous: "+alternatives, @doc_context)
90
88
  end
91
89
  end
92
90
 
@@ -46,8 +46,7 @@ module JsDuck
46
46
  # applies the video template of the specified type
47
47
  def apply_tpl(type, url, alt_text)
48
48
  unless @templates.has_key?(type)
49
- ctx = @doc_context
50
- Logger.warn(nil, "Unknown video type #{type}", ctx[:filename], ctx[:linenr])
49
+ Logger.warn(nil, "Unknown video type #{type}", @doc_context)
51
50
  end
52
51
 
53
52
  @templates[type].gsub(/(%\w)/) do
data/lib/jsduck/js/ast.rb CHANGED
@@ -286,7 +286,7 @@ module JsDuck
286
286
  else
287
287
  m[:private] = true
288
288
  end
289
- m[:autodetected] = true
289
+ m[:autodetected] = {:tagname => m[:tagname]}
290
290
  end
291
291
 
292
292
  if docset
@@ -1,11 +1,15 @@
1
- require 'v8'
1
+ require 'execjs'
2
2
  require 'jsduck/util/json'
3
3
  require 'jsduck/util/singleton'
4
4
 
5
5
  module JsDuck
6
6
  module Js
7
7
 
8
- # Runs Esprima.js through V8.
8
+ # Runs Esprima.js through JavaScript runtime selected by ExecJS.
9
+ # Normally this will be V8 engine within therubyracer gem, but when
10
+ # JSDuck is installed through some other means than rubygems, then
11
+ # one could use any of the runtimes supported by ExecJS. (NodeJS
12
+ # for example.)
9
13
  #
10
14
  # Initialized as singleton to avoid loading the esprima.js more
11
15
  # than once - otherwise performace will severely suffer.
@@ -13,23 +17,34 @@ module JsDuck
13
17
  include Util::Singleton
14
18
 
15
19
  def initialize
16
- @v8 = V8::Context.new
17
- esprima = File.dirname(File.expand_path(__FILE__))+"/esprima/esprima.js";
20
+ esprima_path = File.dirname(File.expand_path(__FILE__)) + "/esprima/esprima.js"
21
+ esprima = IO.read(esprima_path)
18
22
 
19
23
  # Esprima attempts to assign to window.esprima, but our v8
20
24
  # engine has no global window variable defined. So define our
21
25
  # own and then grab esprima out from it again.
22
- @v8.eval("var window = {};")
23
- @v8.load(esprima)
24
- @v8.eval("var esprima = window.esprima;")
26
+ source = <<-EOJS
27
+ if (typeof window === "undefined") {
28
+ var window = {};
29
+ }
30
+
31
+ #{esprima}
32
+
33
+ var esprima = window.esprima;
34
+
35
+ function runEsprima(js) {
36
+ return JSON.stringify(esprima.parse(js, {comment: true, range: true, raw: true}));
37
+ }
38
+ EOJS
39
+
40
+ @context = ExecJS.compile(source)
25
41
  end
26
42
 
27
43
  # Parses JavaScript source code using Esprima.js
28
44
  #
29
45
  # Returns the resulting AST
30
46
  def parse(input)
31
- @v8['js'] = input
32
- json = @v8.eval("JSON.stringify(esprima.parse(js, {comment: true, range: true, raw: true}))")
47
+ json = @context.call("runEsprima", input)
33
48
  return Util::Json.parse(json, :max_nesting => false)
34
49
  end
35
50
 
data/lib/jsduck/logger.rb CHANGED
@@ -17,6 +17,8 @@ module JsDuck
17
17
  [:inheritdoc, "@inheritdoc referring to unknown class or member"],
18
18
  [:extend, "@extend/mixin/requires/uses referring to unknown class"],
19
19
  [:tag, "Use of unsupported @tag"],
20
+ [:tag_repeated, "An @tag used multiple times, but only once allowed"],
21
+ [:tag_syntax, "@tag syntax error"],
20
22
  [:link, "{@link} to unknown class or member"],
21
23
  [:link_ambiguous, "{@link} is ambiguous"],
22
24
  [:link_auto, "Auto-detected link to unknown class or member"],
@@ -24,7 +26,7 @@ module JsDuck
24
26
 
25
27
  [:alt_name, "Name used as both classname and alternate classname"],
26
28
  [:name_missing, "Member or parameter has no name"],
27
- [:no_doc, "Member or class without documentation"],
29
+ [:no_doc, "Public class or member without documentation"],
28
30
  [:dup_param, "Method has two parameters with the same name"],
29
31
  [:dup_member, "Class has two members with the same name"],
30
32
  [:req_after_opt, "Required parameter comes after optional"],
@@ -33,7 +35,7 @@ module JsDuck
33
35
  [:sing_static, "Singleton class member marked as @static"],
34
36
  [:type_syntax, "Syntax error in {type definition}"],
35
37
  [:type_name, "Unknown type referenced in {type definition}"],
36
- [:enum, "Enum with invalid values of no values at all"],
38
+ [:enum, "Enum with invalid values or no values at all"],
37
39
 
38
40
  [:image, "{@img} referring to missing file"],
39
41
  [:image_unused, "An image exists in --images dir that's not used"],
@@ -50,7 +52,7 @@ module JsDuck
50
52
  # When running JSDuck app, the Options class enables most warnings.
51
53
  @warnings = {}
52
54
  @warning_docs.each do |w|
53
- @warnings[w[0]] = false
55
+ @warnings[w[0]] = {:enabled => false, :patterns => []}
54
56
  end
55
57
 
56
58
  @shown_warnings = {}
@@ -63,15 +65,26 @@ module JsDuck
63
65
  end
64
66
  end
65
67
 
66
- # Enabled or disables a particular warning
67
- # or all warnings when type == :all
68
- def set_warning(type, enabled)
68
+ # Enables or disables a particular warning
69
+ # or all warnings when type == :all.
70
+ # Additionally a filename pattern can be specified.
71
+ def set_warning(type, enabled, pattern=nil)
69
72
  if type == :all
73
+ # When used with a pattern, only add the pattern to the rules
74
+ # where it can have an effect - otherwise we get a warning.
70
75
  @warnings.each_key do |key|
71
- @warnings[key] = enabled
76
+ set_warning(key, enabled, pattern) unless pattern && @warnings[key][:enabled] == enabled
72
77
  end
73
78
  elsif @warnings.has_key?(type)
74
- @warnings[type] = enabled
79
+ if pattern
80
+ if @warnings[type][:enabled] == enabled
81
+ warn(nil, "Warning rule '#{enabled ? '+' : '-'}#{type}:#{pattern}' has no effect")
82
+ else
83
+ @warnings[type][:patterns] << Regexp.new(Regexp.escape(pattern))
84
+ end
85
+ else
86
+ @warnings[type] = {:enabled => enabled, :patterns => []}
87
+ end
75
88
  else
76
89
  warn(nil, "Warning of type '#{type}' doesn't exist")
77
90
  end
@@ -79,7 +92,7 @@ module JsDuck
79
92
 
80
93
  # get documentation for all warnings
81
94
  def doc_warnings
82
- @warning_docs.map {|w| " #{@warnings[w[0]] ? '+' : '-'}#{w[0]} - #{w[1]}" }
95
+ @warning_docs.map {|w| " #{@warnings[w[0]][:enabled] ? '+' : '-'}#{w[0]} - #{w[1]}" }
83
96
  end
84
97
 
85
98
  # Prints warning message.
@@ -93,21 +106,46 @@ module JsDuck
93
106
  # warnings greatly also when run multiple processes.
94
107
  #
95
108
  # Optionally filename and line number will be inserted to message.
109
+ # These two last arguments can also be supplied as one hash of:
110
+ #
111
+ # {:filename => "foo.js", :linenr => 17}
112
+ #
96
113
  def warn(type, msg, filename=nil, line=nil)
114
+ if filename.is_a?(Hash)
115
+ line = filename[:linenr]
116
+ filename = filename[:filename]
117
+ end
118
+
97
119
  msg = paint(:yellow, "Warning: ") + format(filename, line) + " " + msg
98
120
 
99
- if type == nil || @warnings[type]
121
+ if warning_enabled?(type, filename)
100
122
  if !@shown_warnings[msg]
101
123
  $stderr.puts msg
102
124
  @shown_warnings[msg] = true
103
125
  end
104
- elsif !@warnings.has_key?(type)
105
- warn(nil, "Unknown warning type #{type}")
106
126
  end
107
127
 
108
128
  return false
109
129
  end
110
130
 
131
+ # True when the warning is enabled for the given type and filename
132
+ # combination.
133
+ def warning_enabled?(type, filename)
134
+ if type == nil
135
+ true
136
+ elsif !@warnings.has_key?(type)
137
+ warn(nil, "Unknown warning type #{type}")
138
+ false
139
+ else
140
+ rule = @warnings[type]
141
+ if rule[:patterns].any? {|re| filename =~ re }
142
+ !rule[:enabled]
143
+ else
144
+ rule[:enabled]
145
+ end
146
+ end
147
+ end
148
+
111
149
  # Formats filename and line number for output
112
150
  def format(filename=nil, line=nil)
113
151
  out = ""
@@ -97,9 +97,8 @@ module JsDuck
97
97
  if hash1[name]
98
98
  hash1.delete(name)
99
99
  else
100
- ctx = m[:files][0]
101
100
  msg = "@hide used but #{m[:tagname]} #{m[:name]} not found in parent class"
102
- Logger.warn(:hide, msg, ctx[:filename], ctx[:linenr])
101
+ Logger.warn(:hide, msg, m[:files][0])
103
102
  end
104
103
  else
105
104
  if hash1[name]
data/lib/jsduck/merger.rb CHANGED
@@ -45,14 +45,32 @@ module JsDuck
45
45
  def general_merge(h, docs, code)
46
46
  # Merge in all items in docs that don't occour already in result.
47
47
  docs.each_pair do |key, value|
48
- h[key] = value unless h.has_key?(key)
48
+ h[key] = value unless h.has_key?(key) || Merger::explicit?(key)
49
49
  end
50
50
  # Then add all in the items from code not already in result.
51
51
  code.each_pair do |key, value|
52
- h[key] = value unless h.has_key?(key)
52
+ h[key] = value unless h.has_key?(key) || Merger::explicit?(key)
53
53
  end
54
54
  end
55
55
 
56
+ # True when given key gets merged explicitly and should therefore
57
+ # be skipped when auto-merging.
58
+ def self.explicit?(key)
59
+ @explicit = explictly_merged_fields unless @explicit
60
+ @explicit[key]
61
+ end
62
+
63
+ # Generates a lookup-hash of tagnames which are explicitly merged.
64
+ def self.explictly_merged_fields
65
+ mergers = {}
66
+ member_types = TagRegistry.member_type_names + [:class]
67
+ tags = member_types.map {|type| TagRegistry.mergers(type) }.flatten.uniq
68
+ tags.each do |tag|
69
+ mergers[tag.tagname] = true
70
+ end
71
+ mergers
72
+ end
73
+
56
74
  end
57
75
 
58
76
  end
@@ -24,6 +24,8 @@ module JsDuck
24
24
  attr_accessor :footer
25
25
  attr_accessor :head_html
26
26
  attr_accessor :body_html
27
+ attr_accessor :css
28
+ attr_accessor :message
27
29
  attr_accessor :welcome
28
30
  attr_accessor :guides
29
31
  attr_accessor :videos
@@ -46,7 +48,6 @@ module JsDuck
46
48
  attr_accessor :template_dir
47
49
  attr_accessor :template_links
48
50
  attr_accessor :extjs_path
49
- attr_accessor :data_path
50
51
  attr_accessor :local_storage_db
51
52
  attr_accessor :touch_examples_ui
52
53
  attr_accessor :ext_namespaces
@@ -91,14 +92,16 @@ module JsDuck
91
92
  ]
92
93
  @ext4_events = nil
93
94
 
94
- @version = "5.0.0.beta01"
95
+ @version = "5.0.0.beta2"
95
96
 
96
97
  # Customizing output
97
98
  @title = "Documentation - JSDuck"
98
99
  @header = "<strong>Documentation</strong> JSDuck"
99
- @footer = "Generated with <a href='https://github.com/senchalabs/jsduck'>JSDuck</a> #{@version}."
100
+ @footer = format_footer("Generated on {DATE} by {JSDUCK} {VERSION}.")
100
101
  @head_html = ""
101
102
  @body_html = ""
103
+ @css = ""
104
+ @message = ""
102
105
  @welcome = nil
103
106
  @guides = nil
104
107
  @videos = nil
@@ -125,7 +128,6 @@ module JsDuck
125
128
  @template_dir = @root_dir + "/template-min"
126
129
  @template_links = false
127
130
  @extjs_path = "extjs/ext-all.js"
128
- @data_path = nil # This gets assigned in JsDuck::WebWriter after writing the data file.
129
131
  @local_storage_db = "docs"
130
132
  @touch_examples_ui = false
131
133
  @imports = []
@@ -138,6 +140,8 @@ module JsDuck
138
140
  Logger.set_warning(:all, true)
139
141
  Logger.set_warning(:link_auto, false)
140
142
  Logger.set_warning(:param_count, false)
143
+
144
+ @optparser = create_option_parser
141
145
  end
142
146
 
143
147
  # Make options object behave like hash.
@@ -150,16 +154,25 @@ module JsDuck
150
154
  end
151
155
 
152
156
  def parse!(argv)
153
- create_option_parser.parse!(argv).each do |fname|
154
- read_filenames(canonical(fname))
155
- end
157
+ parse_options(argv)
158
+ auto_detect_config_file
156
159
  validate
157
160
 
158
- @custom_tag_paths.each {|path| TagRegistry.load_from(path) }
161
+ if @custom_tag_paths.length > 0
162
+ TagRegistry.reconfigure(@custom_tag_paths)
163
+ else
164
+ # Ensure the TagRegistry get instantiated just once.
165
+ # Otherwise the parallel processing causes multiple requests
166
+ # to initialize the TagRegistry, resulting in loading the Tag
167
+ # definitions multiple times.
168
+ TagRegistry.instance
169
+ end
159
170
  end
160
171
 
172
+ private
173
+
161
174
  def create_option_parser
162
- optparser = JsDuck::OptionParser.new do | opts |
175
+ return JsDuck::OptionParser.new do | opts |
163
176
  opts.banner = "Usage: jsduck [options] files/dirs..."
164
177
  opts.separator ""
165
178
  opts.separator "For example:"
@@ -227,6 +240,9 @@ module JsDuck
227
240
  "",
228
241
  "An alternative to listing all options on command line.",
229
242
  "",
243
+ "When the current directory contains jsduck.json file",
244
+ "then options are automatically read from there.",
245
+ "",
230
246
  "See also: https://github.com/senchalabs/jsduck/wiki/Config-file") do |path|
231
247
  path = canonical(path)
232
248
  if File.exists?(path)
@@ -238,7 +254,7 @@ module JsDuck
238
254
  # treat paths inside JSON config relative to the location of
239
255
  # config file. When done, switch back to current working dir.
240
256
  @working_dir = File.dirname(path)
241
- optparser.parse!(config).each {|fname| read_filenames(canonical(fname)) }
257
+ parse_options(config)
242
258
  @working_dir = nil
243
259
  end
244
260
 
@@ -265,11 +281,14 @@ module JsDuck
265
281
  opts.on('--footer=TEXT',
266
282
  "Custom footer text for the documentation.",
267
283
  "",
268
- "Defaults to: 'Generated with JSDuck {VERSION}.'",
284
+ "The text can contain various placeholders:",
285
+ "",
286
+ " {DATE} - current date and time.",
287
+ " {JSDUCK} - link to JSDuck homepage.",
288
+ " {VERSION} - JSDuck version number.",
269
289
  "",
270
- "'{VERSION}' is a placeholder that will get substituted",
271
- "with the current version of JSDuck. See --version.") do |text|
272
- @footer = text.gsub(/\{VERSION\}/, @version)
290
+ "Defaults to: 'Generated on {DATE} by {JSDUCK} {VERSION}.'") do |text|
291
+ @footer = format_footer(text)
273
292
  end
274
293
 
275
294
  opts.on('--head-html=HTML',
@@ -277,9 +296,12 @@ module JsDuck
277
296
  "",
278
297
  "Useful for adding extra <style> and other tags.",
279
298
  "",
299
+ "Also a name of an HTML file can be passed.",
300
+ "Then the contents of that file will be read in.",
301
+ "",
280
302
  "This option can be used repeatedly to append several",
281
303
  "things to the header.") do |html|
282
- @head_html += html
304
+ @head_html += maybe_file(html)
283
305
  end
284
306
 
285
307
  opts.on('--body-html=HTML',
@@ -287,9 +309,32 @@ module JsDuck
287
309
  "",
288
310
  "Useful for adding extra markup to the page.",
289
311
  "",
312
+ "Also a name of an HTML file can be passed.",
313
+ "Then the contents of that file will be read in.",
314
+ "",
290
315
  "This option can be used repeatedly to append several",
291
316
  "things to the body.") do |html|
292
- @body_html += html
317
+ @body_html += maybe_file(html)
318
+ end
319
+
320
+ opts.on('--css=CSS',
321
+ "Extra CSS rules to include to the page.",
322
+ "",
323
+ "Also a name of a CSS file can be passed.",
324
+ "Then the contents of that file will be read in.",
325
+ "",
326
+ "This option can be used repeatedly to append multiple",
327
+ "chunks of CSS.") do |css|
328
+ @css += maybe_file(css)
329
+ end
330
+
331
+ opts.on('--message=HTML',
332
+ "(Warning) message to show prominently.",
333
+ "",
334
+ "Useful for warning users that they are viewing an old",
335
+ "version of the docs, and prividing a link to the new",
336
+ "version.") do |html|
337
+ @message += html
293
338
  end
294
339
 
295
340
  opts.on('--welcome=PATH',
@@ -546,7 +591,7 @@ module JsDuck
546
591
  @touch_examples_ui = true
547
592
  end
548
593
 
549
- opts.on('--ignore-html=TAG',
594
+ opts.on('--ignore-html=TAG1,TAG2', Array,
550
595
  "Ignore a particular unclosed HTML tag.",
551
596
  "",
552
597
  "Normally all tags like <foo> that aren't followed at some",
@@ -557,8 +602,10 @@ module JsDuck
557
602
  "",
558
603
  "Useful for ignoring the ExtJS preprocessor directives",
559
604
  "<locale> and <debug> which would otherwise be reported",
560
- "as unclosed tags.") do |tag|
561
- @ignore_html[tag] = true
605
+ "as unclosed tags.") do |tags|
606
+ tags.each do |tag|
607
+ @ignore_html[tag] = true
608
+ end
562
609
  end
563
610
 
564
611
  opts.separator ""
@@ -568,24 +615,40 @@ module JsDuck
568
615
  opts.on('-v', '--verbose',
569
616
  "Turns on excessive logging.",
570
617
  "",
571
- "Log messages are writted to STDERR.") do
618
+ "Log messages are written to STDERR.") do
572
619
  Logger.verbose = true
573
620
  end
574
621
 
575
622
  opts.on('--warnings=+A,-B,+C', Array,
576
623
  "Turns warnings selectively on/off.",
577
624
  "",
578
- " +all - to turn on all warnings",
625
+ " +all - to turn on all warnings.",
626
+ " -all - to turn off all warnings.",
627
+ "",
628
+ "Additionally a pattern can be specified to only apply the",
629
+ "setting for a particular set of files. For example to turn",
630
+ "off all warnings related to chart classes:",
631
+ "",
632
+ " -all:extjs/src/chart",
633
+ "",
634
+ "Note, that the order of the rules matters. When you first",
635
+ "say +link and then -all, the result will be that all warnings",
636
+ "get disabled.",
637
+ "",
638
+ "Currently one can't mix disabling and enabling file patterns.",
639
+ "For example --warnings=-link,+link:/src,-link:/src/ux will",
640
+ "ignore the last rule about /src/ux.",
579
641
  "",
580
642
  "List of all available warning types:",
581
643
  "(Those with '+' in front of them default to on)",
582
644
  "",
583
645
  *Logger.doc_warnings) do |warnings|
584
646
  warnings.each do |op|
585
- if op =~ /^([-+]?)(.*)$/
647
+ if op =~ /^([-+]?)(\w+)(?::(.*))?$/
586
648
  enable = !($1 == "-")
587
649
  name = $2.to_sym
588
- Logger.set_warning(name, enable)
650
+ path = $3
651
+ Logger.set_warning(name, enable, path)
589
652
  end
590
653
  end
591
654
  end
@@ -628,6 +691,14 @@ module JsDuck
628
691
  @template_links = true
629
692
  end
630
693
 
694
+ opts.on('-d', '--debug',
695
+ "Same as --template=template --template-links.",
696
+ "",
697
+ "Useful shorthand during development.") do
698
+ @template_dir = canonical("template")
699
+ @template_links = true
700
+ end
701
+
631
702
  opts.on('--extjs-path=PATH',
632
703
  "Path for main ExtJS JavaScript file.",
633
704
  "",
@@ -669,8 +740,20 @@ module JsDuck
669
740
  exit
670
741
  end
671
742
  end
743
+ end
744
+
745
+ # Parses the given command line options
746
+ # (could have also been read from config file)
747
+ def parse_options(options)
748
+ @optparser.parse!(options).each {|fname| read_filenames(canonical(fname)) }
749
+ end
672
750
 
673
- return optparser
751
+ # Reads jsduck.json file in current directory
752
+ def auto_detect_config_file
753
+ fname = Dir.pwd + "/jsduck.json"
754
+ if File.exists?(fname)
755
+ parse_options(read_json_config(fname))
756
+ end
674
757
  end
675
758
 
676
759
  # Reads JSON configuration from file and returns an array of
@@ -724,6 +807,17 @@ module JsDuck
724
807
  end.flatten
725
808
  end
726
809
 
810
+ # When given string is a file, returns the contents of the file.
811
+ # Otherwise returns the string unchanged.
812
+ def maybe_file(str)
813
+ path = canonical(str)
814
+ if File.exists?(path)
815
+ Util::IO.read(path)
816
+ else
817
+ str
818
+ end
819
+ end
820
+
727
821
  # Converts relative path to full path
728
822
  #
729
823
  # Especially important for running on Windows where C:\foo\bar
@@ -733,6 +827,13 @@ module JsDuck
733
827
  File.expand_path(path, @working_dir)
734
828
  end
735
829
 
830
+ # Replace special placeholders in footer text
831
+ def format_footer(text)
832
+ jsduck = "<a href='https://github.com/senchalabs/jsduck'>JSDuck</a>"
833
+ date = Time.new.strftime('%a %d %b %Y %H:%M:%S')
834
+ text.gsub(/\{VERSION\}/, @version).gsub(/\{JSDUCK\}/, jsduck).gsub(/\{DATE\}/, date)
835
+ end
836
+
736
837
  # Runs checks on the options
737
838
  def validate
738
839
  if @input_files.length == 0 && !@welcome && !@guides && !@videos && !@examples