diff --git a/JSON.md b/JSON.md index 66e35ba39..0f446c8e9 100644 --- a/JSON.md +++ b/JSON.md @@ -9,17 +9,19 @@ puppet strings generate --format json Document Schema =============== -At the top level, there are seven arrays in the JSON document: - -| Document Key | Description | -| ---------------- | ----------------------------------------------------------------------------- | -| puppet_classes | The list of Puppet classes that were parsed. | -| defined_types | The list of defined types that were parsed. | -| resource_types | The list of resource types that were parsed. | -| providers | The list of resource providers that were parsed. | -| puppet_functions | The list of Puppet functions (4.x, 4.x and Puppet language) that were parsed. | -| puppet_tasks | The list of Puppet tasks that were parsed. | -| puppet_plans | The list of Puppet plans that were parsed. | +At the top level, there are nine arrays in the JSON document: + +| Document Key | Description | +| ----------------- | ----------------------------------------------------------------------------- | +| puppet_classes | The list of Puppet classes that were parsed. | +| data_types | The list of data types that were parsed. | +| data_type_aliases | | The list of data types that were parsed. | +| defined_types | The list of defined types that were parsed. | +| resource_types | The list of resource types that were parsed. | +| providers | The list of resource providers that were parsed. | +| puppet_functions | The list of Puppet functions (4.x, 4.x and Puppet language) that were parsed. | +| puppet_tasks | The list of Puppet tasks that were parsed. | +| puppet_plans | The list of Puppet plans that were parsed. | Puppet Classes -------------- @@ -36,6 +38,34 @@ Each entry in the `puppet_classes` list is an object with the following attribut | defaults | The map of parameter names to default values. | | source | The Puppet source code for the class. | +Data Types +---------- + +Each entry in the `data_types` list is an object with the following attributes: + +| Attribute Key | Description | +| ------------- | ----------------------------------------------------------- | +| name | The name of the data type. | +| file | The file defining the data type. | +| line | The line where the data type is data. | +| docstring | The *DocString* object for the data type (see below). | +| defaults | The map of parameter names to default values. | +| source | The ruby source code for the data type. (Not Implemented) | + +Data Type Aliases +----------------- + +Each entry in the `data_type_aliases` list is an object with the following attributes: + +| Attribute Key | Description | +| ------------- | ----------------------------------------------------------------- | +| name | The name of the data type. | +| file | The file defining the data type. | +| line | The line where the data type is defined. | +| docstring | The *DocString* object for the data type (see below). | +| alias_of | The actual type this is an alias of. | +| source | The Puppet source code for the data type alias. (Not Implemented) | + Defined Types ------------- diff --git a/lib/puppet-strings/json.rb b/lib/puppet-strings/json.rb index be0266ade..ec8cfd022 100644 --- a/lib/puppet-strings/json.rb +++ b/lib/puppet-strings/json.rb @@ -8,6 +8,8 @@ module PuppetStrings::Json def self.render(file = nil) document = { puppet_classes: YARD::Registry.all(:puppet_class).sort_by!(&:name).map!(&:to_hash), + data_types: YARD::Registry.all(:puppet_data_type).sort_by!(&:name).map!(&:to_hash), + data_type_aliases: YARD::Registry.all(:puppet_data_type_alias).sort_by!(&:name).map!(&:to_hash), defined_types: YARD::Registry.all(:puppet_defined_type).sort_by!(&:name).map!(&:to_hash), resource_types: YARD::Registry.all(:puppet_type).sort_by!(&:name).map!(&:to_hash), providers: YARD::Registry.all(:puppet_provider).sort_by!(&:name).map!(&:to_hash), diff --git a/lib/puppet-strings/markdown.rb b/lib/puppet-strings/markdown.rb index 6a9f33ccd..5a2bca9da 100644 --- a/lib/puppet-strings/markdown.rb +++ b/lib/puppet-strings/markdown.rb @@ -5,6 +5,7 @@ module PuppetStrings::Markdown require_relative 'markdown/puppet_classes' require_relative 'markdown/functions' require_relative 'markdown/defined_types' + require_relative 'markdown/data_types' require_relative 'markdown/resource_types' require_relative 'markdown/puppet_tasks' require_relative 'markdown/puppet_plans' @@ -20,6 +21,7 @@ def self.generate final << PuppetStrings::Markdown::DefinedTypes.render final << PuppetStrings::Markdown::ResourceTypes.render final << PuppetStrings::Markdown::Functions.render + final << PuppetStrings::Markdown::DataTypes.render final << PuppetStrings::Markdown::PuppetTasks.render final << PuppetStrings::Markdown::PuppetPlans.render diff --git a/lib/puppet-strings/markdown/data_type.rb b/lib/puppet-strings/markdown/data_type.rb new file mode 100644 index 000000000..903559e3a --- /dev/null +++ b/lib/puppet-strings/markdown/data_type.rb @@ -0,0 +1,18 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + # This class encapsualtes ruby data types and puppet type aliases + class DataType < Base + attr_reader :alias_of + + def initialize(registry) + @template = 'data_type.erb' + super(registry, 'data type') + @alias_of = registry[:alias_of] unless registry[:alias_of].nil? + end + + def render + super(@template) + end + end +end diff --git a/lib/puppet-strings/markdown/data_types.rb b/lib/puppet-strings/markdown/data_types.rb new file mode 100644 index 000000000..7a0fe317e --- /dev/null +++ b/lib/puppet-strings/markdown/data_types.rb @@ -0,0 +1,41 @@ +require_relative 'data_type' + +module PuppetStrings::Markdown + module DataTypes + + # @return [Array] list of data types + def self.in_dtypes + arr = YARD::Registry.all(:puppet_data_type).map!(&:to_hash) + arr.concat(YARD::Registry.all(:puppet_data_type_alias).map!(&:to_hash)) + + arr.sort! { |a,b| a[:name] <=> b[:name] } + arr.map! { |a| PuppetStrings::Markdown::DataType.new(a) } + end + + def self.contains_private? + result = false + unless in_dtypes.nil? + in_dtypes.find { |type| type.private? }.nil? ? false : true + end + end + + def self.render + final = in_dtypes.length > 0 ? "## Data types\n\n" : "" + in_dtypes.each do |type| + final << type.render unless type.private? + end + final + end + + + def self.toc_info + final = ["Data types"] + + in_dtypes.each do |type| + final.push(type.toc_info) + end + + final + end + end +end diff --git a/lib/puppet-strings/markdown/table_of_contents.rb b/lib/puppet-strings/markdown/table_of_contents.rb index 6db5facbd..54aaa5bca 100644 --- a/lib/puppet-strings/markdown/table_of_contents.rb +++ b/lib/puppet-strings/markdown/table_of_contents.rb @@ -7,6 +7,7 @@ def self.render PuppetStrings::Markdown::DefinedTypes, PuppetStrings::Markdown::ResourceTypes, PuppetStrings::Markdown::Functions, + PuppetStrings::Markdown::DataTypes, PuppetStrings::Markdown::PuppetTasks, PuppetStrings::Markdown::PuppetPlans].each do |r| toc = r.toc_info diff --git a/lib/puppet-strings/markdown/templates/data_type.erb b/lib/puppet-strings/markdown/templates/data_type.erb new file mode 100644 index 000000000..6306b69d2 --- /dev/null +++ b/lib/puppet-strings/markdown/templates/data_type.erb @@ -0,0 +1,78 @@ +### <%= name %> + +<% if text -%> +<%= text %> +<% elsif summary -%> +<%= summary %> +<% else -%> +<%= "The #{name} data type." %> +<% end -%> + +<% if todo -%> +* **TODO** <%= todo %> + +<% end -%> +<% if note -%> +* **Note** <%= note %> + +<% end -%> +<% if since -%> +* **Since** <%= since %> + +<% end -%> +<% if see -%> +* **See also** +<% see.each do |sa| -%> +<% if sa[:name] -%> +<%= sa[:name] %> +<% end -%> +<% if sa[:text] -%> +<%= sa[:text] %> +<% end -%> +<% end -%> + +<% end -%> +<% if examples -%> +#### Examples + +<% examples.each do |eg| -%> +##### <%= eg[:name] %> + +```puppet +<%= eg[:text] %> +``` + +<% end -%> +<% end -%> +<% if alias_of -%> +Alias of `<%= alias_of %>` + +<% end -%> +<% if params -%> +#### Parameters + +The following parameters are available in the `<%= name %>` <%= @type %>. + +<% params.each do |param| -%> +##### `<%= param[:name] %>` + +<% if param[:types] -%> +Data type: `<%= param[:types].join(', ') -%>` + +<% end -%> +<%= param[:text] %> + +<% if options_for_param(param[:name]) -%> +Options: + +<% options_for_param(param[:name]).each do |o| -%> +* **<%= o[:opt_name] %>** `<%= o[:opt_types][0] %>`: <%= o[:opt_text] %> +<% end -%> + +<% end -%> +<% if defaults && defaults[param[:name]] -%> +Default value: <%= value_string(defaults[param[:name]]) %> + +<% end -%> +<% end -%> +<% end -%> diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 155a64aca..56ea11145 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -46,6 +46,8 @@ def all_objects :module, :class, :puppet_class, + :puppet_data_type, + :puppet_data_type_alias, :puppet_defined_type, :puppet_type, :puppet_provider, @@ -64,6 +66,14 @@ def stats_for_puppet_classes output 'Puppet Classes', *type_statistics_all(:puppet_class) end + def stats_for_puppet_data_types + output 'Puppet Data Types', *type_statistics_all(:puppet_data_type) + end + + def stats_for_puppet_data_type_aliases + output 'Puppet Data Type Aliases', *type_statistics_all(:puppet_data_type_alias) + end + def stats_for_puppet_defined_types output 'Puppet Defined Types', *type_statistics_all(:puppet_defined_type) end diff --git a/lib/puppet-strings/yard/code_objects.rb b/lib/puppet-strings/yard/code_objects.rb index 0d3ecd44c..f4f6f92b9 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -1,6 +1,8 @@ # The module for custom YARD code objects. module PuppetStrings::Yard::CodeObjects require 'puppet-strings/yard/code_objects/class' + require 'puppet-strings/yard/code_objects/data_type' + require 'puppet-strings/yard/code_objects/data_type_alias' require 'puppet-strings/yard/code_objects/defined_type' require 'puppet-strings/yard/code_objects/type' require 'puppet-strings/yard/code_objects/provider' diff --git a/lib/puppet-strings/yard/code_objects/data_type.rb b/lib/puppet-strings/yard/code_objects/data_type.rb new file mode 100644 index 000000000..ee6d0be79 --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/data_type.rb @@ -0,0 +1,80 @@ +require 'puppet-strings/yard/code_objects/group' +require 'puppet-strings/yard/util' + +# Implements the group for Puppet DataTypes. +class PuppetStrings::Yard::CodeObjects::DataTypes < PuppetStrings::Yard::CodeObjects::Group + # Gets the singleton instance of the group. + # @return Returns the singleton instance of the group. + def self.instance + super(:puppet_data_types) + end + + # Gets the display name of the group. + # @param [Boolean] prefix whether to show a prefix. Ignored for Puppet group namespaces. + # @return [String] Returns the display name of the group. + def name(prefix = false) + 'Puppet Data Types' + end +end + +# Implements the Puppet DataType code object. +class PuppetStrings::Yard::CodeObjects::DataType < PuppetStrings::Yard::CodeObjects::Base + # Initializes a Puppet class code object. + # @param [String] The name of the Data Type + # @return [void] + def initialize(name) + super(PuppetStrings::Yard::CodeObjects::DataTypes.instance, name) + @parameters = [] + @defaults = {} + end + + # Gets the type of the code object. + # @return Returns the type of the code object. + def type + :puppet_data_type + end + + # Gets the source of the code object. + # @return Returns the source of the code object. + def source + # Not implemented, but would be nice! + nil + end + + def parameter_exist?(name) + !docstring.tags(:param).find { |item| item.name == name }.nil? + end + + def add_parameter(name, type, default) + tag = docstring.tags(:param).find { |item| item.name == name } + if tag.nil? + tag = YARD::Tags::Tag.new(:param, '', nil, name) + docstring.add_tag(tag) + end + type = [type] unless type.is_a?(Array) + tag.types = type if tag.types.nil? + set_parameter_default(name, default) + end + + def set_parameter_default(param_name, default) + defaults.delete(param_name) + defaults[param_name] = default unless default.nil? + end + + def parameters + docstring.tags(:param).map { |tag| [tag.name, defaults[tag.name]] } + end + + # Converts the code object to a hash representation. + # @return [Hash] Returns a hash representation of the code object. + def to_hash + hash = {} + hash[:name] = name + hash[:file] = file + hash[:line] = line + hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring) + hash[:defaults] = defaults unless defaults.empty? + hash[:source] = source unless source && source.empty? + hash + end +end diff --git a/lib/puppet-strings/yard/code_objects/data_type_alias.rb b/lib/puppet-strings/yard/code_objects/data_type_alias.rb new file mode 100644 index 000000000..324145628 --- /dev/null +++ b/lib/puppet-strings/yard/code_objects/data_type_alias.rb @@ -0,0 +1,58 @@ +require 'puppet-strings/yard/code_objects/group' +require 'puppet-strings/yard/util' + +# Implements the group for Puppet DataTypeAliases. +class PuppetStrings::Yard::CodeObjects::DataTypeAliases < PuppetStrings::Yard::CodeObjects::Group + # Gets the singleton instance of the group. + # @return Returns the singleton instance of the group. + def self.instance + super(:puppet_data_type_aliases) + end + + # Gets the display name of the group. + # @param [Boolean] prefix whether to show a prefix. Ignored for Puppet group namespaces. + # @return [String] Returns the display name of the group. + def name(prefix = false) + 'Puppet Data Type Aliases' + end +end + +# Implements the Puppet DataTypeAlias code object. +class PuppetStrings::Yard::CodeObjects::DataTypeAlias < PuppetStrings::Yard::CodeObjects::Base + attr_reader :statement + attr_accessor :alias_of + + # Initializes a Puppet data type alias code object. + # @param [PuppetStrings::Parsers::DataTypeAliasStatement] statement The data type alias statement that was parsed. + # @return [void] + def initialize(statement) + @statement = statement + @alias_of = statement.alias_of + super(PuppetStrings::Yard::CodeObjects::DataTypeAliases.instance, statement.name) + end + + # Gets the type of the code object. + # @return Returns the type of the code object. + def type + :puppet_data_type_alias + end + + # Gets the source of the code object. + # @return Returns the source of the code object. + def source + # Not implemented, but would be nice! + nil + end + + # Converts the code object to a hash representation. + # @return [Hash] Returns a hash representation of the code object. + def to_hash + hash = {} + hash[:name] = name + hash[:file] = file + hash[:line] = line + hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring) + hash[:alias_of] = alias_of + hash + end +end diff --git a/lib/puppet-strings/yard/handlers.rb b/lib/puppet-strings/yard/handlers.rb index 7229523b6..705dcd48e 100644 --- a/lib/puppet-strings/yard/handlers.rb +++ b/lib/puppet-strings/yard/handlers.rb @@ -2,6 +2,7 @@ module PuppetStrings::Yard::Handlers # The module for custom Ruby YARD handlers. module Ruby + require 'puppet-strings/yard/handlers/ruby/data_type_handler' require 'puppet-strings/yard/handlers/ruby/type_handler' require 'puppet-strings/yard/handlers/ruby/type_extras_handler' require 'puppet-strings/yard/handlers/ruby/rsapi_handler' @@ -17,6 +18,7 @@ module JSON # The module for custom Puppet YARD handlers. module Puppet require 'puppet-strings/yard/handlers/puppet/class_handler' + require 'puppet-strings/yard/handlers/puppet/data_type_alias_handler' require 'puppet-strings/yard/handlers/puppet/defined_type_handler' require 'puppet-strings/yard/handlers/puppet/function_handler' require 'puppet-strings/yard/handlers/puppet/plan_handler' diff --git a/lib/puppet-strings/yard/handlers/puppet/data_type_alias_handler.rb b/lib/puppet-strings/yard/handlers/puppet/data_type_alias_handler.rb new file mode 100644 index 000000000..39eb9a6ad --- /dev/null +++ b/lib/puppet-strings/yard/handlers/puppet/data_type_alias_handler.rb @@ -0,0 +1,24 @@ +require 'puppet-strings/yard/handlers/helpers' +require 'puppet-strings/yard/handlers/puppet/base' +require 'puppet-strings/yard/parsers' +require 'puppet-strings/yard/code_objects' + +# Implements the handler for Puppet Data Type Alias. +class PuppetStrings::Yard::Handlers::Puppet::DataTypeAliasHandler < PuppetStrings::Yard::Handlers::Puppet::Base + handles PuppetStrings::Yard::Parsers::Puppet::DataTypeAliasStatement + + process do + # Register the object + object = PuppetStrings::Yard::CodeObjects::DataTypeAlias.new(statement) + register object + + # Log a warning if missing documentation + log.warn "Missing documentation for Puppet type alias '#{object.name}' at #{statement.file}:#{statement.line}." if object.docstring.empty? && object.tags.empty? + + # Mark the class as public if it doesn't already have an api tag + object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api + + # Warn if a summary longer than 140 characters was provided + PuppetStrings::Yard::Handlers::Helpers.validate_summary_tag(object) if object.has_tag? :summary + end +end diff --git a/lib/puppet-strings/yard/handlers/ruby/data_type_handler.rb b/lib/puppet-strings/yard/handlers/ruby/data_type_handler.rb new file mode 100644 index 000000000..a7750a0f0 --- /dev/null +++ b/lib/puppet-strings/yard/handlers/ruby/data_type_handler.rb @@ -0,0 +1,236 @@ +require 'puppet-strings/yard/handlers/helpers' +require 'puppet-strings/yard/handlers/ruby/base' +require 'puppet-strings/yard/code_objects' +require 'puppet-strings/yard/util' + +# Implements the handler for Puppet Data Types written in Ruby. +class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard::Handlers::Ruby::Base + namespace_only + handles method_call(:create_type) + + process do + return unless statement.count > 1 + ruby_module_name = statement[0].source + return unless ruby_module_name == 'Puppet::DataTypes' || ruby_module_name == 'DataTypes' # rubocop:disable Style/MultipleComparison This reads better + object = get_datatype_yard_object(get_name(statement, 'Puppet::DataTypes.create_type')) + + actual_params = extract_params_for_data_type # populate_data_type_data(object) + + # Mark the data type as public if it doesn't already have an api tag + object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api + + validate_tags!(object, actual_params) + + # Set the default values for all parameters + actual_params.each { |name, data| object.set_parameter_default(name, data[:default]) } + + # Default any typeless param tag to 'Any' + object.tags(:param).each do |tag| + tag.types = ['Any'] unless tag.types && !tag.types.empty? + end + + # Warn if a summary longer than 140 characters was provided + PuppetStrings::Yard::Handlers::Helpers.validate_summary_tag(object) if object.has_tag? :summary + end + + private + + def get_datatype_yard_object(name) + # Have to guess the path - if we create the object to get the true path from the code, + # it also shows up in the .at call - self registering? + guess_path = "puppet_data_types::#{name}" + object = YARD::Registry.at(guess_path) + + return object unless object.nil? + + # Didn't find, create instead + object = PuppetStrings::Yard::CodeObjects::DataType.new(name) + register object + object + end + + def extract_params_for_data_type + params = {} + # Traverse the block looking for interface + block = statement.block + return unless block && block.count >= 2 + block[1].children.each do |node| + next unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) && + node.method_name + + method_name = node.method_name.source + parameters = node.parameters(false) + if method_name == 'interface' + next unless parameters.count >= 1 + interface_string = node_as_string(parameters[0]) + next unless interface_string + # Ref - https://github.com/puppetlabs/puppet/blob/ba4d1a1aba0095d3c70b98fea5c67434a4876a61/lib/puppet/datatypes.rb#L159 + parsed_interface = Puppet::Pops::Parser::EvaluatingParser.new.parse_string("{ #{interface_string} }").body + next unless parsed_interface + + # Now that we parsed the Puppet code (as a string) into a LiteralHash PCore type (Puppet AST), + # + # We need to convert the LiteralHash into a conventional ruby hash of strings. The + # LazyLiteralEvaluator does this by traversing the AST tree can converting objects to strings + # where possible and ignoring object types which cannot (thus the 'Lazy' name) + # + # Once we have it as a standard ruby hash we can then look at the keys and populate the YARD + # Code object with the correct attributes etc. + literal_eval = LazyLiteralEvaluator.new + populate_data_type_params_from_literal_hash!(literal_eval.literal(parsed_interface), params) + end + end + params + end + + # Lazily evaluates a Pops object, ignoring any objects that cannot + # be converted to a literal value. Based on the Puppet Literal Evaluator + # Ref - https://github.com/puppetlabs/puppet/blob/ba4d1a1aba0095d3c70b98fea5c67434a4876a61/lib/puppet/pops/evaluator/literal_evaluator.rb + # + # Literal values for: + # String (not containing interpolation) + # Numbers + # Booleans + # Undef (produces nil) + # Array + # Hash + # QualifiedName + # Default (produced :default) + # Regular Expression (produces ruby regular expression) + # QualifiedReference e.g. File, FooBar + # AccessExpression + # + # Anything else is ignored + class LazyLiteralEvaluator + def initialize + @literal_visitor ||= ::Puppet::Pops::Visitor.new(self, "literal", 0, 0) + end + + def literal(ast) + @literal_visitor.visit_this_0(self, ast) + end + + # ----- The following methods are different/additions from the original Literal_evaluator + def literal_Object(o) # rubocop:disable Naming/UncommunicativeMethodParamName + # Ignore any other object types + end + + def literal_AccessExpression(o) # rubocop:disable Naming/UncommunicativeMethodParamName + # Extract the raw text of the Access Expression + ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(o).extract_text + end + + def literal_QualifiedReference(o) # rubocop:disable Naming/UncommunicativeMethodParamName + # Extract the raw text of the Qualified Reference + ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(o).extract_text + end + + # ----- The following methods are the same as the original Literal_evaluator + def literal_Factory(o) # rubocop:disable Naming/UncommunicativeMethodParamName + literal(o.model) + end + + def literal_Program(o) # rubocop:disable Naming/UncommunicativeMethodParamName + literal(o.body) + end + + def literal_LiteralString(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.value + end + + def literal_QualifiedName(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.value + end + + def literal_LiteralNumber(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.value + end + + def literal_LiteralBoolean(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.value + end + + def literal_LiteralUndef(o) # rubocop:disable Naming/UncommunicativeMethodParamName + nil + end + + def literal_LiteralDefault(o) # rubocop:disable Naming/UncommunicativeMethodParamName + :default + end + + def literal_LiteralRegularExpression(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.value + end + + def literal_ConcatenatedString(o) # rubocop:disable Naming/UncommunicativeMethodParamName + # use double quoted string value if there is no interpolation + throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Model::LiteralString) + o.segments[0].value + end + + def literal_LiteralList(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.values.map {|v| literal(v) } + end + + def literal_LiteralHash(o) # rubocop:disable Naming/UncommunicativeMethodParamName + o.entries.reduce({}) do |result, entry| + result[literal(entry.key)] = literal(entry.value) + result + end + end + end + + def populate_data_type_params_from_literal_hash!(hash, params_hash) + return if hash.nil? + # Exit early if there are no entries in the hash + return if hash['attributes'].nil? || hash['attributes'].count.zero? + + hash['attributes'].each do |key, value| + data_type = nil + default = nil + case value + when String + data_type = value + when Hash + data_type = value['type'] unless value['type'].nil? + default = value['value'] unless value['value'].nil? + end + data_type = [data_type] unless data_type.nil? || data_type.is_a?(Array) + params_hash[key] = { :types => data_type, :default => default } + end + end + + def validate_tags!(object, actual_params_hash) + actual_param_names = actual_params_hash.keys + tagged_param_names = object.tags(:param).map(&:name) + # Log any errors + # Find attributes which are not documented + (actual_param_names - tagged_param_names).each do |item| + log.warn "Missing @param tag for attribute '#{item}' near #{object.file}:#{object.line}." + end + # Find param tags with no matching attribute + (tagged_param_names - actual_param_names).each do |item| + log.warn "The @param tag for '#{item}' has no matching attribute near #{object.file}:#{object.line}." + end + # Find param tags with a type that is different from the actual definition + object.tags(:param).reject { |tag| tag.types.nil? }.each do |tag| + next if actual_params_hash[tag.name].nil? + actual_data_type = actual_params_hash[tag.name][:types] + next if actual_data_type.nil? + log.warn "The @param tag for '#{tag.name}' has a different type definition than the actual attribute near #{object.file}:#{object.line}." if tag.types != actual_data_type + end + + # Automatically fix missing @param tags + (actual_param_names - tagged_param_names).each do |name| + object.add_parameter(name, actual_params_hash[name][:types], actual_params_hash[name][:default]) + end + # Remove extra param tags + object.docstring.delete_tag_if { |item| item.tag_name == 'param' && !actual_param_names.include?(item.name) } + + # Set the type in the param tag + object.tags(:param).each do |tag| + next if actual_params_hash[tag.name].nil? + tag.types = actual_params_hash[tag.name][:types] + end + end +end diff --git a/lib/puppet-strings/yard/parsers/puppet/parser.rb b/lib/puppet-strings/yard/parsers/puppet/parser.rb index 819057d2f..13a20cf16 100644 --- a/lib/puppet-strings/yard/parsers/puppet/parser.rb +++ b/lib/puppet-strings/yard/parsers/puppet/parser.rb @@ -76,6 +76,12 @@ def transform_PlanDefinition(o) # rubocop:disable Naming/UncommunicativeMethodPa statement end + def transform_TypeAlias(o) # rubocop:disable Naming/UncommunicativeMethodParamName + statement = PuppetStrings::Yard::Parsers::Puppet::DataTypeAliasStatement.new(o, @file) + statement.extract_docstring(@lines) + statement + end + def transform_Object(o) # rubocop:disable Naming/UncommunicativeMethodParamName # Ignore anything else (will be compacted out of the resulting array) end diff --git a/lib/puppet-strings/yard/parsers/puppet/statement.rb b/lib/puppet-strings/yard/parsers/puppet/statement.rb index eea04a744..e5233229c 100644 --- a/lib/puppet-strings/yard/parsers/puppet/statement.rb +++ b/lib/puppet-strings/yard/parsers/puppet/statement.rb @@ -165,4 +165,29 @@ def initialize(object, file) end end + # Implements the Puppet data type alias statement. + class DataTypeAliasStatement < Statement + attr_reader :name + attr_reader :alias_of + + # Initializes the Puppet data type alias statement. + # @param [Puppet::Pops::Model::TypeAlias] object The model object for the type statement. + # @param [String] file The file containing the statement. + def initialize(object, file) + super(object, file) + + type_expr = object.type_expr + case type_expr + when Puppet::Pops::Model::AccessExpression + # TODO: I don't like rebuilding the source from the AST, but AccessExpressions don't expose the original source + @alias_of = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(type_expr.left_expr).extract_text + '[' + @alias_of << type_expr.keys.map { |key| ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(key).extract_text }.join(', ') + @alias_of << ']' + else + adapter = ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(type_expr) + @alias_of = adapter.extract_text + end + @name = object.name + end + end end diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_data_type.erb b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_data_type.erb new file mode 100644 index 000000000..3e253da81 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_data_type.erb @@ -0,0 +1,10 @@ +<% even = false %> +<% @items.each do |item| %> +
  • +
    + <%= linkify item, h(item.name(false)) %> + <% if item.type == :puppet_data_type_alias %>Alias<% end %> +
    +
  • + <% even = !even %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb index 5a3425f6f..4087e9d9c 100644 --- a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb @@ -7,6 +7,15 @@ def generate_puppet_class_list generate_list_contents end +# Generates the searchable Puppet data type list. +# @return [void] +def generate_puppet_data_type_list + @items = Registry.all(:puppet_data_type, :puppet_data_type_alias).sort_by {|dt| dt.name.to_s } + @list_title = 'Data Type List' + @list_type = 'puppet_data_type' + generate_list_contents +end + # Generates the searchable Puppet defined type list. # @return [void] def generate_puppet_defined_type_list diff --git a/lib/puppet-strings/yard/templates/default/layout/html/objects.erb b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb index afc6356d7..d9d90732e 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/objects.erb +++ b/lib/puppet-strings/yard/templates/default/layout/html/objects.erb @@ -23,6 +23,8 @@ (Resource type: <%= obj.type_name %>) <% elsif obj.type == :puppet_function %> (<%= obj.function_type %>) + <% elsif obj.type == :puppet_data_type_alias %> + (Alias) <% end %> <% end %> diff --git a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb index 8a5aaa1a6..0750fce36 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb @@ -4,7 +4,7 @@ def init case object when '_index.html' @page_title = options.title - sections :layout, [:index, [:listing, [:classes, :defined_types, :types, :providers, :functions, :tasks, :plans, :files, :objects]]] + sections :layout, [:index, [:listing, [:classes, :data_types, :defined_types, :types, :providers, :functions, :tasks, :plans, :files, :objects]]] else super end @@ -30,6 +30,10 @@ def layout @nav_url = url_for_list('puppet_class') @page_title = "Puppet Class: #{object.name}" @path = object.path + when PuppetStrings::Yard::CodeObjects::DataType, PuppetStrings::Yard::CodeObjects::DataTypeAlias + @nav_url = url_for_list('puppet_data_type') + @page_title = "Data Type: #{object.name}" + @path = object.path when PuppetStrings::Yard::CodeObjects::DefinedType @nav_url = url_for_list('puppet_defined_type') @page_title = "Defined Type: #{object.name}" @@ -76,6 +80,11 @@ def create_menu_lists title: 'Puppet Classes', search_title: 'Puppet Classes' }, + { + type: 'puppet_data_type', + title: 'Data Types', + search_title: 'Data Types', + }, { type: 'puppet_defined_type', title: 'Defined Types', @@ -155,6 +164,14 @@ def classes erb(:objects) end +# Renders the data types section. +# @return [String] Returns the rendered section. +def data_types + @title = 'Data Type Listing A-Z' + @objects_by_letter = objects_by_letter(:puppet_data_type, :puppet_data_type_alias) + erb(:objects) +end + # Renders the defined types section. # @return [String] Returns the rendered section. def defined_types diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/box_info.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/box_info.erb new file mode 100644 index 000000000..49a646067 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/box_info.erb @@ -0,0 +1,10 @@ +
    +
    +
    Defined in:
    +
    + <%= object.file %><% if object.files.size > 1 %>,
    + <%= object.files[1..-1].map {|f| f.first }.join(",
    ") %>
    + <% end %> + + + diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/header.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/header.erb new file mode 100644 index 000000000..7850ace8e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/header.erb @@ -0,0 +1 @@ +

    Puppet Data Type: <%= object.name %>

    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/note.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/note.erb new file mode 100644 index 000000000..88c9ffb1e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/note.erb @@ -0,0 +1,6 @@ +<% object.tags(:note).each do |tag| %> +
    + Note: + <%= htmlify_line tag.text %> +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/overview.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/overview.erb new file mode 100644 index 000000000..a5b527a30 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/overview.erb @@ -0,0 +1,6 @@ +

    Overview

    +
    +
    + <%= htmlify(object.docstring) %> +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/setup.rb new file mode 100644 index 000000000..d3dbefbe8 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/setup.rb @@ -0,0 +1,5 @@ +# Initializes the template. +# @return [void] +def init + sections :header, :box_info, :summary, :overview, :note, :todo, T('tags'), :source +end diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/source.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/source.erb new file mode 100644 index 000000000..0fd3c5e35 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/source.erb @@ -0,0 +1,12 @@ +
    + + + + + +
    +
    <%= "\n\n\n" %><%= h format_lines(object) %>
    +
    +
    # File '<%= h object.file %>'<% if object.line %>, line <%= object.line %><% end %><%= "\n\n" %><%= html_syntax_highlight object.source %>
    +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/summary.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/summary.erb new file mode 100644 index 000000000..75e98677a --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/summary.erb @@ -0,0 +1,4 @@ +<% if object.docstring.has_tag?(:summary) %> +

    Summary

    + <%= object.docstring.tag(:summary).text %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type/html/todo.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/todo.erb new file mode 100644 index 000000000..8f91636fb --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type/html/todo.erb @@ -0,0 +1,6 @@ +<% object.tags(:todo).each do |tag| %> +
    + TODO: + <%= htmlify_line tag.text %> +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/alias_of.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/alias_of.erb new file mode 100644 index 000000000..956f1ab01 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/alias_of.erb @@ -0,0 +1,10 @@ +<% if @alias_of && !@alias_of.empty? %> +
    +

    <%= @tag_title %>

    +
    +
    +
    <%= @alias_of %>
    +
    +
    +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/box_info.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/box_info.erb new file mode 100644 index 000000000..49a646067 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/box_info.erb @@ -0,0 +1,10 @@ +
    +
    +
    Defined in:
    +
    + <%= object.file %><% if object.files.size > 1 %>,
    + <%= object.files[1..-1].map {|f| f.first }.join(",
    ") %>
    + <% end %> + + + diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/header.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/header.erb new file mode 100644 index 000000000..1929e8d92 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/header.erb @@ -0,0 +1 @@ +

    Puppet Data Type Alias: <%= object.name %>

    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/note.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/note.erb new file mode 100644 index 000000000..88c9ffb1e --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/note.erb @@ -0,0 +1,6 @@ +<% object.tags(:note).each do |tag| %> +
    + Note: + <%= htmlify_line tag.text %> +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/overview.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/overview.erb new file mode 100644 index 000000000..a5b527a30 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/overview.erb @@ -0,0 +1,6 @@ +

    Overview

    +
    +
    + <%= htmlify(object.docstring) %> +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/setup.rb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/setup.rb new file mode 100644 index 000000000..14d9d9002 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/setup.rb @@ -0,0 +1,17 @@ +# Initializes the template. +# @return [void] +def init + sections :header, :box_info, :summary, :overview, :alias_of, :note, :todo, T('tags'), :source +end + +# Renders the alias_of section. +# @return [String] Returns the rendered section. +def alias_of + # Properties are the same thing as parameters (from the documentation standpoint), + # so reuse the same template but with a different title and data source. + #@parameters = object.properties || [] + #@parameters.sort_by! { |p| p.name } + @tag_title = 'Alias of' + @alias_of = object.alias_of + erb(:alias_of) +end diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/source.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/source.erb new file mode 100644 index 000000000..0fd3c5e35 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/source.erb @@ -0,0 +1,12 @@ +
    + + + + + +
    +
    <%= "\n\n\n" %><%= h format_lines(object) %>
    +
    +
    # File '<%= h object.file %>'<% if object.line %>, line <%= object.line %><% end %><%= "\n\n" %><%= html_syntax_highlight object.source %>
    +
    +
    diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/summary.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/summary.erb new file mode 100644 index 000000000..75e98677a --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/summary.erb @@ -0,0 +1,4 @@ +<% if object.docstring.has_tag?(:summary) %> +

    Summary

    + <%= object.docstring.tag(:summary).text %> +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/todo.erb b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/todo.erb new file mode 100644 index 000000000..8f91636fb --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/todo.erb @@ -0,0 +1,6 @@ +<% object.tags(:todo).each do |tag| %> +
    + TODO: + <%= htmlify_line tag.text %> +
    +<% end %> diff --git a/lib/puppet-strings/yard/templates/default/tags/setup.rb b/lib/puppet-strings/yard/templates/default/tags/setup.rb index 4e3f281dc..482438c2d 100644 --- a/lib/puppet-strings/yard/templates/default/tags/setup.rb +++ b/lib/puppet-strings/yard/templates/default/tags/setup.rb @@ -4,6 +4,7 @@ def param tag(:param) if object.type == :method || object.type == :puppet_class || + object.type == :puppet_data_type || object.type == :puppet_defined_type || object.type == :puppet_function || object.type == :puppet_task || diff --git a/spec/acceptance/emit_json_options_spec.rb b/spec/acceptance/emit_json_options_spec.rb index 5e661f47a..b6da4bf8a 100644 --- a/spec/acceptance/emit_json_options_spec.rb +++ b/spec/acceptance/emit_json_options_spec.rb @@ -9,6 +9,8 @@ let(:expected) do { "puppet_classes" => [], + "data_types" => [], + "data_type_aliases" => [], "defined_types" => [], "resource_types" => [], "providers" => [], diff --git a/spec/acceptance/running_strings_generate_spec.rb b/spec/acceptance/running_strings_generate_spec.rb index 7190b1040..cd35352d6 100644 --- a/spec/acceptance/running_strings_generate_spec.rb +++ b/spec/acceptance/running_strings_generate_spec.rb @@ -59,4 +59,20 @@ def expect_file_contain(path, expected_contents) 'database — /usr/bin/database', ]) end + + it 'should generate documentation for puppet data types' do + expect_file_contain('doc/puppet_types/database.html', [ + 'Resource Type: database', + 'type/database.rb', + 'An example server resource type.', + ]) + end + + it 'should generate documentation for puppet data type aliases' do + expect_file_contain('doc/puppet_data_type_aliases/Test_3A_3AElephant.html', [ + 'Data Type: Test::Elephant', + 'types/elephant.pp', + 'A simple elephant type.', + ]) + end end diff --git a/spec/fixtures/acceptance/modules/test/types/elephant.pp b/spec/fixtures/acceptance/modules/test/types/elephant.pp new file mode 100644 index 000000000..14e465cd0 --- /dev/null +++ b/spec/fixtures/acceptance/modules/test/types/elephant.pp @@ -0,0 +1,2 @@ +# A simple elephant type. +type Test::Elephant = String[2] diff --git a/spec/fixtures/unit/markdown/output_with_data_types.md b/spec/fixtures/unit/markdown/output_with_data_types.md new file mode 100644 index 000000000..e53416f96 --- /dev/null +++ b/spec/fixtures/unit/markdown/output_with_data_types.md @@ -0,0 +1,553 @@ +# Reference + + +## Table of Contents + +**Classes** + +_Public Classes_ + +* [`klass`](#klass): A simple class. + +_Private Classes_ + +* `noparams`: Overview for class noparams + +**Defined types** + +* [`klass::dt`](#klassdt): A simple defined type. + +**Resource types** + +* [`apt_key`](#apt_key): Example resource type using the new API. +* [`database`](#database): An example database server type. + +**Functions** + +* [`func`](#func): A simple Puppet function. +* [`func3x`](#func3x): Documentation for an example 3.x function. +* [`func4x`](#func4x): An example 4.x function. +* [`func4x_1`](#func4x_1): An example 4.x function with only one signature. + +**Data types** + +* [`Amodule::ComplexAlias`](#amodulecomplexalias): Documentation for Amodule::ComplexAlias +* [`Amodule::SimpleAlias`](#amodulesimplealias): Documentation for Amodule::SimpleAlias +* [`UnitDataType`](#unitdatatype): An example Puppet Data Type in Ruby. + +**Tasks** + +* [`(stdin)`](#(stdin)): Allows you to backup your database to local file. + +## Classes + +### klass + +An overview for a simple class. + +* **TODO** Do a thing + +* **Note** some note + +* **Since** 1.0.0 + +* **See also** +www.puppet.com + +#### Examples + +##### This is an example + +```puppet +class { 'klass': + param1 => 1, + param3 => 'foo', +} +``` + +##### This is another example + +```puppet +class { 'klass': + param1 => 1, + param3 => 'foo', +} +``` + +#### Parameters + +The following parameters are available in the `klass` class. + +##### `param1` + +Data type: `Integer` + +First param. + +Default value: 1 + +##### `param2` + +Data type: `Any` + +Second param. + +Options: + +* **:opt1** `String`: something about opt1 +* **:opt2** `Hash`: a hash of stuff + +Default value: `undef` + +##### `param3` + +Data type: `String` + +Third param. + +Default value: 'hi' + +## Defined types + +### klass::dt + +An overview for a simple defined type. + +* **Since** 1.1.0 + +* **See also** +www.puppet.com + +#### Examples + +##### Here's an example of this type: + +```puppet +klass::dt { 'foo': + param1 => 33, + param4 => false, +} +``` + +#### Parameters + +The following parameters are available in the `klass::dt` defined type. + +##### `param1` + +Data type: `Integer` + +First param. + +Default value: 44 + +##### `param2` + +Data type: `Any` + +Second param. + +Options: + +* **:opt1** `String`: something about opt1 +* **:opt2** `Hash`: a hash of stuff + +##### `param3` + +Data type: `String` + +Third param. + +Default value: 'hi' + +##### `param4` + +Data type: `Boolean` + +Fourth param. + +Default value: `true` + +## Resource types + +### apt_key + +This type provides Puppet with the capabilities to manage GPG keys needed +by apt to perform package validation. Apt has it's own GPG keyring that can +be manipulated through the `apt-key` command. +**Autorequires**: +If Puppet is given the location of a key file which looks like an absolute +path this type will autorequire that file. + +#### Examples + +##### here's an example + +```puppet +apt_key { '6F6B15509CF8E59E6E469F327F438280EF8D349F': + source => 'http://apt.puppetlabs.com/pubkey.gpg' +} +``` + +#### Properties + +The following properties are available in the `apt_key` type. + +##### `ensure` + +Data type: `Enum[present, absent]` + +Whether this apt key should be present or absent on the target system. + +##### `created` + +Data type: `String` + +Date the key was created, in ISO format. + +#### Parameters + +The following parameters are available in the `apt_key` type. + +##### `id` + +namevar + +Data type: `Variant[Pattern[/A(0x)?[0-9a-fA-F]{8}Z/], Pattern[/A(0x)?[0-9a-fA-F]{16}Z/], Pattern[/A(0x)?[0-9a-fA-F]{40}Z/]]` +_*this data type contains a regex that may not be accurately reflected in generated documentation_ + +The ID of the key you want to manage. + +### database + +An example database server type. + +#### Examples + +##### here's an example + +```puppet +database { 'foo': + address => 'qux.baz.bar', +} +``` + +#### Properties + +The following properties are available in the `database` type. + +##### `ensure` + +Valid values: present, absent, up, down + +Aliases: "up"=>"present", "down"=>"absent" + +What state the database should be in. + +Default value: up + +##### `file` + +The database file to use. + +##### `log_level` + +Valid values: debug, warn, error + +The log level to use. + +Default value: warn + +#### Parameters + +The following parameters are available in the `database` type. + +##### `address` + +namevar + +The database server name. + +##### `encryption_key` + +The encryption key to use. + +Required features: encryption. + +##### `encrypt` + +Valid values: `true`, `false`, yes, no + +Whether or not to encrypt the database. + +Default value: `false` + +## Functions + +### func + +Type: Puppet Language + +A simple Puppet function. + +#### Examples + +##### Test + +```puppet +$result = func(1, 2) +``` + +#### `func(Integer $param1, Any $param2, String $param3 = hi)` + +A simple Puppet function. + +Returns: `Undef` Returns nothing. + +Raises: +* `SomeError` this is some error + +##### Examples + +###### Test + +```puppet +$result = func(1, 2) +``` + +##### `param1` + +Data type: `Integer` + +First param. + +##### `param2` + +Data type: `Any` + +Second param. + +##### `param3` + +Data type: `String` + +Third param. + +Options: + +* **:param3opt** `Array`: Something about this option + +### func3x + +Type: Ruby 3.x API + +Documentation for an example 3.x function. + +#### Examples + +##### Calling the function. + +```puppet +func3x('hi', 10) +``` + +#### `func3x(String $param1, Integer $param2)` + +Documentation for an example 3.x function. + +Returns: `Undef` + +##### Examples + +###### Calling the function. + +```puppet +func3x('hi', 10) +``` + +##### `param1` + +Data type: `String` + +The first parameter. + +##### `param2` + +Data type: `Integer` + +The second parameter. + +### func4x + +Type: Ruby 4.x API + +An example 4.x function. + +#### Examples + +##### Calling the function + +```puppet +$result = func4x(1, 'foo') +``` + +##### Calling the function with all args + +```puppet +$result = func4x(1, 'foo', ['bar']) +``` + +#### `func4x(Integer $param1, Any $param2, Optional[Array[String]] $param3)` + +An overview for the first overload. + +Returns: `Undef` Returns nothing. + +##### Examples + +###### Calling the function foo + +```puppet +$result = func4x(1, 'foooo') +``` + +##### `param1` + +Data type: `Integer` + +The first parameter. + +##### `param2` + +Data type: `Any` + +The second parameter. + +Options: + +* **:option** `String`: an option +* **:option2** `String`: another option + +##### `param3` + +Data type: `Optional[Array[String]]` + +The third parameter. + +#### `func4x(Boolean $param, Callable &$block)` + +An overview for the second overload. + +Returns: `String` Returns a string. + +##### Examples + +###### Calling the function bar + +```puppet +$result = func4x(1, 'bar', ['foo']) +``` + +##### `param` + +Data type: `Boolean` + +The first parameter. + +##### `&block` + +Data type: `Callable` + +The block parameter. + +### func4x_1 + +Type: Ruby 4.x API + +An example 4.x function with only one signature. + +#### `func4x_1(Integer $param1)` + +An example 4.x function with only one signature. + +Returns: `Undef` Returns nothing. + +##### `param1` + +Data type: `Integer` + +The first parameter. + +## Data types + +### Amodule::ComplexAlias + +Documentation for Amodule::ComplexAlias + +Alias of `Struct[{ + value_type => Optional[ValueType], + merge => Optional[MergeType] +}]` + +### Amodule::SimpleAlias + +Documentation for Amodule::SimpleAlias + +Alias of `Variant[Numeric, String[1,20]]` + +### UnitDataType + +An example Puppet Data Type in Ruby. + +#### Parameters + +The following parameters are available in the `UnitDataType` data type. + +##### `param1` + +Data type: `Variant[Numeric, String[1,2]]` + +A variant parameter. + +##### `param2` + +Data type: `Optional[String[1]]` + +Optional String parameter. + +Default value: param2 + +## Tasks + +### (stdin) + +Allows you to backup your database to local file. + +**Supports noop?** false + +#### Parameters + +##### `database` + +Data type: `Optional[String[1]]` + +Database to connect to + +##### `user` + +Data type: `Optional[String[1]]` + +The user + +##### `password` + +Data type: `Optional[String[1]]` + +The password + +##### `sql` + +Data type: `String[1]` + +Path to file you want backup to + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f62c1d33e..56a151d34 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,6 +36,9 @@ # Enable testing of Plans if Puppet version is greater than 5.0.0 TEST_PUPPET_PLANS = Puppet::Util::Package.versioncmp(Puppet.version, "5.0.0") >= 0 +# Enable testing of Data Types if Puppet version is greater than 4.1.0 +TEST_PUPPET_DATATYPES = Puppet::Util::Package.versioncmp(Puppet.version, "4.1.0") >= 0 + RSpec.configure do |config| config.mock_with :mocha diff --git a/spec/unit/puppet-strings/json_spec.rb b/spec/unit/puppet-strings/json_spec.rb index e1faf3e13..79afb2de1 100644 --- a/spec/unit/puppet-strings/json_spec.rb +++ b/spec/unit/puppet-strings/json_spec.rb @@ -43,6 +43,20 @@ class klass(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { } SOURCE + # Only include Puppet types for 5.0+ + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :ruby) if TEST_PUPPET_DATATYPES +# Basic Puppet Data Type in Ruby +# +# @param msg A message parameter +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { + msg => String[1] + } + PUPPET +end +SOURCE + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :json) { "description": "Allows you to backup your database to local file.", @@ -209,6 +223,11 @@ class klass(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { expect(json_output).to include_json(puppet_class_json) end + it 'should include data for Puppet Data Types' do + data_types_json = YARD::Registry.all(:puppet_data_type).sort_by!(&:name).map!(&:to_hash).to_json + expect(json_output).to include_json(data_types_json) + end + it 'should include data for Puppet Defined Types' do defined_types_json = YARD::Registry.all(:puppet_defined_type).sort_by!(&:name).map!(&:to_hash).to_json diff --git a/spec/unit/puppet-strings/markdown_spec.rb b/spec/unit/puppet-strings/markdown_spec.rb index 4f4599315..8510f4528 100644 --- a/spec/unit/puppet-strings/markdown_spec.rb +++ b/spec/unit/puppet-strings/markdown_spec.rb @@ -4,7 +4,7 @@ require 'tempfile' describe PuppetStrings::Markdown do - before :each do + def parse_shared_content # Populate the YARD registry with both Puppet and Ruby source YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) # An overview for a simple class. @@ -64,14 +64,6 @@ class noparams () {} String $param3 = 'hi', Boolean $param4 = true ) { -} - SOURCE - YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) if TEST_PUPPET_PLANS -# A simple plan. -# @param param1 First param. -# @param param2 Second param. -# @param param3 Third param. -plan plann(String $param1, $param2, Integer $param3 = 1) { } SOURCE @@ -275,21 +267,90 @@ class noparams () {} SOURCE end - let(:filename) do - if TEST_PUPPET_PLANS - 'output_with_plan.md' - else - 'output.md' - end + def parse_plan_content + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# A simple plan. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +plan plann(String $param1, $param2, Integer $param3 = 1) { +} + SOURCE + end + + def parse_data_type_content + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :ruby) +# An example Puppet Data Type in Ruby. +# +# @param param1 A variant parameter. +# @param param2 Optional String parameter. +Puppet::DataTypes.create_type('UnitDataType') do + interface <<-PUPPET + attributes => { + param1 => Variant[Numeric, String[1,2]], + param2 => { type => Optional[String[1]], value => "param2" } + } + PUPPET +end + SOURCE + + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# Documentation for Amodule::SimpleAlias +type Amodule::SimpleAlias = Variant[Numeric,String[1,20]] + +# Documentation for Amodule::ComplexAlias +type Amodule::ComplexAlias = Struct[{ + value_type => Optional[ValueType], + merge => Optional[MergeType] +}] + SOURCE end let(:baseline_path) { File.join(File.dirname(__FILE__), "../../fixtures/unit/markdown/#{filename}") } let(:baseline) { File.read(baseline_path) } describe 'rendering markdown to a file' do - it 'should output the expected markdown content' do - Tempfile.open('md') do |file| - PuppetStrings::Markdown.render(file.path) - expect(File.read(file.path)).to eq(baseline) + before(:each) do + parse_shared_content + end + + context 'with common Puppet and ruby content' do + let(:filename) { 'output.md' } + + it 'should output the expected markdown content' do + Tempfile.open('md') do |file| + PuppetStrings::Markdown.render(file.path) + expect(File.read(file.path)).to eq(baseline) + end + end + end + + describe 'with Puppet Plans', :if => TEST_PUPPET_PLANS do + let(:filename) { 'output_with_plan.md' } + + before(:each) do + parse_plan_content + end + + it 'should output the expected markdown content' do + Tempfile.open('md') do |file| + PuppetStrings::Markdown.render(file.path) + expect(File.read(file.path)).to eq(baseline) + end + end + end + + describe 'with Puppet Data Types', :if => TEST_PUPPET_DATATYPES do + let(:filename) { 'output_with_data_types.md' } + + before(:each) do + parse_data_type_content + end + + it 'should output the expected markdown content' do + Tempfile.open('md') do |file| + PuppetStrings::Markdown.render(file.path) + expect(File.read(file.path)).to eq(baseline) + end end end end diff --git a/spec/unit/puppet-strings/yard/handlers/puppet/data_type_alias_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/puppet/data_type_alias_handler_spec.rb new file mode 100644 index 000000000..07da863f1 --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/puppet/data_type_alias_handler_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Puppet::DataTypeAliasHandler, if: TEST_PUPPET_DATATYPES do + subject { + YARD::Parser::SourceParser.parse_string(source, :puppet) + YARD::Registry.all(:puppet_data_type_alias) + } + + describe 'parsing source without a type alias definition' do + let(:source) { 'notice hi' } + + it 'no aliases should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing source with a syntax error' do + let(:source) { 'type Testype =' } + + it 'should log an error' do + expect{ subject }.to output(/\[error\]: Failed to parse \(stdin\): Syntax error at end of (file|input)/).to_stdout_from_any_process + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing a data type alias with a missing docstring' do + let(:source) { 'type Testype = String[1]' } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: Missing documentation for Puppet type alias 'Testype' at \(stdin\):1\./).to_stdout_from_any_process + end + end + + describe 'parsing a data type alias with a summary' do + context 'when the summary has fewer than 140 characters' do + let(:source) { <<-SOURCE + # A simple foo type. + # @summary A short summary. + type Testype = String[1] + SOURCE + } + + it 'should parse the summary' do + expect{ subject }.to output('').to_stdout_from_any_process + expect(subject.size).to eq(1) + summary = subject.first.tags(:summary) + expect(summary.first.text).to eq('A short summary.') + end + end + + context 'when the summary has more than 140 characters' do + let(:source) { <<-SOURCE + # A simple foo type. + # @summary A short summary that is WAY TOO LONG. AHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH this is not what a summary is for! It should be fewer than 140 characters!! + type Testype = String[1] + SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: The length of the summary for puppet_data_type_alias 'Testype' exceeds the recommended limit of 140 characters./).to_stdout_from_any_process + end + end + end +end diff --git a/spec/unit/puppet-strings/yard/handlers/ruby/data_type_handler_spec.rb b/spec/unit/puppet-strings/yard/handlers/ruby/data_type_handler_spec.rb new file mode 100644 index 000000000..1291f531a --- /dev/null +++ b/spec/unit/puppet-strings/yard/handlers/ruby/data_type_handler_spec.rb @@ -0,0 +1,232 @@ +require 'spec_helper' +require 'puppet-strings/yard' + +describe PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler, if: TEST_PUPPET_DATATYPES do + subject { + YARD::Parser::SourceParser.parse_string(source, :ruby) + YARD::Registry.all(:puppet_data_type) + } + + before(:each) do + # Tests may suppress logging to make it easier to read results, + # so remember the logging object prior to running the test + @original_yard_logging_object = YARD::Logger.instance.io + end + + after(:each) do + # Restore the original logging IO object + YARD::Logger.instance.io = @original_yard_logging_object + end + + def suppress_yard_logging + YARD::Logger.instance.io = nil + end + + describe 'parsing source without a data type definition' do + let(:source) { 'puts "hi"' } + + it 'no data types should be in the registry' do + expect(subject.empty?).to eq(true) + end + end + + describe 'parsing an empty data type definition' do + let(:source) { <<-SOURCE +Puppet::DataTypes.create_type('RubyDataType') do +end +SOURCE + } + + it 'should register a data type object with no param tags' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::DataType) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::DataTypes.instance) + expect(object.name).to eq(:RubyDataType) + expect(object.docstring).to eq('') + expect(object.docstring.tags.size).to eq(1) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + + expect(object.parameters.size).to eq(0) + end + end + + describe 'parsing a data type definition with missing param tags' do + let(:source) { <<-SOURCE +# An example Puppet Data Type in Ruby. +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { + msg => String[1], + } + PUPPET +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: Missing @param tag for attribute 'msg' near \(stdin\):2/).to_stdout_from_any_process + end + + it 'should register a data type object with all param tags' do + suppress_yard_logging + + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::DataType) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::DataTypes.instance) + expect(object.name).to eq(:RubyDataType) + expect(object.docstring).to eq('An example Puppet Data Type in Ruby.') + expect(object.docstring.tags.size).to eq(2) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + + # Check that the param tags are created + tags = object.docstring.tags(:param) + expect(tags.size).to eq(1) + expect(tags[0].name).to eq('msg') + expect(tags[0].text).to eq('') + expect(tags[0].types).to eq(['String[1]']) + + # Check for default values for parameters + expect(object.parameters.size).to eq(1) + expect(object.parameters[0]).to eq(['msg', nil]) + end + end + + describe 'parsing a data type definition with extra param tags' do + let(:source) { <<-SOURCE +# An example Puppet Data Type in Ruby. +# @param msg A message parameter. +# @param arg1 Optional String parameter. Defaults to 'param'. +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { + msg => Numeric, + } + PUPPET +end +SOURCE + } + + it 'should output a warning' do + expect{ subject }.to output(/\[warn\]: The @param tag for 'arg1' has no matching attribute near \(stdin\):4/).to_stdout_from_any_process + end + + it 'should register a data type object with extra param tags removed' do + suppress_yard_logging + + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::DataType) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::DataTypes.instance) + expect(object.name).to eq(:RubyDataType) + expect(object.docstring).to eq('An example Puppet Data Type in Ruby.') + expect(object.docstring.tags.size).to eq(2) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + + # Check that the param tags are removed + tags = object.docstring.tags(:param) + expect(tags.size).to eq(1) + expect(tags[0].name).to eq('msg') + expect(tags[0].text).to eq('A message parameter.') + expect(tags[0].types).to eq(['Numeric']) + + # Check that only the actual attributes appear + expect(object.parameters.size).to eq(1) + expect(object.parameters[0]).to eq(['msg', nil]) + end + end + + describe 'parsing a valid data type definition' do + let(:source) { <<-SOURCE +# An example Puppet Data Type in Ruby. +# +# @param msg A message parameter5. +# @param arg1 Optional String parameter5. Defaults to 'param'. +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { + msg => Variant[Numeric, String[1,2]], + arg1 => { type => Optional[String[1]], value => "param" } + } + PUPPET +end +SOURCE + } + + it 'should register a data type object' do + expect(subject.size).to eq(1) + object = subject.first + expect(object).to be_a(PuppetStrings::Yard::CodeObjects::DataType) + expect(object.namespace).to eq(PuppetStrings::Yard::CodeObjects::DataTypes.instance) + expect(object.name).to eq(:RubyDataType) + expect(object.docstring).to eq('An example Puppet Data Type in Ruby.') + expect(object.docstring.tags.size).to eq(3) + tags = object.docstring.tags(:api) + expect(tags.size).to eq(1) + expect(tags[0].text).to eq('public') + + # Check that the param tags are removed + tags = object.docstring.tags(:param) + expect(tags.size).to eq(2) + expect(tags[0].name).to eq('msg') + expect(tags[0].text).to eq('A message parameter5.') + expect(tags[0].types).to eq(['Variant[Numeric, String[1,2]]']) + expect(tags[1].name).to eq('arg1') + expect(tags[1].text).to eq('Optional String parameter5. Defaults to \'param\'.') + expect(tags[1].types).to eq(['Optional[String[1]]']) + + # Check for default values + expect(object.parameters.size).to eq(2) + expect(object.parameters[0]).to eq(['msg', nil]) + expect(object.parameters[1]).to eq(['arg1', 'param']) + end + end + + describe 'parsing a data type with a summary' do + context 'when the summary has fewer than 140 characters' do + let(:source) { <<-SOURCE +# An example Puppet Data Type in Ruby. +# +# @summary A short summary. +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { } + PUPPET +end +SOURCE + } + + it 'should parse the summary' do + expect{ subject }.to output('').to_stdout_from_any_process + expect(subject.size).to eq(1) + summary = subject.first.tags(:summary) + expect(summary.first.text).to eq('A short summary.') + end + end + + context 'when the summary has more than 140 characters' do + let(:source) { <<-SOURCE +# An example Puppet Data Type in Ruby. +# +# @summary A short summary that is WAY TOO LONG. AHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH this is not what a summary is for! It should be fewer than 140 characters!! +Puppet::DataTypes.create_type('RubyDataType') do + interface <<-PUPPET + attributes => { } + PUPPET +end +SOURCE + } + + it 'should log a warning' do + expect{ subject }.to output(/\[warn\]: The length of the summary for puppet_data_type 'RubyDataType' exceeds the recommended limit of 140 characters./).to_stdout_from_any_process + end + end + end +end diff --git a/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb index f0ddb0152..1e83314f9 100644 --- a/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb +++ b/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb @@ -206,4 +206,46 @@ class bar { expect(statement.type).to eq("Struct\[{'a' => Integer[1, 10]}\]") end end + + describe 'parsing type alias definitions', if: TEST_PUPPET_DATATYPES do + context 'given a type alias on a single line' do + let(:source) { <<-SOURCE +# A simple foo type. +type Module::Typename = Variant[Stdlib::Windowspath, Stdlib::Unixpath] +SOURCE + } + + it 'should parse the puppet type statement' do + subject.parse + expect(subject.enumerator.size).to eq(1) + statement = subject.enumerator.first + expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::DataTypeAliasStatement) + expect(statement.docstring).to eq('A simple foo type.') + expect(statement.name).to eq('Module::Typename') + expect(statement.alias_of).to eq('Variant[Stdlib::Windowspath, Stdlib::Unixpath]') + end + end + + context 'given a type alias over multiple lines' do + let(:source) { <<-SOURCE +# A multiline foo type +# with long docs +type OptionsWithoutName = Struct[{ + value_type => Optional[ValueType], + merge => Optional[MergeType] +}] +SOURCE + } + + it 'should parse the puppet type statement' do + subject.parse + expect(subject.enumerator.size).to eq(1) + statement = subject.enumerator.first + expect(statement).to be_a(PuppetStrings::Yard::Parsers::Puppet::DataTypeAliasStatement) + expect(statement.docstring).to eq("A multiline foo type\nwith long docs") + expect(statement.name).to eq('OptionsWithoutName') + expect(statement.alias_of).to eq("Struct[{\n value_type => Optional[ValueType],\n merge => Optional[MergeType]\n}]") + end + end + end end