yard-sketchup 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -15
  3. data/lib/yard-sketchup.rb +36 -36
  4. data/lib/yard-sketchup/patches/c_base_handler.rb +52 -52
  5. data/lib/yard-sketchup/stubs/autoload.rb +134 -134
  6. data/lib/yard-sketchup/templates/changelog/fulldoc/text/setup.rb +44 -44
  7. data/lib/yard-sketchup/templates/coverage/fulldoc/text/setup.rb +41 -41
  8. data/lib/yard-sketchup/templates/default/fulldoc/html/css/rubyapi.css +10 -10
  9. data/lib/yard-sketchup/templates/default/fulldoc/html/css/sketchup.css +120 -120
  10. data/lib/yard-sketchup/templates/default/fulldoc/html/full_list_object_types.erb +1 -1
  11. data/lib/yard-sketchup/templates/default/fulldoc/html/js/sketchup.js +37 -37
  12. data/lib/yard-sketchup/templates/default/fulldoc/html/setup.rb +66 -66
  13. data/lib/yard-sketchup/templates/default/layout/html/embed_meta.erb +78 -78
  14. data/lib/yard-sketchup/templates/default/layout/html/footer.erb +4 -4
  15. data/lib/yard-sketchup/templates/default/layout/html/headers.erb +15 -15
  16. data/lib/yard-sketchup/templates/default/layout/html/layout.erb +55 -55
  17. data/lib/yard-sketchup/templates/default/layout/html/navbar.erb +36 -36
  18. data/lib/yard-sketchup/templates/default/layout/html/setup.rb +19 -19
  19. data/lib/yard-sketchup/templates/default/method_details/html/method_signature.erb +25 -25
  20. data/lib/yard-sketchup/templates/default/method_details/setup.rb +11 -11
  21. data/lib/yard-sketchup/templates/default/module/html/box_info.erb +37 -37
  22. data/lib/yard-sketchup/templates/default/module/html/constant_summary.erb +17 -17
  23. data/lib/yard-sketchup/templates/default/module/html/method_summary.erb +18 -18
  24. data/lib/yard-sketchup/templates/stubs/fulldoc/text/setup.rb +361 -357
  25. data/lib/yard-sketchup/templates/versions/fulldoc/text/setup.rb +25 -25
  26. data/lib/yard-sketchup/version.rb +3 -3
  27. data/yard-sketchup.gemspec +25 -25
  28. metadata +2 -2
@@ -1,19 +1,19 @@
1
- # Consider if this should simply replace the Classes list.
2
-
3
- =begin
4
- TODO(thomthom): Temporarily disabled until we have time to fully implement this.
5
- def menu_lists
6
- # Load the existing menus
7
- super + [ { :type => 'object_types', :title => 'Object Reference', :search_title => 'Object Reference List' } ]
8
- end
9
- =end
10
-
11
- def javascripts
12
- # Using the latest 1.x jQuery from CDN broke the YARD js. For now we must
13
- # continue to use the version shipping with the YARD template we use as base.
14
- %w(js/jquery.js js/app.js)
15
- end
16
-
17
- def stylesheets
18
- %w(css/style.css css/sketchup.css css/rubyapi.css)
19
- end
1
+ # Consider if this should simply replace the Classes list.
2
+
3
+ =begin
4
+ TODO(thomthom): Temporarily disabled until we have time to fully implement this.
5
+ def menu_lists
6
+ # Load the existing menus
7
+ super + [ { :type => 'object_types', :title => 'Object Reference', :search_title => 'Object Reference List' } ]
8
+ end
9
+ =end
10
+
11
+ def javascripts
12
+ # Using the latest 1.x jQuery from CDN broke the YARD js. For now we must
13
+ # continue to use the version shipping with the YARD template we use as base.
14
+ %w(js/jquery.js js/app.js)
15
+ end
16
+
17
+ def stylesheets
18
+ %w(css/style.css css/sketchup.css css/rubyapi.css)
19
+ end
@@ -1,26 +1,26 @@
1
- <h3 class="signature <%= 'first' if @index == 0 %>" id="<%= anchor_for(object) %>">
2
- <a style="float: right;" href="#header" title="Return to Top">&uarr;</a>
3
- <% if object.tags(:overload).size == 1 %>
4
- <%= signature(object.tag(:overload), false) %>
5
- <% elsif object.tags(:overload).size > 1 %>
6
- <% object.tags(:overload).each do |overload| %>
7
- <span class="overload"><%= signature(overload, false) %></span>
8
- <% end %>
9
- <% else %>
10
- <%= signature(object, false) %>
11
- <% end %>
12
-
13
- <% if object.aliases.size > 0 %>
14
- <span class="aliases">Also known as:
15
- <span class="names"><%= object.aliases.map {|o|
16
- "<span id='#{anchor_for(o)}'>" + h(o.name.to_s) + "</span>" }.join(", ") %></span>
17
- </span>
18
- <% end %>
19
-
20
- <% if owner != object.namespace %>
21
- <span class="not_defined_here">
22
- Originally defined in <%= object.namespace.type %>
23
- <%= linkify object, owner.relative_path(object.namespace) %>
24
- </span>
25
- <% end %>
1
+ <h3 class="signature <%= 'first' if @index == 0 %>" id="<%= anchor_for(object) %>">
2
+ <a style="float: right;" href="#header" title="Return to Top">&uarr;</a>
3
+ <% if object.tags(:overload).size == 1 %>
4
+ <%= signature(object.tag(:overload), false) %>
5
+ <% elsif object.tags(:overload).size > 1 %>
6
+ <% object.tags(:overload).each do |overload| %>
7
+ <span class="overload"><%= signature(overload, false) %></span>
8
+ <% end %>
9
+ <% else %>
10
+ <%= signature(object, false) %>
11
+ <% end %>
12
+
13
+ <% if object.aliases.size > 0 %>
14
+ <span class="aliases">Also known as:
15
+ <span class="names"><%= object.aliases.map {|o|
16
+ "<span id='#{anchor_for(o)}'>" + h(o.name.to_s) + "</span>" }.join(", ") %></span>
17
+ </span>
18
+ <% end %>
19
+
20
+ <% if owner != object.namespace %>
21
+ <span class="not_defined_here">
22
+ Originally defined in <%= object.namespace.type %>
23
+ <%= linkify object, owner.relative_path(object.namespace) %>
24
+ </span>
25
+ <% end %>
26
26
  </h3>
