From 1374b67da0cc89df0949aa84da289d9e10a18c91 Mon Sep 17 00:00:00 2001 From: Eric Putnam Date: Mon, 22 Jan 2018 16:22:42 -0800 Subject: [PATCH] (PDOC-184) generate markdown This change does a few things: 1. Fixes up new api handler to return the stuff we want 2. Adds all the logic to parse YARD registries into markdown 3. Adds templates for markdown 4. Changes Face cli to use a --format option that can be used for either markdown or json --- lib/puppet-strings.rb | 36 +- lib/puppet-strings/markdown.rb | 32 ++ lib/puppet-strings/markdown/base.rb | 84 +++++ lib/puppet-strings/markdown/puppet_class.rb | 14 + lib/puppet-strings/markdown/puppet_classes.rb | 27 ++ .../markdown/puppet_defined_type.rb | 14 + .../markdown/puppet_defined_types.rb | 27 ++ .../markdown/puppet_function.rb | 31 ++ .../markdown/puppet_functions.rb | 28 ++ .../markdown/puppet_resource_type.rb | 26 ++ .../markdown/puppet_resource_types.rb | 27 ++ .../markdown/table_of_contents.rb | 15 + .../markdown/templates/puppet_function.erb | 24 ++ .../markdown/templates/puppet_resource.erb | 45 +++ .../templates/puppet_resource_type.erb | 83 +++++ .../markdown/templates/table_of_contents.erb | 24 ++ lib/puppet-strings/yard/code_objects/type.rb | 4 +- lib/puppet-strings/yard/handlers/ruby/base.rb | 2 +- .../yard/handlers/ruby/rsapi_handler.rb | 6 +- lib/puppet/face/strings.rb | 19 +- spec/acceptance/emit_json_options.rb | 8 +- spec/acceptance/generate_markdown_spec.rb | 49 +++ spec/fixtures/unit/markdown/output.md | 313 ++++++++++++++++++ spec/spec_helper.rb | 2 + .../unit/puppet-strings/markdown/base_spec.rb | 133 ++++++++ spec/unit/puppet-strings/markdown_spec.rb | 213 ++++++++++++ 26 files changed, 1259 insertions(+), 27 deletions(-) create mode 100644 lib/puppet-strings/markdown.rb create mode 100644 lib/puppet-strings/markdown/base.rb create mode 100644 lib/puppet-strings/markdown/puppet_class.rb create mode 100644 lib/puppet-strings/markdown/puppet_classes.rb create mode 100644 lib/puppet-strings/markdown/puppet_defined_type.rb create mode 100644 lib/puppet-strings/markdown/puppet_defined_types.rb create mode 100644 lib/puppet-strings/markdown/puppet_function.rb create mode 100644 lib/puppet-strings/markdown/puppet_functions.rb create mode 100644 lib/puppet-strings/markdown/puppet_resource_type.rb create mode 100644 lib/puppet-strings/markdown/puppet_resource_types.rb create mode 100644 lib/puppet-strings/markdown/table_of_contents.rb create mode 100644 lib/puppet-strings/markdown/templates/puppet_function.erb create mode 100644 lib/puppet-strings/markdown/templates/puppet_resource.erb create mode 100644 lib/puppet-strings/markdown/templates/puppet_resource_type.erb create mode 100644 lib/puppet-strings/markdown/templates/table_of_contents.erb create mode 100644 spec/acceptance/generate_markdown_spec.rb create mode 100644 spec/fixtures/unit/markdown/output.md create mode 100644 spec/unit/puppet-strings/markdown/base_spec.rb create mode 100644 spec/unit/puppet-strings/markdown_spec.rb diff --git a/lib/puppet-strings.rb b/lib/puppet-strings.rb index 410d92b..1e1254d 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] :format Specify output format (markdown or json) + # @option options [String] :path Write the selected format to a file path + # @option options [Boolean] :stdout Use this switch to pipe the selected format to STDOUT # @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,13 @@ 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] + if options[:json] || options[:markdown] + file = options[:path] # 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 +46,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(options[:path]) end + + # If outputting Markdown, render the output + if options[:markdown] + render_markdown(options[:path]) + 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/markdown.rb b/lib/puppet-strings/markdown.rb new file mode 100644 index 0000000..d40d35d --- /dev/null +++ b/lib/puppet-strings/markdown.rb @@ -0,0 +1,32 @@ +require 'puppet-strings/json' + +# module for parsing Yard Registries and generating markdown +module PuppetStrings::Markdown + require_relative 'markdown/puppet_classes' + require_relative 'markdown/puppet_functions' + require_relative 'markdown/puppet_defined_types' + require_relative 'markdown/puppet_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::PuppetDefinedTypes.render + final << PuppetStrings::Markdown::PuppetResourceTypes.render + final << PuppetStrings::Markdown::PuppetFunctions.render + + final + end + + def self.render(path = nil) + if path.nil? + puts generate + exit + else + File.open(path, 'w') { |file| file.write(generate) } + 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..528180a --- /dev/null +++ b/lib/puppet-strings/markdown/base.rb @@ -0,0 +1,84 @@ +require 'puppet-strings' +require 'puppet-strings/json' +require 'puppet-strings/yard' + +module PuppetStrings::Markdown + class Base + def initialize(registry, component_type) + @type = component_type + @registry = registry + @tags = registry[:docstring][:tags] || [] + end + + def name + @registry[:name].to_s unless @registry[:name].nil? + end + + def text + @registry[:docstring][:text] unless @registry[:docstring][:text].empty? + end + + def return_val + @tags.select { |tag| tag[:tag_name] == 'return' }[0][:text] unless @tags.select { |tag| tag[:tag_name] == 'return' }[0].nil? + end + + 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] array of @see tag hashes + def see + @tags.select { |tag| tag[:tag_name] == 'see' } unless @tags.select { |tag| tag[:tag_name] == 'see' }[0].nil? + end + + # return [String] text from @summary tag + def summary + @tags.select { |tag| tag[:tag_name] == 'summary' }[0][:text] unless @tags.select { |tag| tag[:tag_name] == 'summary' }[0].nil? + end + + # return [Array] array of 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] array of example tag hashes + def examples + @tags.select { |tag| tag[:tag_name] == 'example' } unless @tags.select { |tag| tag[:tag_name] == 'example' }[0].nil? + end + + def toc_info + { + name: name.to_s, + link: link, + desc: summary || @registry[:docstring][:text].gsub("\n", ". ") + } + end + + def link + name.delete('::').strip.gsub(' ','-').downcase + end + + def defaults + @registry[:defaults] unless @registry[:defaults].nil? + end + + def value_string(value) + to_symbol = %w[undef true false] + if to_symbol.include? value + return "`#{value}`" + else + return value + end + end + + 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/puppet_class.rb b/lib/puppet-strings/markdown/puppet_class.rb new file mode 100644 index 0000000..7907acb --- /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 = 'puppet_resource.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..83237d8 --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_classes.rb @@ -0,0 +1,27 @@ +require_relative 'puppet_class' + +module PuppetStrings::Markdown + module PuppetClasses + def self.in_classes + YARD::Registry.all(:puppet_class).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = "## 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/puppet_defined_type.rb b/lib/puppet-strings/markdown/puppet_defined_type.rb new file mode 100644 index 0000000..052052c --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_defined_type.rb @@ -0,0 +1,14 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class PuppetDefinedType < Base + def initialize(registry) + @template = 'puppet_resource.erb' + super(registry, 'defined type') + end + + def render + super(@template) + end + end +end diff --git a/lib/puppet-strings/markdown/puppet_defined_types.rb b/lib/puppet-strings/markdown/puppet_defined_types.rb new file mode 100644 index 0000000..7936871 --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_defined_types.rb @@ -0,0 +1,27 @@ +require_relative 'puppet_defined_type' + +module PuppetStrings::Markdown + module PuppetDefinedTypes + def self.in_dtypes + YARD::Registry.all(:puppet_defined_type).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = "## Defined types\n\n" + in_dtypes.each do |type| + final << PuppetStrings::Markdown::PuppetDefinedType.new(type).render + end + final + end + + def self.toc_info + final = [] + + in_dtypes.each do |type| + final.push(PuppetStrings::Markdown::PuppetDefinedType.new(type).toc_info) + end + + final + end + end +end diff --git a/lib/puppet-strings/markdown/puppet_function.rb b/lib/puppet-strings/markdown/puppet_function.rb new file mode 100644 index 0000000..cf39e54 --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_function.rb @@ -0,0 +1,31 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class PuppetFunction < Base + attr_reader :signatures + + def initialize(registry) + @template = 'puppet_function.erb' + super(registry, 'function') + @signatures = [] + registry[:signatures].each do |sig| + @signatures.push(Signature.new(sig)) + end + end + + def render + super(@template) + end + end + + class PuppetFunction::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/puppet_functions.rb b/lib/puppet-strings/markdown/puppet_functions.rb new file mode 100644 index 0000000..4f0dbd3 --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_functions.rb @@ -0,0 +1,28 @@ +require_relative 'puppet_function' + +module PuppetStrings::Markdown + module PuppetFunctions + def self.in_functions + YARD::Registry.all(:puppet_function).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = "## Functions\n\n" + in_functions.each do |func| + final << PuppetStrings::Markdown::PuppetFunction.new(func).render + end + final + end + + def self.toc_info + final = [] + + in_functions.each do |func| + final.push(PuppetStrings::Markdown::PuppetFunction.new(func).toc_info) + end + + final + end + end +end + diff --git a/lib/puppet-strings/markdown/puppet_resource_type.rb b/lib/puppet-strings/markdown/puppet_resource_type.rb new file mode 100644 index 0000000..bb9cc3d --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_resource_type.rb @@ -0,0 +1,26 @@ +require 'puppet-strings/markdown/base' + +module PuppetStrings::Markdown + class PuppetResourceType < Base + def initialize(registry) + @template = 'puppet_resource_type.erb' + super(registry, 'resource type') + end + + def render + super(@template) + end + + def properties + @registry[:properties] + end + + def parameters + @registry[:parameters] + end + + def regex_in_data_type?(data_type) + data_type.match(/\w+\[\/.*\/\]/).length > 0 + end + end +end diff --git a/lib/puppet-strings/markdown/puppet_resource_types.rb b/lib/puppet-strings/markdown/puppet_resource_types.rb new file mode 100644 index 0000000..faee0cb --- /dev/null +++ b/lib/puppet-strings/markdown/puppet_resource_types.rb @@ -0,0 +1,27 @@ +require_relative 'puppet_resource_type' + +module PuppetStrings::Markdown + module PuppetResourceTypes + def self.in_rtypes + YARD::Registry.all(:puppet_type).sort_by!(&:name).map!(&:to_hash) + end + + def self.render + final = "## Resource types\n\n" + in_rtypes.each do |type| + final << PuppetStrings::Markdown::PuppetResourceType.new(type).render + end + final + end + + def self.toc_info + final = [] + + in_rtypes.each do |type| + final.push(PuppetStrings::Markdown::PuppetResourceType.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..ec9a660 --- /dev/null +++ b/lib/puppet-strings/markdown/table_of_contents.rb @@ -0,0 +1,15 @@ +require 'puppet-strings/markdown/puppet_classes' + +module PuppetStrings::Markdown + module TableOfContents + def self.render + puppet_classes = PuppetStrings::Markdown::PuppetClasses.toc_info + puppet_defined_types = PuppetStrings::Markdown::PuppetDefinedTypes.toc_info + puppet_resource_types = PuppetStrings::Markdown::PuppetResourceTypes.toc_info + puppet_functions = PuppetStrings::Markdown::PuppetFunctions.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/puppet_function.erb b/lib/puppet-strings/markdown/templates/puppet_function.erb new file mode 100644 index 0000000..8be04f5 --- /dev/null +++ b/lib/puppet-strings/markdown/templates/puppet_function.erb @@ -0,0 +1,24 @@ +### <%= name %> + +<% signatures.each do |sig| -%> +#### `<%= sig.signature %>` + +<% if sig.text -%> +<%= sig.text %> + +<% end -%> +<% if sig.return_val -%> +Returns: `<%= sig.return_type %>` <%= sig.return_val %> + +<% end -%> +<% if sig.params -%> +<% sig.params.each do |param| -%> +##### `<%= param[:name] %>` + +Data type: `<%= param[:types][0] %>` + +<%= param[:text] %> + +<% end -%> +<% end -%> +<% end -%> diff --git a/lib/puppet-strings/markdown/templates/puppet_resource.erb b/lib/puppet-strings/markdown/templates/puppet_resource.erb new file mode 100644 index 0000000..f3fe15e --- /dev/null +++ b/lib/puppet-strings/markdown/templates/puppet_resource.erb @@ -0,0 +1,45 @@ +### <%= 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 defaults && defaults[param[:name]] -%> +Default value: <%= value_string(defaults[param[:name]]) %> + +<% end -%> +<% end -%> +<% end -%> + diff --git a/lib/puppet-strings/markdown/templates/puppet_resource_type.erb b/lib/puppet-strings/markdown/templates/puppet_resource_type.erb new file mode 100644 index 0000000..c12b8e8 --- /dev/null +++ b/lib/puppet-strings/markdown/templates/puppet_resource_type.erb @@ -0,0 +1,83 @@ +### <%= 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[:aliases] -%> +Aliases: <%= prop[:aliases].to_s.delete('{').delete('}') %> + +<% end -%> +<% if prop[:data_type] -%> +Data type: `<%= prop[:data_type] %>` + +<% end -%> +<%= prop[:description] %> + +<% 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[: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 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..ecf0735 --- /dev/null +++ b/lib/puppet-strings/markdown/templates/table_of_contents.erb @@ -0,0 +1,24 @@ +<% if puppet_classes -%> +## Classes +<% puppet_classes.each do |klassy| -%> +* [`<%= klassy[:name] %>`](#<%= klassy[:link] %>): <%= klassy[:desc] %> +<% end -%> +<% end -%> +<% if puppet_defined_types -%> +## Defined types +<% puppet_defined_types.each do |dtype| -%> +* [`<%= dtype[:name] %>`](#<%= dtype[:link] %>): <%= dtype[:desc] %> +<% end -%> +<% end -%> +<% if puppet_resource_types -%> +## Resource types +<% puppet_resource_types.each do |rtype| -%> +* [`<%= rtype[:name] %>`](#<%= rtype[:link] %>): <%= rtype[:desc] %> +<% end -%> +<% end -%> +<% if puppet_functions -%> +## Functions +<% puppet_functions.each do |func| -%> +* [`<%= func[:name] %>`](#<%= func[:link] %>): <%= func[:desc] %> +<% end -%> +<% end -%> 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..5b5f0a3 100644 --- a/lib/puppet/face/strings.rb +++ b/lib/puppet/face/strings.rb @@ -7,11 +7,11 @@ 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')." @@ -96,9 +96,14 @@ Puppet::Face.define(:strings, '0.0.1') do if options 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.casecmp 'markdown' + generate_options[:markdown] = true + elsif format.casecmp 'json' + generate_options[:json] = true + 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/markdown/output.md b/spec/fixtures/unit/markdown/output.md new file mode 100644 index 0000000..94a4ee2 --- /dev/null +++ b/spec/fixtures/unit/markdown/output.md @@ -0,0 +1,313 @@ +# 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 resource type. +## Functions +* [`func`](#func): A simple Puppet 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', +} +``` + + +#### 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. + +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. + +##### `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` resource 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` resource 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 resource type. + +#### Examples +##### here's an example +```puppet +database { 'foo': + address => 'qux.baz.bar', +} +``` + +#### Properties + +The following properties are available in the `database` resource 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` resource 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 + +#### `func(Integer $param1, Any $param2, String $param3 = hi)` + +A simple Puppet function. + +Returns: `Undef` Returns nothing. + +##### `param1` + +Data type: `Integer` + +First param. + +##### `param2` + +Data type: `Any` + +Second param. + +##### `param3` + +Data type: `String` + +Third param. + +### func4x + +#### `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. + +##### `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 + +#### `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 0b616ea..73abc3f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,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/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..18f5909 --- /dev/null +++ b/spec/unit/puppet-strings/markdown_spec.rb @@ -0,0 +1,213 @@ +require 'spec_helper' +require 'puppet-strings/markdown' +require 'puppet-strings/markdown/puppet_classes' +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', +# } +# @param param1 First param. +# @param param2 Second param. +# @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 +# @param param1 First param. +# @param param2 Second param. +# @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. +# @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. + # @param param1 The first parameter. + # @param param2 The second parameter. + # @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 + +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 resource type. +@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. + +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