From 132121c2d158966589d1650ed14721cac251954e Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Mon, 24 Jun 2019 09:39:31 +0800 Subject: [PATCH 1/4] (PDOC-226) Refactor Markdown testing Previously the markdown unit testing used an a simple if statement to switch between common and Puppet Plans. However with the introduction of Puppet Types we need another combination. This commit refactors the Markdown testing use an additive style to figure out test fixtures which makes adding conditional tests easier in the future. --- spec/unit/puppet-strings/markdown_spec.rb | 57 +++++++++++++++-------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/spec/unit/puppet-strings/markdown_spec.rb b/spec/unit/puppet-strings/markdown_spec.rb index 4f4599315..2b139851c 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,48 @@ 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 + 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 end From 7493d8826c08e14b984fbba0757b6f90389e2917 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Mon, 17 Jun 2019 16:41:47 +0800 Subject: [PATCH 2/4] (PDOC-226) Add Puppet Data Type documentation Previously Puppet-Strings could not document Ruby Data Types. This commit: * Updates the ruby handler to receive `Puppet::DataTypes.create_type` method invocations and document the call site. The documention is similar to that of a puppet custom type. * Adds tests for ruby parsing to ensure the Yard Code Object is populated correctly * Adds support for JSON, Markdown and HTML rendering. * Adds tests for JSON and Markdown rendering --- JSON.md | 17 +- lib/puppet-strings/json.rb | 1 + lib/puppet-strings/markdown.rb | 2 + lib/puppet-strings/markdown/data_type.rb | 14 + lib/puppet-strings/markdown/data_types.rb | 38 ++ .../markdown/table_of_contents.rb | 1 + .../markdown/templates/data_type.erb | 74 +++ lib/puppet-strings/yard.rb | 5 + lib/puppet-strings/yard/code_objects.rb | 1 + .../yard/code_objects/data_type.rb | 80 +++ lib/puppet-strings/yard/handlers.rb | 1 + .../yard/handlers/ruby/data_type_handler.rb | 236 ++++++++ .../html/full_list_puppet_data_type.erb | 9 + .../templates/default/fulldoc/html/setup.rb | 9 + .../templates/default/layout/html/setup.rb | 19 +- .../puppet_data_type/html/box_info.erb | 10 + .../default/puppet_data_type/html/header.erb | 1 + .../default/puppet_data_type/html/note.erb | 6 + .../puppet_data_type/html/overview.erb | 6 + .../default/puppet_data_type/html/setup.rb | 5 + .../default/puppet_data_type/html/source.erb | 12 + .../default/puppet_data_type/html/summary.erb | 4 + .../default/puppet_data_type/html/todo.erb | 6 + .../yard/templates/default/tags/setup.rb | 1 + .../unit/markdown/output_with_data_types.md | 536 ++++++++++++++++++ spec/spec_helper.rb | 3 + spec/unit/puppet-strings/json_spec.rb | 19 + spec/unit/puppet-strings/markdown_spec.rb | 31 + .../handlers/ruby/data_type_handler_spec.rb | 232 ++++++++ 29 files changed, 1377 insertions(+), 2 deletions(-) create mode 100644 lib/puppet-strings/markdown/data_type.rb create mode 100644 lib/puppet-strings/markdown/data_types.rb create mode 100644 lib/puppet-strings/markdown/templates/data_type.erb create mode 100644 lib/puppet-strings/yard/code_objects/data_type.rb create mode 100644 lib/puppet-strings/yard/handlers/ruby/data_type_handler.rb create mode 100644 lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_data_type.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/box_info.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/header.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/note.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/overview.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/setup.rb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/source.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/summary.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type/html/todo.erb create mode 100644 spec/fixtures/unit/markdown/output_with_data_types.md create mode 100644 spec/unit/puppet-strings/yard/handlers/ruby/data_type_handler_spec.rb diff --git a/JSON.md b/JSON.md index 66e35ba39..e2858d2be 100644 --- a/JSON.md +++ b/JSON.md @@ -9,11 +9,12 @@ puppet strings generate --format json Document Schema =============== -At the top level, there are seven arrays in the JSON document: +At the top level, there are eight 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. | | 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. | @@ -36,6 +37,20 @@ 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) | + Defined Types ------------- diff --git a/lib/puppet-strings/json.rb b/lib/puppet-strings/json.rb index be0266ade..30743f655 100644 --- a/lib/puppet-strings/json.rb +++ b/lib/puppet-strings/json.rb @@ -8,6 +8,7 @@ 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), 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..f1fd6cfca --- /dev/null +++ b/lib/puppet-strings/markdown/data_type.rb @@ -0,0 +1,14 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class DataType < Base + def initialize(registry) + @template = 'data_type.erb' + super(registry, 'data type') + 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..80558ba1f --- /dev/null +++ b/lib/puppet-strings/markdown/data_types.rb @@ -0,0 +1,38 @@ +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).sort_by!(&:name).map!(&:to_hash) + 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..212fd1aad --- /dev/null +++ b/lib/puppet-strings/markdown/templates/data_type.erb @@ -0,0 +1,74 @@ +### <%= 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 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..5a6313e6d 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -46,6 +46,7 @@ def all_objects :module, :class, :puppet_class, + :puppet_data_type, :puppet_defined_type, :puppet_type, :puppet_provider, @@ -64,6 +65,10 @@ 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_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..024b548ce 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -1,6 +1,7 @@ # 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/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/handlers.rb b/lib/puppet-strings/yard/handlers.rb index 7229523b6..eb11168b0 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' 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/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..7eeb64eb0 --- /dev/null +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/full_list_puppet_data_type.erb @@ -0,0 +1,9 @@ +<% even = false %> +<% @items.each do |item| %> +
  • +
    + <%= linkify item, h(item.name(false)) %> +
    +
  • + <% 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..9f006c6be 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).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/setup.rb b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb index 8a5aaa1a6..ebe760bdb 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 + @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) + 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/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/fixtures/unit/markdown/output_with_data_types.md b/spec/fixtures/unit/markdown/output_with_data_types.md new file mode 100644 index 000000000..511aa7b81 --- /dev/null +++ b/spec/fixtures/unit/markdown/output_with_data_types.md @@ -0,0 +1,536 @@ +# 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** + +* [`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 + +### 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 2b139851c..a676c3b5f 100644 --- a/spec/unit/puppet-strings/markdown_spec.rb +++ b/spec/unit/puppet-strings/markdown_spec.rb @@ -278,6 +278,22 @@ def parse_plan_content 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 + end let(:baseline_path) { File.join(File.dirname(__FILE__), "../../fixtures/unit/markdown/#{filename}") } let(:baseline) { File.read(baseline_path) } @@ -311,5 +327,20 @@ def parse_plan_content 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 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 From 8a802f2379b0083cf203c85318fe32c58fe4dbab Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Fri, 21 Jun 2019 13:10:12 +0800 Subject: [PATCH 3/4] (PDOC-226) Add Puppet Data Type Alias documentation Previously Puppet-Strings could not document Data Type Aliases. This commit: * Updates the puppet parser to interpret TypeAlias statements. * Adds a DataTypeAlias code object and handler to document the Type Alias statement correctly. * Adds tests for puppet parsing to ensure the Yard Code Object is populated correctly * Adds support for JSON, Markdown and HTML rendering. Note that JSON separates Data Types from Data Type Aliases whereas Markdown and HTML rendering lump them together. This is because from a human documentation point of view (i.e Mardown or HTML) they are very similar things. Much like Puppet V3 vs V4 functions. However the JSON output can be used by other systems so it is important to diffentiate them as they are different from a code inspection point of view. * Adds tests for JSON and Markdown rendering --- JSON.md | 39 +++++++---- lib/puppet-strings/json.rb | 1 + lib/puppet-strings/markdown/data_type.rb | 4 ++ lib/puppet-strings/markdown/data_types.rb | 5 +- .../markdown/templates/data_type.erb | 4 ++ lib/puppet-strings/yard.rb | 5 ++ lib/puppet-strings/yard/code_objects.rb | 1 + .../yard/code_objects/data_type_alias.rb | 58 +++++++++++++++++ lib/puppet-strings/yard/handlers.rb | 1 + .../puppet/data_type_alias_handler.rb | 24 +++++++ .../yard/parsers/puppet/parser.rb | 6 ++ .../yard/parsers/puppet/statement.rb | 25 +++++++ .../html/full_list_puppet_data_type.erb | 1 + .../templates/default/fulldoc/html/setup.rb | 2 +- .../templates/default/layout/html/objects.erb | 2 + .../templates/default/layout/html/setup.rb | 4 +- .../puppet_data_type_alias/html/alias_of.erb | 10 +++ .../puppet_data_type_alias/html/box_info.erb | 10 +++ .../puppet_data_type_alias/html/header.erb | 1 + .../puppet_data_type_alias/html/note.erb | 6 ++ .../puppet_data_type_alias/html/overview.erb | 6 ++ .../puppet_data_type_alias/html/setup.rb | 17 +++++ .../puppet_data_type_alias/html/source.erb | 12 ++++ .../puppet_data_type_alias/html/summary.erb | 4 ++ .../puppet_data_type_alias/html/todo.erb | 6 ++ .../unit/markdown/output_with_data_types.md | 17 +++++ spec/unit/puppet-strings/markdown_spec.rb | 11 ++++ .../puppet/data_type_alias_handler_spec.rb | 65 +++++++++++++++++++ .../yard/parsers/puppet/parser_spec.rb | 42 ++++++++++++ 29 files changed, 373 insertions(+), 16 deletions(-) create mode 100644 lib/puppet-strings/yard/code_objects/data_type_alias.rb create mode 100644 lib/puppet-strings/yard/handlers/puppet/data_type_alias_handler.rb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/alias_of.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/box_info.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/header.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/note.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/overview.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/setup.rb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/source.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/summary.erb create mode 100644 lib/puppet-strings/yard/templates/default/puppet_data_type_alias/html/todo.erb create mode 100644 spec/unit/puppet-strings/yard/handlers/puppet/data_type_alias_handler_spec.rb diff --git a/JSON.md b/JSON.md index e2858d2be..0f446c8e9 100644 --- a/JSON.md +++ b/JSON.md @@ -9,18 +9,19 @@ puppet strings generate --format json Document Schema =============== -At the top level, there are eight 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. | -| 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 -------------- @@ -51,6 +52,20 @@ Each entry in the `data_types` list is an object with the following attributes: | 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 30743f655..ec8cfd022 100644 --- a/lib/puppet-strings/json.rb +++ b/lib/puppet-strings/json.rb @@ -9,6 +9,7 @@ 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/data_type.rb b/lib/puppet-strings/markdown/data_type.rb index f1fd6cfca..903559e3a 100644 --- a/lib/puppet-strings/markdown/data_type.rb +++ b/lib/puppet-strings/markdown/data_type.rb @@ -1,10 +1,14 @@ 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 diff --git a/lib/puppet-strings/markdown/data_types.rb b/lib/puppet-strings/markdown/data_types.rb index 80558ba1f..7a0fe317e 100644 --- a/lib/puppet-strings/markdown/data_types.rb +++ b/lib/puppet-strings/markdown/data_types.rb @@ -5,7 +5,10 @@ module DataTypes # @return [Array] list of data types def self.in_dtypes - arr = YARD::Registry.all(:puppet_data_type).sort_by!(&:name).map!(&:to_hash) + 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 diff --git a/lib/puppet-strings/markdown/templates/data_type.erb b/lib/puppet-strings/markdown/templates/data_type.erb index 212fd1aad..6306b69d2 100644 --- a/lib/puppet-strings/markdown/templates/data_type.erb +++ b/lib/puppet-strings/markdown/templates/data_type.erb @@ -43,6 +43,10 @@ ``` <% end -%> +<% end -%> +<% if alias_of -%> +Alias of `<%= alias_of %>` + <% end -%> <% if params -%> #### Parameters diff --git a/lib/puppet-strings/yard.rb b/lib/puppet-strings/yard.rb index 5a6313e6d..56ea11145 100644 --- a/lib/puppet-strings/yard.rb +++ b/lib/puppet-strings/yard.rb @@ -47,6 +47,7 @@ def all_objects :class, :puppet_class, :puppet_data_type, + :puppet_data_type_alias, :puppet_defined_type, :puppet_type, :puppet_provider, @@ -69,6 +70,10 @@ 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 024b548ce..f4f6f92b9 100644 --- a/lib/puppet-strings/yard/code_objects.rb +++ b/lib/puppet-strings/yard/code_objects.rb @@ -2,6 +2,7 @@ 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_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 eb11168b0..705dcd48e 100644 --- a/lib/puppet-strings/yard/handlers.rb +++ b/lib/puppet-strings/yard/handlers.rb @@ -18,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/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 index 7eeb64eb0..3e253da81 100644 --- 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 @@ -3,6 +3,7 @@
  • <%= linkify item, h(item.name(false)) %> + <% if item.type == :puppet_data_type_alias %>Alias<% end %>
  • <% even = !even %> 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 9f006c6be..4087e9d9c 100644 --- a/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/fulldoc/html/setup.rb @@ -10,7 +10,7 @@ def generate_puppet_class_list # Generates the searchable Puppet data type list. # @return [void] def generate_puppet_data_type_list - @items = Registry.all(:puppet_data_type).sort_by {|dt| dt.name.to_s } + @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 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 ebe760bdb..0750fce36 100644 --- a/lib/puppet-strings/yard/templates/default/layout/html/setup.rb +++ b/lib/puppet-strings/yard/templates/default/layout/html/setup.rb @@ -30,7 +30,7 @@ def layout @nav_url = url_for_list('puppet_class') @page_title = "Puppet Class: #{object.name}" @path = object.path - when PuppetStrings::Yard::CodeObjects::DataType + 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 @@ -168,7 +168,7 @@ def classes # @return [String] Returns the rendered section. def data_types @title = 'Data Type Listing A-Z' - @objects_by_letter = objects_by_letter(:puppet_data_type) + @objects_by_letter = objects_by_letter(:puppet_data_type, :puppet_data_type_alias) erb(:objects) 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/spec/fixtures/unit/markdown/output_with_data_types.md b/spec/fixtures/unit/markdown/output_with_data_types.md index 511aa7b81..e53416f96 100644 --- a/spec/fixtures/unit/markdown/output_with_data_types.md +++ b/spec/fixtures/unit/markdown/output_with_data_types.md @@ -31,6 +31,8 @@ _Private Classes_ **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** @@ -478,6 +480,21 @@ 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. diff --git a/spec/unit/puppet-strings/markdown_spec.rb b/spec/unit/puppet-strings/markdown_spec.rb index a676c3b5f..8510f4528 100644 --- a/spec/unit/puppet-strings/markdown_spec.rb +++ b/spec/unit/puppet-strings/markdown_spec.rb @@ -293,6 +293,17 @@ def parse_data_type_content 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) } 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/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 From a05b9c825ef95076dd573c0afa7dcec56b0ded0b Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Wed, 17 Jul 2019 15:00:29 +0800 Subject: [PATCH 4/4] (PDOC-226) Update acceptance tests for Data Types and Aliases This commit updates the acceptance tests for the new Data Types and Data Type Aliases. --- spec/acceptance/emit_json_options_spec.rb | 2 ++ spec/acceptance/running_strings_generate_spec.rb | 16 ++++++++++++++++ .../acceptance/modules/test/types/elephant.pp | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 spec/fixtures/acceptance/modules/test/types/elephant.pp 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]