jsduck 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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*/)