jsduck 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -54,7 +54,7 @@ it would like that you wrote comments like that instead:
54
54
  /**
55
55
  * Basic text field. Can be used as a direct replacement for traditional
56
56
  * text inputs, or as the base class for more sophisticated input controls
57
- * (like {@link Ext.form.TextArea} and {@link Ext.form.ComboBox}).
57
+ * (like Ext.form.TextArea and Ext.form.ComboBox).
58
58
  *
59
59
  * Validation
60
60
  * ----------
@@ -197,12 +197,40 @@ Copying
197
197
  JsDuck is distributed under the terms of the GNU General Public License version 3.
198
198
 
199
199
  JsDuck was developed by [Rene Saarsoo](http://triin.net),
200
- with contributions from [Ondřej Jirman](https://github.com/megous).
200
+ with contributions from [Ondřej Jirman](https://github.com/megous)
201
+ and [Nick Poulden](https://github.com/nick).
201
202
 
202
203
 
203
204
  Changelog
204
205
  ---------
205
206
 
207
+ * 0.6 - JsDuck is now used for creating the official ExtJS4 documentation.
208
+ * Automatic linking of class names found in comments. Instead of writing
209
+ `{@link Ext.Panel}` one can simply write `Ext.Panel` and link will be
210
+ automatically created.
211
+ * In generated docs, method return types and parameter types are also
212
+ automatically linked to classes if such class is included to docs.
213
+ * Support for `{@img}` tag for including images to documentation.
214
+ The markup created by `{@link}` and `{@img}` tags can now be customized using
215
+ the --img and --link command line options to supply HTML templates.
216
+ * Links to source code are no more simply links to line numbers.
217
+ Instead the source code files will contain ID-s like `MyClass-cfg-style`.
218
+ * New tags: `@docauthor`, `@alternateClassName`, `@mixins`.
219
+ The latter two Ext4 class properties are both detected from code and
220
+ can also be defined (or overriden) in doc-comments.
221
+ * Global methods are now placed to separate "global" class.
222
+ Creation of this can be turned off using `--ignore-global`.
223
+ * Much improved search feature.
224
+ Search results are now ordered so that best matches are at the top.
225
+ No more is there a select-box to match at beginning/middle/end -
226
+ we automatically search first by exact match, then beginning and
227
+ finally by middle. Additionally the search no more lists a lot of
228
+ duplicates - only the class that defines a method is listed, ignoring
229
+ all the classes that inherit it.
230
+ * Support for doc-comments in [SASS](http://sass-lang.com/) .scss files:
231
+ For now, it's possible to document SASS variables and mixins.
232
+ * Several bug fixes.
233
+
206
234
  * 0.5 - Search and export
207
235
  * Search from the actually generated docs (not through sencha.com)
208
236
  * JSON export with --json switch.
data/bin/jsduck CHANGED
@@ -34,10 +34,28 @@ opts = OptionParser.new do | opts |
34
34
  app.template_dir = path
35
35
  end
36
36
 
37
- opts.on('--json', "Produces JSON export instead of HTML documentation.") do |path|
37
+ opts.on('--json', "Produces JSON export instead of HTML documentation.") do
38
38
  app.export = :json
39
39
  end
40
40
 
41
+ opts.on('--link=TPL', "HTML template for replacing {@link}.",
42
+ "Possible placeholders:",
43
+ "%c - full class name (e.g. 'Ext.Panel')",
44
+ "%m - class member name (e.g. 'urlEncode')",
45
+ "%M - class member name, prefixed with hash (e.g. '#urlEncode')",
46
+ "%a - anchor text for link",
47
+ "Default value in export: '<a href=\"%c%M\">%a</a>'") do |tpl|
48
+ app.link_tpl = tpl
49
+ end
50
+
51
+ opts.on('--img=TPL', "HTML template for replacing {@img}.",
52
+ "Possible placeholders:",
53
+ "%u - URL from @img tag (e.g. 'some/path.png')",
54
+ "%a - alt text for image",
55
+ "Default value in export: '<img src=\"%u\" alt=\"%a\"/>'") do |tpl|
56
+ app.img_tpl = tpl
57
+ end
58
+
41
59
  # For debugging it's often useful to set --processes=0 to get deterministic results.
42
60
  opts.on('-p', '--processes=COUNT', "The number of parallel processes to use.",
43
61
  "Defaults to the number of processors/cores.",
@@ -45,6 +63,10 @@ opts = OptionParser.new do | opts |
45
63
  app.processes = count.to_i
46
64
  end
47
65
 
66
+ opts.on('--ignore-global', "Turns off the creation of global class.") do
67
+ app.ignore_global = true
68
+ end
69
+
48
70
  opts.on('-v', '--verbose', "This will fill up your console.") do
49
71
  app.verbose = true
50
72
  end
@@ -60,7 +82,7 @@ js_files = []
60
82
  opts.parse!(ARGV).each do |fname|
61
83
  if File.exists?(fname)
62
84
  if File.directory?(fname)
63
- Dir[fname+"/**/*.js"].each {|f| js_files << f }
85
+ Dir[fname+"/**/*.{js,css,scss}"].each {|f| js_files << f }
64
86
  else
