jsduck 4.0.beta → 4.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -41,7 +41,10 @@ For **Windows** users out there, you can download the binary version,
41
41
  which includes Ruby interpreter and all dependencies bundled in a
42
42
  single .exe file. Grab it from the [download page][].
43
43
 
44
+ If you are brave enough: [try out JSDuck 4.0 beta.][beta]
45
+
44
46
  [download page]: https://github.com/senchalabs/jsduck/downloads
47
+ [beta]: https://github.com/senchalabs/jsduck/wiki/4.0-beta
45
48
 
46
49
  Usage
47
50
  -----
data/Rakefile CHANGED
@@ -233,6 +233,12 @@ task :sdk => :sass do
233
233
  "--output", OUT_DIR,
234
234
  "--config", "#{SDK_DIR}/extjs/docs/config.json",
235
235
  "--examples-base-url", "extjs-build/examples/",
236
+ "--import", "Ext JS 1:compare/ext11",
237
+ "--import", "Ext JS 2:compare/ext23",
238
+ "--import", "Ext JS 3:compare/ext34",
239
+ "--import", "Ext JS 4.0.7:compare/ext407",
240
+ "--import", "Ext JS 4.1.0:compare/ext410",
241
+ "--import", "Ext JS 4.1.1",
236
242
  "--seo",
237
243
  "--tests"
238
244
  )
data/bin/compare CHANGED
@@ -1,22 +1,80 @@
1
1
  #!/usr/bin/env ruby
2
- # Compare .json files of different ExtJS versions
2
+ # Produces diff of two JSDuck exports.
3
3
 
4
4
  # For running when gem not installed
5
5
  $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib"
6
6
 
7
7
  require "rubygems"
8
+ require "cgi"
9
+ require "optparse"
8
10
  require "jsduck/json_duck"
9
11
 
12
+ options = {
13
+ :title => "Comparison of Ext 4.0.7 and Ext 4.1.1",
14
+ }
15
+
16
+ input_files = OptionParser.new do |opts|
17
+ opts.banner = "Produces diff of two JSDuck exports.\n\n" +
18
+ "Usage: compare [options] old/classes/ new/classes/ output.html\n\n"
19
+
20
+ opts.on("--ignore=FILE", "A file listing members to be ignored") do |file|
21
+ options[:ignore] = file
22
+ end
23
+
24
+ opts.on("--type=NAME", "Only produce diff of cfg, property, method or event.") do |name|
25
+ options[:type] = name
26
+ end
27
+
28
+ opts.on("--title=TEXT", "Title for the generated HTML page.",
29
+ "Defaults to: 'Comparison of Ext 4.0.7 and Ext 4.1.1'") do |text|
30
+ options[:title] = text
31
+ end
32
+
33
+ opts.on("-h", "--help", "Show this help message") do
34
+ puts opts
35
+ exit
36
+ end
37
+ end.parse!
38
+
39
+ if input_files.length == 3
40
+ options[:old_classes] = input_files[0]
41
+ options[:new_classes] = input_files[1]
42
+ options[:out_file] = input_files[2]
43
+ else
44
+ puts "You must specify exactly 3 filenames: old/classes/ new/classes/ output.html"
45
+ exit 1
46
+ end
47
+
48
+
10
49
  def read_class(filename)
11
50
  JsDuck::JsonDuck.read(filename)
12
51
  end
13
52
 
53
+ # loops through all members of class
54
+ def each_member(cls)
55
+ ["members", "statics"].each do |category|
56
+ cls[category].each_pair do |k, members|
57
+ members.each do |m|
58
+ yield m
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def discard_docs(cls)
65
+ each_member(cls) do |m|
66
+ m["doc"] = nil
67
+ m["params"] = nil
68
+ end
69
+ cls
70
+ end
71
+
14
72
  def read_all_classes(dir, ignore_private=false)
15
73
  map = {}
16
74
  Dir[dir+"/*.json"].each do |filename|
17
75
  print "."
18
76
  STDOUT.flush
19
- cls = read_class(filename)
77
+ cls = discard_docs(read_class(filename))
20
78
  unless ignore_private && cls["private"]
21
79
  map[cls["name"]] = cls
22
80
  cls["alternateClassNames"].each do |name|
