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