65
87
  js_files << fname
66
88
  end
data/jsduck.gemspec CHANGED
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
2
2
  s.required_rubygems_version = ">= 1.3.7"
3
3
 
4
4
  s.name = 'jsduck'
5
- s.version = '0.5'
6
- s.date = '2011-03-29'
5
+ s.version = '0.6'
6
+ s.date = '2011-04-27'
7
7
  s.summary = "Simple JavaScript Duckumentation generator"
8
8
  s.description = "Better ext-doc like JavaScript documentation generator for ExtJS"
9
9
  s.homepage = "https://github.com/nene/jsduck"
@@ -1,5 +1,3 @@
1
- require 'jsduck/merger'
2
-
3
1
  module JsDuck
4
2
 
5
3
  # Combines JavaScript Parser, DocParser and Merger.
@@ -10,44 +8,17 @@ module JsDuck
10
8
  @classes = {}
11
9
  @orphans = []
12
10
  @current_class = nil
13
- @merger = Merger.new
14
11
  end
15
12
 
16
13
  # Combines chunk of parsed JavaScript together with previously
17
14
  # added chunks. The resulting documentation is accumulated inside
18
15
  # this class and can be later accessed through #result method.
19
16
  #
20
- # - input parse result from JsDuck::Parser
21
- # - filename name of the JS file where it came from
22
- # - html_filename name of the HTML file where the source was saved.
17
+ # - file SoureFile class instance
23
18
  #
24
- def aggregate(input, filename="", html_filename="")
19
+ def aggregate(file)
25
20
  @current_class = nil
26
- input.each do |docset|
27
- doc = @merger.merge(docset[:comment], docset[:code])
28
-
29
- add_source_data(doc, {
30
- :filename => filename,
31
- :html_filename => html_filename,
32
- :linenr => docset[:linenr],
33
- })
34
-
35
- register(doc)
36
- end
37
- end
38
-
39
- # Links doc-object to source code where it came from.
40
- def add_source_data(doc, src)
41
- doc[:href] = src[:html_filename] + "#line-" + src[:linenr].to_s
42
- doc[:filename] = src[:filename]
43
- doc[:linenr] = src[:linenr]
44
- # class-level doc-comment can contain constructor and config
45
- # options, link those to the same location in source.
46
- if doc[:tagname] == :class
47
- doc[:cfg].each {|cfg| add_source_data(cfg, src) }
48
- doc[:method].each {|method| add_source_data(method, src) }
49
- end
50
- doc
21
+ file.each {|doc| register(doc) }
51
22
  end
52
23
 
53
24
  # Registers documentation node either as class or as member of
@@ -80,6 +51,9 @@ module JsDuck
80
51
  [:extends, :xtype, :singleton, :private].each do |tag|
81
52
  old[tag] = old[tag] || new[tag]
82
53
  end