@@ -40,42 +98,136 @@ def normal_public_member?(m)
40
98
  !m["private"] && !m["meta"]["protected"] && !m["meta"]["deprecated"] && !m["meta"]["removed"]
41
99
  end
42
100
 
43
- # Gathers class members that are in cls1, but are missing in cls2
44
- # Ignoring members listed in ignored_members hash.
45
- def compare_classes(cls1, cls2, ignored_members)
101
+ def unify_default(v)
102
+ (v || "undefined").gsub(/"/, "'").sub(/^Ext\.baseCSSPrefix \+ '/, "'x-")
103
+ end
104
+
105
+ def visibility(m)
106
+ if m["private"]
107
+ "private"
108
+ elsif m["meta"]["protected"]
109
+ "protected"
110
+ else
111
+ "public"
112
+ end
113
+ end
114
+
115
+ # Finds equivalent member to given member from class.
116
+ # Returns the member found or nil.
117
+ def find_member(cls, member)
118
+ cls[member["meta"]["static"] ? "statics" : "members"][member["tagname"]].find do |m|
119
+ member["name"] == m["name"]
120
+ end
121
+ end
122
+
123
+
124
+ # Compares members of cls1 to cls2.
125
+ # Returns diff array of added, removed and changed members.
126
+ def compare_classes(cls1, cls2)
46
127
  diff = []
47
- cls1["members"].each_pair do |group_name, group_items|
48
- group_items.find_all {|m1| normal_public_member?(m1) && m1["owner"] == cls1["name"] }.each do |m1|
49
- match = cls2["members"][group_name].find do |m2|
50
- m2["name"] == m1["name"] && !m2["meta"]["protected"] && !m2["private"]
128
+
129
+ checked_members = {}
130
+
131
+ each_member(cls1) do |m1|
132
+ next unless normal_public_member?(m1) && m1["owner"] == cls1["name"]
133
+
134
+ checked_members[m1["id"]] = true
135
+
136
+ m2 = find_member(cls2, m1)
137
+
138
+ if m2
139
+ changes = []
140
+ if m1["type"] != m2["type"]
141
+ changes << {
142
+ :what => "type",
143
+ :a => m1["type"],
144
+ :b => m2["type"],
145
+ }
51
146
  end
52
- if !match && m1["name"] != "constructor" && m1["name"] != "" && !ignored_members[cls1["name"]+"#"+m1["name"]]
53
- other = nil
54
- ["cfg", "property", "method", "event"].each do |g|
55
- other = other || cls2["members"][g].find {|m2| m2["name"] == m1["name"] }
56
- other = other || cls2["statics"][g].find {|m2| m2["name"] == m1["name"] }
57
- end
147
+ if unify_default(m1["default"]) != unify_default(m2["default"])
148
+ changes << {
149
+ :what => "default",
150
+ :a => unify_default(m1["default"]),
151
+ :b => unify_default(m2["default"]),
152
+ }
153
+ end
154
+ if visibility(m1) != visibility(m2)
155
+ changes << {
156
+ :what => "visibility",
157
+ :a => visibility(m1),
158
+ :b => visibility(m2),
159
+ }
160
+ end
161
+
162
+ if changes.length > 0
58
163
  diff << {
59
- :type => group_name,
164
+ :type => m1["tagname"],
60
165
  :name => m1["name"],
61
- :other => other ? {
62
- :type => other["tagname"],
63
- :static => other["meta"]["static"],
64
- :private => other["private"],
65
- :protected => other["meta"]["protected"],
66
- } : nil
166
+ :what => "changed",
167
+ :changes => changes,
67
168
  }
68
169
  end
170
+
171
+ elsif !m2 && m1["name"] != "constructor" && m1["name"] != ""
172
+ other = nil
173
+ ["cfg", "property", "method", "event"].each do |g|
174
+ other = other || cls2["members"][g].find {|m2| m2["name"] == m1["name"] }
175
+ other = other || cls2["statics"][g].find {|m2| m2["name"] == m1["name"] }
176
+ end
177
+ diff << {
178
+ :type => m1["tagname"],
179
+ :name => m1["name"],
180
+ :what => "removed",
181
+ :other => other ? {
182
+ :type => other["tagname"],
183
+ :static => other["meta"]["static"],
184
+ :private => other["private"],
185
+ :protected => other["meta"]["protected"],
186
+ } : nil
187
+ }
69
188
  end
