puppet-strings 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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