puppet-strings 0.4.0

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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/lib/puppet-strings/rake_tasks.rb +18 -0
  3. data/lib/puppet/application/strings.rb +4 -0
  4. data/lib/puppet/face/strings.rb +64 -0
  5. data/lib/puppet/feature/rgen.rb +3 -0
  6. data/lib/puppet/feature/yard.rb +3 -0
  7. data/lib/puppet_x/puppetlabs/strings.rb +64 -0
  8. data/lib/puppet_x/puppetlabs/strings/actions.rb +92 -0
  9. data/lib/puppet_x/puppetlabs/strings/pops/yard_statement.rb +79 -0
  10. data/lib/puppet_x/puppetlabs/strings/pops/yard_transformer.rb +47 -0
  11. data/lib/puppet_x/puppetlabs/strings/util.rb +65 -0
  12. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/defined_type_object.rb +33 -0
  13. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/host_class_object.rb +22 -0
  14. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/method_object.rb +62 -0
  15. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb +24 -0
  16. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/puppet_namespace_object.rb +48 -0
  17. data/lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb +42 -0
  18. data/lib/puppet_x/puppetlabs/strings/yard/core_ext/yard.rb +40 -0
  19. data/lib/puppet_x/puppetlabs/strings/yard/handlers/base.rb +13 -0
  20. data/lib/puppet_x/puppetlabs/strings/yard/handlers/defined_type_handler.rb +31 -0
  21. data/lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb +80 -0
  22. data/lib/puppet_x/puppetlabs/strings/yard/handlers/host_class_handler.rb +42 -0
  23. data/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb +95 -0
  24. data/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb +54 -0
  25. data/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_4x_function_handler.rb +234 -0
  26. data/lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb +295 -0
  27. data/lib/puppet_x/puppetlabs/strings/yard/json_registry_store.rb +85 -0
  28. data/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb +68 -0
  29. data/lib/puppet_x/puppetlabs/strings/yard/parser.rb +30 -0
  30. data/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb +9 -0
  31. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/docstring.erb +34 -0
  32. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/header.erb +5 -0
  33. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/parameter_details.erb +6 -0
  34. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/setup.rb +1 -0
  35. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/setup.rb +49 -0
  36. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_class.erb +2 -0
  37. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_manifest.erb +1 -0
  38. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_plugin.erb +21 -0
  39. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb +1 -0
  40. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb +1 -0
  41. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb +82 -0
  42. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/box_info.erb +22 -0
  43. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/setup.rb +1 -0
  44. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/subclasses.erb +4 -0
  45. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/setup.rb +21 -0
  46. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb +139 -0
  47. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb +18 -0
  48. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/html/header.erb +17 -0
  49. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/setup.rb +21 -0
  50. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/text/header.erb +2 -0
  51. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb +8 -0
  52. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb +10 -0
  53. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb +10 -0
  54. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb +34 -0
  55. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb +10 -0
  56. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb +5 -0
  57. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb +1 -0
  58. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb +50 -0
  59. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/box_info.erb +11 -0
  60. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/header.erb +5 -0
  61. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/method_details_list.erb +53 -0
  62. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/method_summary.erb +20 -0
  63. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/setup.rb +1 -0
  64. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/setup.rb +91 -0
  65. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/template_helper.rb +192 -0
  66. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb +34 -0
  67. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb +5 -0
  68. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb +12 -0
  69. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb +10 -0
  70. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb +1 -0
  71. data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb +55 -0
  72. metadata +142 -0