70
189
  end
190
+
191
+ each_member(cls2) do |m2|
192
+ next unless normal_public_member?(m2) && m2["owner"] == cls2["name"] && !checked_members[m2["id"]]
193
+
194
+ m1 = find_member(cls1, m2)
195
+
196
+ if m1
197
+ changes = []
198
+ if visibility(m1) != visibility(m2)
199
+ changes << {
200
+ :what => "visibility",
201
+ :a => visibility(m1),
202
+ :b => visibility(m2),
203
+ }
204
+ end
205
+
206
+ if changes.length > 0
207
+ diff << {
208
+ :type => m2["tagname"],
209
+ :name => m2["name"],
210
+ :what => "changed",
211
+ :changes => changes,
212
+ }
213
+ end
214
+
215
+ elsif !m1 && m2["name"] != "constructor" && m2["name"] != ""
216
+ diff << {
217
+ :type => m2["tagname"],
218
+ :name => m2["name"],
219
+ :what => "added",
220
+ }
221
+ end
222
+ end
223
+
71
224
  diff
72
225
  end
73
226
 
74
227
 
75
- old_classes = read_all_classes(ARGV[0], :ignore_private)
76
- new_classes = read_all_classes(ARGV[1])
77
- out_file = ARGV[2]
78
- ignored_members = ARGV[3] ? read_ignored_members(ARGV[3]) : {}
228
+ old_classes = read_all_classes(options[:old_classes], :ignore_private)
229
+ new_classes = read_all_classes(options[:new_classes])
230
+ ignored_members = options[:ignore] ? read_ignored_members(options[:ignore]) : {}
79
231
 
80
232
  # Explicit remapping of classes
81
233
  remap = {
@@ -101,16 +253,23 @@ old_classes.each_pair do |name, cls|
101
253
  :name => cls["name"],
102
254
  :found => !!new_cls || ignored_members[name],
103
255
  :new_name => new_cls && new_cls["name"],
104
- :diff => new_cls ? compare_classes(cls, new_cls, ignored_members) : [],
256
+ :diff => new_cls ? compare_classes(cls, new_cls) : [],
105
257
  }
106
258
  end
107
259
  end
108
260
 
261
+ # Throw away ignored members and everything except configs
262
+ diff_data.each do |cls|
263
+ cls[:diff] = cls[:diff].find_all do |m|
264
+ !ignored_members[cls[:name]+"#"+m[:name]] && (!options[:type] || m[:type] == options[:type])
265
+ end
266
+ end
267
+
109
268
  # Choose title based on filename
110
- if out_file =~ /touch/
269
+ if options[:out_file] =~ /touch/
111
270
  title = "Comparison of Touch 1.1.1 and Touch 2.0.0"
112
271
  else
113
- title = "Comparison of Ext 4.0.7 and Ext 4.1.0"
272
+ title = "Comparison of Ext 4.0.7 and Ext 4.1.1"
114
273
  end
115
274
 
116
275
  # do HTML output
@@ -119,7 +278,7 @@ html << <<-EOHTML
119
278
  <!DOCTYPE html>
120
279
  <html>
121
280
  <head>
122
- <title>#{title}</title>
281
+ <title>#{options[:title]}</title>
123
282
  <style type="text/css">
124
283
  body { font-family: Georgia, serif; }
125
284
  li h2 { font-size: medium; font-weight: normal; }
@@ -136,7 +295,7 @@ $(function() {
136
295
  </script>
137
296
  </head>
138
297
  <body>
139
- <h1>#{title}</h1>
298
+ <h1>#{options[:title]}</h1>
140
299
  EOHTML
141
300
 
142
301
  html << "<ul>"
@@ -146,20 +305,24 @@ diff_data.each do |cls|
146
305
  html << "<li>"
147
306
  if cls[:found]
148
307
  new_name = (cls[:new_name] == cls[:name] ? "" : " --> " + cls[:new_name])
149
- diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} missing members)" : ""
308
+ diff_count = cls[:diff].length > 0 ? "(#{cls[:diff].length} changes)" : ""
150
309
  link = cls[:diff].length > 0 ? "<a href='#expand'>#{cls[:name]}</a>" : cls[:name]
