From 44829192350d6bea83e9c443ce2b9bce06e680a4 Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 14:18:50 +0100 Subject: [PATCH 1/7] Add custom deploy port --- lib/tasks/blueprint.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tasks/blueprint.rake b/lib/tasks/blueprint.rake index 6b9b936..9a21791 100644 --- a/lib/tasks/blueprint.rake +++ b/lib/tasks/blueprint.rake @@ -126,9 +126,10 @@ namespace :blueprint do source = blueprintfile(:write_blueprint => false)['html'] target = blueprintfile(:write_blueprint => false)['deploy'] + deploy_port = blueprintfile(:write_blueprint => false)['deploy_port'] if source.present? && target.present? - cmd = "scp -q #{source} #{target}" + cmd = "scp #{target_port ? "-P #{deploy_port}": ''} -q #{source} #{target}" puts "\nDeploying to '#{target}'..." From a623965f9bf242e4baa514b907de1ae95b820790 Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 15:26:29 +0100 Subject: [PATCH 2/7] Change config loading scope, add opttion to bypass parameters fix for empty body parsing --- lib/api_blueprint/collect/controller_hook.rb | 4 +- lib/api_blueprint/railtie.rb | 26 ++++++++++ lib/tasks/blueprint.rake | 52 +++++--------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/api_blueprint/collect/controller_hook.rb b/lib/api_blueprint/collect/controller_hook.rb index 44ec667..3a6ddeb 100644 --- a/lib/api_blueprint/collect/controller_hook.rb +++ b/lib/api_blueprint/collect/controller_hook.rb @@ -32,7 +32,7 @@ def headers def body if input.content_type == 'application/json' - if input.body != 'null' + unless ['null', ''].include? input.body.strip JSON.parse(input.body) else "" @@ -66,7 +66,7 @@ def dump_blueprint 'request' => { 'path' => request.path, 'method' => in_parser.method, - 'params' => in_parser.params, + 'params' => in_parser.params.except(*Array(ApiBlueprint.blueprintfile['bypass_params'])), 'headers' => in_parser.headers, 'content_type' => request.content_type, 'accept' => request.accept diff --git a/lib/api_blueprint/railtie.rb b/lib/api_blueprint/railtie.rb index 4cfcf6f..de084b4 100644 --- a/lib/api_blueprint/railtie.rb +++ b/lib/api_blueprint/railtie.rb @@ -12,4 +12,30 @@ class Railtie < Rails::Railtie load 'tasks/blueprint.rake' end end + + def self.blueprintfile(opts = {}) + file = Rails.root.join("Blueprintfile") + + if File.exists?(file) + file = YAML.load_file(file) + + if ENV['group'] + hash = file[ENV['group']] || {} + else + hash = file.any? ? file.first[1] : {} + end + else + hash = {} + end + + if opts[:write_blueprint] != false && hash['blueprint'].present? && File.exists?(hash['blueprint']) + hash.delete('blueprint') + end + + ['spec', 'blueprint', 'html'].each do |param| + hash[param] = ENV[param] if ENV[param].present? + end + + hash + end end diff --git a/lib/tasks/blueprint.rake b/lib/tasks/blueprint.rake index 9a21791..e6220bf 100644 --- a/lib/tasks/blueprint.rake +++ b/lib/tasks/blueprint.rake @@ -1,29 +1,3 @@ -def blueprintfile(opts = {}) - file = Rails.root.join("Blueprintfile") - - if File.exists?(file) - file = YAML.load_file(file) - - if ENV['group'] - hash = file[ENV['group']] || {} - else - hash = file.any? ? file.first[1] : {} - end - else - hash = {} - end - - if opts[:write_blueprint] != false && hash['blueprint'].present? && File.exists?(hash['blueprint']) - hash.delete('blueprint') - end - - ['spec', 'blueprint', 'html'].each do |param| - hash[param] = ENV[param] if ENV[param].present? - end - - hash -end - def compile(source, target) compiler = ApiBlueprint::Compile::Compile.new(:source => source, :target => target, :logger => :stdout) compiler.compile @@ -58,7 +32,7 @@ namespace :blueprint do desc 'Generate request dumps for specified request spec(s)' task :generate => :environment do - args = blueprintfile['spec'] || "spec/requests/#{ENV['group'] || 'api'}" + args = ApiBlueprint.blueprintfile['spec'] || "spec/requests/#{ENV['group'] || 'api'}" opts = { :order => 'default', :format => 'documentation' } cmd = "API_BLUEPRINT_DUMP=1 bundle exec rspec #{opts.map{|k,v| "--#{k} #{v}"}.join(' ')} #{args}" @@ -69,30 +43,30 @@ namespace :blueprint do desc 'Merge all existing request dumps into single blueprint' task :merge => :environment do - target = blueprintfile['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile['blueprint'] || Rails.root.join('tmp', 'merge.md') - ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout, :naming => blueprintfile['naming']).merge + ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout, :naming => ApiBlueprint.blueprintfile['naming']).merge end end namespace :examples do desc 'Clear existing examples in blueprint' task :clear => :environment do - target = blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout).clear_examples end desc 'Uuse dumps to update examples in blueprint' task :update => :environment do - target = blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout).update_examples end desc 'Use dumps to replace examples in blueprint' task :replace => :environment do - target = blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout).clear_examples ApiBlueprint::Collect::Merge.new(:target => target, :logger => :stdout).update_examples @@ -101,16 +75,16 @@ namespace :blueprint do desc 'Compile the blueprint into complete HTML documentation' task :compile => :environment do - source = blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') - target = blueprintfile(:write_blueprint => false)['html'] || source.to_s.sub(/\.md$/, '.html') + source = ApiBlueprint.blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['html'] || source.to_s.sub(/\.md$/, '.html') compile(source, target) end desc 'Watch for changes in the blueprint and compile it into HTML on every change' task :watch => :environment do - source = blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') - target = blueprintfile(:write_blueprint => false)['html'] || source.to_s.sub(/\.md$/, '.html') + source = ApiBlueprint.blueprintfile(:write_blueprint => false)['blueprint'] || Rails.root.join('tmp', 'merge.md') + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['html'] || source.to_s.sub(/\.md$/, '.html') files = compile(source, target).partials @@ -124,9 +98,9 @@ namespace :blueprint do task :deploy => :environment do Rake::Task["blueprint:compile"].execute - source = blueprintfile(:write_blueprint => false)['html'] - target = blueprintfile(:write_blueprint => false)['deploy'] - deploy_port = blueprintfile(:write_blueprint => false)['deploy_port'] + source = ApiBlueprint.blueprintfile(:write_blueprint => false)['html'] + target = ApiBlueprint.blueprintfile(:write_blueprint => false)['deploy'] + deploy_port = ApiBlueprint.blueprintfile(:write_blueprint => false)['deploy_port'] if source.present? && target.present? cmd = "scp #{target_port ? "-P #{deploy_port}": ''} -q #{source} #{target}" From d71a320ad2fb56c2c4faece809b74154e3b2c25c Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 15:50:30 +0100 Subject: [PATCH 3/7] Optimize config file loading --- lib/api_blueprint/railtie.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/api_blueprint/railtie.rb b/lib/api_blueprint/railtie.rb index de084b4..e057bc5 100644 --- a/lib/api_blueprint/railtie.rb +++ b/lib/api_blueprint/railtie.rb @@ -14,28 +14,30 @@ class Railtie < Rails::Railtie end def self.blueprintfile(opts = {}) - file = Rails.root.join("Blueprintfile") + @hash ||= begin + file = Rails.root.join("Blueprintfile") - if File.exists?(file) - file = YAML.load_file(file) + if File.exists?(file) + file = YAML.load_file(file) - if ENV['group'] - hash = file[ENV['group']] || {} + if ENV['group'] + file[ENV['group']] || {} + else + file.any? ? file.first[1] : {} + end else - hash = file.any? ? file.first[1] : {} + {} end - else - hash = {} end - - if opts[:write_blueprint] != false && hash['blueprint'].present? && File.exists?(hash['blueprint']) - hash.delete('blueprint') + + if opts[:write_blueprint] != false && @hash['blueprint'].present? && File.exists?(@hash['blueprint']) + @hash.delete('blueprint') end ['spec', 'blueprint', 'html'].each do |param| - hash[param] = ENV[param] if ENV[param].present? + @hash[param] = ENV[param] if ENV[param].present? end - hash + @hash end end From ba56e1dfea77b1050e53f7af4bbaf0acbb629151 Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 17:09:50 +0100 Subject: [PATCH 4/7] Add change in markup structure to use smaller, not overridable portions --- lib/api_blueprint.rb | 2 +- lib/api_blueprint/collect/controller_hook.rb | 2 +- lib/api_blueprint/collect/merge.rb | 4 +- lib/api_blueprint/collect/renderer.rb | 81 +++++++++++++++----- lib/api_blueprint/railtie.rb | 32 ++++---- 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/lib/api_blueprint.rb b/lib/api_blueprint.rb index dff291a..a181b1e 100644 --- a/lib/api_blueprint.rb +++ b/lib/api_blueprint.rb @@ -4,6 +4,7 @@ module ApiBlueprint end require 'redcarpet' +require 'api_blueprint/railtie' require 'api_blueprint/collect/controller_hook' require 'api_blueprint/collect/merge' require 'api_blueprint/collect/preprocessor' @@ -12,5 +13,4 @@ module ApiBlueprint require 'api_blueprint/collect/storage' require 'api_blueprint/compile/compile' require 'api_blueprint/compile/storage' -require 'api_blueprint/railtie' require 'api_blueprint/version' diff --git a/lib/api_blueprint/collect/controller_hook.rb b/lib/api_blueprint/collect/controller_hook.rb index 3a6ddeb..8b56b67 100644 --- a/lib/api_blueprint/collect/controller_hook.rb +++ b/lib/api_blueprint/collect/controller_hook.rb @@ -66,7 +66,7 @@ def dump_blueprint 'request' => { 'path' => request.path, 'method' => in_parser.method, - 'params' => in_parser.params.except(*Array(ApiBlueprint.blueprintfile['bypass_params'])), + 'params' => in_parser.params.except(*Array(ApiBlueprint.blueprintfile(write_blueprint: false)['bypass_params'])), 'headers' => in_parser.headers, 'content_type' => request.content_type, 'accept' => request.accept diff --git a/lib/api_blueprint/collect/merge.rb b/lib/api_blueprint/collect/merge.rb index c88f4e1..c2916ed 100644 --- a/lib/api_blueprint/collect/merge.rb +++ b/lib/api_blueprint/collect/merge.rb @@ -100,10 +100,12 @@ def requests def body_content library.collect do |resource, actions| + renderer.resource = resource text = renderer.resource_header(resource) text += actions.collect do |action, info| - text = renderer.action_header(action) + renderer.action = action + text = renderer.action_header text += renderer.description_header text += renderer.signature(info[:path], info[:method]) diff --git a/lib/api_blueprint/collect/renderer.rb b/lib/api_blueprint/collect/renderer.rb index d7bc6e3..f8f3a2a 100644 --- a/lib/api_blueprint/collect/renderer.rb +++ b/lib/api_blueprint/collect/renderer.rb @@ -1,5 +1,7 @@ class ApiBlueprint::Collect::Renderer - def parameter_table(params, level = 0) + attr_reader :action, :resource + + def parameter_table(params, level = 0, nested_name = '') text = '' if level == 0 @@ -9,41 +11,32 @@ def parameter_table(params, level = 0) end params.each do |name, info| - comment = '' - comment = "Params for each #{name.singularize}:" if info[:type] == 'array' + comment = info[:type] == 'array' ? "Params for each #{name.singularize}:" : '' - text += "#{'[]' * level} #{name} | *#{info[:type]}*#{info[:example].present? ? " `Example: #{info[:example]}`" : ''} | #{comment}\n" + md_name = level > 0 ? "#{nested_name}_#{name}" : name + create_parameter_md(md_name, "*#{info[:type]}*#{info[:example].present? ? " `Example: #{info[:example]}`" : ''}", comment) + text += "#{'[]' * level} #{name} | | \n" if info[:type] == 'nested' || info[:type] == 'array' - text += parameter_table(info[:params], level + 1) + text += parameter_table(info[:params], level + 1, name) end end text += "\n" if level == 0 - - # text += "#### Parameters:\n\n" if level == 0 - # text += params.collect do |name, info| - # if info[:type] == 'nested' - # "#{' ' * (level * 2)}- **#{name}**\n" + - # parameter_table(info[:params], level + 1) - # else - # "#{' ' * (level * 2)}- **#{name}** (#{info[:type]}, `#{info[:example]}`)" - # end - # end.join("\n") - # text += "\n\n" if level == 0 - text end def resource_header(content) - "# Resource: #{content}\n\n" + create_resource_md + "# Resource: #{content}\n\n\n\n" end - def action_header(content) - "## Action: #{content}\n\n" + def action_header + "## Action: #{action}\n\n" end def description_header - "### Description:\n\n" + create_action_md + "### Description:\n\n\n\n" end def signature(url, method) @@ -67,4 +60,50 @@ def example_subheader(content) def code_block(content) content.split("\n").collect { |line| " " * 4 + line }.join("\n") + "\n\n" end + + def action=(value) + @action = safe_name(value) + end + + def resource=(value) + @resource = safe_name(value) + end + + private + + def chapters_path + @chapters_path ||= ApiBlueprint.blueprintfile(write_blueprint: false, force_load: true)['blueprint'].gsub(/\/?[^\/]+$/, '') + '/chapters' + end + + def create_resource_md + unless File.exists?(filepath = "#{chapters_path}/desc/#{resource}.md") + FileUtils.mkdir_p("#{chapters_path}/desc") unless File.exists?("#{chapters_path}/desc") + File.open(filepath, 'w') { |file| file.write("#{resource.singularize.capitalize} object preview:\n\n") } + end + end + + def create_action_md + unless File.exists?(filepath = "#{chapters_path}/desc/#{resource}_#{action}.md") + FileUtils.mkdir_p("#{chapters_path}/desc") unless File.exists?("#{chapters_path}/desc") + File.open(filepath, 'w') { |file| file.write("Method #{action} related to #{resource.singularize.capitalize} resource") } + end + end + + def create_parameter_md(name, type, comment) + unless File.directory?(dirpath = "#{chapters_path}/param/#{resource}/#{action}") + FileUtils.mkdir_p(dirpath) + end + + unless File.exists?(filepath = "#{dirpath}/#{name}_type.md") + File.open(filepath, 'w') { |file| file.write(type) } + end + + unless File.exists?(filepath = "#{dirpath}/#{name}_comment.md") + File.open(filepath, 'w') { |file| file.write(comment) } + end + end + + def safe_name(value) + ActiveSupport::Inflector.parameterize(value, '_').downcase + end end diff --git a/lib/api_blueprint/railtie.rb b/lib/api_blueprint/railtie.rb index e057bc5..50927a8 100644 --- a/lib/api_blueprint/railtie.rb +++ b/lib/api_blueprint/railtie.rb @@ -14,22 +14,8 @@ class Railtie < Rails::Railtie end def self.blueprintfile(opts = {}) - @hash ||= begin - file = Rails.root.join("Blueprintfile") + @hash = (opts[:force_load] ? load_yaml : @hash) || load_yaml - if File.exists?(file) - file = YAML.load_file(file) - - if ENV['group'] - file[ENV['group']] || {} - else - file.any? ? file.first[1] : {} - end - else - {} - end - end - if opts[:write_blueprint] != false && @hash['blueprint'].present? && File.exists?(@hash['blueprint']) @hash.delete('blueprint') end @@ -40,4 +26,20 @@ def self.blueprintfile(opts = {}) @hash end + + def self.load_yaml + file = Rails.root.join("Blueprintfile") + + if File.exists?(file) + file = YAML.load_file(file) + + if ENV['group'] + file[ENV['group']] || {} + else + file.any? ? file.first[1] : {} + end + else + {} + end + end end From c3d097d7c24353f87557ed1348baf7665b168533 Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 17:40:41 +0100 Subject: [PATCH 5/7] Add option to use custom handler for exceptions --- lib/api_blueprint/collect/controller_hook.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/api_blueprint/collect/controller_hook.rb b/lib/api_blueprint/collect/controller_hook.rb index 8b56b67..3e422b3 100644 --- a/lib/api_blueprint/collect/controller_hook.rb +++ b/lib/api_blueprint/collect/controller_hook.rb @@ -53,6 +53,12 @@ def human_header_key(key) def dump_blueprint_around yield + rescue StandardError => e + if respond_to?(:blueprint_rescue_from) + blueprint_rescue_from(e) + else + raise e + end ensure dump_blueprint end From 0c328079adfae74220858bd6159aeabd31d5035a Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 18:08:39 +0100 Subject: [PATCH 6/7] Update README --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b4e720..909a72d 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,30 @@ In order to auto-generate API method information you should invoke: > By default, all specs inside `spec/requests/api` are run. You can configure that by creating a [Blueprintfile](#configuration) configuration. -This will generate the `doc/api.md` file with a Markdown documentation ready to compile. If this file already exists, **api-blueprint** will not override it. It will write to `tmp/merge.md` instead so you can merge both existing and generated documentation manually in whatever way you want. +This will generate the `doc/api.md` file with a Markdown documentation. If this file already exists, **api-blueprint** will not override it. It will write to `tmp/merge.md` instead so you can merge both existing and generated documentation manually in whatever way you want. -Of course, it's just a starting point and you should at least fill in some resource, action and parameter descriptions. But that's a story for the **Compile** module. +Some reusable pieces of documentation will be placed in `doc/chapters/desc` and `doc/chapters/param` directories in addition. *desc* directory is a place for resource structure preview and methods' descriptions. + +Params directory keeps each method's parameters' types and descriptions, you can adjust them by giving better example and comments. + +Placeholders from `doc/api.md` markup will be replaced with the ones from *desc*/*param* directories, but you need to divide `api.md` into "chapters" manually and put them into `chapters` directory. + +Final `api.md` file (with resources extracted and moved to chapters) should look similar to: + + Title: Example.com API + + Host: http://example.com + + Copyright: Some company name + + + + + + + + + #### Regenerate examples @@ -82,6 +103,8 @@ api: blueprint: "doc/api.md" html: "doc/api.html" deploy: "user@server.com:/home/app/public/api.html" + deploy_port: "2022" + bypass_params: ['on', 'format'] naming: sessions: create: "Sign In" From 02dd321a2686e18b1badd07681f84b80532a1e8b Mon Sep 17 00:00:00 2001 From: Marcin Kot Date: Fri, 30 Oct 2015 18:10:42 +0100 Subject: [PATCH 7/7] Bump version number --- lib/api_blueprint/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api_blueprint/version.rb b/lib/api_blueprint/version.rb index 66793ff..e2e17c2 100644 --- a/lib/api_blueprint/version.rb +++ b/lib/api_blueprint/version.rb @@ -1,3 +1,3 @@ module ApiBlueprint - VERSION = '0.1.1' + VERSION = '0.1.2' end