aboutsummaryrefslogtreecommitdiff
path: root/src/guff.cr
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2016-05-15 17:07:16 -0400
committerPaul Duncan <pabs@pablotron.org>2016-05-15 17:07:16 -0400
commitae667b4222a718f99091ec8bb1fb130970b051e7 (patch)
tree08b2844213893ba614896b6dd06d7cfac22f0656 /src/guff.cr
downloadguff-ae667b4222a718f99091ec8bb1fb130970b051e7.tar.bz2
guff-ae667b4222a718f99091ec8bb1fb130970b051e7.zip
initial commit
Diffstat (limited to 'src/guff.cr')
-rw-r--r--src/guff.cr319
1 files changed, 319 insertions, 0 deletions
diff --git a/src/guff.cr b/src/guff.cr
new file mode 100644
index 0000000..944612a
--- /dev/null
+++ b/src/guff.cr
@@ -0,0 +1,319 @@
+require "option_parser"
+require "http/server"
+require "./guff/*"
+
+module Guff
+ class Config
+ property :mode, :env, :host, :port, :data_dir, :assets_dir
+
+ DEFAULTS = {
+ mode: "help",
+ env: "production",
+ host: "127.0.0.1",
+ port: "8989",
+ data_dir: "./data",
+ assets_dir: "/usr/local/share/guff",
+ }
+
+ def initialize
+ @mode = DEFAULTS[:mode] as String
+ @env = (ENV["GUFF_ENVIRONMENT"]? || DEFAULTS[:env]) as String
+ @host = (ENV["GUFF_HOST"]? || DEFAULTS[:host]) as String
+ @port = (ENV["GUFF_PORT"]? || DEFAULTS[:port]) as String
+ @data_dir = (ENV["GUFF_DATA_DIR"]? || DEFAULTS[:data_dir]) as String
+ @assets_dir = (ENV["GUFF_ASSETS_DIR"]? || DEFAULTS[:assets_dir]) as String
+ end
+
+ VALID_MODES = %w{init run help}
+
+ def mode=(mode : String)
+ raise "unknown mode: \"#{mode}\"" unless VALID_MODES.includes?(mode)
+ @mode = mode
+ end
+
+ VALID_ENVS = %w{development production}
+
+ def env=(env : String)
+ raise "unknown environment: \"#{env}\"" unless VALID_ENVS.includes?(env)
+ @env = env
+ end
+
+ def port=(port : String)
+ val = port.to_i
+ raise "invalid port: #{port}" unless val > 0 && val < 65535
+ @port = port
+ end
+
+ def assets_dir=(dir : String)
+ raise "missing assets dir: \"#{dir}\"" unless Dir.exists?(dir)
+ @assets_dir = dir
+ end
+
+ def self.parse(
+ app : String,
+ args : Array(String)
+ ) : Config
+ r = Config.new
+
+ raise "missing mode" unless args.size > 0
+
+ # get mode
+ r.mode = case mode = args.shift
+ when "-h", "--help"
+ "help"
+ else
+ mode
+ end
+
+ # parse arguments
+ p = OptionParser.parse(args) do |p|
+ p.banner = "Usage: #{app} [mode] <args>"
+
+ p.separator
+ p.separator("Run Options:")
+
+ p.on(
+ "-H HOST", "--host HOST",
+ "TCP host (defaults to \"#{DEFAULTS[:host]}\")"
+ ) do |arg|
+ r.host = arg
+ end
+
+ p.on(
+ "-p PORT", "--port PORT",
+ "TCP port (defaults to \"#{DEFAULTS[:port]}\")"
+ ) do |arg|
+ r.port = arg
+ end
+
+ p.separator
+ p.separator("Directory Options:")
+
+ p.on(
+ "-D DIR", "--data-dir DIR",
+ "Data directory (defaults to \"#{DEFAULTS[:data_dir]}\")"
+ ) do |arg|
+ r.data_dir = arg
+ end
+
+ p.on(
+ "-A DIR", "--assets-dir DIR",
+ "Guff assets directory (defaults to \"#{DEFAULTS[:assets_dir]}\")"
+ ) do |arg|
+ r.assets_dir = arg
+ end
+
+ p.separator
+ p.separator("Development Options:")
+
+ p.on(
+ "-E ENV", "--environment ENV",
+ "Environment (defaults to \"#{DEFAULTS[:env]})\""
+ ) do |arg|
+ r.env = arg
+ end
+
+ p.separator
+ p.separator("Other Options:")
+
+ p.on("-h", "--help", "Print usage.") do
+ r.mode = "help"
+ end
+ end
+
+ case r.mode
+ when "init"
+ # shortcut for -D parameter
+ r.data_dir = args.shift if args.size > 0
+ when "help"
+ # print help
+ puts p
+ end
+
+ # return config
+ r
+ end
+ end
+
+ abstract class ModeRunner
+ def self.run(config : Config)
+ new(config).run
+ end
+
+ def initialize(@config : Config)
+ end
+
+ abstract def run
+ end
+
+ class Builder < ModeRunner
+ def run
+ STDERR.puts "TODO: building directory"
+ end
+ end
+
+ class Context
+ property :config #, :models
+
+ def initialize(@config : Config)
+ # @models = nil
+ end
+ end
+
+ module Handlers
+ abstract class Handler < HTTP::Handler
+ def initialize(@context : Context)
+ super()
+ end
+ end
+
+ abstract class AuthenticatedHandler < Handler
+ def initialize(context : Context, roles : Array(String))
+ super(context)
+ @roles = roles
+ end
+
+ def call(context : HTTP::Server::Context)
+ role = context.request.headers["x-guff-role"]?
+
+ if role && @roles.includes?(role)
+ authenticated_call(context)
+ else
+ call_next(context)
+ end
+ end
+
+ abstract def authenticated_call(context : HTTP::Server::Context)
+ end
+
+ class SessionHandler < Guff::Handlers::Handler
+ def call(context : HTTP::Server::Context)
+ if @context.config.env == "development"
+ # TODO: add handler support
+ context.request.headers["x-guff-user-id"] = "0"
+ context.request.headers["x-guff-role"] = "admin"
+ end
+
+ call_next(context)
+ end
+ end
+
+ class AssetsHandler < AuthenticatedHandler
+ PATH_RE = %r{^/_guff/assets/}
+
+ def authenticated_call(context : HTTP::Server::Context)
+ path = context.request.path.not_nil!
+
+ if path.match(PATH_RE)
+ # TODO
+ else
+ call_next(context)
+ end
+ end
+ end
+
+ HANDLERS = [{
+ dev: true,
+ id: :error,
+ }, {
+ dev: false,
+ id: :log,
+ }, {
+ dev: false,
+ id: :deflate,
+ }, {
+ dev: false,
+ id: :session,
+ }, {
+ dev: false,
+ id: :assets,
+ }]
+
+ def self.get(context : Context) : Array(HTTP::Handler)
+ is_dev = (context.config.env == "development")
+
+ HANDLERS.select { |row|
+ !(row[:dev] as Bool) || is_dev
+ }.map { |row|
+ make_handler(row[:id] as Symbol, context)
+ }
+ end
+
+ def self.make_handler(
+ handler_id : Symbol,
+ context : Context
+ ) : HTTP::Handler
+ case handler_id
+ when :error
+ HTTP::ErrorHandler.new
+ when :log
+ HTTP::LogHandler.new
+ when :deflate
+ HTTP::DeflateHandler.new
+ when :session
+ SessionHandler.new(context)
+ when :assets
+ AssetsHandler.new(context, %w{admin editor})
+ else
+ raise "unknown handler id: #{handler_id}"
+ end
+ end
+ end
+
+ class Server < ModeRunner
+ def run
+ STDERR.puts "TODO: running web server"
+ check_dirs
+
+ # create context
+ context = Context.new(@config)
+
+ STDERR.puts "listening on %s:%s" % [@config.host, @config.port]
+
+ # run server
+ HTTP::Server.new(
+ @config.host,
+ @config.port.to_i,
+ Handlers.get(context)
+ ).listen
+ end
+
+ private def check_dirs
+ raise "missing assets directory: \"#{@config.assets_dir}\"" unless Dir.exists?(@config.assets_dir)
+ raise "missing data directory: \"#{@config.data_dir}\"" unless Dir.exists?(@config.data_dir)
+ end
+ end
+
+ module CLI
+ def self.print_usage(app : String)
+ STDERR.puts "Usage: #{app} [mode] <args>"
+ end
+
+ def self.run(app : String, args : Array(String))
+ begin
+ begin
+ config = Config.parse(app, args)
+ rescue err
+ raise "#{err}. Use --help for usage"
+ end
+
+ case config.mode
+ when "init"
+ Builder.run(config)
+ when "run"
+ Server.run(config)
+ when "help"
+ # do nothing
+ else
+ # never reached
+ raise "unknown mode: #{config.mode}"
+ end
+ rescue err
+ STDERR.puts "ERROR: #{err}."
+ exit -1
+ end
+ end
+ end
+end
+
+Guff::CLI.run($0, ARGV)