diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bc854320 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +*.gem +*.rbc +*.log +/.idea +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Used by dotenv library to load environment variables. +# .env + +# Ignore Byebug command history file. +.byebug_history + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc + +# Used by RuboCop. Remote config files pulled in from inherit_from directive. +# .rubocop-https?--* diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..1f670a23 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,16 @@ +AllCops: + NewCops: enable + Exclude: + - 'vendor/**/*' + - 'spec/fixtures/**/*' + - 'tmp/**/*' + - '.git/**/*' + - 'bin/*' + TargetRubyVersion: 3.0 + SuggestExtensions: false + +Metrics/AbcSize: + Max: 17 + +Style/Documentation: + Enabled: false diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 1526a689..ed7e9732 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -1,11 +1,14 @@ -class TestsController < Simpler::Controller +# frozen_string_literal: true +class TestsController < Simpler::Controller def index @time = Time.now + @tests = Test.all end - def create + def create; end + def show + @params = params end - end diff --git a/app/models/test.rb b/app/models/test.rb index 86376668..db135e04 100644 --- a/app/models/test.rb +++ b/app/models/test.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + # Simpler.application.db.create_table(:tests) do # primary_key :id # String :title, null: false # Integer :level, default: 0 # end class Test < Sequel::Model - end diff --git a/app/views/tests/index.html.erb b/app/views/tests/index.html.erb index 39fce580..6bf643d0 100644 --- a/app/views/tests/index.html.erb +++ b/app/views/tests/index.html.erb @@ -8,5 +8,11 @@

Simpler framework at work!

<%= @time %>

+ + - \ No newline at end of file + diff --git a/app/views/tests/show.html.erb b/app/views/tests/show.html.erb new file mode 100644 index 00000000..0429eda3 --- /dev/null +++ b/app/views/tests/show.html.erb @@ -0,0 +1,14 @@ + + + + + Show | Simpler application + + + +

<%= @time %>

+ +

<%= @params %>