@@ -1,12 +1,12 @@
1
- # We don't want to expose our source code in the API docs.
2
-
3
- # https://groups.google.com/forum/#!topic/yardoc/-HH48h-aifs
4
- def source
5
- return
6
- end
7
-
8
- # http://stackoverflow.com/a/10345917/486990
9
- #def init
10
- # super
11
- # sections.first.delete(:source)
1
+ # We don't want to expose our source code in the API docs.
2
+
3
+ # https://groups.google.com/forum/#!topic/yardoc/-HH48h-aifs
4
+ def source
5
+ return
6
+ end
7
+
8
+ # http://stackoverflow.com/a/10345917/486990
9
+ #def init
10
+ # super
11
+ # sections.first.delete(:source)
12
12
  #end
@@ -1,37 +1,37 @@
1
- <div class="box_info">
2
- <% if CodeObjects::ClassObject === object && object.superclass %>
3
- <dl>
4
- <dt>Inherits:</dt>
5
- <dd>
6
- <span class="inheritName"><%= linkify object.superclass %></span>
7
- <% if object.superclass.name != :BasicObject %>
8
- <ul class="fullTree">
9
- <li><%= linkify P(:Object) %></li>
10
- <% object.inheritance_tree.reverse.each_with_index do |obj, i| %>
11
- <li class="next"><%= obj == object ? obj.path : linkify(obj) %></li>
12
- <% end %>
13
- </ul>
14
- <a href="#" class="inheritanceTree">show all</a>
15
- <% end %>
16
- </dd>
17
- </dl>
18
- <% end %>
19
-
20
- <% [[:class, "Extended by"], [:instance, "Includes"]].each do |scope, name| %>
21
- <% if (mix = run_verifier(object.mixins(scope))).size > 0 %>
22
- <dl>
23
- <dt><%= name %>:</dt>
24
- <dd><%= mix.sort_by {|o| o.path }.map {|o| linkify(o) }.join(", ") %></dd>
25
- </dl>
26
- <% end %>
27
- <% end %>
28
-
29
- <% if (mixed_into = mixed_into(object)).size > 0 %>
30
- <dl>
31
- <dt>Included in:</dt>
32
- <dd><%= mixed_into.sort_by {|o| o.path }.map {|o| linkify(o) }.join(", ") %></dd>
33
- </dl>
34
- <% end %>
35
-
36
- </div>
37
-
1
+ <div class="box_info">
2
+ <% if CodeObjects::ClassObject === object && object.superclass %>
3
+ <dl>
4
+ <dt>Inherits:</dt>
5
+ <dd>
6
+ <span class="inheritName"><%= linkify object.superclass %></span>
7
+ <% if object.superclass.name != :BasicObject %>
8
+ <ul class="fullTree">
9
+ <li><%= linkify P(:Object) %></li>
10
+ <% object.inheritance_tree.reverse.each_with_index do |obj, i| %>
11
+ <li class="next"><%= obj == object ? obj.path : linkify(obj) %></li>
12
+ <% end %>
13
+ </ul>
14
+ <a href="#" class="inheritanceTree">show all</a>
15
+ <% end %>
16
+ </dd>
17
+ </dl>
18
+ <% end %>
19
+
20
+ <% [[:class, "Extended by"], [:instance, "Includes"]].each do |scope, name| %>
21
+ <% if (mix = run_verifier(object.mixins(scope))).size > 0 %>
22
+ <dl>
23
+ <dt><%= name %>:</dt>
24
+ <dd><%= mix.sort_by {|o| o.path }.map {|o| linkify(o) }.join(", ") %></dd>
25
+ </dl>
26
+ <% end %>
27
+ <% end %>
28
+
29
+ <% if (mixed_into = mixed_into(object)).size > 0 %>
30
+ <dl>
31
+ <dt>Included in:</dt>
32
+ <dd><%= mixed_into.sort_by {|o| o.path }.map {|o| linkify(o) }.join(", ") %></dd>
33
+ </dl>
34
+ <% end %>
35
+
36
+ </div>
37
+
@@ -1,17 +1,17 @@
1
- <% if constant_listing.size > 0 || object.constants.size > 0 %>
2
- <h2>
3
- Constant Summary
4
- <a
5
- id="constant_summary"
6
- href="#constant_summary"
7
- title="Permalink">#</a>
8
- </h2>
9
- <% if constant_listing.size > 0 %>
10
- <dl class="constants">
11
- <% constant_listing.each do |cnst| %>
12
- <dt id="<%= anchor_for(cnst) %>" class="<%= cnst.has_tag?(:deprecated) ? 'deprecated' : '' %>"><%= cnst.namespace.to_s.empty? ? '' : "#{cnst.namespace}::" %><%= cnst.name %>
13
- </dt>
14
- <% end %>
15
- </dl>
16
- <% end %>
17
- <% end %>
1
+ <% if constant_listing.size > 0 || object.constants.size > 0 %>
2
+ <h2>
3
+ Constant Summary
4
+ <a
5
+ id="constant_summary"
6
+ href="#constant_summary"
7
+ title="Permalink">#</a>
8
+ </h2>
9
+ <% if constant_listing.size > 0 %>
10
+ <dl class="constants">
11
+ <% constant_listing.each do |cnst| %>
12
+ <dt id="<%= anchor_for(cnst) %>" class="<%= cnst.has_tag?(:deprecated) ? 'deprecated' : '' %>"><%= cnst.namespace.to_s.empty? ? '' : "#{cnst.namespace}::" %><%= cnst.name %>
13
+ </dt>
14
+ <% end %>
15
+ </dl>
16
+ <% end %>
17
+ <% end %>
@@ -1,18 +1,18 @@
1
- <% if method_listing.size > 0 %>
2
- <% groups(method_listing) do |list, name| %>
3
- <h2>
4
- <%= name %>
5
- <a
6
- id="<%= name.gsub(' ', '_').downcase %>"
7
- href="#<%= name.gsub(' ', '_').downcase %>"
8
- title="Permalink">#</a>
9
- <small><a href="#" class="summary_toggle">collapse</a></small>
10
- </h2>
11
-
12
- <ul class="summary">
13
- <% list.each do |meth| %>
14
- <%= yieldall :item => meth %>
15
- <% end %>
16
- </ul>
17
- <% end %>
18
- <% end %>
1
+ <% if method_listing.size > 0 %>
2
+ <% groups(method_listing) do |list, name| %>
3
+ <h2>
4
+ <%= name %>
5
+ <a
6
+ id="<%= name.gsub(' ', '_').downcase %>"
7
+ href="#<%= name.gsub(' ', '_').downcase %>"
8
+ title="Permalink">#</a>
9
+ <small><a href="#" class="summary_toggle">collapse</a></small>
10
+ </h2>
11
+
12
+ <ul class="summary">
13
+ <% list.each do |meth| %>
14
+ <%= yieldall :item => meth %>
15
+ <% end %>
16
+ </ul>
17
+ <% end %>
18
+ <% end %>
@@ -1,357 +1,361 @@
1
- require 'fileutils'
2
- require 'pathname'
3
- require 'set'
4
- require 'stringio'
5
-
6
- include Helpers::ModuleHelper
7
-
8
- def init
9
- generate_stubs
10
- end
11
-
12
-
13
- # NOTE: Remember to run objects outputted through `run_verifier` first in order
14
- # to filter out items that should be excluded by command line arguments.
15
-
16
- def namespace_objects
17
- run_verifier(Registry.all(:class, :module))
18
- end
19
-
20
- def generate_stubs
21
- puts "Generating stubs..."
22
- generate_module_stubs(Registry.root)
23
- namespace_objects.each do |object|
24
- generate_module_stubs(object)
25
- end
26
- generate_autoloader(namespace_objects)
27
- end
28
-
29
- def generate_autoloader(namespace_objects)
30
- generator = SketchUpYARD::Stubs::AutoLoadGenerator.new
31
- autoload_file = File.join(stubs_gem_path, 'sketchup.rb')
32
- File.open(autoload_file, 'w') do |file|
33
- generator.generate(namespace_objects, file)
34
- end
35
- end
36
-
37
- def print_section(io, title, content)
38
- return if content.strip.empty?
39
- io.puts
40
- io.puts " # #{title}"
41
- io.puts
42
- io.puts content
43
- end
44
-
45
- def generate_module_stubs(object)
46
- filename = stub_filename(object)
47
- ensure_exist(File.dirname(filename))
48
- StubFile.open(filename, 'w') { |file|
49
- file.puts file_header(object)
50
- file.puts
51
- file.puts namespace_definition(object)
52
- print_section(file, 'Extends', generate_mixins(object, :class))
53
- print_section(file, 'Includes', generate_mixins(object, :instance))
54
- print_section(file, 'Constants', generate_constants_grouped(object))
55
- print_section(file, 'Class Methods', generate_class_methods(object))
56
- print_section(file, 'Instance Methods', generate_instance_methods(object))
57
- file.puts
58
- file.puts file_footer(object)
59
- }
60
- #trim_trailing_white_space(filename)
61
- end
62
-
63
- def file_header(object)
64
- header = StringIO.new
65
- header.puts "# Copyright:: Copyright #{Time.now.year} Trimble Inc."
66
- header.puts "# License:: The MIT License (MIT)"
67
- #header.puts "# Generated:: #{Time.now.strftime('%F %R')}"
68
- header.string
69
- end
70
-
71
- def file_footer(object)
72
- return if object.root?
73
- footer = StringIO.new
74
- footer.puts 'end'
75
- footer.string
76
- end
77
-
78
- def namespace_definition(object)
79
- return if object.root?
80
- definition = "#{object.type} #{object.path}"
81
- if object.type == :class && object.superclass.name != :Object
82
- definition << " < #{object.superclass.path}"
83
- end
84
- output = StringIO.new
85
- output.puts generate_docstring(object)
86
- output.puts definition
87
- output.string
88
- end
89
-
90
- def output_path
91
- options.serializer.options[:basepath] || File.join(Dir.pwd, 'stubs')
92
- end
93
-
94
- def stubs_root_path
95
- ensure_exist(output_path)
96
- end
97
-
98
- def stubs_lib_path
99
- ensure_exist(File.join(stubs_root_path, 'lib'))
100
- end
101
-
102
- def stubs_gem_path
103
- ensure_exist(File.join(stubs_lib_path, 'sketchup-api-stubs'))
104
- end
105
-
106
- def stubs_path
107
- ensure_exist(File.join(stubs_gem_path, 'stubs'))
108
- end
109
-
110
- def stub_filename(object)
111
- basename = object.path.gsub('::', '/')
112
- basename = '_top_level' if basename.empty?
113
- File.join(stubs_path, "#{basename}.rb")
114
- end
115
-
116
- # A stable sort_by method.
117
- #
118
- # @param [Enumerable]
119
- # @return [Array]
120
- def stable_sort_by(list)
121
- list.each_with_index.sort_by { |item, i| [yield(item), i] }.map(&:first)
122
- end
123
-
124
- CAMELCASE_CONSTANT = /^([A-Z]+[a-z]+)/
125
-
126
- def group_constant(constant)
127
- constant_name = constant.name.to_s
128
- MANUAL_CONSTANT_GROUPS.each { |rule|
129
- if rule[:constants]
130
- return rule[:group] if rule[:constants].include?(constant_name)
131
- else
132
- return rule[:group] if rule[:regex].match(constant_name)
133
- end
134
- }
135
- if constant_name.include?('_')
136
- constant_name.split('_').first
137
- else
138
- constant_name[CAMELCASE_CONSTANT] || constant_name
139
- end
140
- end
141
-
142
- # Sorts and groups constants for easier reading.
143
- def generate_constants_grouped(object)
144
- constants = run_verifier(object.constants(object_options))
145
- # The constants are not sorted before chunking. This is because `chunk` groups
146
- # consecutive items - and we want to chunk them based their relationship
147
- # with each other. This ensure that constants that doesn't follow the normal
148
- # pattern of PREFIX_SOME_NAME will still be grouped next to each other.
149
- groups = constants.chunk { |constant|
150
- group_constant(constant)
151
- }
152
- grouped_output = groups.map { |group, group_constants|
153
- output = StringIO.new
154
- # Each group itself is sorted in order to more easily scan the list.
155
- sorted = stable_sort_by(group_constants, &:name)
156
- sorted.each { |constant|
157
- output.puts " #{constant.name} = nil # Stub value."
158
- }
159
- output.string
160
- }
161
- # Finally each group is also sorted, again to ease scanning for a particular
162
- # name. We simply use the first character of each group.
163
- stable_sort_by(grouped_output) { |item| item.lstrip[0] }.join("\n")
164
- end
165
-
166
- # Sort constants without grouping.
167
- def generate_constants(object)
168
- output = StringIO.new
169
- constants = run_verifier(object.constants(object_options))
170
- constants = stable_sort_by(constants, &:name)
171
- constants.each { |constant|
172
- output.puts " #{constant.name} = nil # Stub value."
173
- }
174
- output.string
175
- end
176
-
177
- def generate_mixins(object, scope)
178
- output = StringIO.new
179
- mixin_type = (scope == :class) ? 'extend' : 'include'
180
- mixins = run_verifier(object.mixins(scope))
181
- mixins = stable_sort_by(mixins, &:path)
182
- mixins.each { |mixin|
183
- output.puts " #{mixin_type} #{mixin.path}"
184
- }
185
- output.string
186
- end
187
-
188
- def generate_class_methods(object)
189
- generate_methods(object, :class, 'self.')
190
- end
191
-
192
- def generate_instance_methods(object)
193
- generate_methods(object, :instance)
194
- end
195
-
196
- def generate_methods(object, scope, prefix = '')
197
- methods = sort_methods(object, scope)
198
- signatures = methods.map { |method|
199
- output = StringIO.new
200
- # Cannot use `methods.signature` here as it would return the C/C++ function
201
- # signature. Must generate one from the YARD data.
202
- signature = generate_method_signature(method)
203
- # NOTE: We must call `generate_docstring` after `generate_method_signature`
204
- # because `generate_method_signature` will also clean up docstrings with
205
- # a single @overload tag.
206
- output.puts generate_docstring(method, 1)
207
- output.puts " def #{prefix}#{signature}"
208
- output.puts " end"
209
- output.string
210
- }
211
- signatures.join("\n")
212
- end
213
-
214
- # NOTE: This may modify the docstring of the object.
215
- def generate_method_signature(object)
216
- signature = "#{object.name}"
217
- # If there is a single overload then use that as the parameter list. Many of
218
- # the SketchUp Ruby API methods will have this as it was safer to add an
219
- # @overload tag instead of renaming the function argument names.
220
- overloads = object.docstring.tags(:overload)
221
- if overloads.size == 1
222
- overload = overloads.first
223
- parameters = overload.parameters
224
- # Move the tags from the @overload tag to the root of the docstring. No need
225
- # for a single overload tag - it's unexpected when reading the source.
226
- object.docstring.add_tag(*overload.tags)
227
- object.docstring.delete_tags(:overload)
228
- else
229
- parameters = object.parameters
230
- end
231
- # Compile the signature for the arguments and default values.
232
- params = parameters.map { |param|
233
- param.last.nil? ? param.first : param.join(' = ')
234
- }.join(', ')
235
- signature << "(#{params})" unless params.empty?
236
- signature
237
- end
238
-
239
- def generate_docstring(object, indent_step = 0)
240
- output = StringIO.new
241
- indent = ' ' * indent_step
242
- docstring = object.docstring
243
- docstring.delete_tags(:par) # Remove obsolete @par tags.
244
- docstring.to_raw.lines.each { |line|
245
- # Naive check for tags with no indent - if it is we insert an extra line
246
- # in order to get some space for easier reader. Doing it this way in order
247
- # to avoid hacking YARD too much.
248
- output.puts "#{indent}#" if line.start_with?('@')
249
- # This is the original docstring line.
250
- output.puts "#{indent}# #{line}"
251
- }
252
- output.string
253
- end
254
-
255
- def sort_methods(object, scope)
256
- methods = run_verifier(object.meths(object_options))
257
- objects = methods.select { |method|
258
- method.scope == scope
259
- }
260
- stable_sort_by(objects, &:name)
261
- end
262
-
263
- def object_options
264
- {
265
- :inherited => false,
266
- :included => false
267
- }
268
- end
269
-
270
-
271
- def ensure_exist(path)
272
- unless File.directory?(path)
273
- FileUtils.mkdir_p(path)
274
- end
275
- path
276
- end
277
-
278
-
279
- class StubFile < File
280
-
281
- def puts(*args)
282
- case args.size
283
- when 0
284
- super
285
- when 1
286
- super trim_trailing_white_space(args[0].to_s)
287
- else
288
- raise NotImplementedError
289
- end
290
- end
291
-
292
- private
293
-
294
- TRAILING_WHITE_SPACE = /([\t ]+)$/
295
- def trim_trailing_white_space(string)
296
- string.gsub(TRAILING_WHITE_SPACE, '')
297
- end
298
-
299
- end
300
-
301
-
302
- MANUAL_CONSTANT_GROUPS = [
303
- # UI.messagebox return values.
304
- {
305
- constants: %w{IDABORT IDCANCEL IDIGNORE IDNO IDOK IDRETRY IDYES},
306
- group: 'ID_MESSAGEBOX'
307
- },
308
- # Axes
309
- {
310
- constants: %w{X_AXIS Y_AXIS Z_AXIS},
311
- group: 'AXES'
312
- },
313
- # Axes 2D
314
- {
315
- constants: %w{X_AXIS_2D Y_AXIS_2D},
316
- group: 'AXES2D'
317
- },
318
- # Transformation
319
- {
320
- constants: %w{IDENTITY IDENTITY_2D},
321
- group: 'IDENTITY'
322
- },
323
- # Geom::PolygonMesh
324
- {
325
- constants: %w{
326
- AUTO_SOFTEN HIDE_BASED_ON_INDEX NO_SMOOTH_OR_HIDE SMOOTH_SOFT_EDGES
327
- SOFTEN_BASED_ON_INDEX},
328
- group: 'SOFTEN'
329
- },
330
- # Sketchup::Importer
331
- # The other constants start with Import, this was odd one out.
332
- {
333
- constants: %w{ImporterNotFound},
334
- group: 'Import'
335
- },
336
- # Sketchup::Http
337
- {
338
- constants: %w{DELETE GET HEAD OPTIONS PATCH POST PUT},
339
- group: 'HTTP'
340
- },
341
- # Sketchup::Licensing
342
- {
343
- constants: %w{EXPIRED LICENSED NOT_LICENSED TRIAL TRIAL_EXPIRED},
344
- group: 'EX_LICENSE'
345
- },
346
- # Sketchup::Model
347
- {
348
- constants: %w{Make MakeTrial ProLicensed ProTrial},
349
- group: 'SU_LICENSE'
350
- },
351
- # Sketchup::RenderingOptions
352
- # Most ROP constants start with ROPSet, with a handful of exceptions.
353
- {
354
- regex: /^ROP/,
355
- group: 'ROP'
356
- },
357
- ]
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'set'
4
+ require 'stringio'
5
+
6
+ include Helpers::ModuleHelper
7
+
8
+ def init
9
+ generate_stubs
10
+ end
11
+
12
+
13
+ # NOTE: Remember to run objects outputted through `run_verifier` first in order
14
+ # to filter out items that should be excluded by command line arguments.
15
+
16
+ def namespace_objects
17
+ run_verifier(Registry.all(:class, :module))
18
+ end
19
+
20
+ def generate_stubs
21
+ puts "Generating stubs..."
22
+ generate_module_stubs(Registry.root)
23
+ namespace_objects.each do |object|
24
+ generate_module_stubs(object)
25
+ end
26
+ generate_autoloader(namespace_objects)
27
+ end
28
+
29
+ def generate_autoloader(namespace_objects)
30
+ generator = SketchUpYARD::Stubs::AutoLoadGenerator.new
31
+ autoload_file = File.join(stubs_gem_path, 'sketchup.rb')
32
+ File.open(autoload_file, 'w') do |file|
33
+ generator.generate(namespace_objects, file)
34
+ end
35
+ end
36
+
37
+ def print_section(io, title, content)
38
+ return if content.strip.empty?
39
+ io.puts
40
+ io.puts " # #{title}"
41
+ io.puts
42
+ io.puts content
43
+ end
44
+
45
+ def generate_module_stubs(object)
46
+ filename = stub_filename(object)
47
+ ensure_exist(File.dirname(filename))
48
+ StubFile.open(filename, 'w') { |file|
49
+ file.puts file_header(object)
50
+ file.puts
51
+ file.puts namespace_definition(object)
52
+ print_section(file, 'Extends', generate_mixins(object, :class))
53
+ print_section(file, 'Includes', generate_mixins(object, :instance))
54
+ print_section(file, 'Constants', generate_constants_grouped(object))
55
+ print_section(file, 'Class Methods', generate_class_methods(object))
56
+ print_section(file, 'Instance Methods', generate_instance_methods(object))
57
+ file.puts
58
+ file.puts file_footer(object)
59
+ }
60
+ #trim_trailing_white_space(filename)
61
+ end
62
+
63
+ def file_header(object)
64
+ header = StringIO.new
65
+ header.puts "# Copyright:: Copyright #{Time.now.year} Trimble Inc."
66
+ header.puts "# License:: The MIT License (MIT)"
67
+ #header.puts "# Generated:: #{Time.now.strftime('%F %R')}"
68
+ header.string
69
+ end
70
+
71
+ def file_footer(object)
72
+ return if object.root?
73
+ footer = StringIO.new
74
+ footer.puts 'end'
75
+ footer.string
76
+ end
77
+
78
+ def namespace_definition(object)
79
+ return if object.root?
80
+ definition = "#{object.type} #{object.path}"
81
+ if object.type == :class && object.superclass.name != :Object
82
+ definition << " < #{object.superclass.path}"
83
+ end
84
+ output = StringIO.new
85
+ output.puts generate_docstring(object)
86
+ output.puts definition
87
+ output.string
88
+ end
89
+
90
+ def output_path
91
+ options.serializer.options[:basepath] || File.join(Dir.pwd, 'stubs')
92
+ end
93
+
94
+ def stubs_root_path
95
+ ensure_exist(output_path)
96
+ end
97
+
98
+ def stubs_lib_path
99
+ ensure_exist(File.join(stubs_root_path, 'lib'))
100
+ end
101
+
102
+ def stubs_gem_path
103
+ ensure_exist(File.join(stubs_lib_path, 'sketchup-api-stubs'))
104
+ end
105
+
106
+ def stubs_path
107
+ ensure_exist(File.join(stubs_gem_path, 'stubs'))
108
+ end
109
+
110
+ def stub_filename(object)
111
+ basename = object.path.gsub('::', '/')
112
+ basename = '_top_level' if basename.empty?
113
+ File.join(stubs_path, "#{basename}.rb")
114
+ end
115
+
116
+ # A stable sort_by method.
117
+ #
118
+ # @param [Enumerable]
119
+ # @return [Array]
120
+ def stable_sort_by(list)
121
+ list.each_with_index.sort_by { |item, i| [yield(item), i] }.map(&:first)
122
+ end
123
+
124
+ CAMELCASE_CONSTANT = /^([A-Z]+[a-z]+)/
125
+
126
+ def group_constant(constant)
127
+ constant_name = constant.name.to_s
128
+ MANUAL_CONSTANT_GROUPS.each { |rule|
129
+ if rule[:constants]
130
+ return rule[:group] if rule[:constants].include?(constant_name)
131
+ else
132
+ return rule[:group] if rule[:regex].match(constant_name)
133
+ end
134
+ }
135
+ if constant_name.include?('_')
136
+ constant_name.split('_').first
137
+ else
138
+ constant_name[CAMELCASE_CONSTANT] || constant_name
139
+ end
140
+ end
141
+
142
+ # Sorts and groups constants for easier reading.
143
+ def generate_constants_grouped(object)
144
+ constants = run_verifier(object.constants(object_options))
145
+ # The constants are not sorted before chunking. This is because `chunk` groups
146
+ # consecutive items - and we want to chunk them based their relationship
147
+ # with each other. This ensure that constants that doesn't follow the normal
148
+ # pattern of PREFIX_SOME_NAME will still be grouped next to each other.
149
+ groups = constants.chunk { |constant|
150
+ group_constant(constant)
151
+ }
152
+ grouped_output = groups.map { |group, group_constants|
153
+ output = StringIO.new
154
+ # Each group itself is sorted in order to more easily scan the list.
155
+ sorted = stable_sort_by(group_constants, &:name)
156
+ sorted.each { |constant|
157
+ output.puts " #{constant.name} = nil # Stub value."
158
+ }
159
+ output.string
160
+ }
161
+ # Finally each group is also sorted, again to ease scanning for a particular
162
+ # name. We simply use the first character of each group.
163
+ stable_sort_by(grouped_output) { |item| item.lstrip[0] }.join("\n")
164
+ end
165
+
166
+ # Sort constants without grouping.
167
+ def generate_constants(object)
168
+ output = StringIO.new
169
+ constants = run_verifier(object.constants(object_options))
170
+ constants = stable_sort_by(constants, &:name)
171
+ constants.each { |constant|
172
+ output.puts " #{constant.name} = nil # Stub value."
173
+ }
174
+ output.string
175
+ end
176
+
177
+ def generate_mixins(object, scope)
178
+ output = StringIO.new
179
+ mixin_type = (scope == :class) ? 'extend' : 'include'
180
+ mixins = run_verifier(object.mixins(scope))
181
+ mixins = stable_sort_by(mixins, &:path)
182
+ mixins.each { |mixin|
183
+ output.puts " #{mixin_type} #{mixin.path}"
184
+ }
185
+ output.string
186
+ end
187
+
188
+ def generate_class_methods(object)
189
+ generate_methods(object, :class, 'self.')
190
+ end
191
+
192
+ def generate_instance_methods(object)
193
+ generate_methods(object, :instance)
194
+ end
195
+
196
+ def generate_methods(object, scope, prefix = '')
197
+ methods = sort_methods(object, scope)
198
+ signatures = methods.map { |method|
199
+ output = StringIO.new
200
+ # Cannot use `methods.signature` here as it would return the C/C++ function
201
+ # signature. Must generate one from the YARD data.
202
+ signature = generate_method_signature(method)
203
+ # NOTE: We must call `generate_docstring` after `generate_method_signature`
204
+ # because `generate_method_signature` will also clean up docstrings with
205
+ # a single @overload tag.
206
+ output.puts generate_docstring(method, 1)
207
+ output.puts " def #{prefix}#{signature}"
208
+ output.puts " end"
209
+ # Include aliases.
210
+ method.aliases.each { |method_alias|
211
+ output.puts " alias_method :#{method_alias.name}, :#{method.name}"
212
+ }
213
+ output.string
214
+ }
215
+ signatures.join("\n")
216
+ end
217
+
218
+ # NOTE: This may modify the docstring of the object.
219
+ def generate_method_signature(object)
220
+ signature = "#{object.name}"
221
+ # If there is a single overload then use that as the parameter list. Many of
222
+ # the SketchUp Ruby API methods will have this as it was safer to add an
223
+ # @overload tag instead of renaming the function argument names.
224
+ overloads = object.docstring.tags(:overload)
225
+ if overloads.size == 1
226
+ overload = overloads.first
227
+ parameters = overload.parameters
228
+ # Move the tags from the @overload tag to the root of the docstring. No need
229
+ # for a single overload tag - it's unexpected when reading the source.
230
+ object.docstring.add_tag(*overload.tags)
231
+ object.docstring.delete_tags(:overload)
232
+ else
233
+ parameters = object.parameters
234
+ end
235
+ # Compile the signature for the arguments and default values.
236
+ params = parameters.map { |param|
237
+ param.last.nil? ? param.first : param.join(' = ')
238
+ }.join(', ')
239
+ signature << "(#{params})" unless params.empty?
240
+ signature
241
+ end
242
+
243
+ def generate_docstring(object, indent_step = 0)
244
+ output = StringIO.new
245
+ indent = ' ' * indent_step
246
+ docstring = object.docstring
247
+ docstring.delete_tags(:par) # Remove obsolete @par tags.
248
+ docstring.to_raw.lines.each { |line|
249
+ # Naive check for tags with no indent - if it is we insert an extra line
250
+ # in order to get some space for easier reader. Doing it this way in order
251
+ # to avoid hacking YARD too much.
252
+ output.puts "#{indent}#" if line.start_with?('@')
253
+ # This is the original docstring line.
254
+ output.puts "#{indent}# #{line}"
255
+ }
256
+ output.string
257
+ end
258
+
259
+ def sort_methods(object, scope)
260
+ methods = run_verifier(object.meths(object_options))
261
+ objects = methods.select { |method|
262
+ !method.is_alias? && method.scope == scope
263
+ }
264
+ stable_sort_by(objects, &:name)
265
+ end
266
+
267
+ def object_options
268
+ {
269
+ :inherited => false,
270
+ :included => false
271
+ }
272
+ end
273
+
274
+
275
+ def ensure_exist(path)
276
+ unless File.directory?(path)
277
+ FileUtils.mkdir_p(path)
278
+ end
279
+ path
280
+ end
281
+
282
+
283
+ class StubFile < File
284
+
285
+ def puts(*args)
286
+ case args.size
287
+ when 0
288
+ super
289
+ when 1
290
+ super trim_trailing_white_space(args[0].to_s)
291
+ else
292
+ raise NotImplementedError
293
+ end
294
+ end
295
+
296
+ private
297
+
298
+ TRAILING_WHITE_SPACE = /([\t ]+)$/
299
+ def trim_trailing_white_space(string)
300
+ string.gsub(TRAILING_WHITE_SPACE, '')
301
+ end
302
+
303
+ end
304
+
305
+
306
+ MANUAL_CONSTANT_GROUPS = [
307
+ # UI.messagebox return values.
308
+ {
309
+ constants: %w{IDABORT IDCANCEL IDIGNORE IDNO IDOK IDRETRY IDYES},
310
+ group: 'ID_MESSAGEBOX'
311
+ },
312
+ # Axes
313
+ {
314
+ constants: %w{X_AXIS Y_AXIS Z_AXIS},
315
+ group: 'AXES'
316
+ },
317
+ # Axes 2D
318
+ {
319
+ constants: %w{X_AXIS_2D Y_AXIS_2D},
320
+ group: 'AXES2D'
321
+ },
322
+ # Transformation
323
+ {
324
+ constants: %w{IDENTITY IDENTITY_2D},
325
+ group: 'IDENTITY'
326
+ },
327
+ # Geom::PolygonMesh
328
+ {
329
+ constants: %w{
330
+ AUTO_SOFTEN HIDE_BASED_ON_INDEX NO_SMOOTH_OR_HIDE SMOOTH_SOFT_EDGES
331
+ SOFTEN_BASED_ON_INDEX},
332
+ group: 'SOFTEN'
333
+ },
334
+ # Sketchup::Importer
335
+ # The other constants start with Import, this was odd one out.
336
+ {
337
+ constants: %w{ImporterNotFound},
338
+ group: 'Import'
339
+ },
340
+ # Sketchup::Http
341
+ {
342
+ constants: %w{DELETE GET HEAD OPTIONS PATCH POST PUT},
343
+ group: 'HTTP'
344
+ },
345
+ # Sketchup::Licensing
346
+ {
347
+ constants: %w{EXPIRED LICENSED NOT_LICENSED TRIAL TRIAL_EXPIRED},
348
+ group: 'EX_LICENSE'
349
+ },
350
+ # Sketchup::Model
351
+ {
352
+ constants: %w{Make MakeTrial ProLicensed ProTrial},
353
+ group: 'SU_LICENSE'
354
+ },
355
+ # Sketchup::RenderingOptions
356
+ # Most ROP constants start with ROPSet, with a handful of exceptions.
357
+ {
358
+ regex: /^ROP/,
359
+ group: 'ROP'
360
+ },
361
+ ]