yard-sketchup 1.1.4 → 1.1.5

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