151
310
  html << "<h2>#{link} #{new_name} #{diff_count}</h2>"
152
311
  if cls[:diff].length > 0
153
312
  html << "<ul>"
154
313
  cls[:diff].each do |m|
155
314
  html << "<li>"
156
- html << m[:type] + " " + m[:name]
315
+ html << m[:what] + " " + m[:type] + " " + m[:name]
157
316
  if m[:other]
158
317
  o = m[:other]
159
318
  stat = o[:static] ? 'static' : ''
160
319
  priv = o[:private] ? 'private' : ''
161
320
  prot = o[:protected] ? 'protected' : ''
162
321
  html << " (found #{stat} #{priv} #{prot} #{o[:type]} with the same name)"
322
+ elsif m[:changes]
323
+ m[:changes].each do |c|
324
+ html << " (#{c[:what]} changed from #{CGI.escapeHTML(c[:a])} to #{CGI.escapeHTML(c[:b])})"
325
+ end
163
326
  end
164
327
  html << "</li>"
165
328
  end
@@ -178,12 +341,11 @@ dd = diff_data
178
341
  html << "<p>" + dd.find_all {|c| !c[:found] }.length.to_s + " classes not found</p>"
179
342
  html << "<p>" + dd.find_all {|c| c[:found] && c[:name] == c[:new_name] }.length.to_s + " classes with same name</p>"
180
343
  html << "<p>" + dd.find_all {|c| c[:found] && c[:name] != c[:new_name] }.length.to_s + " renamed classes</p>"
181
- html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with missing members</p>"
182
- html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " missing members</p>"
183
- html << "<p>" + dd.map {|c| c[:diff].find_all {|m| !m[:other]}.length }.inject {|sum,x| sum + x }.to_s + " completely missing members</p>"
344
+ html << "<p>" + dd.find_all {|c| c[:diff].length > 0 }.length.to_s + " classes with changed members</p>"
345
+ html << "<p>" + dd.map {|c| c[:diff].length }.inject {|sum,x| sum + x }.to_s + " changed members</p>"
184
346
 
185
347
 
186
348
  html << "</body>"
187
349
  html << "</html>"
188
350
 
189
- File.open(out_file, 'w') {|f| f.write(html.join("\n")) }
351
+ File.open(options[:out_file], 'w') {|f| f.write(html.join("\n")) }
data/bin/graph ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # For running when gem not installed
4
+ $:.unshift File.dirname(File.dirname(__FILE__)) + "/lib"
5
+
6
+ require "rubygems"
7
+ require "pp"
8
+ require "jsduck/json_duck"
9
+
10
+ def with_each_class(dir)
11
+ Dir[dir+"/*.json"].each do |filename|
12
+ yield JsDuck::JsonDuck.read(filename)
13
+ end
14
+ end
15
+
16
+ def quote(a)
17
+ '"'+a+'"'
18
+ end
19
+
20
+ def arrow(a, b, opts="")
21
+ " #{quote(a)}->#{quote(b)} #{opts};"
22
+ end
23
+
24
+ input_dir = ARGV[0]
25
+
26
+ # Build a map that links each classname or alternate classname to its
27
+ # canonical form
28
+ $canonical_map = {}
29
+ with_each_class(input_dir) do |cls|
30
+ $canonical_map[cls["name"]] = cls["name"]
31
+ cls["alternateClassNames"].each do |name|
32
+ $canonical_map[name] = cls["name"]
33
+ end
34
+ end
35
+
36
+ def canonical(name)
37
+ $canonical_map[name] || name
38
+ end
39
+
40
+ # Print out the graph description
41
+ puts 'digraph G {'
42
+ puts 'rankdir=LR;'
43
+ with_each_class(input_dir) do |cls|
44
+ if cls["extends"] && cls["extends"] != "Object"
45
+ puts arrow(canonical(cls["extends"]), cls['name'], '[style=bold,weight=10]')
46
+ end
47
+ if cls["mixins"]
48
+ cls["mixins"].each {|mx| puts arrow(canonical(mx), cls['name'], '[weight=1,style=dashed]') }
49
+ end
50
+ end
51
+ puts '}'
52
+
data/jsduck.gemspec CHANGED
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
2
2
  s.required_rubygems_version = ">= 1.3.5"
