30 |
31 |
--------------------------------------------------------------------------------
/sabo.tabby.yaml:
--------------------------------------------------------------------------------
1 | # Basic
2 | # Host to bind
3 | host: 0.0.0.0
4 | # Port to listen for connections
5 | port: 1312
6 | # Folder of static files to server
7 | # relative to this file
8 | public_folder: ./
9 | # Whether to server hidden folders
10 | # and files
11 | serve_hidden: false
12 |
13 | # SSL
14 | ssl:
15 | # Location of your SSL key
16 | # relative to this file
17 | key: ./spec/keys/openssl.key
18 | ## Location of your SSL crt
19 | ## relative to this file
20 | cert: ./spec/keys/openssl.crt
21 |
22 | # Theming
23 | theme:
24 | # Either one of the preincluded
25 | # themes (see `-h`) for the
26 | # error page
27 | # or path to a .mst/.mustache/.html
28 | # file relative to this file
29 | error: Default
30 | # Either one of the preincluded
31 | # themes (see `-h`) for the
32 | # dir listing page
33 | # or path to a .mst/.mustache/.html
34 | # file relative to this file
35 | dir: Default
36 | # One of the preincluded logging
37 | # formats (see `-h`)
38 | logging: Default
39 |
40 | # Logging
41 | # Whether to enable logging of
42 | # requests
43 | logging: true
44 | # Whether to enable emojis in logs
45 | emoji: true
46 | # Whether to enable colors in logs
47 | # (already disabled in non-tty)
48 | colors: true
49 |
50 | # Options
51 | # Whether to set the 'Server'
52 | # header to the name and version
53 | # of the server
54 | server_header: true
55 | # Whether to enable gzip
56 | gzip: true
57 | # Directory options
58 | dir:
59 | # Whether to serve index.html of
60 | # folders if available
61 | # e.g. GET /some_dir/ =>
62 | # serve /some_dir/index.html
63 | index: true
64 | # Whether to enable directory
65 | # listing
66 | listing: true
67 | # Whether to enable custom HTML
68 | # error pages
69 | # If disable, server will respond
70 | # with text/plain
71 | # #{status_code}: #{message}
72 | custom_error_page: true
--------------------------------------------------------------------------------
/src/sabo-tabby/error_handler.cr:
--------------------------------------------------------------------------------
1 | module Sabo::Tabby
2 | create_theme_index "error_page", ["tqila", "gradient", "boring"]
3 |
4 | class HTTP::Server::Response
5 | record ErrorPage, status_code : Int32, message : String, theme : String do
6 | getter logo : String = Sabo::Tabby::LOGO
7 |
8 | def css : Tuple
9 | {Sabo::Tabby::RESET_CSS, Sabo::Tabby::ERROR_PAGE_INDEX[theme.downcase]}
10 | end
11 |
12 | ECR.def_to_s "#{__DIR__}/ecr/error_page.ecr"
13 | end
14 |
15 | private def error_page(status_code : Int32, message : String)
16 | theme = Sabo::Tabby.config.theme["error_page"]
17 | # If it's a Mustache file, create a model and render it, else render the ecr theme.
18 | if theme.is_a?(Crustache::Syntax::Template)
19 | model = {
20 | "status_code" => status_code,
21 | "message" => message,
22 | }
23 |
24 | Crustache.render theme, model
25 | else
26 | ErrorPage.new(status_code, message, theme.to_s).to_s
27 | end
28 | end
29 |
30 | def respond_with_status(status : HTTP::Status, message : String? = nil)
31 | check_headers
32 | reset
33 | @status = status
34 | @status_message = message ||= @status.description
35 | self.headers.add "Server", Sabo::Tabby::SERVER_HEADER if Sabo::Tabby.config.server_header
36 |
37 | # If it exists, use file in the public dir matching error_code.html (eg 404.html).
38 | static_error_page = Path[Sabo::Tabby.config.public_folder, "#{status_code}.html"]
39 | if File.exists?(static_error_page)
40 | self.content_type = "text/html; charset=UTF-8"
41 | IO.copy File.open(static_error_page), self
42 |
43 | # If HTML pages are enabled, call `error_page` else return a basic text/plain one.
44 | elsif Sabo::Tabby.config.error_page
45 | self.content_type = "text/html; charset=UTF-8"
46 | self << error_page(@status.code, @status_message.to_s) << '\n'
47 | else
48 | self.content_type = "text/plain; charset=UTF-8"
49 | self << @status.code << ' ' << message << '\n'
50 | end
51 | close
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/helpers_spec.cr:
--------------------------------------------------------------------------------
1 | require "./spec_helper"
2 |
3 | describe "::log" do
4 | message = "No gods, no masters"
5 | io = IO::Memory.new
6 |
7 | before_each do
8 | io = IO::Memory.new
9 | Sabo::Tabby.config.logger = Sabo::Tabby::LogHandler.new(io)
10 | end
11 |
12 | it "should use Sabo::Tabby::Config.logger to log a message" do
13 | log message
14 |
15 | result = io.to_s.split(' ')
16 | emoji = result.shift
17 |
18 | result.join(' ').should eq("#{message}\n")
19 | Sabo::Tabby::EMOJIS[:base].should contain(emoji)
20 | end
21 |
22 | it "should use Sabo::Tabby::Config.logger to log a without emojis set explicitly" do
23 | log message, emoji: false
24 |
25 | result = io.to_s.split(' ')
26 |
27 | result.join(' ').should eq("#{message}\n")
28 | end
29 |
30 | it "should use Sabo::Tabby::Config.logger to log a without emojis set on config" do
31 | Sabo::Tabby.config.emoji = false
32 | log message
33 |
34 | result = io.to_s.split(' ')
35 |
36 | result.join(' ').should eq("#{message}\n")
37 | Sabo::Tabby.config.emoji = true
38 | end
39 |
40 | it "should use Sabo::Tabby::Config.logger to log a message but with a newline at the start" do
41 | log message, newline: true
42 |
43 | result = io.to_s.split(' ')
44 | newline, emoji = result.shift
45 | result[0] = "#{newline}#{result[0]}"
46 |
47 | result.join(' ').should eq("\n#{message}\n")
48 | Sabo::Tabby::EMOJIS[:base].should contain(emoji.to_s)
49 | end
50 | end
51 |
52 | describe "::abort_log" do
53 | message = "No gods, no masters"
54 |
55 | it "should return a formatted abort error message" do
56 | abort_message = disable_colorize do
57 | abort_log(message).to_s
58 | end
59 |
60 | abort_message.should eq("[ERROR][#{Sabo::Tabby::APP_NAME}]: #{message}")
61 | end
62 | end
63 |
64 | describe Sabo::Tabby::Utils do
65 | it "should return whether a file should be compressed" do
66 | gzip_file = Path["cat", "meow#{Sabo::Tabby::Utils::ZIP_TYPES.sample}"]
67 | non_gzip_file = Path["cat", "meow.cr"]
68 |
69 | Sabo::Tabby::Utils.zip_types(gzip_file).should be_true
70 | Sabo::Tabby::Utils.zip_types(non_gzip_file).should be_false
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/src/licenses.cr:
--------------------------------------------------------------------------------
1 | require "colorize"
2 | Colorize.enabled = true
3 |
4 | APP_NAME = {{read_file("#{__DIR__}/../shard.yml").split("name: ")[1].split("\n")[0]}}
5 | LICENSE_FILES = {"LICENSE", "LICENSE.md", "UNLICENSE"}
6 | OLD_LICENSE = <<-KEMAL_LICENSE
7 | #{"Kemal".capitalize.colorize.mode(:underline).mode(:bold)}
8 | Copyright (c) 2016 Serdar Doğruyol
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE
27 |
28 | KEMAL_LICENSE
29 |
30 | licenses = [] of String
31 | root = Path[__DIR__, ".."]
32 | lib_folder = root / "lib"
33 |
34 | def get_license_content(parent : Path) : String?
35 | license = nil
36 | LICENSE_FILES.each do |license_file|
37 | license_path = parent / license_file
38 | if File.exists?(license_path)
39 | license = File.read(license_path)
40 | break
41 | end
42 | end
43 | license
44 | end
45 |
46 | unless (license = get_license_content(root)).nil?
47 | licenses << license
48 | end
49 |
50 | licenses << OLD_LICENSE
51 |
52 | Dir.each_child(lib_folder) do |shard|
53 | path = lib_folder / shard
54 | next if File.file?(path)
55 |
56 | unless (license = get_license_content(path)).nil?
57 | licenses << "#{shard.capitalize.colorize.mode(:underline).mode(:bold)}\n#{license}"
58 | end
59 | end
60 |
61 | licenses.unshift("#{APP_NAME.capitalize.colorize.mode(:underline).mode(:bold)}")
62 |
63 | puts licenses.join('\n')
64 |
--------------------------------------------------------------------------------
/src/sabo-tabby/ecr/error_page.ecr:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |