jsduck 0.5 → 0.6

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.
@@ -1,17 +1,31 @@
1
1
  require 'rubygems'
2
2
  require 'rdiscount'
3
+ require 'strscan'
4
+ require 'cgi'
3
5
 
4
6
  module JsDuck
5
7
 
6
8
  # Formats doc-comments
7
9
  class DocFormatter
8
- # CSS class to add to each link
9
- attr_accessor :css_class
10
+ # Template HTML that replaces {@link Class#member anchor text}.
11
+ # Can contain placeholders:
12
+ #
13
+ # %c - full class name (e.g. "Ext.Panel")
14
+ # %m - class member name (e.g. "urlEncode")
15
+ # %M - class member name, prefixed with hash (e.g. "#urlEncode")
16
+ # %a - anchor text for link
17
+ #
18
+ # Default value: '<a href="%c%M">%a</a>'
19
+ attr_accessor :link_tpl
10
20
 
11
- # Template for the href URL.
12
- # Can contain %cls% which is replaced with actual classname.
13
- # Also '#' and member name is appended to link if needed
14
- attr_accessor :url_template
21
+ # Template HTML that replaces {@img URL alt text}
22
+ # Can contain placeholders:
23
+ #
24
+ # %u - URL from @img tag (e.g. "some/path.png")
25
+ # %a - alt text for image
26
+ #
27
+ # Default value: '<img src="%u" alt="%a"/>'
28
+ attr_accessor :img_tpl
15
29
 
16
30
  # Sets up instance to work in context of particular class, so
17
31
  # that when {@link #blah} is encountered it knows that
@@ -21,19 +35,53 @@ module JsDuck
21
35
  # Maximum length for text that doesn't get shortened, defaults to 120
22
36
  attr_accessor :max_length
23
37
 
38
+ # JsDuck::Relations for looking up class names.
39
+ #
40
+ # When auto-creating class links from CamelCased names found from
41
+ # text, we check the relations object to see if a class with that
42
+ # name actually exists.
43
+ attr_accessor :relations
44
+
24
45
  def initialize
25
46
  @context = ""
26
- @css_class = nil
27
- @url_template = "%cls%"
28
47
  @max_length = 120
48
+ @relations = {}
49
+ @link_tpl = '<a href="%c%M">%a</a>'
50
+ @img_tpl = '<img src="%u" alt="%a"/>'
51
+ @link_re = /\{@link\s+(\S*?)(?:\s+(.+?))?\}/m
52
+ @img_re = /\{@img\s+(\S*?)(?:\s+(.+?))?\}/m
29
53
  end
30
54
 
55
+ # Replaces {@link} and {@img} tags, auto-generates links for
56
+ # recognized classnames.
57
+ #
31
58
  # Replaces {@link Class#member link text} in given string with
32
- # HTML links pointing to documentation. In addition to the href
33
- # attribute links will also contain ext:cls and ext:member
34
- # attributes.
59
+ # HTML from @link_tpl.
60
+ #
61
+ # Replaces {@img path/to/image.jpg Alt text} with HTML from @img_tpl.
62
+ #
63
+ # Additionally replaces strings recognized as ClassNames with
64
+ # links to these classes. So one doesn even need to use the @link
65
+ # tag to create a link.
35
66
  def replace(input)
