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