lib/apipie/extractor.rb
require 'singleton'
require 'fileutils'
require 'set'
require 'yaml'
require 'apipie/extractor/recorder'
require 'apipie/extractor/writer'
require 'apipie/extractor/collector'
class Apipie::Railtie
initializer 'apipie.extractor' do |app|
ActiveSupport.on_load :action_controller do
before_action do |controller|
if Apipie.configuration.record
Apipie::Extractor.call_recorder.analyse_controller(controller)
end
end
end
if Apipie.configuration.record
app.middleware.use ::Apipie::Extractor::Recorder::Middleware
ActionController::TestCase.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
ActionController::TestCase::Behavior.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
end
end
end
module Apipie
module Extractor
class << self
def start(record)
Apipie.configuration.record = record
Apipie.configuration.force_dsl = true
end
def finish
record_params, record_examples = false, false
case Apipie.configuration.record
when "params" then record_params = true
when "examples" then record_examples = true
when "all" then record_params = true, record_examples = true
end
if record_examples
puts "Writing examples to a file"
write_examples
end
if record_params
puts "Updating auto-generated documentation"
write_docs
end
end
def logger
Rails.logger
end
def call_recorder
Thread.current[:apipie_call_recorder] ||= Recorder.new
end
def call_finished
@collector ||= Collector.new
if record = call_recorder.record
@collector.handle_record(record)
end
end
def clean_call_recorder
Thread.current[:apipie_call_recorder] = nil
end
def write_docs
Writer.new(@collector).write_docs if @collector
end
def write_examples
Writer.new(@collector).write_examples if @collector
end
def apis_from_routes
return @apis_from_routes if @apis_from_routes
@api_prefix = Apipie.api_base_url.sub(%r{/$},"")
populate_api_routes
update_api_descriptions
@apis_from_routes
end
def controller_path(name)
Apipie.api_controllers_paths.detect { |p| p.include?("#{name}_controller.rb") }
end
private
def all_api_routes
all_routes = Apipie.configuration.api_routes.routes.map do |r|
{
:verb => case r.verb
when Regexp then r.verb.source[/\w+/]
else r.verb.to_s
end,
:path => case
when r.path.respond_to?(:spec) then r.path.spec.to_s
else r.path.to_s
end,
:controller => r.requirements[:controller],
:action => r.requirements[:action]
}
end
return all_routes.find_all do |r|
r[:path].starts_with?(Apipie.api_base_url)
end
end
def populate_api_routes
@apis_from_routes = Hash.new { |h, k| h[k] = [] }
all_api_routes.each do |route|
controller_path, action = route[:controller], route[:action]
next unless controller_path && action
controller_path = controller_path.split('::').map(&:camelize).join('::')
controller = "#{controller_path}Controller"
path = if /^#{Regexp.escape(@api_prefix)}(.*)$/ =~ route[:path]
$1.sub(/\(\.:format\)$/,'')
else
nil
end
if route[:verb].present?
@apis_from_routes[[controller, action]] << {:method => route[:verb], :path => path}
end
end
end
def all_apis_from_docs
resource_descriptions = Apipie.resource_descriptions.values.map(&:values).flatten
method_descriptions = resource_descriptions.map(&:method_descriptions).flatten
return method_descriptions.reduce({}) do |h, desc|
apis = desc.method_apis_to_json.map do |api|
{ :method => api[:http_method],
:path => api[:api_url],
:desc => api[:short_description] }
end
h.update(desc.id => apis)
end
end
def update_api_descriptions
apis_from_docs = all_apis_from_docs
@apis_from_routes.each do |(controller, action), new_apis|
method_key = "#{Apipie.get_resource_id(controller.safe_constantize || next)}##{action}"
old_apis = apis_from_docs[method_key] || []
new_apis.each do |new_api|
new_api[:path]&.sub!(/\(\.:format\)$/,"")
old_api = old_apis.find do |api|
api[:path] == "#{@api_prefix}#{new_api[:path]}"
end
if old_api
new_api[:desc] = old_api[:desc]
end
end
end
end
end
end
end
if ENV["APIPIE_RECORD"]
Apipie::Extractor.start ENV["APIPIE_RECORD"]
end
at_exit do
Apipie::Extractor.finish
end