54
+ [:mixins, :alternateClassNames].each do |tag|
55
+ old[tag] = old[tag] + new[tag]
56
+ end
83
57
  old[:doc] = old[:doc].length > 0 ? old[:doc] : new[:doc]
84
58
  old[:cfg] = old[:cfg] + new[:cfg]
85
59
  end
@@ -121,6 +95,53 @@ module JsDuck
121
95
  end
122
96
  end
123
97
 
98
+ # Creates classes for orphans that have :member property defined,
99
+ # and then inserts orphans to these classes.
100
+ def classify_orphans
101
+ @orphans.each do |orph|
102
+ if orph[:member]
103
+ class_name = orph[:member]
104
+ if !@classes[class_name]
105
+ add_empty_class(class_name)
106
+ end
107
+ add_member(orph)
108
+ @orphans.delete(orph)
109
+ end
110
+ end
111
+ end
112
+
113
+ # Creates class with name "global" and inserts all the remaining
114
+ # orphans into it (but only if there are any orphans).
115
+ def create_global_class
116
+ return if @orphans.length == 0
117
+
118
+ add_empty_class("global", "Global variables and functions.")
119
+ @orphans.each do |orph|
120
+ orph[:member] = "global"
121
+ add_member(orph)
122
+ end
123
+ @orphans = []
124
+ end
125
+
126
+ def add_empty_class(name, doc = "")
127
+ add_class({
128
+ :tagname => :class,
129
+ :name => name,
130
+ :doc => doc,
131
+ :mixins => [],
132
+ :alternateClassNames => [],
133
+ :cfg => [],
134
+ :property => [],
135
+ :method => [],
136
+ :event => [],
137
+ :css_var => [],
138
+ :css_mixin => [],
139
+ :filename => "",
140
+ :html_filename => "",
141
+ :linenr => 0,
142
+ })
143
+ end
144
+
124
145
  def result
125
146
  @documentation + @orphans
126
147
  end
data/lib/jsduck/app.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
- require 'jsduck/parser'
3
2
  require 'jsduck/aggregator'
4
- require 'jsduck/source_formatter'
3
+ require 'jsduck/source_file'
4
+ require 'jsduck/source_writer'
5
5
  require 'jsduck/class'
6
6
  require 'jsduck/tree'
7
7
  require 'jsduck/tree_icons'
@@ -24,6 +24,9 @@ module JsDuck
24
24
  attr_accessor :input_files
25
25
  attr_accessor :verbose
26
26
  attr_accessor :export
27
+ attr_accessor :link_tpl
28
+ attr_accessor :img_tpl
29
+ attr_accessor :ignore_global
27
30
 
28
31
  def initialize
29
32
  @output_dir = nil
@@ -31,6 +34,9 @@ module JsDuck
31
34
  @input_files = []
32
35
  @verbose = false
33
36
  @export = nil
37
+ @link_tpl = nil
38
+ @img_tpl = nil
39
+ @ignore_global = false
34
40
  @timer = Timer.new
35
41
  @parallel = ParallelWrap.new
36
42
  end
@@ -43,21 +49,21 @@ module JsDuck
43
49
 
44
50
  # Call this after input parameters set
45
51
  def run
46
- clear_dir(@output_dir)
47
- if @export
48
- FileUtils.mkdir(@output_dir)
49
- init_output_dirs(@output_dir)
50
- else
51
- copy_template(@template_dir, @output_dir)
52
- end
53
-
54
52
  parsed_files = @timer.time(:parsing) { parallel_parse(@input_files) }
55
53
  result = @timer.time(:aggregating) { aggregate(parsed_files) }
56
54
  relations = @timer.time(:aggregating) { filter_classes(result) }
55
+ warn_globals(relations)
56
+ warn_unnamed(relations)
57
57
 
58
+ clear_dir(@output_dir)
58
59
  if @export == :json
60
+ FileUtils.mkdir(@output_dir)
61
+ init_output_dirs(@output_dir)
62
+ @timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
59
63
  @timer.time(:generating) { write_json(@output_dir+"/output", relations) }
60
64
  else