@@ -0,0 +1,95 @@
1
+ # Handles `dispatch` calls within a future parser function declaration. For
2
+ # now, it just treats any docstring as an `@overlaod` tag and attaches the
3
+ # overload to the parent function.
4
+ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetProviderHandler < YARD::Handlers::Ruby::Base
5
+ include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
6
+
7
+ handles :command_call, :call
8
+
9
+ process do
10
+ @heredoc_helper = HereDocHelper.new
11
+ # Puppet types always begin with:
12
+ # Puppet::Types.newtype...
13
+ # Therefore, we match the corresponding trees which look like this:
14
+ # s(:call,
15
+ # s(:const_path_ref,
16
+ # s(:var_ref, s(:const, "Puppet", ...), ...),
17
+ # s(:const, "Type", ...),
18
+ # You think this is ugly? It's better than the alternative.
19
+ return unless statement.children.length > 2
20
+ first = statement.children.first.first
21
+ return unless (first.source == 'Puppet::Type') ||
22
+ (first.type == :var_ref &&
23
+ first.source == 'Type') &&
24
+ statement[2].source == 'provide'
25
+ i = statement.index { |s| YARD::Parser::Ruby::AstNode === s && s.type == :ident && s.source == 'provide' }
26
+ provider_name = statement[i+1].jump(:ident).source
27
+ type_name = statement.jump(:symbol).first.source
28
+
29
+ obj = ProviderObject.new(:root, "#{provider_name}_provider")
30
+
31
+ docstring = nil
32
+ features = []
33
+ commands = []
34
+ confines = {}
35
+ defaults = {}
36
+ do_block = statement.jump(:do_block)
37
+ do_block.traverse do |node|
38
+ if is_a_func_call_named?('desc', node)
39
+ content = node.jump(:tstring_content)
40
+ # if we found the string content extract its source
41
+ if content != node
42
+ # The docstring is either the source stripped of heredoc
43
+ # annotations or the raw source.
44
+ if @heredoc_helper.is_heredoc?(content.source)
45
+ docstring = @heredoc_helper.process_heredoc content.source
46
+ else
47
+ docstring = content.source
48
+ end
49
+ end
50
+ elsif is_a_func_call_named?('confine', node)
51
+ node.traverse do |s|
52
+ if s.type == :assoc
53
+ k = s.first.jump(:ident).source
54
+ v = s[1].first.source
55
+ confines[k] = v
56
+ end
57
+ end
58
+ elsif is_a_func_call_named?('has_feature', node)
59
+ list = node.jump :list
60
+ if list != nil && list != node
61
+ features += list.map { |s| s.source if YARD::Parser::Ruby::AstNode === s }.compact
62
+ end
63
+ elsif is_a_func_call_named?('commands', node)
64
+ assoc = node.jump(:assoc)
65
+ if assoc && assoc != node
66
+ ident = assoc.jump(:ident)
67
+ if ident && ident != assoc
68
+ commands << ident.source
69
+ end
70
+ end
71
+ elsif is_a_func_call_named?('defaultfor', node)
72
+ node.traverse do |s|
73
+ if s.type == :assoc
74
+ k = s.first.jump(:ident).source
75
+ v = s[1].first.source
76
+ defaults[k] = v
77
+ end
78
+ end
79
+ end
80
+ end
81
+ obj.features = features
82
+ obj.commands = commands
83
+ obj.confines = confines
84
+ obj.defaults = defaults
85
+ obj.type_name = type_name
86
+ obj.header_name = provider_name
87
+
88
+ register_docstring(obj, docstring, nil)
89
+ register obj
90
+ end
91
+
92
+ def is_a_func_call_named?(name, node)
93
+ (node.type == :fcall || node.type == :command || node.type == :vcall) && node.children.first.source == name
94
+ end
95
+ end
@@ -0,0 +1,54 @@
1
+ require File.join(File.dirname(__FILE__),'./heredoc_helper')
2
+
3
+ class PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet3xFunctionHandler < YARD::Handlers::Ruby::Base
4
+ include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
5
+
6
+ handles method_call(:newfunction)
7
+
8
+ process do
9
+ @heredoc_helper = HereDocHelper.new
10
+ name, options = @heredoc_helper.process_parameters statement
11
+
12
+ obj = MethodObject.new(function_namespace, name)
13
+ obj[:puppet_3x_function] = true
14
+
15
+ register obj
16
+ if options['doc']
17
+ register_docstring(obj, options['doc'], nil)
18
+ end
19
+
20
+ # This has to be done _after_ register_docstring as all tags on the
21
+ # object are overwritten by tags parsed out of the docstring.
22
+ return_type = options['type']
23
+ return_type ||= 'statement' # Default for newfunction
24
+ obj.add_tag YARD::Tags::Tag.new(:return, '', return_type)
25
+
26
+ # FIXME: This is a hack that allows us to document the Puppet Core which
27
+ # uses `--no-transitive-tag api` and then only shows things explicitly
28
+ # tagged with `public` or `private` api. This is kind of insane and
29
+ # should be fixed upstream.
30
+ obj.add_tag YARD::Tags::Tag.new(:api, 'public')
31
+ end
32
+
33
+ private
34
+
35
+ # Returns a {PuppetNamespaceObject} for holding functions. Creates this
36
+ # object if necessary.
37
+ #
38
+ # @return [PuppetNamespaceObject]
39
+ def function_namespace
40
+ # NOTE: This tricky. If there is ever a Ruby class or module with the
41
+ # name ::Puppet3xFunctions, then there will be a clash. Hopefully the name
42
+ # is sufficiently uncommon.
43
+ obj = P(:root, 'Puppet3xFunctions')
44
+ if obj.is_a? Proxy
45
+ namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet3xFunctions')
46
+ namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
47
+
48
+ register namespace_obj
49
+ end
50
+
51
+ obj
52
+ end
53
+
54
+ end
@@ -0,0 +1,234 @@
1
+ # Handles `dispatch` calls within a future parser function declaration. For
2
+ # now, it just treats any docstring as an `@overlaod` tag and attaches the
3
+ # overload to the parent function.
4
+ class PuppetX::PuppetLabs::Strings::YARD::Handlers::Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
5
+ include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
6
+
7
+ handles method_call(:dispatch)
8
+
9
+ process do
10
+ return unless owner.is_a?(MethodObject) && owner['puppet_4x_function']
11
+ return unless statement.docstring
12
+
13
+ docstring = ::YARD::Docstring.new(statement.docstring, nil)
14
+
15
+ # FIXME: This does a wholesale copy of all possible tags. But, we're only
16
+ # interested in the @overload tag.
17
+ owner.add_tag *docstring.tags
18
+ end
19
+ end
20
+
21
+ class Puppet4xFunctionHandler < YARD::Handlers::Ruby::Base
22
+ include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
23
+
24
+ handles method_call(:create_function)
25
+
26
+ # Given a command node which represents code like this:
27
+ # param 'Optional[Type]', :value_type
28
+ # Extract the type name and type signature and return them as a array.
29
+ def extract_type_from_command command
30
+ return [] if command.children.length < 2 or command.children[1].children.length < 2
31
+ type_specifier = command.children[1]
32
+ # the parameter signature is the first child of the specifier and an
33
+ # identifier. Jump to the content inside the quotes and convert it to a
34
+ # string.
35
+ param_signature = type_specifier.children[0].jump(:tstring_content).source
36
+ # The parameter name is the second child of the specifier and a symbol.
37
+ # convert it to a string.
38
+ param_name_ident = type_specifier.jump :ident
39
+ return [] if param_name_ident == type_specifier
40
+ param_name = param_name_ident.source
41
+ [param_name, param_signature]
42
+ end
43
+
44
+ process do
45
+ name = process_parameters
46
+
47
+ method_arguments = []
48
+
49
+ # To attach the method parameters to the new code object, traverse the
50
+ # ruby AST until a node is found which defines a array of parameters.
51
+ # Then, traverse the children of the parameters, storing each identifier
52
+ # in the array of method arguments.
53
+ obj = MethodObject.new(function_namespace, name) do |o|
54
+ end
55
+
56
+ # The data structure for overload_signatures is an array of hashes. Each
57
+ # hash represents the arguments a single function dispatch (aka overload)
58
+ # can take.
59
+ # overload_signatures = [
60
+ # { # First function dispatch arguments
61
+ # # argument name, argument type
62
+ # 'arg0': 'Variant[String,Array[String]]',
63
+ # 'arg1': 'Optional[Type]'
64
+ # },
65
+ # { # Second function dispatch arguments
66
+ # 'arg0': 'Variant[String,Array[String]]',
67
+ # 'arg1': 'Optional[Type]',
68
+ # 'arg2': 'Any'
69
+ # }
70
+ # ]
71
+ # Note that the order for arguments to a function doesn't actually matter
72
+ # because we allow users flexibility when listing their arguments in the
73
+ # comments.
74
+ overload_signatures = []
75
+ statement.traverse do |node|
76
+ # Find all of the dispatch methods
77
+ if node.type == :ident and node.source == 'dispatch'
78
+ command = node.parent
79
+ do_block = command.jump :do_block
80
+ # If the command doesn't have a do_block we can't extract type info
81
+ if do_block == command
82
+ next
83
+ end
84
+ signature = {}
85
+ # Iterate through each of the children of the do block and build
86
+ # tuples of parameter names and parameter type signatures
87
+ do_block.children.first.children.each do |child|
88
+ name, type = extract_type_from_command(child)
89
+ # This can happen if there is a function or something we aren't
90
+ # expecting.
91
+ if name != nil and type != nil
92
+ signature[name] = type
93
+ end
94
+ end
95
+ overload_signatures <<= signature
96
+ end
97
+ end
98
+
99
+ # If the overload_signatures list is empty because we couldn't find any
100
+ # dispatch blocks, then there must be one function named the same as the
101
+ # name of the function being created.
102
+ if overload_signatures.length == 0
103
+ statement.traverse do |node|
104
+ # Find the function definition with the same name as the puppet
105
+ # function being created.
106
+ if (node.type == :def and node.children.first.type == :ident and
107
+ node.children.first.source == obj.name.to_s)
108
+ signature = {}
109
+ # Find its parameters. If they don't exist, fine
110
+ params = node.jump :params
111
+ break if params == node
112
+ params.traverse do |param|
113
+ if param.type == :ident
114
+ # The parameters of Puppet functions with no defined dispatch are
115
+ # as though they are Any type.
116
+ signature[param[0]] = 'Any'
117
+ end
118
+ end
119
+ overload_signatures <<= signature
120
+ # Now that the parameters have been found, break out of the traversal
121
+ break
122
+ end
123
+ end
124
+ end
125
+
126
+ # Preserve this type information. We'll need it later when we look
127
+ # at the docstring.
128
+ obj.type_info = overload_signatures
129
+
130
+ # The yard docstring parser expects a list of lists, not a list of lists of
131
+ # lists.
132
+ obj.parameters = overload_signatures.map { |sig| sig.to_a }.flatten(1)
133
+
134
+ obj['puppet_4x_function'] = true
135
+
136
+ register obj
137
+
138
+ obj.add_tag YARD::Tags::Tag.new(:api, 'public')
139
+
140
+ blk = statement.block.children.first
141
+ parse_block(blk, :owner => obj)
142
+ end
143
+
144
+ private
145
+
146
+ # Returns a {PuppetNamespaceObject} for holding functions. Creates this
147
+ # object if necessary.
148
+ #
149
+ # @return [PuppetNamespaceObject]
150
+ def function_namespace
151
+ # NOTE: This tricky. If there is ever a Ruby class or module with the
152
+ # name ::Puppet4xFunctions, then there will be a clash. Hopefully the name
153
+ # is sufficiently uncommon.
154
+ obj = P(:root, 'Puppet4xFunctions')
155
+ if obj.is_a? Proxy
156
+ namespace_obj = PuppetNamespaceObject.new(:root, 'Puppet4xFunctions')
157
+
158
+ register namespace_obj
159
+ # FIXME: The docstring has to be cleared. Otherwise, the namespace
160
+ # object will be registered using the docstring of the
161
+ # `create_function` call that is currently being processed.
162
+ #
163
+ # Figure out how to properly register the namespace without using the
164
+ # function handler object.
165
+ register_docstring(namespace_obj, '', nil)
166
+ namespace_obj.add_tag YARD::Tags::Tag.new(:api, 'public')
167
+ end
168
+
169
+ obj
170
+ end
171
+
172
+ # NOTE: The following methods duplicate functionality from
173
+ # Puppet::Util::Reference and Puppet::Parser::Functions.functiondocs
174
+ #
175
+ # However, implementing this natively in YARD is a good test for the
176
+ # feasibility of extracting custom Ruby documentation. In the end, the
177
+ # existing approach taken by Puppet::Util::Reference may be the best due to
178
+ # the heavy use of metaprogramming in Types and Providers.
179
+
180
+ # Extracts the Puppet function name and options hash from the parsed
181
+ # definition.
182
+ #
183
+ # @return [(String, Hash{String => String})]
184
+ def process_parameters
185
+ # Passing `false` to parameters excludes the block param from the returned
186
+ # array.
187
+ name, _ = statement.parameters(false).compact
188
+
189
+ name = process_element(name)
190
+
191
+
192
+ name
193
+ end
194
+
195
+ # Sometimes the YARD parser returns Heredoc strings that start with `<-`
196
+ # instead of `<<-`.
197
+ HEREDOC_START = /^<?<-/
198
+
199
+ # Turns an entry in the method parameter array into a string.
200
+ #
201
+ # @param ele [YARD::Parser::Ruby::AstNode]
202
+ # @return [String]
203
+ def process_element(ele)
204
+ ele = ele.jump(:ident, :string_content, :tstring_content)
205
+
206
+ case ele.type
207
+ when :ident
208
+ ele.source
209
+ when :string_content, :tstring_content
210
+ source = ele.source
211
+ if HEREDOC_START.match(source)
212
+ process_heredoc(source)
213
+ else
214
+ source
215
+ end
216
+ end
217
+ end
218
+
219
+ # Cleans up and formats Heredoc contents parsed by YARD.
220
+ #
221
+ # @param source [String]
222
+ # @return [String]
223
+ def process_heredoc(source)
224
+ source = source.lines.to_a
225
+
226
+ # YARD adds a line of source context on either side of the Heredoc
227
+ # contents.
228
+ source.shift
229
+ source.pop
230
+
231
+ # This utility method normalizes indentation and trims whitespace.
232
+ Puppet::Util::Docs.scrub(source.join)
233
+ end
234
+ end
@@ -0,0 +1,295 @@
1
+ # Handles `dispatch` calls within a future parser function declaration. For
2
+ # now, it just treats any docstring as an `@overlaod` tag and attaches the
3
+ # overload to the parent function.
4
+ class PuppetX::PuppetLabs::Strings::YARD::Handlers::PuppetTypeHandler < YARD::Handlers::Ruby::Base
5
+ include PuppetX::PuppetLabs::Strings::YARD::CodeObjects
6
+
7
+ handles :call
8
+
9
+ process do
10
+ @heredoc_helper = HereDocHelper.new
11
+ # Puppet types always begin with:
12
+ # Puppet::Types.newtype...
13
+ # Therefore, we match the corresponding trees which look like this:
14
+ # s(:call,
15
+ # s(:const_path_ref,
16
+ # s(:var_ref, s(:const, "Puppet", ...), ...),
17
+ # s(:const, "Type", ...),
18
+ # You think this is ugly? It's better than the alternative.
19
+ return unless statement.children.length > 2
20
+ first = statement.children.first
21
+ return unless (first.type == :const_path_ref and
22
+ first.source == 'Puppet::Type') or
23
+ (first.type == :var_ref and
24
+ first.source == 'Type') and
25
+ statement.children[1].source == "newtype"
26
+
27
+ # Fetch the docstring for the types. The docstring is the string literal
28
+ # assigned to the @doc parameter or absent, like this:
29
+ # @doc "docstring goes here"
30
+ # We assume that docstrings nodes have the following shape in the source
31
+ # code:
32
+ # ...
33
+ # s(s(:assign,
34
+ # s(:..., s(:ivar, "@doc", ...), ...),
35
+ # s(:...,
36
+ # s(:...,
37
+ # s(:tstring_content,
38
+ # "Manages files, including their content, etc.", ...
39
+ # Initialize the docstring to nil, the default value if we don't find
40
+ # anything
41
+ docstring = nil
42
+ # Walk the tree searching for assignments
43
+ statement.traverse do |node|
44
+ if node.type == :assign
45
+ # Once we have found and assignment, jump to the first ivar
46
+ # (the l-value)
47
+ # If we can't find an ivar return the node.
48
+ ivar = node.jump(:ivar)
49
+ # If we found and ivar and its source reads '@doc' then...
50
+ if ivar != node and ivar.source == '@doc'
51
+ # find the next string content
52
+ content = node.jump(:tstring_content)
53
+ # if we found the string content extract its source
54
+ if content != node
55
+ # The docstring is either the source stripped of heredoc
56
+ # annotations or the raw source.
57
+ if @heredoc_helper.is_heredoc? content.source
58
+ docstring = @heredoc_helper.process_heredoc content.source
59
+ else
60
+ docstring = content.source
61
+ end
62
+ end
63
+ # Since we found the @doc parameter (regardless of whether we
64
+ # successfully extracted its source), we're done.
65
+ break
66
+ # But if we didn't find the ivar loop around again.
67
+ else
68
+ next
69
+ end
70
+ end
71
+ end
72
+
73
+ # The types begin with:
74
+ # Puppet::Types.newtype(:symbol)
75
+ # Jump to the first identifier (':symbol') after the third argument
76
+ # ('(:symbol)') to the current statement
77
+ name = statement.children[2].jump(:ident).source
78
+ parameter_details = []
79
+ property_details = []
80
+ features = []
81
+ obj = TypeObject.new(:root, name)
82
+ obj.parameters = []
83
+
84
+ # Find the do block following the Type.
85
+ do_block = statement.jump(:do_block)
86
+ # traverse the do block's children searching for function calls whose
87
+ # identifier is newparam (we're calling the newparam function)
88
+ do_block.traverse do |node|
89
+ if is_param? node
90
+ # The first member of the parameter tuple is the parameter name.
91
+ # Find the second identifier node under the fcall tree. The first one
92
+ # is 'newparam', the second one is the function name.
93
+ # Get its source.
94
+ # The second parameter is nil because we cannot infer types for these
95
+ # functions. In fact, that's a silly thing to ask because ruby
96
+ # types were deprecated with puppet 4 at the same time the type
97
+ # system was created.
98
+
99
+ # Because of a ripper bug a symbol identifier is sometimes incorrectly parsed as a keyword.
100
+ # That is, the symbol `:true` will be represented as s(:symbol s(:kw, true...
101
+ param_name = node.children[1].jump(:ident)
102
+ if param_name == node.children[1]
103
+ param_name = node.children[1].jump(:kw)
104
+ end
105
+ param_name = param_name.source
106
+ obj.parameters << [param_name, nil]
107
+ parameter_details << {:name => param_name,
108
+ :desc => fetch_description(node), :exists? => true,
109
+ :puppet_type => true,
110
+ :default => fetch_default(node),
111
+ :namevar => is_namevar?(node, param_name, name),
112
+ :parameter => true,
113
+ :allowed_values => get_parameter_allowed_values(node),
114
+ }
115
+ elsif is_prop? node
116
+ # Because of a ripper bug a symbol identifier is sometimes incorrectly parsed as a keyword.
117
+ # That is, the symbol `:true` will be represented as s(:symbol s(:kw, true...
118
+ prop_name = node.children[1].jump(:ident)
119
+ if prop_name == node.children[1]
120
+ prop_name = node.children[1].jump(:kw)
121
+ end
122
+ prop_name = prop_name.source
123
+ property_details << {:name => prop_name,
124
+ :desc => fetch_description(node), :exists? => true,
125
+ :default => fetch_default(node),
126
+ :puppet_type => true,
127
+ :property => true,
128
+ :allowed_values => get_property_allowed_values(node),
129
+ }
130
+ elsif is_feature? node
131
+ features << get_feature(node)
132
+ elsif is_a_func_call_named? 'ensurable', node
133
+ # Someone could call the ensurable method and create an ensure
134
+ # property. If that happens, they it will be documented twice. Serves
135
+ # them right.
136
+ property_details << {:name => 'ensure',
137
+ :desc => '', :exists? => true,
138
+ :default => nil,
139
+ :puppet_type => true,
140
+ :property => true,
141
+ :allowed_values => [],
142
+ }
143
+ end
144
+ end
145
+ obj.parameter_details = parameter_details
146
+ obj.property_details = property_details
147
+ obj.features = features
148
+ obj.header_name = name
149
+
150
+ register obj
151
+ # Register docstring after the object. If the object already has a
152
+ # docstring, or more likely has parameters documented with the type
153
+ # directive and an empty docstring, we want to override it with the
154
+ # docstring we found, assuming we found one.
155
+ register_docstring(obj, docstring, nil) if docstring
156
+ end
157
+
158
+
159
+ # See:
160
+ # https://docs.puppetlabs.com/guides/custom_types.html#namevar
161
+ # node should be a parameter
162
+ def is_namevar? node, param_name, type_name
163
+ # Option 1:
164
+ # Puppet::Type.newtype(:name) do
165
+ # ...
166
+ # newparam(:name) do
167
+ # ...
168
+ # end
169
+ if type_name == param_name
170
+ return true
171
+ end
172
+ # Option 2:
173
+ # newparam(:path, :namevar => true) do
174
+ # ...
175
+ # end
176
+ if node.children.length >= 2
177
+ node.traverse do |s|
178
+ if s.type == :assoc and s.jump(:ident).source == 'namevar' and s.jump(:kw).source == 'true'
179
+ return true
180
+ end
181
+ end
182
+ end
183
+ # Option 3:
184
+ # newparam(:path) do
185
+ # isnamevar
186
+ # ...
187
+ # end
188
+ do_block = node.jump(:do_block).traverse do |s|
189
+ if is_a_func_call_named? 'isnamevar', s
190
+ return true
191
+ end
192
+ end
193
+ # Crazy implementations of types may just call #isnamevar directly on the object.
194
+ # We don't handle this today.
195
+ return false
196
+ end
197
+
198
+ def is_param? node
199
+ is_a_func_call_named? 'newparam', node
200
+ end
201
+ def is_prop? node
202
+ is_a_func_call_named? 'newproperty', node
203
+ end
204
+
205
+ def is_feature? node
206
+ is_a_func_call_named? 'feature', node
207
+ end
208
+
209
+ def is_a_func_call_named? name, node
210
+ (node.type == :fcall or node.type == :command or node.type == :vcall) and node.children.first.source == name
211
+ end
212
+
213
+ def get_feature node
214
+ name = node[1].jump(:ident).source
215
+ desc = node[1].jump(:tstring_content).source
216
+ methods = []
217
+ if node[1].length == 4 and node.children[1][2].jump(:ident).source == 'methods'
218
+ arr = node[1][2].jump(:array)
219
+ if arr != node[1][2]
220
+ arr.traverse do |s|
221
+ if s.type == :ident
222
+ methods << s.source
223
+ end
224
+ end
225
+ end
226
+ end
227
+ {
228
+ :name => name,
229
+ :desc => desc,
230
+ :methods => methods != [] ? methods : nil,
231
+ }
232
+ end
233
+
234
+ def get_parameter_allowed_values node
235
+ vals = []
236
+ node.traverse do |s|
237
+ if is_a_func_call_named? 'newvalues', s
238
+ list = s.jump(:list)
239
+ if list != s
240
+ vals += list.map { |item| [item.source] if YARD::Parser::Ruby::AstNode === item }
241
+ end
242
+ end
243
+ end
244
+ vals.compact
245
+ end
246
+
247
+ # Calls to newvalue only apply to properties, according to Dan & Nan's
248
+ # "Puppet Types and Providers", page 30.
249
+ def get_property_allowed_values node
250
+ vals = get_parameter_allowed_values node
251
+ node.traverse do |s|
252
+ if is_a_func_call_named? 'newvalue', s
253
+ required_features = nil
254
+ s.traverse do |ss|
255
+ if ss.type == :assoc and ss[0].source == ':required_features'
256
+ required_features = ss[1].source
257
+ end
258
+ end
259
+ list = s.jump(:list)
260
+ if list != s
261
+ vals << [list[0].source, required_features].compact
262
+ end
263
+ end
264
+ end
265
+ vals
266
+ end
267
+
268
+ def fetch_default node
269
+ do_block = node.jump(:do_block)
270
+ do_block.traverse do |s|
271
+ if is_a_func_call_named? 'defaultto', s
272
+ return s[-1].source
273
+ end
274
+ end
275
+ nil
276
+ end
277
+
278
+ def fetch_description(fcall)
279
+ fcall.traverse do |node|
280
+ if is_a_func_call_named? 'desc', node
281
+ content = node.jump(:string_content)
282
+ if content != node
283
+ @heredoc_helper = HereDocHelper.new
284
+ if @heredoc_helper.is_heredoc? content.source
285
+ docstring = @heredoc_helper.process_heredoc content.source
286
+ else
287
+ docstring = content.source
288
+ end
289
+ return docstring
290
+ end
291
+ end
292
+ end
293
+ return nil
294
+ end
295
+ end