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