Skip to content
This repository has been archived by the owner on Jul 29, 2020. It is now read-only.

Multiple customizations #1

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<require:chapters/introduction>
<require:chapters/changelog>
<require:chapters/resource1>
<require:chapters/resource2>
<require:chapters/resource3>
<require:chapters/resource4>
<require:chapters/resource5>
<require:chapters/errors>

#### Regenerate examples

Expand Down Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion lib/api_blueprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
10 changes: 8 additions & 2 deletions lib/api_blueprint/collect/controller_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
""
Expand All @@ -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
Expand All @@ -66,7 +72,7 @@ def dump_blueprint
'request' => {
'path' => request.path,
'method' => in_parser.method,
'params' => in_parser.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
Expand Down
4 changes: 3 additions & 1 deletion lib/api_blueprint/collect/merge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
81 changes: 60 additions & 21 deletions lib/api_blueprint/collect/renderer.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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} | <require:param/#{resource}/#{action}/#{md_name}_type> | <require:param/#{resource}/#{action}/#{md_name}_comment>\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<require:desc/#{resource}>\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<require:desc/#{resource}_#{action}>\n\n"
end

def signature(url, method)
Expand All @@ -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
30 changes: 30 additions & 0 deletions lib/api_blueprint/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,34 @@ class Railtie < Rails::Railtie
load 'tasks/blueprint.rake'
end
end

def self.blueprintfile(opts = {})
@hash = (opts[:force_load] ? load_yaml : @hash) || load_yaml

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 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
2 changes: 1 addition & 1 deletion lib/api_blueprint/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ApiBlueprint
VERSION = '0.1.1'
VERSION = '0.1.2'
end
53 changes: 14 additions & 39 deletions lib/tasks/blueprint.rake
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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}"

Expand All @@ -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
Expand All @@ -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

Expand All @@ -124,11 +98,12 @@ namespace :blueprint do
task :deploy => :environment do
Rake::Task["blueprint:compile"].execute

source = blueprintfile(:write_blueprint => false)['html']
target = blueprintfile(:write_blueprint => false)['deploy']
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 -q #{source} #{target}"
cmd = "scp #{target_port ? "-P #{deploy_port}": ''} -q #{source} #{target}"

puts "\nDeploying to '#{target}'..."

Expand Down