yokunai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c1274ce0c2f7dacdd99c1abc30bed8deab94fc7b
4
+ data.tar.gz: e7d4494b0ffd794ee6044c899b78cb3e5738ce48
5
+ SHA512:
6
+ metadata.gz: 8c4886093c184821c2f726d584e6c56c3e76d7eafb7f5a276bef6d65363abe4bd71929e2c322a8426665a2dc0321d86931e194b25384dbac6d824526a99ef590
7
+ data.tar.gz: 8811bce56b21078bef89fb97dcdb7ac54557631635f38199a5b552700dd7065584443a988ed85d270eeb6b4031093cb31aa5014127f6864552518c5d778d9ad9
data/README.markdown ADDED
@@ -0,0 +1,72 @@
1
+ # よくない
2
+
3
+ It's not very good.
4
+
5
+ Please don't actually use this.
6
+
7
+ ## How to use it
8
+
9
+ Add the dependency:
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ source "https://rubygems.org/"
14
+
15
+ gem "yokunai"
16
+ ```
17
+
18
+ Run `bundle`, of course. Then make a controller:
19
+
20
+ ```ruby
21
+ # lib/my_app.rb
22
+ require "yokunai"
23
+
24
+ ROUTES = {
25
+ %r{^/$} => {class: "HomeController", methods: ["GET"]}
26
+ }.freeze
27
+
28
+ class HomeController < Yokunai::AbstractController
29
+ def get
30
+ respond body: "It works!"
31
+ end
32
+ end
33
+ ```
34
+
35
+ Add some configuration for where you want to keep things:
36
+
37
+ ```yml
38
+ # config/development.yml
39
+ ---
40
+ template_dir: web/views
41
+ asset_dir: web/assets
42
+ ```
43
+
44
+ Now just make a rackup config so we can run something:
45
+
46
+ ```ruby
47
+ # config.ru
48
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
49
+ require "my_app"
50
+
51
+ run Yokunai::Application.new(
52
+ route_map: ROUTES,
53
+ base_dir: File.dirname(__FILE__)
54
+ )
55
+ ```
56
+
57
+ Then just `bundle exec rackup` and you're gold, Ponyboy.
58
+
59
+ ## Assets
60
+
61
+ You probably have frontend assets, if this is a web page. There's a controller
62
+ built-in for that if you want it. Just point a path with a capture called `name`
63
+ to `Yokunai::StaticController`.
64
+
65
+ ```ruby
66
+ ROUTES = {
67
+ %r{^/assets/(?<name>.+)$} => {class: "Yokunai::StaticController", methods: ["GET"]}
68
+ }
69
+ ```
70
+
71
+ Now assets will be served out of the directory you set as your `asset_dir` in
72
+ the YAML config.
data/lib/yokunai.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Dependencies
2
+ require "mimemagic"
3
+ require "ostruct"
4
+ require "yaml"
5
+
6
+ # Core
7
+ require "yokunai/config"
8
+ require "yokunai/application"
9
+ require "yokunai/render_context"
10
+ require "yokunai/template"
11
+ require "yokunai/mime"
12
+
13
+ # Controllers
14
+ require "yokunai/abstract_controller"
15
+ require "yokunai/errors_controller"
16
+ require "yokunai/static_controller"
@@ -0,0 +1,79 @@
1
+ module Yokunai
2
+ class AbstractController
3
+
4
+ # --------------------------------------------------------------------------
5
+ # Constants
6
+ # --------------------------------------------------------------------------
7
+
8
+ DEFAULT_HEADERS = {
9
+ "Content-Type" => "text/html",
10
+ "Server" => "yokunai/1.0"
11
+ }
12
+
13
+
14
+ # --------------------------------------------------------------------------
15
+ # Instance Methods
16
+ # --------------------------------------------------------------------------
17
+
18
+ # @param env [Rack::Env] The Rack ENV
19
+ # @param captures [MatchData] The named captures from the route regex
20
+ def initialize(env, captures=nil)
21
+ @env = env
22
+ @captures = captures
23
+ @templates = Yokunai::Template.new
24
+ end
25
+
26
+
27
+ # --------------------------------------------------------------------------
28
+ # Default HTTP method handlers
29
+ # --------------------------------------------------------------------------
30
+
31
+ def get
32
+ unsupported_method
33
+ end
34
+
35
+ def post
36
+ unsupported_method
37
+ end
38
+
39
+ def put
40
+ unsupported_method
41
+ end
42
+
43
+ def patch
44
+ unsupported_method
45
+ end
46
+
47
+ def delete
48
+ unsupported_method
49
+ end
50
+
51
+ def options
52
+ unsupported_method
53
+ end
54
+
55
+ private
56
+
57
+ def respond(code: 200, headers: {}, body: "", template: nil, context: {})
58
+ if template
59
+ return respond_error(:not_found) unless @templates.exist?(template)
60
+ body = @templates.render(template, context)
61
+ end
62
+
63
+ [
64
+ code,
65
+ DEFAULT_HEADERS.merge(headers),
66
+ [body]
67
+ ]
68
+ end
69
+
70
+ def respond_error(meth)
71
+ Yokunai::ErrorsController.new(@env).public_send(meth)
72
+ end
73
+
74
+ def unsupported_method
75
+ [405, {}, ["Error 405: Method not supported on this resource.\n"]]
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,40 @@
1
+ module Yokunai
2
+ # The Rack-compatible entrypoint class. Acts as the router, sending the
3
+ # request to the appropriate controller for handling.
4
+ class Application
5
+
6
+ # @param route_map [Hash] A hash with path regexes as keys, and hashes as values with the controller to send matching requests to.
7
+ # @param base_dir [String] The absolute base directory to use for various lookups.
8
+ def initialize(route_map:, base_dir:)
9
+ @routes = route_map
10
+ Yokunai::Config.base_dir = base_dir
11
+ Yokunai::Config.populate(ENV["YOKUNAI_ENV"] || "development")
12
+ end
13
+
14
+ # Route a request to the correct controller based on the given data.
15
+ #
16
+ # @param path [String] the domain-relative path being requested.
17
+ # @param env [Rack::Env] the full Rack environment
18
+ # @return [Array] a Rack-compatible response array.
19
+ def call(env)
20
+ route = @routes.map do |exp, meta|
21
+ next unless matches = env["PATH_INFO"].match(exp)
22
+ meta.merge({captures: matches})
23
+ end.compact.first
24
+
25
+ unless route
26
+ return Yokunai::ErrorsController.new(env).not_found
27
+ end
28
+
29
+ request_method = env["REQUEST_METHOD"]
30
+ if route[:methods].include?(request_method)
31
+ Object.const_get(route[:class])
32
+ .new(env, route[:captures])
33
+ .public_send(request_method.downcase)
34
+ else
35
+ Yokunai::ErrorsController.new(env).unsupported_method
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ module Yokunai
2
+ # A centralized way to get configuration options. Reads values out of
3
+ # `config.yml` (or whatever name is passed to `populate`).
4
+ class Config
5
+
6
+ # Set the base directory path.
7
+ #
8
+ # @param base_dir [String]
9
+ # @return [String]
10
+ def self.base_dir=(dir)
11
+ @@base_dir = dir
12
+ end
13
+
14
+ # Get the base directory path.
15
+ #
16
+ # @return [String]
17
+ def self.base_dir
18
+ @@base_dir
19
+ end
20
+
21
+ # Loads the config into memory from disk. Called automatically on the first
22
+ # cold-get, but can be called manually to warm up.
23
+ #
24
+ # @param name [String] the name of the confg file to load. If not provided,
25
+ # will use the value of YOKUNAI_ENV.
26
+ def self.populate(name = nil)
27
+ name ||= ENV["YOKUNAI_ENV"]
28
+ @@config = YAML.load_file(File.join(@@base_dir, "config", "#{name}.yml"))
29
+ end
30
+
31
+ # Get the value of a config option
32
+ #
33
+ # @param key [String] the key to fetch
34
+ # @return [any] whatever key contains
35
+ def self.get(key)
36
+ populate unless class_variable_defined?(:@@config)
37
+ @@config[key]
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ module Yokunai
2
+ class ErrorsController < AbstractController
3
+
4
+ def not_found
5
+ respond(code: 404, body: "Error 404: Could not locate resource.")
6
+ end
7
+
8
+ def unsupported_method
9
+ respond(code: 405, body: "Error 405: Unsupported HTTP method.")
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Yokunai
2
+ # Provides a light wrapper around MimeMagic to make it a little easier to use.
3
+ class Mime
4
+
5
+ # Detect the mime type from the given path string.
6
+ #
7
+ # @param path [String] the path
8
+ # @return [String] the mime type string
9
+ def self.detect_from_path(path, fallback = "text/plain")
10
+ mime = MimeMagic.by_path(path)
11
+ mime ? mime.type : fallback
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ module Yokunai
2
+ # Provides a clean binding and basic API to add variables to it. Meant to be
3
+ # used only for template rendering.
4
+ class RenderContext
5
+
6
+ # Instantiate a new instance and create local variables for the key/value
7
+ # pairs in the given hash.
8
+ #
9
+ # @param context [Hash] variables to set on the local binding
10
+ # @return [Yokunai::RenderContext]
11
+ def initialize(context = {})
12
+ @binding = binding
13
+
14
+ context.each do |key, value|
15
+ @binding.local_variable_set(key, value)
16
+ end
17
+ end
18
+
19
+ # Returns the binding of this class, with the variables that have been set.
20
+ def get_binding
21
+ @binding
22
+ end
23
+
24
+ # Sort of a hack, since if a var doesn't exist it will try and call a method
25
+ # and so we'll end up here...
26
+ def method_missing(m, *args, &block)
27
+ nil
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module Yokunai
2
+ class StaticController < AbstractController
3
+
4
+ def get
5
+ asset_dir = File.join(Yokunai::Config.base_dir, Yokunai::Config.get("asset_dir"))
6
+ asset_file = File.join(asset_dir, @captures[:name])
7
+
8
+ if File.exist?(asset_file)
9
+ mime = Yokunai::Mime.detect_from_path(asset_file)
10
+ asset_body = File.read(asset_file)
11
+
12
+ respond(body: asset_body, headers: {"Content-Type" => mime})
13
+ else
14
+ respond_error(:not_found)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ module Yokunai
2
+ # Renders ERB templates.
3
+ class Template
4
+
5
+ FALLBACK_EMPTY_LAYOUT = "<%= partial %>".freeze
6
+
7
+ def initialize(template_path: nil)
8
+ @template_path = template_path || File.join(Yokunai::Config.base_dir, Yokunai::Config.get("template_dir"))
9
+ @raw_layout = get_layout
10
+ end
11
+
12
+ # Render an ERB template with the given name, and cache the result for
13
+ # subsequent calls.
14
+ #
15
+ # @param template [String] the name of a template
16
+ # @param context [Hash] key/value pairs of variables to bind the template
17
+ # @return [String] the ERB render result
18
+ def render(template, context = {})
19
+ return nil unless exist?(template)
20
+
21
+ path = File.join(@template_path, template + ".erb")
22
+ layout_context = context.merge({
23
+ partial: ERB.new(File.read(path)).result(Yokunai::RenderContext.new(context).get_binding)
24
+ })
25
+
26
+ ERB.new(@raw_layout).result(Yokunai::RenderContext.new(layout_context).get_binding)
27
+ end
28
+
29
+ # Checks if a template exists. Useful for gating before rendering.
30
+ #
31
+ # @param template [String] the template name
32
+ # @return [Boolean]
33
+ def exist?(template)
34
+ File.exist?(File.join(@template_path, template + ".erb"))
35
+ end
36
+
37
+ private
38
+
39
+ def get_layout
40
+ layout_path = File.join(@template_path, "layout.erb")
41
+ if File.exist?(layout_path)
42
+ File.read(layout_path)
43
+ else
44
+ FALLBACK_EMPTY_LAYOUT
45
+ end
46
+ end
47
+
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yokunai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Blackie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mimemagic
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
41
+ description: '["Alex Blackie"]'
42
+ email: alex@alexblackie.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.markdown
48
+ - lib/yokunai.rb
49
+ - lib/yokunai/abstract_controller.rb
50
+ - lib/yokunai/application.rb
51
+ - lib/yokunai/config.rb
52
+ - lib/yokunai/errors_controller.rb
53
+ - lib/yokunai/mime.rb
54
+ - lib/yokunai/render_context.rb
55
+ - lib/yokunai/static_controller.rb
56
+ - lib/yokunai/template.rb
57
+ homepage: https://github.com/alexblackie/yokunai
58
+ licenses:
59
+ - BSD-3-Clause
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.6.11
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Helps you make really simple dynamic Internet Web Pages.
81
+ test_files: []