From 150cf077f1acd36476139a63dd3f7ae5b42c33e7 Mon Sep 17 00:00:00 2001 From: Giovanni Cappellotto Date: Mon, 10 Mar 2014 15:44:37 +0100 Subject: [PATCH] Add support for Markdown format --- README.md | 3 +- features/markdown_documentation.feature | 228 ++++++++++++++++++ lib/rspec_api_documentation.rb | 3 + .../views/markdown_example.rb | 16 ++ .../views/markdown_index.rb | 14 ++ .../writers/markdown_writer.rb | 19 ++ spec/writers/markdown_writer_spec.rb | 35 +++ .../markdown_example.mustache | 68 ++++++ .../markdown_index.mustache | 10 + 9 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 features/markdown_documentation.feature create mode 100644 lib/rspec_api_documentation/views/markdown_example.rb create mode 100644 lib/rspec_api_documentation/views/markdown_index.rb create mode 100644 lib/rspec_api_documentation/writers/markdown_writer.rb create mode 100644 spec/writers/markdown_writer_spec.rb create mode 100644 templates/rspec_api_documentation/markdown_example.mustache create mode 100644 templates/rspec_api_documentation/markdown_index.mustache diff --git a/README.md b/README.md index 912a922a..9aaedc3d 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ RspecApiDocumentation.configure do |config| # An array of output format(s). # Possible values are :json, :html, :combined_text, :combined_json, - # :json_iodocs, :textile, :append_json + # :json_iodocs, :textile, :markdown, :append_json config.format = [:html] # Location of templates @@ -144,6 +144,7 @@ end * **combined_json**: Generates a single file for all examples. * **json_iodocs**: Generates [I/O Docs](http://www.mashery.com/product/io-docs) style documentation. * **textile**: Generates an index file and example files in Textile. +* **markdown**: Generates an index file and example files in Markdown. * **append_json**: Lets you selectively run specs without destroying current documentation. See section below. ### append_json diff --git a/features/markdown_documentation.feature b/features/markdown_documentation.feature new file mode 100644 index 00000000..681855f3 --- /dev/null +++ b/features/markdown_documentation.feature @@ -0,0 +1,228 @@ +Feature: Generate Markdown documentation from test examples + + Background: + Given a file named "app.rb" with: + """ + require 'sinatra' + + class App < Sinatra::Base + get '/orders' do + content_type :json + + [200, [{ name: 'Order 1', amount: 9.99, description: nil }, + { name: 'Order 2', amount: 100.0, description: 'A great order' }].to_json] + end + + get '/orders/:id' do + content_type :json + + [200, { order: { name: 'Order 1', amount: 100.0, description: 'A great order' } }.to_json] + end + + post '/orders' do + 201 + end + + put '/orders/:id' do + 200 + end + + delete '/orders/:id' do + 200 + end + + get '/help' do + [200, 'Welcome Henry !'] + end + end + """ + And a file named "app_spec.rb" with: + """ + require "rspec_api_documentation" + require "rspec_api_documentation/dsl" + + RspecApiDocumentation.configure do |config| + config.app = App + config.api_name = "Example API" + config.format = :markdown + config.request_headers_to_include = %w[Content-Type Host] + config.response_headers_to_include = %w[Content-Type Content-Length] + end + + resource 'Orders' do + get '/orders' do + + example_request 'Getting a list of orders' do + status.should eq(200) + response_body.should eq('[{"name":"Order 1","amount":9.99,"description":null},{"name":"Order 2","amount":100.0,"description":"A great order"}]') + end + end + + get '/orders/:id' do + let(:id) { 1 } + + example_request 'Getting a specific order' do + status.should eq(200) + response_body.should == '{"order":{"name":"Order 1","amount":100.0,"description":"A great order"}}' + end + end + + post '/orders' do + parameter :name, 'Name of order', :required => true + parameter :amount, 'Amount paid', :required => true + parameter :description, 'Some comments on the order' + + let(:name) { "Order 3" } + let(:amount) { 33.0 } + + example_request 'Creating an order' do + status.should == 201 + end + end + + put '/orders/:id' do + parameter :name, 'Name of order', :required => true + parameter :amount, 'Amount paid', :required => true + parameter :description, 'Some comments on the order' + + let(:id) { 2 } + let(:name) { "Updated name" } + + example_request 'Updating an order' do + status.should == 200 + end + end + + delete "/orders/:id" do + let(:id) { 1 } + + example_request "Deleting an order" do + status.should == 200 + end + end + end + + resource 'Help' do + get '/help' do + example_request 'Getting welcome message' do + status.should eq(200) + response_body.should == 'Welcome Henry !' + end + end + + end + """ + When I run `rspec app_spec.rb --require ./app.rb --format RspecApiDocumentation::ApiFormatter` + + Scenario: Output helpful progress to the console + Then the output should contain: + """ + Generating API Docs + Orders + GET /orders + * Getting a list of orders + GET /orders/:id + * Getting a specific order + POST /orders + * Creating an order + PUT /orders/:id + * Updating an order + DELETE /orders/:id + * Deleting an order + Help + GET /help + * Getting welcome message + """ + And the output should contain "6 examples, 0 failures" + And the exit status should be 0 + + Scenario: Index file should look like we expect + Then the file "doc/api/index.markdown" should contain exactly: + """ + # Example API + + ## Help + + * [Getting welcome message](help/getting_welcome_message.markdown) + + ## Orders + + * [Creating an order](orders/creating_an_order.markdown) + * [Deleting an order](orders/deleting_an_order.markdown) + * [Getting a list of orders](orders/getting_a_list_of_orders.markdown) + * [Getting a specific order](orders/getting_a_specific_order.markdown) + * [Updating an order](orders/updating_an_order.markdown) + + + """ + + Scenario: Example 'Creating an order' file should look like we expect + Then the file "doc/api/orders/creating_an_order.markdown" should contain exactly: + """ + # Orders API + + ## Creating an order + + ### POST /orders + + + ### Parameters + + Name : name *- required -* + Description : Name of order + + Name : amount *- required -* + Description : Amount paid + + Name : description + Description : Some comments on the order + + ### Request + + #### Headers + +
Host: example.org
+    Content-Type: application/x-www-form-urlencoded
+ + #### Route + +
POST /orders
+ + + #### Body + +
name=Order+3&amount=33.0
+ + + ### Response + + #### Headers + +
Content-Type: text/html;charset=utf-8
+    Content-Length: 0
+ + #### Status + +
201 Created
+ + + + + """ + + Scenario: Example 'Deleting an order' file should be created + Then a file named "doc/api/orders/deleting_an_order.markdown" should exist + + Scenario: Example 'Getting a list of orders' file should be created + Then a file named "doc/api/orders/getting_a_list_of_orders.markdown" should exist + + Scenario: Example 'Getting a specific order' file should be created + Then a file named "doc/api/orders/getting_a_specific_order.markdown" should exist + + Scenario: Example 'Updating an order' file should be created + Then a file named "doc/api/orders/updating_an_order.markdown" should exist + + Scenario: Example 'Getting welcome message' file should be created + Then a file named "doc/api/help/getting_welcome_message.markdown" should exist + + diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index f3bba058..b50569c9 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -32,6 +32,7 @@ module Writers autoload :GeneralMarkupWriter autoload :HtmlWriter autoload :TextileWriter + autoload :MarkdownWriter autoload :JsonWriter autoload :AppendJsonWriter autoload :JsonIodocsWriter @@ -49,6 +50,8 @@ module Views autoload :HtmlExample autoload :TextileIndex autoload :TextileExample + autoload :MarkdownIndex + autoload :MarkdownExample end def self.configuration diff --git a/lib/rspec_api_documentation/views/markdown_example.rb b/lib/rspec_api_documentation/views/markdown_example.rb new file mode 100644 index 00000000..86de45e8 --- /dev/null +++ b/lib/rspec_api_documentation/views/markdown_example.rb @@ -0,0 +1,16 @@ +module RspecApiDocumentation + module Views + class MarkdownExample < MarkupExample + EXTENSION = 'markdown' + + def initialize(example, configuration) + super + self.template_name = "rspec_api_documentation/markdown_example" + end + + def extension + EXTENSION + end + end + end +end diff --git a/lib/rspec_api_documentation/views/markdown_index.rb b/lib/rspec_api_documentation/views/markdown_index.rb new file mode 100644 index 00000000..5f1262dd --- /dev/null +++ b/lib/rspec_api_documentation/views/markdown_index.rb @@ -0,0 +1,14 @@ +module RspecApiDocumentation + module Views + class MarkdownIndex < MarkupIndex + def initialize(index, configuration) + super + self.template_name = "rspec_api_documentation/markdown_index" + end + + def examples + @index.examples.map { |example| MarkdownExample.new(example, @configuration) } + end + end + end +end diff --git a/lib/rspec_api_documentation/writers/markdown_writer.rb b/lib/rspec_api_documentation/writers/markdown_writer.rb new file mode 100644 index 00000000..a4231cbf --- /dev/null +++ b/lib/rspec_api_documentation/writers/markdown_writer.rb @@ -0,0 +1,19 @@ +module RspecApiDocumentation + module Writers + class MarkdownWriter < GeneralMarkupWriter + EXTENSION = 'markdown' + + def markup_index_class + RspecApiDocumentation::Views::MarkdownIndex + end + + def markup_example_class + RspecApiDocumentation::Views::MarkdownExample + end + + def extension + EXTENSION + end + end + end +end diff --git a/spec/writers/markdown_writer_spec.rb b/spec/writers/markdown_writer_spec.rb new file mode 100644 index 00000000..24f295c1 --- /dev/null +++ b/spec/writers/markdown_writer_spec.rb @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +require 'spec_helper' + +describe RspecApiDocumentation::Writers::MarkdownWriter do + let(:index) { RspecApiDocumentation::Index.new } + let(:configuration) { RspecApiDocumentation::Configuration.new } + + describe ".write" do + let(:writer) { double(:writer) } + + it "should build a new writer and write the docs" do + described_class.stub(:new).with(index, configuration).and_return(writer) + writer.should_receive(:write) + described_class.write(index, configuration) + end + end + + describe "#write" do + let(:writer) { described_class.new(index, configuration) } + + before do + template_dir = File.join(configuration.template_path, "rspec_api_documentation") + FileUtils.mkdir_p(template_dir) + File.open(File.join(template_dir, "markdown_index.mustache"), "w+") { |f| f << "{{ mustache }}" } + FileUtils.mkdir_p(configuration.docs_dir) + end + + it "should write the index" do + writer.write + index_file = File.join(configuration.docs_dir, "index.markdown") + File.exists?(index_file).should be_true + end + end + +end diff --git a/templates/rspec_api_documentation/markdown_example.mustache b/templates/rspec_api_documentation/markdown_example.mustache new file mode 100644 index 00000000..cbbedb40 --- /dev/null +++ b/templates/rspec_api_documentation/markdown_example.mustache @@ -0,0 +1,68 @@ +# {{ resource_name }} API + +## {{ description }} + +### {{ http_method }} {{ route }} + +{{# explanation }} +{{ explanation }} +{{/ explanation }} + +{{# has_parameters? }} +### Parameters +{{# parameters }} + +Name : {{ name }} {{# required }} *- required -*{{/ required }} +Description : {{ description }} +{{/ parameters }} +{{/ has_parameters? }} + +{{# requests }} +### Request + +#### Headers + +
{{ request_headers_text }}
+ +#### Route + +
{{ request_method }} {{ request_path }}
+ +{{# request_query_parameters_text }} +#### Query Parameters + +
{{ request_query_parameters_text }}
+{{/ request_query_parameters_text }} + +{{# request_body }} +#### Body + +
{{{ request_body }}}
+{{/ request_body }} + +{{# curl }} +#### cURL + +
{{ curl }}
+{{/ curl }} + +{{# response_status }} +### Response + +#### Headers + +
{{ response_headers_text }}
+ +#### Status + +
{{ response_status }} {{ response_status_text}}
+ +{{# response_body }} +#### Body + +
{{{ response_body }}}
+{{/ response_body }} +{{/ response_status }} + +{{/ requests }} + diff --git a/templates/rspec_api_documentation/markdown_index.mustache b/templates/rspec_api_documentation/markdown_index.mustache new file mode 100644 index 00000000..bdd11f55 --- /dev/null +++ b/templates/rspec_api_documentation/markdown_index.mustache @@ -0,0 +1,10 @@ +# {{ api_name }} + +{{# sections }} +## {{ resource_name }} + +{{# examples }} +* [{{ description }}]({{ dirname }}/{{ filename }}) +{{/ examples }} + +{{/ sections }}