36
- input.gsub(/\{@link\s+(\S*?)(?:\s+(.+?))?\}/m) do
67
+ s = StringScanner.new(input)
68
+ out = ""
69
+ while !s.eos? do
70
+ if s.check(@link_re)
71
+ out += replace_link_tag(s.scan(@link_re))
72
+ elsif s.check(@img_re)
73
+ out += replace_img_tag(s.scan(@img_re))
74
+ elsif s.check(/\{/)
75
+ out += s.scan(/\{/)
76
+ else
77
+ out += replace_class_names(s.scan(/[^{]+/))
78
+ end
79
+ end
80
+ out
81
+ end
82
+
83
+ def replace_link_tag(input)
84
+ input.sub(@link_re) do
37
85
  target = $1
38
86
  text = $2
39
87
  if target =~ /^(.*)#(.*)$/
@@ -57,14 +105,57 @@ module JsDuck
57
105
  end
58
106
  end
59
107
 
60
- # Creates HTML link to class and/or member
61
- def link(cls, member, label)
62
- anchor = member ? "#" + member : ""
63
- url = @url_template.sub(/%cls%/, cls) + anchor
64
- href = ' href="' + url + '"'
65
- rel = ' rel="' + cls + anchor + '"'
66
- cssCls = @css_class ? ' class="' + @css_class + '"' : ''
67
- "<a" + href + rel + cssCls + ">" + label + "</a>"
108
+ def replace_img_tag(input)
109
+ input.sub(@img_re) { img($1, $2) }
110
+ end
111
+
112
+ def replace_class_names(input)
113
+ input.gsub(/(\A|\s)([A-Z][A-Za-z0-9.]*[A-Za-z0-9])(?:(#)([A-Za-z0-9]+))?([.,]?(?:\s|\Z))/m) do
114
+ before = $1
115
+ cls = $2
116
+ hash = $3
117
+ method = $4
118
+ after = $5
119
+
120
+ if @relations[cls]
121
+ label = method ? cls+"."+method : cls
122
+ before + link(cls, method, label) + after
123
+ else
124
+ before + cls + (hash || "") + (method || "") + after
125
+ end
126
+ end
127
+ end
128
+
129
+ # applies the image template
130
+ def img(url, alt_text)
131
+ @img_tpl.gsub(/(%\w)/) do
132
+ case $1
133
+ when '%u'
134
+ url
135
+ when '%a'
136
+ CGI.escapeHTML(alt_text||"")
137
+ else
138
+ $1
139
+ end
140
+ end
141
+ end
142
+
143
+ # applies the link template
144
+ def link(cls, member, anchor_text)
145
+ @link_tpl.gsub(/(%\w)/) do
146
+ case $1
147
+ when '%c'
148
+ cls
149
+ when '%m'
150
+ member ? member : ""
151
+ when '%M'
152
+ member ? "#"+member : ""
153
+ when '%a'
154
+ CGI.escapeHTML(anchor_text||"")
155
+ else
156
+ $1
157
+ end
158
+ end
68
159
  end
69
160
 
70
161
  # Formats doc-comment for placement into HTML.
@@ -21,6 +21,11 @@ module JsDuck
21
21
  # @see and {@link} are parsed separately in JsDuck::DocFormatter.
22
22
  #
23
23
  class DocParser
24
+ # Pass in :css to be able to parse CSS doc-comments
25
+ def initialize(mode = :js)
26
+ @ident_pattern = (mode == :css) ? /\$[a-zA-Z0-9_-]*/ : /\w+/
27
+ end
28
+
24
29
  def parse(input)
25
30
  @tags = []
26
31
  @input = StringScanner.new(purify(input))
@@ -66,8 +71,12 @@ module JsDuck
66
71
  while !@input.eos? do
67
72
  if look(/@class\b/)
68
73
  at_class
69
- elsif look(/@extends\b/)
74
+ elsif look(/@extends?\b/)
70
75
  at_extends
76
+ elsif look(/@mixins?\b/)
77
+ at_mixins
78
+ elsif look(/@alternateClassNames?\b/)
79
+ at_alternateClassName
71
80
  elsif look(/@singleton\b/)
72
81
  boolean_at_tag(/@singleton/, :singleton)
73
82
  elsif look(/@event\b/)
@@ -92,6 +101,10 @@ module JsDuck
92
101
  at_member
93
102
  elsif look(/@author\b/)
94
103
  at_author
104
+ elsif look(/@docauthor\b/)
105
+ at_docauthor
106
+ elsif look(/@var\b/)
107
+ at_var
95
108
  elsif look(/@static\b/)
96
109
  boolean_at_tag(/@static/, :static)
97
110
  elsif look(/@(private|ignore|hide|protected)\b/)
@@ -117,12 +130,30 @@ module JsDuck
117
130
 
118
131
  # matches @extends name ...
119
132
  def at_extends
120
- match(/@extends/)
133
+ match(/@extends?/)
121
134
  add_tag(:extends)
122
135
  maybe_ident_chain(:extends)
123
136
  skip_white
124
137
  end
125
138
 
139
+ # matches @mixins name1 name2 ...
140
+ def at_mixins
141
+ match(/@mixins?/)
142
+ add_tag(:mixins)
143
+ skip_horiz_white
144
+ @current_tag[:mixins] = class_list
145
+ skip_white
146
+ end
147
+
148
+ # matches @alternateClassName name1 name2 ...
149
+ def at_alternateClassName
150
+ match(/@alternateClassNames?/)
151
+ add_tag(:alternateClassNames)
152
+ skip_horiz_white
153
+ @current_tag[:alternateClassNames] = class_list
154
+ skip_white
155
+ end
156
+
126
157
  # matches @event name ...
127
158
  def at_event
128
159
  match(/@event/)
@@ -179,6 +210,15 @@ module JsDuck
179
210
  skip_white
180
211
  end
181
212
 
213
+ # matches @var {type} $name ...
214
+ def at_var
215
+ match(/@var/)
216
+ add_tag(:css_var)
217
+ maybe_type
218
+ maybe_name
219
+ skip_white
220
+ end
221
+
182
222
  # matches @type {type} or @type type
183
223
  #
184
224
  # The presence of @type implies that we are dealing with property.
@@ -221,6 +261,15 @@ module JsDuck
221
261
  skip_white
222
262
  end
223
263
 
264
+ # matches @docauthor some name ... newline
265
+ def at_docauthor
266
+ match(/@docauthor/)
267
+ add_tag(:docauthor)
268
+ skip_horiz_white
269
+ @current_tag[:name] = @input.scan(/.*$/)
270
+ skip_white
271
+ end
272
+
224
273
  # Used to match @private, @ignore, @hide, ...
225
274
  def boolean_at_tag(regex, propname)
226
275
  match(regex)
@@ -239,8 +288,8 @@ module JsDuck
239
288
  # matches identifier name if possible and sets it on @current_tag
240
289
  def maybe_name
241
290
  skip_horiz_white
242
- if look(/\w/)
243
- @current_tag[:name] = ident
291
+ if look(@ident_pattern)
292
+ @current_tag[:name] = @input.scan(@ident_pattern)
244
293
  end
245
294
  end
246
295
 
@@ -260,6 +309,17 @@ module JsDuck
260
309
  return name
261
310
  end
262
311
 
312
+ # matches <ident_chain> <ident_chain> ... until line end
313
+ def class_list
314
+ skip_horiz_white
315
+ classes = []
316
+ while look(/\w/)
317
+ classes << ident_chain
318
+ skip_horiz_white
319
+ end
320
+ classes
321
+ end
322
+
263
323
  # matches chained.identifier.name and returns it
264
324
  def ident_chain
265
325
  @input.scan(/[\w.]+/)
@@ -5,15 +5,15 @@ require 'jsduck/long_params'
5
5
  module JsDuck
6
6
 
7
7
  class EventTable < Table
8
- def initialize(cls, cache={})
9
- super(cls, cache)
8
+ def initialize(cls, formatter, cache={})
9
+ super(cls, formatter, cache)
10
10
  @type = :event
11
11
  @id = @cls.full_name + "-events"
12
12
  @title = "Public Events"
13
13
  @column_title = "Event"
14
14
  @row_class = "event-row"
15
15
  @short_params = ShortParams.new
16
- @long_params = LongParams.new(@cls)
16
+ @long_params = LongParams.new(@formatter)
17
17
  end
18
18
 
19
19
  def signature_suffix(item)
@@ -9,29 +9,31 @@ module JsDuck
9
9
  # Also all the :doc elements will be formatted - converted from
10
10
  # markdown to HTML and @links resolved.
11
11
  class Exporter
12
- attr_accessor :relations
13
-
14
- def initialize(relations)
12
+ def initialize(relations, formatter)
15
13
  @relations = relations
16
- @formatter = DocFormatter.new
17
- @formatter.css_class = 'docClass'
14
+ @formatter = formatter
18
15
  end
19
16
 
20
17
  # Returns all data in Class object as hash.
21
18
  def export(cls)
22
19
  h = cls.to_hash
23
20
  h[:cfgs] = cls.members(:cfg)
24
- h[:properties] = cls.members(:property)
25
- h[:methods] = cls.members(:method)
26
- h[:events] = cls.members(:event)
27
21
  h.delete(:cfg)
22
+ h[:properties] = cls.members(:property)
28
23
  h.delete(:property)
24
+ h[:methods] = cls.members(:method)
29
25
  h.delete(:method)
26
+ h[:events] = cls.members(:event)
30
27
  h.delete(:event)
28
+ h[:cssVars] = cls.members(:css_var)
29
+ h.delete(:css_var)
30
+ h[:cssMixins] = cls.members(:css_mixin)
31
+ h.delete(:css_mixin)
31
32
  h[:component] = cls.inherits_from?("Ext.Component")
32
33
  h[:superclasses] = cls.superclasses.collect {|c| c.full_name }
33
34
  h[:subclasses] = @relations.subclasses(cls).collect {|c| c.full_name }
34
35
  h[:mixedInto] = @relations.mixed_into(cls).collect {|c| c.full_name }
36
+ h[:allMixins] = cls.all_mixins.collect {|c| c.full_name }
35
37
  format_class(h)
36
38
  end
37
39
 
@@ -39,7 +41,7 @@ module JsDuck
39
41
  def format_class(c)
40
42
  @formatter.context = c[:name]
41
43
  c[:doc] = @formatter.format(c[:doc]) if c[:doc]
42
- [:cfgs, :properties, :methods, :events].each do |type|
44
+ [:cfgs, :properties, :methods, :events, :cssVars, :cssMixins].each do |type|
43
45
  c[type] = c[type].map {|m| format_member(m) }
44
46
  end
45
47
  c
@@ -1,15 +1,10 @@
1
- require 'jsduck/doc_formatter'
2
-
3
1
  module JsDuck
4
2
 
5
3
  # Creates the inheritance tree shown on class documentation page.
6
4
  class InheritanceTree
7
- def initialize(cls)
5
+ def initialize(cls, formatter)
8
6
  @cls = cls
9
- @formatter = DocFormatter.new
10
- @formatter.context = cls.full_name
11
- @formatter.css_class = 'docClass'
12
- @formatter.url_template = 'output/%cls%.html'
7
+ @formatter = formatter
13
8
  end
14
9
 
15
10
  # Renders the tree using HTML <pre> element
@@ -3,7 +3,7 @@ require 'jsduck/doc_parser'
3
3
 
4
4
  module JsDuck
5
5
 
6
- class Parser
6
+ class JsParser
7
7
  def initialize(input)
8
8
  @lex = Lexer.new(input)
9
9
  @doc_parser = DocParser.new
@@ -195,7 +195,7 @@ module JsDuck
195
195
  cfg
196
196
  end
197
197
 
198
- # <ext-define-cfg> := "{" ( <extend> | <mixins> | <?> )*
198
+ # <ext-define-cfg> := "{" ( <extend> | <mixins> | <alternate-class-name> | <?> )*
199
199
  def ext_define_cfg
200
200
  match("{")
201
201
  cfg = {}
@@ -208,6 +208,9 @@ module JsDuck
208
208
  elsif look("mixins", ":", "{")
209
209
  cfg[:mixins] = ext_define_mixins
210
210
  found = true
211
+ elsif look("alternateClassName", ":")
212
+ cfg[:alternateClassNames] = ext_define_alternate_class_name
213
+ found = true
211
214
  elsif look(:ident, ":")
212
215
  match(:ident, ":")
213
216
  if look(:string) || look(:number) || look(:regex) ||
@@ -231,6 +234,18 @@ module JsDuck
231
234
  match("extend", ":", :string)
232
235
  end
233
236
 
237
+ # <ext-define-alternate-class-name> := "alternateClassName" ":" ( <string> | <array-of-strings> )
238
+ def ext_define_alternate_class_name
239
+ match("alternateClassName", ":")
240
+ if look(:string)
241
+ [ match(:string) ]
242
+ elsif look("[")
243
+ array_of_strings
244
+ else
245
+ []
246
+ end
247
+ end
248
+
234
249
  # <ext-define-mixins> := "mixins" ":" "{" [ <ident> ":" <string> ","? ]* "}"
235
250
  def ext_define_mixins
236
251
  match("mixins", ":", "{")
@@ -246,14 +261,15 @@ module JsDuck
246
261
  # <array-of-strings> := "[" [ <string> ","? ]* "]"
247
262
  def array_of_strings
248
263
  match("[")
264
+ strs = []
249
265
  while look(:string)
250
- match(:string)
266
+ strs << match(:string)
251
267
  match(",") if look(",")
252
268
  end
253
269
 
254
270
  if look("]")
255
271
  match("]")
256
- true
272
+ strs
257
273
  else
258
274
  false
259
275
  end
data/lib/jsduck/lexer.rb CHANGED
@@ -135,8 +135,7 @@ module JsDuck
135
135
  nr = @input.scan(/[0-9]+(\.[0-9]*)?/)
136
136
  @tokens << {
137
137
  :type => :number,
138
- # When number ends with ".", append "0" so Ruby eval will work
139
- :value => eval(/\.$/ =~ nr ? nr+"0" : nr)
138
+ :value => nr
140
139
  }
141
140
  elsif @input.check(/\$/)
142
141
  value = @input.scan(/\$\w*/)