lib/atd.rb in atd-0.4.0 vs lib/atd.rb in atd-0.5.0
- old
+ new
@@ -1,15 +1,19 @@
require_relative "atd/version"
require "rack"
require "webrick"
+require "mime-types"
+require_relative "atd/internal_helpers"
require_relative "atd/builtin_class_modifications"
+require_relative "atd/internal_helpers"
require_relative "atd/routes"
# Extension packs
# require_relative "extensions/precompilers"
# The assistant technical director of your website. It does the dirty work so you can see the big picture.
module ATD
+ using Refinements
# Creates a new ATD App based on the template of {ATD::App}.
# @return [ATD::App]
# @param [Symbol] name The name of the new app and new class generated.
def new(name)
app = Class.new(App)
@@ -18,15 +22,20 @@
end
# So called because each instance stores a route, and will be called if that route is reached.
# A route for the purposes of {ATD} is a parser that will be fed env in {ATD::App#call the rack app}.
class Route
- attr_accessor :args, :method, :block, :path, :output, :app, :actions, :status_code
+ include InternalHelpers
+ attr_reader :app
+ attr_accessor :args, :method, :block, :path, :output, :actions, :status_code, :filename, :headers
+
# The first two arguments must me the path and the output.
def initialize(*args, &block)
+ @args, @block, @path, @output, @actions, @status_code, @filename = nil
@status_code = 200
+ @headers = {}
@method = [:get, :post, :put, :patch, :delete]
@method = [] if args.last.is_a?(Hash) && !(args.last[:respond_to].nil? || args.last[:ignore].nil?)
@app = :DefaultApp
parse_args(*args, &block)
end
@@ -69,10 +78,11 @@
define_method(method) do |*args, &block|
# This conditional allows the syntax get post put "/", "Hello" because it passes
# the variables up through the different method calls.
if args.first.is_a?(ATD::Route)
@method = args.first.method
+ @filename = args.first.filename
@output = args.first.output
@path = args.first.path
@args = args.first.args
@block = args.first.block
@app = args.first.app
@@ -83,71 +93,69 @@
@method.uniq!
parse_args(*args, &block)
end
end
- # Converts an instance of {ATD::Route} into it's Hash representation.
- # The format for the Hash is listed {ATD::App#initialize here}
- # @api private
- def to_h
- routes = {}
- routes[@path] = {}
- routes[@path][@method] = {}
- routes[@path][@method] = {
- status_code: @status_code,
- output: @output,
- block: @block,
- args: @args,
- route: self
- }
- routes
- end
-
private
# This should also manage @method at some point
- def parse_args(*args, &block)
- args.compact!
- args.flatten!
- args.reject! { |arg| arg.is_a?(File) || arg.is_a?(Proc) || arg ? false : arg.empty? } # File doesn't respond to empty
- @block = block
- # This requires the format ATD::Route.new(path, route, args)
- @path ||= args.shift
- @output ||= args.shift
- @args = Array(@args).concat(args) unless args.nil?
+ def parse_args(path = "", output = "", *args, &block)
+ args = Hash(args[0]) if args.is_a? Array
+ if output.is_a? Hash
+ args.merge(output)
+ output = ""
+ end
+ @args = Hash(@args).merge(args)
+ # rubocop:disable Style/EmptyElse
+ if !path.is_a? ATD::Route
+ @block = block
+ @path = path
+ @output = output
+ else
+ # Maybe here it would make sense to assign path and output into args
+ end
+ # rubocop:enable Style/EmptyElse
# @output should be whatever the input is unless the input is a controller/action or the input is_file_string?
if @output =~ /^\w*#\w*$/ # Check if @path is a controller#action combo
controller, action = @output.split("#")
@action = Object.const_get(controller.to_sym).method(action.to_sym)
@output = @action.call
end
- # TODO: Choose one! They all work... I think...
- # Method 1:
- target_location = []
- caller_locations.each do |caller_location|
- target_dir = File.dirname(caller_location.absolute_path.to_s)
- target_location.push(target_dir) unless target_dir.include?(__dir__)
- end
- # Method 2:
- target_location = caller_locations.reject do |caller_location|
- File.dirname(caller_location.absolute_path.to_s).include? __dir__
- end
- output_full_path = "#{File.dirname(target_location[0].absolute_path)}/assets/#{@output}"
+ # These next few lines are working on the assumption that if the file exists we want it, and the precompiler is
+ # counting on that.
+ output_full_path = asset @output
@output = File.new(output_full_path) if File.exist?(output_full_path) && !Dir.exist?(output_full_path)
- if args.is_a?(Hash) || args.last.is_a?(Hash)
- @method += Array(args.last[:respond_to]) unless args.last[:respond_to].nil?
- @method -= Array(args.last[:ignore]) unless args.last[:ignore].nil?
- @status_code = args.last[:status] unless args.last[:status].nil?
- @status_code = args.last[:status_code] unless args.last[:status_code].nil?
- end
+ @method += Array(args[:respond_to]) unless args[:respond_to].nil?
+ @method -= Array(args[:ignore]) unless args[:ignore].nil?
+ @status_code = args[:status] unless args[:status].nil?
+ @status_code = args[:status_code] unless args[:status_code].nil?
+ Compilation.precompile(self)
+ @headers["content-type"] = MIME::Types.of(@filename)[0].to_s unless @filename.nil?
+ @block = @actions unless @actions.nil?
self
end
end
+ module Helpers
+ def http
+ { request: @request, response: @response, view: @view, method: @method, headers: @headers, status_code: @status_code }
+ end
+
+ def params
+ @request.params
+ end
+
+ def view
+ @view
+ end
+ end
+
# A template {App} that all Apps extend. When a new App is created with {ATD.new ATD.new} it extends this class.
class App
- attr_accessor :http
+ include Helpers
+ include Compilation
+
class << self
attr_accessor :routes # An array of instances of {ATD::Route} that belong to this {App}.
# Generates an instance of {ATD::Route}.
# Passes all arguments and the block to {Route.new the constructor} and sets the app where it was called from.
@@ -190,34 +198,19 @@
# }
# }
# }
# @param [Array] routes An array of instances of {ATD::Route}.
def initialize(routes = [])
- @routes = {}
- Array(routes + self.class.routes).each do |route|
- route = route.clone
- filename = ATD::Compilation.precompile(route, (route.args.last.is_a?(Hash) ? route.args.last[:precompile] : nil))
- route_hash = route.to_h
- current_route = route_hash[route.path][route.method]
- current_route[:filename] = filename
- block = current_route[:block]
- # An instance method must be defined from the block make it the same as the controller actions. We don't want to
- # convert the controller actions to blocks because if we did that, we would have to take them out of scope to allow
- # them to use the @http variables.
- current_route[:block] = define_singleton_method(block.object_id.to_s.tr("0-9", "a-j").to_sym, &block) unless block.nil?
- current_route[:block] = route.actions unless route.actions.nil?
- @routes = @routes.to_h.deep_merge(route_hash)
+ @routes = (routes + Array(self.class.routes)).each do |route|
+ Compilation.precompile(route) unless route.output.is_a? Hash
end
end
# Allows instance method route creation. Just another way of creating routes.
def request(*args, &block)
route = ATD::Route.new(*args, &block)
- filename = ATD::Compilation.precompile(route, (route.args.last.is_a?(Hash) ? route.args.last[:precompile] : nil))
- route_hash = route.to_h
- route_hash[route.path][route.method][:filename] = filename
- @routes = @routes.to_h.deep_merge(route_hash)
+ @routes += Array(route)
route
end
alias req request
alias r request
@@ -230,42 +223,44 @@
# This is the method which responds to .call, as the Rack spec requires.
# It will return status code 200 and whatever output corresponds the that route if it exists, and if it doesn't
# it will return status code 404 and the message "Error 404"
def call(env)
- @http = nil
- route = route(env)
- return error(404) if route.nil?
- route[:output] = Compilation.compile(route[:filename], route[:output]) unless !route[:args].nil? && !route[:args].empty? && route[:args][0].is_a?(Hash) && route[:args][0][:compile] == false
- return [route[:status_code].to_i, Hash(route[:headers]), Array(route[:output])] if route[:block].nil?
- http output: route[:output], request: Rack::Request.new(env), method: env["REQUEST_METHOD"], response: Rack::Response.new(env)
- return_val = method(route[:block]).call
- @http[:output] = return_val if @http[:output].nil?
- [@http[:status_code].to_i, Hash(@http[:headers]), Array(@http[:output])]
+ routes = @routes.where(path: env["PATH_INFO"], method: env["REQUEST_METHOD"].downcase.to_sym)
+ warn "WARNING: Multiple routes matched the request" if routes.length > 1
+ route = routes.first
+ return error 404 if route.nil?
+ output = route.output
+ output = Compilation.compile(route)[:content] unless route.args[:compile] == false
+ return [route.status_code.to_i, Hash(route.headers), Array(output)] if route.block.nil?
+ generate_variables(env, route)
+ return_val = instance_eval(&route.block) if route.block.is_a? Proc
+ return_val = method(route.block).call if route.block.is_a? Method
+ @view[:raw] = return_val if @view[:raw].nil? || @view[:raw].empty?
+ [@status_code.to_i, Hash(@headers), Array(@view[:raw])]
end
private
- def route(env)
- return nil if @routes[env["PATH_INFO"]].nil?
- # return @routes[env["PATH_INFO"]][[]] unless @routes[env["PATH_INFO"]][[]].nil?
- @routes[env["PATH_INFO"]].include_in_key?(env["REQUEST_METHOD"].downcase.to_sym)
+ def generate_variables(env, route)
+ @status_code = 200
+ @headers = {}
+ @view = { raw: route.output }
+ @request = Rack::Request.new(env)
+ @method = env["REQUEST_METHOD"]
+ @response = Rack::Response.new(env)
end
- def http(additional_params)
- @http = { status_code: 200, headers: {} }.merge(additional_params)
- end
-
def error(number)
[number, {}, ["Error #{number}"]]
end
end
module_function :new
end
# @return [ATD::Route]
def request(*args, &block)
- ATD::App.request(args, block)
+ ATD::App.request(*args, &block)
end
alias req request
alias r request
# Starts the rack server