diff --git a/README.md b/README.md index aeddf16..958f806 100644 --- a/README.md +++ b/README.md @@ -162,17 +162,33 @@ Strings can produce documentation in JSON and then either generate a `.json` fil To generate JSON documentation to a file, run: ``` -$ puppet strings generate --emit-json documentation.json +$ puppet strings generate --format json --out documentation.json ``` To generate and then print JSON documentation to stdout, run: ``` -$ puppet strings generate --emit-json-stdout +$ puppet strings generate --format json ``` For details about Strings JSON output, see [Strings JSON schema](https://github.com/puppetlabs/puppet-strings/blob/master/JSON.md). +### Output documents in Markdown + +Strings can also produce documentation in Markdown and then either generate an `.md` file or print Markdown to stdout. The generated markdown layout has been reviewed and approved by Puppet's tech pubs team and is the same that is used in Puppet Supported modules. + +To generate REFERENCE.md: + +``` +$ puppet strings generate --format markdown +``` + +To write Markdown documentation to another file, use the --out option: + +``` +$ puppet strings generate --format markdown --out docs/INFO.md +``` + ### Output documents to GitHub Pages To generate documents and then make them available on [GitHub Pages](https://pages.github.com/), use the Strings rake task `strings:gh_pages:update`. See [Rake tasks](#rake-tasks) for setup and usage details. @@ -285,6 +301,35 @@ end All provider method calls, including `confine`, `defaultfor`, and `commands`, are automatically parsed and documented by Strings. The `desc` method is used to generate the docstring, and can include tags such as `@example` if written as a heredoc. +Document types that use the new [Resource API](https://github.com/puppetlabs/puppet-resource_api): + +```ruby +Puppet::ResourceApi.register_type( + name: 'database', + docs: 'An example database server resource type.', + attributes: { + ensure: { + type: 'Enum[present, absent, up, down]', + desc: 'What state the database should be in.', + default: 'up', + }, + address: { + type: 'String', + desc: 'The database server name.', + behaviour: :namevar, + }, + encrypt: { + type: 'Boolean', + desc: 'Whether or not to encrypt the database.', + default: false, + behaviour: :parameter, + }, + }, +) +``` + +Here, the `docs` key acts like the `desc` method of the traditional resource type. Everything else is the same, except that now everything is a value in the data structure, not passed to methods. + **Note**: Puppet Strings can not evaluate your Ruby code, so only certain static expressions are supported. ### Documenting functions diff --git a/lib/puppet-strings.rb b/lib/puppet-strings.rb index 410d92b..31f6338 100644 --- a/lib/puppet-strings.rb +++ b/lib/puppet-strings.rb @@ -14,7 +14,9 @@ module PuppetStrings # @option options [Boolean] :debug Enable YARD debug output. # @option options [Boolean] :backtrace Enable YARD backtraces. # @option options [String] :markup The YARD markup format to use (defaults to 'markdown'). - # @option options [String] :json Enables JSON output to the given file. If the file is nil, STDOUT is used. + # @option options [String] :path Write the selected format to a file path + # @option options [Boolean] :markdown From the --format option, is the format Markdown? + # @option options [Boolean] :json Is the format JSON? # @option options [Array] :yard_args The arguments to pass to yard. # @return [void] def self.generate(search_patterns = DEFAULT_SEARCH_PATTERNS, options = {}) @@ -27,15 +29,18 @@ module PuppetStrings args << '--backtrace' if options[:backtrace] args << "-m#{options[:markup] || 'markdown'}" - render_as_json = options.key? :json - json_file = nil - if render_as_json - json_file = options[:json] + file = nil + if options[:json] || options[:markdown] + file = if options[:json] + options[:path] + elsif options[:markdown] + options[:path] || "REFERENCE.md" + end # Disable output and prevent stats/progress when writing to STDOUT args << '-n' - args << '-q' unless json_file - args << '--no-stats' unless json_file - args << '--no-progress' unless json_file + args << '-q' unless file + args << '--no-stats' unless file + args << '--no-progress' unless file end yard_args = options[:yard_args] @@ -46,10 +51,24 @@ module PuppetStrings YARD::CLI::Yardoc.run(*args) # If outputting JSON, render the output - if render_as_json - require 'puppet-strings/json' - PuppetStrings::Json.render(json_file) + if options[:json] + render_json(file) end + + # If outputting Markdown, render the output + if options[:markdown] + render_markdown(file) + end + end + + def self.render_json(path) + require 'puppet-strings/json' + PuppetStrings::Json.render(path) + end + + def self.render_markdown(path) + require 'puppet-strings/markdown' + PuppetStrings::Markdown.render(path) end # Runs the YARD documentation server. diff --git a/lib/puppet-strings/json.rb b/lib/puppet-strings/json.rb index ae433c1..ff85a7e 100644 --- a/lib/puppet-strings/json.rb +++ b/lib/puppet-strings/json.rb @@ -34,6 +34,13 @@ module PuppetStrings::Json next t.to_hash if t.respond_to?(:to_hash) tag = { tag_name: t.tag_name } + # grab nested information for @option tags + if tag[:tag_name] == 'option' + tag[:opt_name] = t.pair.name + tag[:opt_text] = t.pair.text + tag[:opt_types] = t.pair.types + tag[:parent] = t.name + end tag[:text] = t.text if t.text tag[:types] = t.types if t.types tag[:name] = t.name if t.name diff --git a/lib/puppet-strings/markdown.rb b/lib/puppet-strings/markdown.rb new file mode 100644 index 0000000..6370099 --- /dev/null +++ b/lib/puppet-strings/markdown.rb @@ -0,0 +1,35 @@ +require 'puppet-strings/json' + +# module for parsing Yard Registries and generating markdown +module PuppetStrings::Markdown + require_relative 'markdown/puppet_classes' + require_relative 'markdown/functions' + require_relative 'markdown/defined_types' + require_relative 'markdown/resource_types' + require_relative 'markdown/table_of_contents' + + # generates markdown documentation + # @return [String] markdown doc + def self.generate + final = "# Reference\n\n" + final << PuppetStrings::Markdown::TableOfContents.render + final << PuppetStrings::Markdown::PuppetClasses.render + final << PuppetStrings::Markdown::DefinedTypes.render + final << PuppetStrings::Markdown::ResourceTypes.render + final << PuppetStrings::Markdown::Functions.render + + final + end + + # mimicks the behavior of the json render, although path will never be nil + # @param [String] path path to destination file + def self.render(path = nil) + if path.nil? + puts generate + exit + else + File.open(path, 'w') { |file| file.write(generate) } + YARD::Logger.instance.debug "Wrote markdown to #{path}" + end + end +end diff --git a/lib/puppet-strings/markdown/base.rb b/lib/puppet-strings/markdown/base.rb new file mode 100644 index 0000000..8e9792a --- /dev/null +++ b/lib/puppet-strings/markdown/base.rb @@ -0,0 +1,158 @@ +require 'puppet-strings' +require 'puppet-strings/json' +require 'puppet-strings/yard' + +module PuppetStrings::Markdown + # This class makes elements in a YARD::Registry hash easily accessible for templates. + # + # Here's an example hash: + #{:name=>:klass, + # :file=>"(stdin)", + # :line=>16, + # :inherits=>"foo::bar", + # :docstring=> + # {:text=>"An overview for a simple class.", + # :tags=> + # [{:tag_name=>"summary", :text=>"A simple class."}, + # {:tag_name=>"since", :text=>"1.0.0"}, + # {:tag_name=>"see", :name=>"www.puppet.com"}, + # {:tag_name=>"example", + # :text=> + # "class { 'klass':\n" + + # " param1 => 1,\n" + + # " param3 => 'foo',\n" + + # "}", + # :name=>"This is an example"}, + # {:tag_name=>"author", :text=>"eputnam"}, + # {:tag_name=>"option", :name=>"opts"}, + # {:tag_name=>"raise", :text=>"SomeError"}, + # {:tag_name=>"param", + # :text=>"First param.", + # :types=>["Integer"], + # :name=>"param1"}, + # {:tag_name=>"param", + # :text=>"Second param.", + # :types=>["Any"], + # :name=>"param2"}, + # {:tag_name=>"param", + # :text=>"Third param.", + # :types=>["String"], + # :name=>"param3"}]}, + # :defaults=>{"param1"=>"1", "param2"=>"undef", "param3"=>"'hi'"}, + # :source=> + # "class klass (\n" + + # " Integer $param1 = 1,\n" + + # " $param2 = undef,\n" + + # " String $param3 = 'hi'\n" + + # ") inherits foo::bar {\n" + + # "}"} + class Base + def initialize(registry, component_type) + @type = component_type + @registry = registry + @tags = registry[:docstring][:tags] || [] + end + + # generate 1:1 tag methods + # e.g. {:tag_name=>"author", :text=>"eputnam"} + { :return_val => 'return', + :since => 'since', + :summary => 'summary' }.each do |method_name, tag_name| + # @return [String] unless the tag is nil or the string.length == 0 + define_method method_name do + @tags.select { |tag| tag[:tag_name] == "#{tag_name}" }[0][:text] unless @tags.select { |tag| tag[:tag_name] == "#{tag_name}" }[0].nil? || @tags.select { |tag| tag[:tag_name] == "#{tag_name}" }[0][:text].length.zero? + end + end + + # @return [String] top-level name + def name + @registry[:name].to_s unless @registry[:name].nil? + end + + # @return [String] 'Overview' text (untagged text) + def text + @registry[:docstring][:text] unless @registry[:docstring][:text].empty? + end + + # @return [String] data type of return value + def return_type + @tags.select { |tag| tag[:tag_name] == 'return' }[0][:types][0] unless @tags.select { |tag| tag[:tag_name] == 'return' }[0].nil? + end + + # @return [String] text from @since tag + def since + @tags.select { |tag| tag[:tag_name] == 'since' }[0][:text] unless @tags.select { |tag| tag[:tag_name] == 'since' }[0].nil? + end + + # @return [Array] @see tag hashes + def see + @tags.select { |tag| tag[:tag_name] == 'see' } unless @tags.select { |tag| tag[:tag_name] == 'see' }[0].nil? + end + + # @return [Array] parameter tag hashes + def params + @tags.select { |tag| tag[:tag_name] == 'param' } unless @tags.select { |tag| tag[:tag_name] == 'param' }[0].nil? + end + + # @return [Array] example tag hashes + def examples + @tags.select { |tag| tag[:tag_name] == 'example' } unless @tags.select { |tag| tag[:tag_name] == 'example' }[0].nil? + end + + # @return [Array] raise tag hashes + def raises + @tags.select { |tag| tag[:tag_name] == 'raise' } unless @tags.select { |tag| tag[:tag_name] == 'raise' }[0].nil? + end + + # @return [Array] option tag hashes + def options + @tags.select { |tag| tag[:tag_name] == 'option' } unless @tags.select { |tag| tag[:tag_name] == 'option' }[0].nil? + end + + # @param parameter_name + # parameter name to match to option tags + # @return [Array] option tag hashes that have a parent parameter_name + def options_for_param(parameter_name) + opts_for_p = options.select { |o| o[:parent] == parameter_name } unless options.nil? + opts_for_p unless opts_for_p.nil? || opts_for_p.length.zero? + end + + # @return [Array] any defaults found for the component + def defaults + @registry[:defaults] unless @registry[:defaults].nil? + end + + # @return [Hash] information needed for the table of contents + def toc_info + { + name: name.to_s, + link: link, + desc: summary || @registry[:docstring][:text].gsub("\n", ". ") + } + end + + # @return [String] makes the component name suitable for a GitHub markdown link + def link + name.delete('::').strip.gsub(' ','-').downcase + end + + # Some return, default, or valid values need to be in backticks. Instead of fu in the handler or code_object, this just does the change on the front. + # @param value + # any string + # @return [String] value or value in backticks if it is in a list + def value_string(value) + to_symbol = %w[undef true false] + if to_symbol.include? value + return "`#{value}`" + else + return value + end + end + + # @return [String] full markdown rendering of a component + def render(template) + file = File.join(File.dirname(__FILE__),"templates/#{template}") + ERB.new(File.read(file), nil, '-').result(binding) + end + end +end diff --git a/lib/puppet-strings/markdown/defined_type.rb b/lib/puppet-strings/markdown/defined_type.rb new file mode 100644 index 0000000..86e5c14 --- /dev/null +++ b/lib/puppet-strings/markdown/defined_type.rb @@ -0,0 +1,14 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class DefinedType < Base + def initialize(registry) + @template = 'classes_and_defines.erb' + super(registry, 'defined type') + end + + def render + super(@template) + end + end +end diff --git a/lib/puppet-strings/markdown/defined_types.rb b/lib/puppet-strings/markdown/defined_types.rb new file mode 100644 index 0000000..e23c6a3 --- /dev/null +++ b/lib/puppet-strings/markdown/defined_types.rb @@ -0,0 +1,29 @@ +require_relative 'defined_type' + +module PuppetStrings::Markdown + module DefinedTypes + + # @return [Array] list of defined types + def self.in_dtypes + YARD::Registry.all(:puppet_defined_type).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = in_dtypes.length > 0 ? "## Defined types\n\n" : "" + in_dtypes.each do |type| + final << PuppetStrings::Markdown::DefinedType.new(type).render + end + final + end + + def self.toc_info + final = [] + + in_dtypes.each do |type| + final.push(PuppetStrings::Markdown::DefinedType.new(type).toc_info) + end + + final + end + end +end diff --git a/lib/puppet-strings/markdown/function.rb b/lib/puppet-strings/markdown/function.rb new file mode 100644 index 0000000..5680d9e --- /dev/null +++ b/lib/puppet-strings/markdown/function.rb @@ -0,0 +1,52 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class Function < Base + attr_reader :signatures + + def initialize(registry) + @template = 'function.erb' + super(registry, 'function') + @signatures = [] + registry[:signatures].each do |sig| + @signatures.push(Signature.new(sig)) + end + end + + def render + super(@template) + end + + def type + t = @registry[:type] + if t =~ /ruby4x/ + "Ruby 4.x API" + elsif t =~ /ruby3/ + "Ruby 3.x API" + elsif t =~ /ruby/ + "Ruby" + else + "Puppet Language" + end + end + + def error_type(r) + "`#{r.split(' ')[0]}`" + end + + def error_text(r) + "#{r.split(' ').drop(1).join(' ')}" + end + end + + class Function::Signature < Base + def initialize(registry) + @registry = registry + super(@registry, 'function signature') + end + + def signature + @registry[:signature] + end + end +end diff --git a/lib/puppet-strings/markdown/functions.rb b/lib/puppet-strings/markdown/functions.rb new file mode 100644 index 0000000..1996938 --- /dev/null +++ b/lib/puppet-strings/markdown/functions.rb @@ -0,0 +1,30 @@ +require_relative 'function' + +module PuppetStrings::Markdown + module Functions + + # @return [Array] list of functions + def self.in_functions + YARD::Registry.all(:puppet_function).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = in_functions.length > 0 ? "## Functions\n\n" : "" + in_functions.each do |func| + final << PuppetStrings::Markdown::Function.new(func).render + end + final + end + + def self.toc_info + final = [] + + in_functions.each do |func| + final.push(PuppetStrings::Markdown::Function.new(func).toc_info) + end + + final + end + end +end + diff --git a/lib/puppet-strings/markdown/puppet_class.rb b/lib/puppet-strings/markdown/puppet_class.rb new file mode 100644 index 0000000..8bf02be --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_class.rb @@ -0,0 +1,14 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class PuppetClass < Base + def initialize(registry) + @template = 'classes_and_defines.erb' + super(registry, 'class') + end + + def render + super(@template) + end + end +end diff --git a/lib/puppet-strings/markdown/puppet_classes.rb b/lib/puppet-strings/markdown/puppet_classes.rb new file mode 100644 index 0000000..0818f9f --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_classes.rb @@ -0,0 +1,29 @@ +require_relative 'puppet_class' + +module PuppetStrings::Markdown + module PuppetClasses + + # @return [Array] list of classes + def self.in_classes + YARD::Registry.all(:puppet_class).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = in_classes.length > 0 ? "## Classes\n\n" : "" + in_classes.each do |klass| + final << PuppetStrings::Markdown::PuppetClass.new(klass).render + end + final + end + + def self.toc_info + final = [] + + in_classes.each do |klass| + final.push(PuppetStrings::Markdown::PuppetClass.new(klass).toc_info) + end + + final + end + end +end diff --git a/lib/puppet-strings/markdown/resource_type.rb b/lib/puppet-strings/markdown/resource_type.rb new file mode 100644 index 0000000..3645c93 --- /dev/null +++ b/lib/puppet-strings/markdown/resource_type.rb @@ -0,0 +1,27 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class ResourceType < Base + def initialize(registry) + @template = 'resource_type.erb' + super(registry, 'type') + end + + def render + super(@template) + end + + def properties + @registry[:properties] + end + + def parameters + @registry[:parameters] + end + + def regex_in_data_type?(data_type) + m = data_type.match(/\w+\[\/.*\/\]/) + m unless m.nil? || m.length.zero? + end + end +end diff --git a/lib/puppet-strings/markdown/resource_types.rb b/lib/puppet-strings/markdown/resource_types.rb new file mode 100644 index 0000000..214a885 --- /dev/null +++ b/lib/puppet-strings/markdown/resource_types.rb @@ -0,0 +1,29 @@ +require_relative 'resource_type' + +module PuppetStrings::Markdown + module ResourceTypes + + # @return [Array] list of resource types + def self.in_rtypes + YARD::Registry.all(:puppet_type).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = in_rtypes.length > 0 ? "## Resource types\n\n" : "" + in_rtypes.each do |type| + final << PuppetStrings::Markdown::ResourceType.new(type).render + end + final + end + + def self.toc_info + final = [] + + in_rtypes.each do |type| + final.push(PuppetStrings::Markdown::ResourceType.new(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 new file mode 100644 index 0000000..3afa119 --- /dev/null +++ b/lib/puppet-strings/markdown/table_of_contents.rb @@ -0,0 +1,13 @@ +module PuppetStrings::Markdown + module TableOfContents + def self.render + puppet_classes = PuppetStrings::Markdown::PuppetClasses.toc_info + puppet_defined_types = PuppetStrings::Markdown::DefinedTypes.toc_info + puppet_resource_types = PuppetStrings::Markdown::ResourceTypes.toc_info + puppet_functions = PuppetStrings::Markdown::Functions.toc_info + + template = File.join(File.dirname(__FILE__),"templates/table_of_contents.erb") + ERB.new(File.read(template), nil, '-').result(binding) + end + end +end diff --git a/lib/puppet-strings/markdown/templates/classes_and_defines.erb b/lib/puppet-strings/markdown/templates/classes_and_defines.erb new file mode 100644 index 0000000..8cf528c --- /dev/null +++ b/lib/puppet-strings/markdown/templates/classes_and_defines.erb @@ -0,0 +1,53 @@ +### <%= name %> + +<% if since -%> +* **Since** <%= since %> + +<% end -%> +<% if see -%> +* **See also** +<% see.each do |sa| -%> +<%= sa[:name] %> +<%= sa[:text] %> +<% 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/markdown/templates/function.erb b/lib/puppet-strings/markdown/templates/function.erb new file mode 100644 index 0000000..40a6ca8 --- /dev/null +++ b/lib/puppet-strings/markdown/templates/function.erb @@ -0,0 +1,40 @@ +### <%= name %> +Type: <%= type %> + +<% signatures.each do |sig| -%> +#### `<%= sig.signature %>` + +<% if sig.text -%> +<%= sig.text %> + +<% end -%> +<% if sig.return_type -%> +Returns: `<%= sig.return_type %>`<% if sig.return_val %> <%= sig.return_val %><% end %> + +<% end -%> +<% if raises -%> +Raises: +<% raises.each do |r| -%> +* <%= error_type(r[:text]) %> <%= error_text(r[:text]) %> +<% end -%> + +<% end -%> +<% if sig.params -%> +<% sig.params.each do |param| -%> +##### `<%= param[:name] %>` + +Data type: `<%= param[:types][0] %>` + +<%= param[:text] %> + +<% if sig.options_for_param(param[:name]) -%> +Options: + +<% sig.options_for_param(param[:name]).each do |o| -%> +* **<%= o[:opt_name] %>** `<%= o[:opt_types][0] %>`: <%= o[:opt_text] %> +<% end -%> + +<% end -%> +<% end -%> +<% end -%> +<% end -%> diff --git a/lib/puppet-strings/markdown/templates/resource_type.erb b/lib/puppet-strings/markdown/templates/resource_type.erb new file mode 100644 index 0000000..12cff6f --- /dev/null +++ b/lib/puppet-strings/markdown/templates/resource_type.erb @@ -0,0 +1,107 @@ +### <%= name %> + +<% if since -%> +* **Since** <%= since %> + +<% end -%> +<% if see -%> +* **See also** +<% see.each do |sa| -%> +<%= sa[:name] %> +<%= sa[:text] %> +<% end -%> + +<% end -%> +<% if text -%> +<%= text %> +<% end %> +<% if examples -%> +#### Examples +<% examples.each do |eg| -%> +##### <%= eg[:name] %> +```puppet +<%= eg[:text] %> +``` +<% end -%> +<% end -%> +<% if properties %> +#### Properties + +The following properties are available in the `<%= name %>` <%= @type %>. + +<% properties.each do |prop| -%> +##### `<%= prop[:name] %>` + +<% if prop[:values] -%> +Valid values: <%= prop[:values].map { |value| value_string(value) }.join(', ') %> + +<% end -%> +<% if prop[:isnamevar] -%> +namevar + +<% end -%> +<% if prop[:aliases] -%> +Aliases: <%= prop[:aliases].to_s.delete('{').delete('}') %> + +<% end -%> +<% if prop[:data_type] -%> +Data type: `<%= prop[:data_type] %>`<%= "\n_\*this data type contains a regex that may not be accurately reflected in generated documentation_" if regex_in_data_type?(prop[:data_type]) %> + +<% end -%> +<%= prop[:description] %> + +<% if options_for_param(prop[:name]) -%> +Options: + +<% options_for_param(prop[:name]).each do |o| -%> +* **<%= o[:opt_name] %>** `<%= o[:opt_types][0] %>`: <%= o[:opt_text] %> +<% end -%> + +<% end -%> +<% if prop[:default] -%> +Default value: <%= prop[:default] %> + +<% end -%> +<% end -%> +<% end -%> +<% if parameters -%> +#### Parameters + +The following parameters are available in the `<%= name %>` <%= @type %>. + +<% parameters.each do |param| -%> +##### `<%= param[:name] %>` + +<% if param[:values] -%> +Valid values: <%= param[:values].map { |value| value_string(value) }.join(', ') %> + +<% end -%> +<% if param[:isnamevar] -%> +namevar + +<% end -%> +<% if param[:aliases] -%> +Aliases: <%= param[:aliases].to_s.delete('{').delete('}') %> + +<% end -%> +<% if param[:data_type] -%> +Data type: `<%= param[:data_type] %>`<%= "\n_\*this data type contains a regex that may not be accurately reflected in generated documentation_" if regex_in_data_type?(param[:data_type]) %> + +<% end -%> +<%= param[:description] %> + +<% 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 param[:default] -%> +Default value: <%= value_string(param[:default]) %> + +<% end -%> +<% end -%> +<% end -%> + diff --git a/lib/puppet-strings/markdown/templates/table_of_contents.erb b/lib/puppet-strings/markdown/templates/table_of_contents.erb new file mode 100644 index 0000000..d726b5e --- /dev/null +++ b/lib/puppet-strings/markdown/templates/table_of_contents.erb @@ -0,0 +1,24 @@ +<% if puppet_classes.length > 0 -%> +## Classes +<% puppet_classes.each do |klassy| -%> +* [`<%= klassy[:name] %>`](#<%= klassy[:link] %>): <%= klassy[:desc] %> +<% end -%> +<% end -%> +<% if puppet_defined_types.length > 0 -%> +## Defined types +<% puppet_defined_types.each do |dtype| -%> +* [`<%= dtype[:name] %>`](#<%= dtype[:link] %>): <%= dtype[:desc] %> +<% end -%> +<% end -%> +<% if puppet_resource_types.length > 0 -%> +## Resource types +<% puppet_resource_types.each do |rtype| -%> +* [`<%= rtype[:name] %>`](#<%= rtype[:link] %>): <%= rtype[:desc] %> +<% end -%> +<% end -%> +<% if puppet_functions.length > 0 -%> +## Functions +<% puppet_functions.each do |func| -%> +* [`<%= func[:name] %>`](#<%= func[:link] %>): <%= func[:desc] %> +<% end -%> +<% end -%> diff --git a/lib/puppet-strings/yard/code_objects/function.rb b/lib/puppet-strings/yard/code_objects/function.rb index 4c2edf2..0ea3a93 100644 --- a/lib/puppet-strings/yard/code_objects/function.rb +++ b/lib/puppet-strings/yard/code_objects/function.rb @@ -84,14 +84,14 @@ class PuppetStrings::Yard::CodeObjects::Function < PuppetStrings::Yard::CodeObje hash[:line] = line hash[:type] = @function_type.to_s hash[:signatures] = [] - + if self.has_tag? :overload # loop over overloads and append onto the signatures array self.tags(:overload).each do |o| - hash[:signatures] << { :signature => o.signature, :docstring => PuppetStrings::Json.docstring_to_hash(o.docstring, [:param, :return]) } + hash[:signatures] << { :signature => o.signature, :docstring => PuppetStrings::Json.docstring_to_hash(o.docstring, [:param, :option, :return]) } end else - hash[:signatures] << { :signature => self.signature, :docstring => PuppetStrings::Json.docstring_to_hash(docstring, [:param, :return]) } + hash[:signatures] << { :signature => self.signature, :docstring => PuppetStrings::Json.docstring_to_hash(docstring, [:param, :option, :return]) } end hash[:docstring] = PuppetStrings::Json.docstring_to_hash(docstring) diff --git a/lib/puppet-strings/yard/code_objects/type.rb b/lib/puppet-strings/yard/code_objects/type.rb index fe0a4b6..5190368 100644 --- a/lib/puppet-strings/yard/code_objects/type.rb +++ b/lib/puppet-strings/yard/code_objects/type.rb @@ -22,7 +22,7 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects: # Represents a resource type parameter. class Parameter attr_reader :name, :values, :aliases - attr_accessor :docstring, :isnamevar, :default + attr_accessor :docstring, :isnamevar, :default, :data_type # Initializes a resource type parameter or property. # @param [String] name The name of the parameter or property. @@ -31,6 +31,7 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects: @name = name @docstring = docstring || '' @values = [] + @data_type = [] @aliases = {} @isnamevar = false @default = nil @@ -59,6 +60,7 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects: hash[:name] = name hash[:description] = docstring unless docstring.empty? hash[:values] = values unless values.empty? + hash[:data_type] = data_type unless data_type.empty? hash[:aliases] = aliases unless aliases.empty? hash[:isnamevar] = true if isnamevar hash[:default] = default if default diff --git a/lib/puppet-strings/yard/handlers/ruby/base.rb b/lib/puppet-strings/yard/handlers/ruby/base.rb index d2fb041..dfc730f 100644 --- a/lib/puppet-strings/yard/handlers/ruby/base.rb +++ b/lib/puppet-strings/yard/handlers/ruby/base.rb @@ -29,7 +29,7 @@ class PuppetStrings::Yard::Handlers::Ruby::Base < YARD::Handlers::Ruby::Base source = node.source if source =~ HEREDOC_START lines = source.split("\n") - source = lines[1..(lines.last.include?($1) ? -2 : -1)].join("\n") if lines.size > 1 + source = lines[1..(lines.last.include?($1[0..-2]) ? -2 : -1)].join("\n") if lines.size > 1 end source diff --git a/lib/puppet-strings/yard/handlers/ruby/rsapi_handler.rb b/lib/puppet-strings/yard/handlers/ruby/rsapi_handler.rb index de3b02d..80f8989 100644 --- a/lib/puppet-strings/yard/handlers/ruby/rsapi_handler.rb +++ b/lib/puppet-strings/yard/handlers/ruby/rsapi_handler.rb @@ -24,7 +24,7 @@ class PuppetStrings::Yard::Handlers::Ruby::RsapiHandler < PuppetStrings::Yard::H object = PuppetStrings::Yard::CodeObjects::Type.new(schema['name']) register object - docstring = schema['docs'] + docstring = schema['desc'] || "" if docstring register_docstring(object, PuppetStrings::Yard::Util.scrub_string(docstring.to_s), nil) else @@ -49,7 +49,7 @@ class PuppetStrings::Yard::Handlers::Ruby::RsapiHandler < PuppetStrings::Yard::H # check that the params of the register_type call are key/value pairs. def kv_arg_list?(params) - params.type == :list && params.children.count > 0 && params.children.first.type == :list && params.children.first.children.count > 0 && statement.parameters.children.first.children.first.type == :assoc + params.type == :list && params.children.count > 0 && params.children.first.type == :list && params.children.first.children.count > 0 && statement.parameters.children.first.children.first.type == :assoc end def extract_schema @@ -134,7 +134,7 @@ class PuppetStrings::Yard::Handlers::Ruby::RsapiHandler < PuppetStrings::Yard::H end def set_values(definition, object) - object.add(definition['type']) if definition.key? 'type' + object.data_type = definition['type'] if definition.key? 'type' object.default = definition['default'] if definition.key? 'default' object.isnamevar = definition.key?('behaviour') && definition['behaviour'] == 'namevar' end diff --git a/lib/puppet/face/strings.rb b/lib/puppet/face/strings.rb index 06d4ce6..da1c458 100644 --- a/lib/puppet/face/strings.rb +++ b/lib/puppet/face/strings.rb @@ -7,15 +7,21 @@ Puppet::Face.define(:strings, '0.0.1') do action(:generate) do default - option '--emit-json-stdout' do - summary 'Print JSON representation of the documentation to stdout.' + option '--format OUTPUT_FORMAT' do + summary 'Designate output format, JSON or markdown.' end - option '--emit-json FILE' do - summary 'Write JSON representation of the documentation to the given file.' + option '--out PATH' do + summary 'Write selected format to PATH. If no path is designated, strings prints to STDOUT.' end option '--markup FORMAT' do summary "The markup format to use for docstring text (defaults to 'markdown')." end + option '--emit-json-stdout' do + summary 'DEPRECATED: Print JSON representation of the documentation to stdout.' + end + option '--emit-json PATH' do + summary 'DEPRECATED: Write JSON representation of the documentation to the given file.' + end summary 'Generate documentation from files.' arguments '[[search_pattern] ...]' @@ -94,11 +100,26 @@ Puppet::Face.define(:strings, '0.0.1') do generate_options[:yard_args] = yard_args unless yard_args.empty? if options + if options[:emit_json] + $stderr.puts "WARNING: '--emit-json PATH' is deprecated. Use '--format json --out PATH' instead." + end + if options[:emit_json_stdout] + $stderr.puts "WARNING: '--emit-json-stdout' is deprecated. Use '--format json' instead." + end markup = options[:markup] generate_options[:markup] = markup if markup - json_file = options[:emit_json] - generate_options[:json] = json_file if json_file - generate_options[:json] = nil if options[:emit_json_stdout] + generate_options[:path] = options[:out] if options[:out] + generate_options[:stdout] = options[:stdout] + format = options[:format] + if format + if format.casecmp('markdown').zero? + generate_options[:markdown] = true + elsif format.casecmp('json').zero? || options[:emit_json] || options[:emit_json_stdout] + generate_options[:json] = true + else + raise RuntimeError, "Invalid format #{options[:format]}. Please select 'json' or 'markdown'." + end + end end generate_options end diff --git a/spec/acceptance/emit_json_options.rb b/spec/acceptance/emit_json_options.rb index 3e64874..79c53e8 100644 --- a/spec/acceptance/emit_json_options.rb +++ b/spec/acceptance/emit_json_options.rb @@ -37,18 +37,18 @@ describe 'Emitting JSON' do ] } - it 'should emit JSON to stdout when using the --emit-json-stdout option' do + it 'should emit JSON to stdout when using --format json and --stdout' do test_module_path = get_test_module_path(master, /Module test/) - on master, puppet('strings', 'generate', '--emit-json-stdout', "#{test_module_path}/lib/puppet/parser/functions/function3x.rb") do + on master, puppet('strings', 'generate', '--format json', "#{test_module_path}/lib/puppet/parser/functions/function3x.rb") do output = stdout.chomp expect(JSON.parse(output)).to eq(expected) end end - it 'should write JSON to a file when using the --emit-json option' do + it 'should write JSON to a file when using --format json and --out' do test_module_path = get_test_module_path(master, /Module test/) tmpfile = master.tmpfile('json_output.json') - on master, puppet('strings', 'generate', "--emit-json #{tmpfile}", "#{test_module_path}/lib/puppet/parser/functions/function3x.rb") + on master, puppet('strings', 'generate', '--format json', "--out #{tmpfile}", "#{test_module_path}/lib/puppet/parser/functions/function3x.rb") output = read_file_on(master, tmpfile) expect(JSON.parse(output)).to eq(expected) end diff --git a/spec/acceptance/generate_markdown_spec.rb b/spec/acceptance/generate_markdown_spec.rb new file mode 100644 index 0000000..5562483 --- /dev/null +++ b/spec/acceptance/generate_markdown_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper_acceptance' +require 'util' + +include PuppetStrings::Acceptance::Util + +describe 'Generating Markdown' do + expected = <<-EOF +# Reference + +## Classes +* [`test`](#test): This class exists to serve as fixture data for testing the puppet strings face + +## Classes + +### test + +#### Examples +```puppet +class { "test": } +``` + +#### Parameters + +##### `package_name` + +The name of the package + +##### `service_name` + +The name of the service + +EOF + + it 'should render Markdown to stdout when using --format markdown and --stdout' do + test_module_path = get_test_module_path(master, /Module test/) + on master, puppet('strings', 'generate', '--format markdown', "#{test_module_path}/manifests/init.pp") do + output = stdout.chomp + expect(JSON.parse(output)).to eq(expected) + end + end + + it 'should write Markdown to a file when using --format markdown and --out' do + test_module_path = get_test_module_path(master, /Module test/) + tmpfile = master.tmpfile('md_output.md') + on master, puppet('strings', 'generate', '--format markdown', "--out #{tmpfile}", "#{test_module_path}/manifests/init.pp") + output = read_file_on(master, tmpfile) + expect(JSON.parse(output)).to eq(expected) + end +end diff --git a/spec/fixtures/unit/json/output.json b/spec/fixtures/unit/json/output.json index c6f3c2a..c65bb65 100644 --- a/spec/fixtures/unit/json/output.json +++ b/spec/fixtures/unit/json/output.json @@ -81,6 +81,49 @@ } ], "resource_types": [ + { + "name": "apt_key", + "file": "(stdin)", + "line": 92, + "docstring": { + "text": "This type provides Puppet with the capabilities to manage GPG keys needed\nby apt to perform package validation. Apt has it's own GPG keyring that can\nbe manipulated through the `apt-key` command.\n**Autorequires**:\nIf Puppet is given the location of a key file which looks like an absolute\npath this type will autorequire that file.", + "tags": [ + { + "tag_name": "summary", + "text": "Example resource type using the new API." + }, + { + "tag_name": "raise", + "text": "SomeError" + }, + { + "tag_name": "example", + "text": "apt_key { '6F6B15509CF8E59E6E469F327F438280EF8D349F':\n source => 'http://apt.puppetlabs.com/pubkey.gpg'\n}", + "name": "here's an example" + } + ] + }, + "properties": [ + { + "name": "ensure", + "description": "Whether this apt key should be present or absent on the target system.", + "data_type": "Enum[present, absent]" + }, + { + "name": "created", + "description": "Date the key was created, in ISO format.", + "data_type": "String" + } + ], + "parameters": [ + { + "name": "id", + "description": "The ID of the key you want to manage.", + "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/]]", + "isnamevar": true + } + ] + }, { "name": "database", "file": "(stdin)", diff --git a/spec/fixtures/unit/markdown/output.md b/spec/fixtures/unit/markdown/output.md new file mode 100644 index 0000000..005277d --- /dev/null +++ b/spec/fixtures/unit/markdown/output.md @@ -0,0 +1,368 @@ +# Reference + +## Classes +* [`klass`](#klass): A simple class. +## 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. +## Classes + +### klass + +* **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 + +* **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. + +##### `encrypt` + +Valid values: `true`, `false`, yes, no + +Whether or not to encrypt the database. + +Default value: `false` + + +## Functions + +### func +Type: Puppet Language + +#### `func(Integer $param1, Any $param2, String $param3 = hi)` + +A simple Puppet function. + +Returns: `Undef` Returns nothing. + +Raises: +* `SomeError` this is some error + +##### `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 + +#### `func3x(String $param1, Integer $param2)` + +Documentation for an example 3.x function. + +Returns: `Undef` + +##### `param1` + +Data type: `String` + +The first parameter. + +##### `param2` + +Data type: `Integer` + +The second parameter. + +### func4x +Type: Ruby 4.x API + +#### `func4x(Integer $param1, Any $param2, Optional[Array[String]] $param3)` + +An overview for the first overload. + +Returns: `Undef` Returns nothing. + +##### `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. + +##### `param` + +Data type: `Boolean` + +The first parameter. + +##### `&block` + +Data type: `Callable` + +The block parameter. + +### func4x_1 +Type: Ruby 4.x API + +#### `func4x_1(Integer $param1)` + +An example 4.x function with only one signature. + +Returns: `Undef` Returns nothing. + +##### `param1` + +Data type: `Integer` + +The first parameter. + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a203858..32b6351 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,6 +19,8 @@ require 'mocha' require 'rspec' require 'puppet/version' require 'puppet-strings' +require 'puppet-strings/markdown' +require 'puppet-strings/markdown/base' require 'puppet-strings/yard' # Explicitly set up YARD once diff --git a/spec/unit/puppet-strings/json_spec.rb b/spec/unit/puppet-strings/json_spec.rb index 60f9cd5..c81adf8 100644 --- a/spec/unit/puppet-strings/json_spec.rb +++ b/spec/unit/puppet-strings/json_spec.rb @@ -123,6 +123,46 @@ Puppet::Type.newtype(:database) do defaultto 'warn' end end + +Puppet::ResourceApi.register_type( + name: 'apt_key', + desc: <<-EOS, +@summary Example resource type using the new API. +@raise SomeError +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. +@example here's an example + apt_key { '6F6B15509CF8E59E6E469F327F438280EF8D349F': + source => 'http://apt.puppetlabs.com/pubkey.gpg' + } + +**Autorequires**: +If Puppet is given the location of a key file which looks like an absolute +path this type will autorequire that file. + EOS + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this apt key should be present or absent on the target system.' + }, + id: { + 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/]]', + behaviour: :namevar, + desc: 'The ID of the key you want to manage.', + }, + # ... + created: { + type: 'String', + behaviour: :read_only, + desc: 'Date the key was created, in ISO format.', + }, + }, + autorequires: { + file: '$source', # will evaluate to the value of the `source` attribute + package: 'apt', + }, +) SOURCE end diff --git a/spec/unit/puppet-strings/markdown/base_spec.rb b/spec/unit/puppet-strings/markdown/base_spec.rb new file mode 100644 index 0000000..6713f2a --- /dev/null +++ b/spec/unit/puppet-strings/markdown/base_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe PuppetStrings::Markdown::Base do + context 'basic class' do + before :each do + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# An overview +# @summary A simple class. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +class klass(Integer $param1, $param2, String $param3 = hi) inherits foo::bar { +} +SOURCE + end + + let(:reg) { YARD::Registry.all(:puppet_class).sort_by!(&:name).map!(&:to_hash)[0] } + let(:component) { PuppetStrings::Markdown::Base.new(reg, 'class') } + + describe '#name' do + it 'returns the expected name' do + expect(component.name).to eq 'klass' + end + end + + [ 'examples', + 'see', + 'since', + 'return_val', + 'return_type',].each do |method| + describe "##{method}" do + it 'returns nil' do + expect(component.method(method.to_sym).call).to be_nil + end + end + + end + + describe '#params' do + it 'returns the expected params' do + expect(component.params.size).to eq 3 + end + end + + describe '#summary' do + it 'returns the expected summary' do + expect(component.summary).to eq 'A simple class.' + end + end + + describe '#toc_info' do + let(:toc) { component.toc_info } + it 'returns a hash' do + expect(toc).to be_instance_of Hash + end + it 'prefers the summary for :desc' do + expect(toc[:desc]).to eq 'A simple class.' + end + end + end + context 'less basic class' do + before :each do + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# An overview +# It's a longer overview +# Ya know? +# @example A simple example. +# class { 'klass::yeah': +# param1 => 1, +# } +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +class klass::yeah( + Integer $param1, + $param2, + String $param3 = hi +) inherits foo::bar { + +} +SOURCE + end + + let(:reg) { YARD::Registry.all(:puppet_class).sort_by!(&:name).map!(&:to_hash)[0] } + let(:component) { PuppetStrings::Markdown::Base.new(reg, 'class') } + + describe '#name' do + it 'returns the expected name' do + expect(component.name).to eq 'klass::yeah' + end + end + + ['summary', + 'see', + 'since', + 'return_val', + 'return_type'].each do |method| + describe "##{method}" do + it 'returns nil' do + expect(component.method(method.to_sym).call).to be_nil + end + end + end + + describe '#examples' do + it 'should return one example' do + expect(component.examples.size).to eq 1 + end + end + + describe '#params' do + it 'returns the expected params' do + expect(component.params.size).to eq 3 + end + end + + describe '#toc_info' do + let(:toc) { component.toc_info } + it 'returns a hash' do + expect(toc).to be_instance_of Hash + end + it 'uses overview for :desc in absence of summary' do + expect(toc[:desc]).to eq 'An overview. It\'s a longer overview. Ya know?' + end + end + + describe '#link' do + it 'returns a valid link' do + expect(component.link).to eq 'klassyeah' + end + end + end +end diff --git a/spec/unit/puppet-strings/markdown_spec.rb b/spec/unit/puppet-strings/markdown_spec.rb new file mode 100644 index 0000000..6eab938 --- /dev/null +++ b/spec/unit/puppet-strings/markdown_spec.rb @@ -0,0 +1,244 @@ +require 'spec_helper' +require 'puppet-strings/markdown' +require 'puppet-strings/markdown/table_of_contents' +require 'tempfile' + +describe PuppetStrings::Markdown do + before :each do + # Populate the YARD registry with both Puppet and Ruby source + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# An overview for a simple class. +# @summary A simple class. +# @since 1.0.0 +# @see www.puppet.com +# @example This is an example +# class { 'klass': +# param1 => 1, +# param3 => 'foo', +# } +# @example This is another example +# class { 'klass': +# param1 => 1, +# param3 => 'foo', +# } +# @raise SomeError +# @param param1 First param. +# @param param2 Second param. +# @option param2 [String] :opt1 something about opt1 +# @option param2 [Hash] :opt2 a hash of stuff +# @param param3 Third param. +# +class klass ( + Integer $param1 = 1, + $param2 = undef, + String $param3 = 'hi' +) inherits foo::bar { +} + +# An overview for a simple defined type. +# @summary A simple defined type. +# @since 1.1.0 +# @see www.puppet.com +# @example Here's an example of this type: +# klass::dt { 'foo': +# param1 => 33, +# param4 => false, +# } +# @return shouldn't return squat +# @raise SomeError +# @param param1 First param. +# @param param2 Second param. +# @option param2 [String] :opt1 something about opt1 +# @option param2 [Hash] :opt2 a hash of stuff +# @param param3 Third param. +# @param param4 Fourth param. +define klass::dt ( + Integer $param1 = 44, + $param2, + String $param3 = 'hi', + Boolean $param4 = true +) { +} +SOURCE + + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :puppet) +# A simple Puppet function. +# @param param1 First param. +# @param param2 Second param. +# @param param3 Third param. +# @option param3 [Array] :param3opt Something about this option +# @raise SomeError this is some error +# @return [Undef] Returns nothing. +function func(Integer $param1, $param2, String $param3 = hi) { +} +SOURCE + + YARD::Parser::SourceParser.parse_string(<<-SOURCE, :ruby) +# An example 4.x function. +Puppet::Functions.create_function(:func4x) do + # An overview for the first overload. + # @raise SomeError this is some error + # @param param1 The first parameter. + # @param param2 The second parameter. + # @option param2 [String] :option an option + # @option param2 [String] :option2 another option + # @param param3 The third parameter. + # @return Returns nothing. + dispatch :foo do + param 'Integer', :param1 + param 'Any', :param2 + optional_param 'Array[String]', :param3 + return_type 'Undef' + end + + # An overview for the second overload. + # @param param The first parameter. + # @param block The block parameter. + # @return Returns a string. + dispatch :other do + param 'Boolean', :param + block_param + return_type 'String' + end +end + +# An example 4.x function with only one signature. +Puppet::Functions.create_function(:func4x_1) do + # @param param1 The first parameter. + # @return [Undef] Returns nothing. + dispatch :foobarbaz do + param 'Integer', :param1 + end +end + +# An example 3.x function +Puppet::Parser::Functions.newfunction(:func3x, doc: <<-DOC + Documentation for an example 3.x function. + @param param1 [String] The first parameter. + @param param2 [Integer] The second parameter. + @return [Undef] + @example Calling the function. + func3x('hi', 10) + DOC + ) do |*args| + #... +end + +Puppet::Type.type(:database).provide :linux do + desc 'An example provider on Linux.' + confine kernel: 'Linux' + confine osfamily: 'RedHat' + defaultfor :kernel => 'Linux' + defaultfor :osfamily => 'RedHat', :operatingsystemmajrelease => '7' + has_feature :implements_some_feature + has_feature :some_other_feature + commands foo: '/usr/bin/foo' +end + +Puppet::Type.newtype(:database) do + desc <<-DESC +An example database server type. +@option opts :foo bar +@raise SomeError +@example here's an example + database { 'foo': + address => 'qux.baz.bar', + } +DESC + feature :encryption, 'The provider supports encryption.', methods: [:encrypt] + ensurable do + desc 'What state the database should be in.' + defaultvalues + aliasvalue(:up, :present) + aliasvalue(:down, :absent) + defaultto :up + end + + newparam(:address) do + isnamevar + desc 'The database server name.' + end + + newparam(:encryption_key, required_features: :encryption) do + desc 'The encryption key to use.' + end + + newparam(:encrypt, :parent => Puppet::Parameter::Boolean) do + desc 'Whether or not to encrypt the database.' + defaultto false + end + + newproperty(:file) do + desc 'The database file to use.' + end + + newproperty(:log_level) do + desc 'The log level to use.' + newvalue(:debug) + newvalue(:warn) + newvalue(:error) + defaultto 'warn' + end +end + +Puppet::ResourceApi.register_type( + name: 'apt_key', + desc: <<-EOS, +@summary Example resource type using the new API. +@raise SomeError +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. +@example here's an example + apt_key { '6F6B15509CF8E59E6E469F327F438280EF8D349F': + source => 'http://apt.puppetlabs.com/pubkey.gpg' + } + +**Autorequires**: +If Puppet is given the location of a key file which looks like an absolute +path this type will autorequire that file. + EOS + attributes: { + ensure: { + type: 'Enum[present, absent]', + desc: 'Whether this apt key should be present or absent on the target system.' + }, + id: { + 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/]]', + behaviour: :namevar, + desc: 'The ID of the key you want to manage.', + }, + # ... + created: { + type: 'String', + behaviour: :read_only, + desc: 'Date the key was created, in ISO format.', + }, + }, + autorequires: { + file: '$source', # will evaluate to the value of the `source` attribute + package: 'apt', + }, +) +SOURCE + end + + let(:filename) { 'output.md' } + 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) + end + end + end + + describe 'rendering markdown to stdout' do + it 'should output the expected markdown content' do + expect{ PuppetStrings::Markdown.render }.to output(baseline).to_stdout + end + end +end