65
+ copy_template(@template_dir, @output_dir)
66
+ @timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
61
67
  @timer.time(:generating) { write_tree(@output_dir+"/output/tree.js", relations) }
62
68
  @timer.time(:generating) { write_members(@output_dir+"/output/members.js", relations) }
63
69
  @timer.time(:generating) { write_pages(@output_dir+"/output", relations) }
@@ -68,15 +74,9 @@ module JsDuck
68
74
 
69
75
  # Parses the files in parallel using as many processes as available CPU-s
70
76
  def parallel_parse(filenames)
71
- src = SourceFormatter.new(@output_dir + "/source", @export ? :format_pre : :format_page)
72
77
  @parallel.map(filenames) do |fname|
73
78
  puts "Parsing #{fname} ..." if @verbose
74
- code = IO.read(fname)
75
- {
76
- :filename => fname,
77
- :html_filename => File.basename(src.write(code, fname)),
78
- :data => Parser.new(code).parse,
79
- }
79
+ SourceFile.new(IO.read(fname), fname)
80
80
  end
81
81
  end
82
82
 
@@ -84,9 +84,11 @@ module JsDuck
84
84
  def aggregate(parsed_files)
85
85
  agr = Aggregator.new
86
86
  parsed_files.each do |file|
87
- puts "Aggregating #{file[:filename]} ..." if @verbose
88
- agr.aggregate(file[:data], file[:filename], file[:html_filename])
87
+ puts "Aggregating #{file.filename} ..." if @verbose
88
+ agr.aggregate(file)
89
89
  end
90
+ agr.classify_orphans
91
+ agr.create_global_class unless @ignore_global
90
92
  agr.result
91
93
  end
92
94
 
@@ -108,6 +110,35 @@ module JsDuck
108
110
  Relations.new(classes)
109
111
  end
110
112
 
113
+ # print warning for each global member
114
+ def warn_globals(relations)
115
+ global = relations["global"]
116
+ return unless global
117
+ [:cfg, :property, :method, :event].each do |type|
118
+ global.members(type).each do |member|
119
+ name = member[:name]
120
+ file = member[:filename]
121
+ line = member[:linenr]
122
+ puts "Warning: Global #{type}: #{name} in #{file} line #{line}"
123
+ end
124
+ end
125
+ end
126
+
127
+ # print warning for each member with no name
128
+ def warn_unnamed(relations)
129
+ relations.each do |cls|
130
+ [:cfg, :property, :method, :event].each do |type|
131
+ cls[type].each do |member|
132
+ if !member[:name] || member[:name] == ""
133
+ file = member[:filename]
134
+ line = member[:linenr]
135
+ puts "Warning: Unnamed #{type} in #{file} line #{line}"
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+
111
142
  # Given all classes, generates namespace tree and writes it
112
143
  # in JSON form into a file.
113
144
  def write_tree(filename, relations)
@@ -133,14 +164,20 @@ module JsDuck
133
164
  @parallel.each(relations.classes) do |cls|
134
165
  filename = path + "/" + cls[:name] + ".html"
135
166
  puts "Writing to #{filename} ..." if @verbose
136
- html = Page.new(cls, relations, cache).to_html
137
- File.open(filename, 'w') {|f| f.write(html) }
167
+ page = Page.new(cls, relations, cache)
168
+ page.link_tpl = @link_tpl if @link_tpl
169
+ page.img_tpl = @img_tpl if @img_tpl
170
+ File.open(filename, 'w') {|f| f.write(page.to_html) }
138
171
  end
139
172
  end
140
173
 
141
174
  # Writes JSON export file for each class
142
175
  def write_json(path, relations)
143
- exporter = Exporter.new(relations)
176
+ formatter = DocFormatter.new
177
+ formatter.link_tpl = @link_tpl if @link_tpl
178
+ formatter.img_tpl = @img_tpl if @img_tpl
179
+ formatter.relations = relations
180
+ exporter = Exporter.new(relations, formatter)
144
181
  @parallel.each(relations.classes) do |cls|
145
182
  filename = path + "/" + cls[:name] + ".json"