3
3
 
4
4
  s.name = 'jsduck'
5
- s.version = '4.0.beta'
6
- s.date = '2012-06-27'
5
+ s.version = '4.0.beta2'
6
+ s.date = '2012-08-01'
7
7
  s.summary = "Simple JavaScript Duckumentation generator"
8
8
  s.description = "Documentation generator for Sencha JS frameworks"
9
9
  s.homepage = "https://github.com/senchalabs/jsduck"
@@ -1,6 +1,7 @@
1
1
  require 'jsduck/class'
2
2
  require 'jsduck/accessors'
3
3
  require 'jsduck/logger'
4
+ require 'jsduck/enum'
4
5
 
5
6
  module JsDuck
6
7
 
@@ -228,6 +229,11 @@ module JsDuck
228
229
  end
229
230
  end
230
231
 
232
+ # Loops through all enums and auto-detects their types if needed.
233
+ def process_enums
234
+ Enum.new(@classes).process_all!
235
+ end
236
+
231
237
  # Are we dealing with ExtJS 4?
232
238
  # True if any of the classes is defined with Ext.define()
233
239
  def ext4?
data/lib/jsduck/app.rb CHANGED
@@ -11,6 +11,7 @@ require 'jsduck/logger'
11
11
  require 'jsduck/assets'
12
12
  require 'jsduck/json_duck'
13
13
  require 'jsduck/io'
14
+ require 'jsduck/importer'
14
15
  require 'jsduck/lint'
15
16
  require 'jsduck/template_dir'
16
17
  require 'jsduck/class_writer'
@@ -35,7 +36,7 @@ module JsDuck
35
36
  @opts = opts
36
37
  # Sets the nr of parallel processes to use.
37
38
  # Set to 0 to disable parallelization completely.
38
- @parallel = ParallelWrap.new(:in_processes => @opts.processes)
39
+ ParallelWrap.in_processes = @opts.processes
39
40
  # Turn JSON pretty-printing on/off
40
41
  JsonDuck.pretty = @opts.pretty_json
41
42
  end
@@ -46,6 +47,7 @@ module JsDuck
46
47
  result = aggregate(parsed_files)
47
48
  @relations = filter_classes(result)
48
49
  InheritDoc.new(@relations).resolve_all
50
+ Importer.import(@opts.imports, @relations)
49
51
  Lint.new(@relations).run
50
52
 
51
53
  # Initialize guides, videos, examples, ...
@@ -81,7 +83,7 @@ module JsDuck
81
83
  # between source files and classes. Therefore it MUST to be done
82
84
  # after writing sources which needs the links to work.
83
85
  if @opts.source
84
- source_writer = SourceWriter.new(parsed_files, @parallel)
86
+ source_writer = SourceWriter.new(parsed_files)
85
87
  source_writer.write(@opts.output_dir + "/source")
86
88
  end
87
89
  format_classes
@@ -102,7 +104,7 @@ module JsDuck
102
104
 
103
105
  # Parses the files in parallel using as many processes as available CPU-s
104
106
  def parallel_parse(filenames)
105
- @parallel.map(filenames) do |fname|
107
+ ParallelWrap.map(filenames) do |fname|
106
108
  Logger.instance.log("Parsing", fname)
107
109
  begin
108
110
  SourceFile.new(JsDuck::IO.read(fname), fname, @opts)
@@ -125,6 +127,7 @@ module JsDuck
125
127
  agr.remove_ignored_classes
126
128
  agr.create_accessors
127
129
  agr.append_ext4_event_options
130
+ agr.process_enums
128
131
  agr.result
129
132
  end
130
133
 
@@ -162,7 +165,7 @@ module JsDuck
162
165
  # Don't format types when exporting
163
166
  class_formatter.include_types = !@opts.export
164
167
  # Format all doc-objects in parallel
165
- formatted_classes = @parallel.map(@relations.classes) do |cls|
168
+ formatted_classes = ParallelWrap.map(@relations.classes) do |cls|
166
169
  Logger.instance.log("Markdown formatting #{cls[:name]}")
167
170
  begin
168
171
  {