puppet-strings 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/puppet-strings/rake_tasks.rb +18 -0
- data/lib/puppet/application/strings.rb +4 -0
- data/lib/puppet/face/strings.rb +64 -0
- data/lib/puppet/feature/rgen.rb +3 -0
- data/lib/puppet/feature/yard.rb +3 -0
- data/lib/puppet_x/puppetlabs/strings.rb +64 -0
- data/lib/puppet_x/puppetlabs/strings/actions.rb +92 -0
- data/lib/puppet_x/puppetlabs/strings/pops/yard_statement.rb +79 -0
- data/lib/puppet_x/puppetlabs/strings/pops/yard_transformer.rb +47 -0
- data/lib/puppet_x/puppetlabs/strings/util.rb +65 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/defined_type_object.rb +33 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/host_class_object.rb +22 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/method_object.rb +62 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/provider_object.rb +24 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/puppet_namespace_object.rb +48 -0
- data/lib/puppet_x/puppetlabs/strings/yard/code_objects/type_object.rb +42 -0
- data/lib/puppet_x/puppetlabs/strings/yard/core_ext/yard.rb +40 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/base.rb +13 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/defined_type_handler.rb +31 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/heredoc_helper.rb +80 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/host_class_handler.rb +42 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/provider_handler.rb +95 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_3x_function_handler.rb +54 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/puppet_4x_function_handler.rb +234 -0
- data/lib/puppet_x/puppetlabs/strings/yard/handlers/type_handler.rb +295 -0
- data/lib/puppet_x/puppetlabs/strings/yard/json_registry_store.rb +85 -0
- data/lib/puppet_x/puppetlabs/strings/yard/monkey_patches.rb +68 -0
- data/lib/puppet_x/puppetlabs/strings/yard/parser.rb +30 -0
- data/lib/puppet_x/puppetlabs/strings/yard/tags/directives.rb +9 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/docstring.erb +34 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/header.erb +5 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/parameter_details.erb +6 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/html/setup.rb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/definedtype/setup.rb +49 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_class.erb +2 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_manifest.erb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_plugin.erb +21 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_provider.erb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/full_list_puppet_type.erb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/fulldoc/html/setup.rb +82 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/box_info.erb +22 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/setup.rb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/html/subclasses.erb +4 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/hostclass/setup.rb +21 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/html_helper.rb +139 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/layout/html/setup.rb +18 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/html/header.erb +17 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/setup.rb +21 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/method_details/text/header.erb +2 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/command_details.erb +8 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/confine_details.erb +10 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/default_details.erb +10 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/docstring.erb +34 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/feature_details.erb +10 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/header.erb +5 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/html/setup.rb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/provider/setup.rb +50 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/box_info.erb +11 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/header.erb +5 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/method_details_list.erb +53 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/method_summary.erb +20 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/html/setup.rb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/puppetnamespace/setup.rb +91 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/template_helper.rb +192 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/docstring.erb +34 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/header.erb +5 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/parameter_details.erb +12 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/provider_details.erb +10 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/html/setup.rb +1 -0
- data/lib/puppet_x/puppetlabs/strings/yard/templates/default/type/setup.rb +55 -0
- 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
|