146
183
  puts "Writing to #{filename} ..." if @verbose
@@ -150,6 +187,18 @@ module JsDuck
150
187
  end
151
188
  end
152
189
 
190
+ # Writes formatted HTML source code for each input file
191
+ def write_src(path, parsed_files)
192
+ src = SourceWriter.new(path, @export ? nil : :page)
193
+ # Can't be done in parallel, because file.html_filename= method
194
+ # updates all the doc-objects related to the file
195
+ parsed_files.each do |file|
196
+ html_filename = src.write(file.to_html, file.filename)
197
+ puts "Writing to #{html_filename} ..." if @verbose
198
+ file.html_filename = File.basename(html_filename)
199
+ end
200
+ end
201
+
153
202
  def copy_template(template_dir, dir)
154
203
  puts "Copying template files to #{dir}..." if @verbose
155
204
  FileUtils.cp_r(template_dir, dir)
@@ -3,8 +3,8 @@ require 'jsduck/table'
3
3
  module JsDuck
4
4
 
5
5
  class CfgTable < Table
6
- def initialize(cls, cache={})
7
- super(cls, cache)
6
+ def initialize(cls, formatter, cache={})
7
+ super(cls, formatter, cache)
8
8
  @type = :cfg
9
9
  @id = @cls.full_name + "-configs"
10
10
  @title = "Config Options"
data/lib/jsduck/class.rb CHANGED
@@ -36,6 +36,11 @@ module JsDuck
36
36
  @doc[:mixins] ? @doc[:mixins].collect {|classname| lookup(classname) }.compact : []
37
37
  end
38
38
 
39
+ # Returns all mixins this class and its parent classes
40
+ def all_mixins
41
+ mixins + (parent ? parent.all_mixins : [])
42
+ end
43
+
39
44
  # Looks up class object by name
40
45
  # When not found, prints warning message.
41
46
  def lookup(classname)
@@ -0,0 +1,75 @@
1
+ require 'jsduck/lexer'
2
+ require 'jsduck/doc_parser'
3
+
4
+ module JsDuck
5
+
6
+ class CssParser
7
+ def initialize(input)
8
+ @lex = Lexer.new(input)
9
+ @doc_parser = DocParser.new(:css)
10
+ @docs = []
11
+ end
12
+
13
+ # Parses the whole CSS block and returns same kind of structure
14
+ # that JavaScript parser does.
15
+ def parse
16
+ while !@lex.empty? do
17
+ if look(:doc_comment)
18
+ comment = @lex.next(true)
19
+ @docs << {
20
+ :comment => @doc_parser.parse(comment[:value]),
21
+ :linenr => comment[:linenr],
22
+ :code => code_block
23
+ }
24
+ else
25
+ @lex.next
26
+ end
27
+ end
28
+ @docs
29
+ end
30
+
31
+ # <code-block> := <mixin> | <nop>
32
+ def code_block
33
+ if look("@", "mixin")
34
+ mixin
35
+ else
36
+ {:type => :nop}
37
+ end
38
+ end
39
+
40
+ # <mixin> := "@mixin" <css-ident>
41
+ def mixin
42
+ match("@", "mixin")
43
+ return {
44
+ :type => :css_mixin,
45
+ :name => look(:ident) ? css_ident : nil,
46
+ }
47
+ end
48
+
49
+ # <css-ident> := <ident> [ "-" <ident> ]*
50
+ def css_ident
51
+ chain = [match(:ident)]
52
+ while look("-", :ident) do
53
+ chain << match("-", :ident)
54
+ end
55
+ return chain.join("-")
56
+ end
57
+
58
+ # Matches all arguments, returns the value of last match
59
+ # When the whole sequence doesn't match, throws exception
60
+ def match(*args)
61
+ if look(*args)
62
+ last = nil
63
+ args.length.times { last = @lex.next }
64
+ last
65
+ else
66
+ throw "Expected: " + args.join(", ")
67
+ end
68
+ end
69
+
70
+ def look(*args)
71
+ @lex.look(*args)
72
+ end
73
+ end
74
+
75
+ end