jsduck 4.0.beta → 4.0.beta2

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
@@ -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
  {