+ + + diff --git a/config.ru b/config.ru index 3060cc20..c582bf46 100644 --- a/config.ru +++ b/config.ru @@ -1,3 +1,7 @@ +# frozen_string_literal: true + require_relative 'config/environment' +require_relative 'lib/middleware/logger' -run Simpler.application +use AppLogger +run Simpler.application.app_routes diff --git a/config/environment.rb b/config/environment.rb index 7a0d38c3..a566c8d3 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative '../lib/simpler' Simpler.application.bootstrap! diff --git a/config/routes.rb b/config/routes.rb index 4a751251..f6c3b1e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,7 @@ +# frozen_string_literal: true + Simpler.application.routes do + get '/tests/:id', 'tests#show' get '/tests', 'tests#index' post '/tests', 'tests#create' end diff --git a/lib/middleware/logger.rb b/lib/middleware/logger.rb new file mode 100644 index 00000000..6b54d0c5 --- /dev/null +++ b/lib/middleware/logger.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'logger' + +class AppLogger + def initialize(app) + @logger = Logger.new(Simpler.root.join('log/app.log')) + @app = app + end + + def call(env) + status, headers, response = @app.call(env) + @logger.info(log_create(env, status, headers)) + [status, headers, response] + end + + private + + def log_create(env, status, headers) + { + Request: "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']}", + Handler: "#{env['simpler.controller'].class}##{env['simpler.action']}", + Parameters: env['simpler.request_params'], + Response: "#{status} [#{headers['Content-Type']}] #{env['simpler.template_path']}" + } + end +end diff --git a/lib/simpler.rb b/lib/simpler.rb index f9dfe3c4..6fb23105 100644 --- a/lib/simpler.rb +++ b/lib/simpler.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'pathname' require_relative 'simpler/application' module Simpler - class << self def application Application.instance @@ -12,5 +13,4 @@ def root Pathname.new(File.expand_path('..', __dir__)) end end - end diff --git a/lib/simpler/application.rb b/lib/simpler/application.rb index 711946a9..b3f7f45d 100644 --- a/lib/simpler/application.rb +++ b/lib/simpler/application.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' require 'singleton' require 'sequel' @@ -6,10 +8,9 @@ module Simpler class Application - include Singleton - attr_reader :db + attr_reader :db, :app_routes, :router def initialize @router = Router.new @@ -24,12 +25,17 @@ def bootstrap! def routes(&block) @router.instance_eval(&block) + + route_paths = {} + @router.routes.each { |route| route_paths[route.path] = Simpler.application } + @app_routes = Rack::URLMap.new(route_paths) end def call(env) route = @router.route_for(env) controller = route.controller.new(env) action = route.action + env['simpler.request_params'] = setup_params(route, env['REQUEST_PATH']) make_response(controller, action) end @@ -54,5 +60,17 @@ def make_response(controller, action) controller.make_response(action) end + def setup_params(route, path) + params = {} + self_elements = route.route_elements + path_elements = path.split('/') + + path_elements.each do |e| + p_index = path_elements.index(e) + params[self_elements[p_index]] = e.to_i if e.to_i.positive? && self_elements[p_index].is_a?(Symbol) + end + + params + end end end diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index 9383b035..915aec4b 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + require_relative 'view' module Simpler class Controller - attr_reader :name, :request, :response def initialize(env) @name = extract_name @request = Rack::Request.new(env) @response = Rack::Response.new + @status_code = nil end def make_response(action) @@ -18,6 +20,7 @@ def make_response(action) set_default_headers send(action) write_response + @response.status = @status_code unless @status_code.nil? || @response.status == 500 @response.finish end @@ -43,12 +46,20 @@ def render_body end def params - @request.params + @request.env['simpler.request_params'] end def render(template) + template = { file: template } unless template.is_a?(Hash) @request.env['simpler.template'] = template end + def responce_status_set(code) + @status_code = code + end + + def responce_headers_set(headers) + headers.each { |k, v| @response[k.to_s] = v.to_s } + end end end diff --git a/lib/simpler/router.rb b/lib/simpler/router.rb index 14b3415c..a84ced96 100644 --- a/lib/simpler/router.rb +++ b/lib/simpler/router.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + require_relative 'router/route' module Simpler class Router + attr_reader :routes def initialize @routes = [] @@ -17,7 +20,7 @@ def post(path, route_point) def route_for(env) method = env['REQUEST_METHOD'].downcase.to_sym - path = env['PATH_INFO'] + path = env['REQUEST_PATH'] @routes.find { |route| route.match?(method, path) } end @@ -36,6 +39,5 @@ def add_route(method, path, route_point) def controller_from_string(controller_name) Object.const_get("#{controller_name.capitalize}Controller") end - end end diff --git a/lib/simpler/router/route.rb b/lib/simpler/router/route.rb index 4c66b4b7..5c8ac51f 100644 --- a/lib/simpler/router/route.rb +++ b/lib/simpler/router/route.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + module Simpler class Router class Route - - attr_reader :controller, :action + attr_reader :controller, :action, :path def initialize(method, path, controller, action) @method = method @@ -12,9 +13,24 @@ def initialize(method, path, controller, action) end def match?(method, path) - @method == method && path.match(@path) + @method == method && convert_to_route_format(path).match(@path) + end + + def route_elements + @path.split('/').map { |e| e.start_with?(':') ? e[1..].to_sym : e } end + private + + def convert_to_route_format(path) + self_elements = route_elements + path_elements = path.split('/') + + path_elements.map do |e| + p_index = path_elements.index(e) + e.to_i.positive? && self_elements[p_index].is_a?(Symbol) ? ":#{self_elements[p_index]}" : e + end.join('/') + end end end end diff --git a/lib/simpler/view.rb b/lib/simpler/view.rb index 19a73b34..4d60b5ca 100644 --- a/lib/simpler/view.rb +++ b/lib/simpler/view.rb @@ -1,18 +1,21 @@ +# frozen_string_literal: true + require 'erb' module Simpler class View - - VIEW_BASE_PATH = 'app/views'.freeze + VIEW_BASE_PATH = 'app/views' + RENDER_TYPE = { + file: :file_render, + plain: :plain_render + }.freeze def initialize(env) @env = env end def render(binding) - template = File.read(template_path) - - ERB.new(template).result(binding) + send(template ? RENDER_TYPE[template.keys.first] : :file_render, binding) end private @@ -30,10 +33,20 @@ def template end def template_path - path = template || [controller.name, action].join('/') + path = template ? template[:file] : [controller.name, action].join('/') + @env['simpler.template_path'] = "#{path}.html.erb" - Simpler.root.join(VIEW_BASE_PATH, "#{path}.html.erb") + Simpler.root.join(VIEW_BASE_PATH, @env['simpler.template_path']) end + def file_render(binding) + template = File.read(template_path) + + ERB.new(template).result(binding) + end + + def plain_render(_binding) + template[:plain] + end end end