From 36bba1cacd2353d97f26ade5aeeca01a3ce9a4cb Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Sat, 5 Mar 2016 18:39:03 -0500 Subject: initial commit --- .gitignore | 6 ++ .travis.yml | 1 + LICENSE | 21 +++++++ README.md | 31 ++++++++++ shard.lock | 6 ++ shard.yml | 11 ++++ spec/guff_spec.cr | 9 +++ spec/spec_helper.cr | 2 + src/guff.cr | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/guff/config.cr | 98 ++++++++++++++++++++++++++++++++ src/guff/version.cr | 3 + 11 files changed, 348 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 shard.lock create mode 100644 shard.yml create mode 100644 spec/guff_spec.cr create mode 100644 spec/spec_helper.cr create mode 100644 src/guff.cr create mode 100644 src/guff/config.cr create mode 100644 src/guff/version.cr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37a3029 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/doc/ +/libs/ +/.crystal/ +/.shards/ + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ffc7b6a --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: crystal diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3f522c4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Paul Duncan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5129c51 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# guff + +TODO: Write a description here + +## Installation + + +TODO: Write installation instructions here + + +## Usage + + + +TODO: Write usage instructions here + +## Development + +TODO: Write development instructions here + +## Contributing + +1. Fork it ( https://github.com/[your-github-name]/guff/fork ) +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Add some feature') +4. Push to the branch (git push origin my-new-feature) +5. Create a new Pull Request + +## Contributors + +- [[your-github-name]](https://github.com/[your-github-name]) Paul Duncan - creator, maintainer diff --git a/shard.lock b/shard.lock new file mode 100644 index 0000000..63e9126 --- /dev/null +++ b/shard.lock @@ -0,0 +1,6 @@ +version: 1.0 +shards: + sqlite3: + github: manastech/crystal-sqlite3 + version: 0.1.0 + diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..a0d1d15 --- /dev/null +++ b/shard.yml @@ -0,0 +1,11 @@ +name: guff +version: 0.1.0 + +dependencies: + sqlite3: + github: manastech/crystal-sqlite3 + +authors: + - Paul Duncan + +license: MIT diff --git a/spec/guff_spec.cr b/spec/guff_spec.cr new file mode 100644 index 0000000..b7c87db --- /dev/null +++ b/spec/guff_spec.cr @@ -0,0 +1,9 @@ +require "./spec_helper" + +describe Guff do + # TODO: Write tests + + it "works" do + false.should eq(true) + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..b69b555 --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/guff" diff --git a/src/guff.cr b/src/guff.cr new file mode 100644 index 0000000..7e32bfc --- /dev/null +++ b/src/guff.cr @@ -0,0 +1,160 @@ +require "sqlite3" +require "http/server" +require "./guff/*" + +module Guff + class Database < ::SQLite3::Database + # TODO (add table_exists?) + end + + class Model + getter :db + + def initialize(@config : Config) + # create site database + db_path = "%s/site.db" % [config["data"]] + @db = Database.new(db_path) + end + end + + class Handler < ::HTTP::Handler + getter :model + getter :config + + def initialize(@model : Model, @config : Config) + end + + def call(context : HTTP::Server::Context) + # do nothing by default + call_next(context) + end + end + + + class BlogHandler < Handler + MATCHES = [%r{ + ^/ + + # match YYYY/MM/DD/SLUG.html + (?\d{4}) + / + (?\d{2}) + / + (?\d{2}) + / + (?[a-z0-9._-]+) + \.html + + $ + }x, %r{ + ^/ + + # match YYYY/MM/DD + (?\d{4}) + / + (?\d{2}) + / + (?\d{2}) + /? + + $ + }x, %r{ + ^/ + + # match YYYY/MM + (?\d{4}) + / + (?\d{2}) + /? + + $ + }x, %r{ + ^/ + + # match YYYY + (?\d{4}) + /? + + $ + }x, %r{ + ^/ + + # match index + blog/? + | + + $ + }x] + + def call(context : HTTP::Server::Context) + path = context.request.path || "" + if md = MATCHES.reduce(nil) { |r, m| r || m.match(path) } + context.response.puts "blog match: %s" % [md.to_s] + else + call_next(context) + end + end + end + + class SlugHandler < Handler + MOCK_SLUGS = { + "foo": "test slug foo", + "bar": "test slug bar", + "baz": "test slug baz", + } + + def call(context : HTTP::Server::Context) + puts "SlugHandler: path = %s" % [context.request.path] + call_next(context) + end + end + + class Server + def self.run(model : Model, config : Config) + new(model, config).run + end + + def initialize(@model : Model, @config : Config) + # create server + @server = HTTP::Server.new( + config["host"], + config["port"].to_i, + get_handlers(model, config) + ) do |context| + context.response.puts "asdf" + end + end + + def run + puts "listening on %s:%s" % %w{host port}.map { |k| @config[k] } + @server.listen + end + + private def get_handlers( + model : Model, + config : Config + ) : Array(HTTP::Handler) + @handlers ||= [ + HTTP::ErrorHandler.new, + HTTP::LogHandler.new, + HTTP::DeflateHandler.new, + BlogHandler.new(model, config), + SlugHandler.new(model, config), + HTTP::StaticFileHandler.new(config["public"]), + ] + end + end + + def self.run(app, args) + # parse env and cli options + config = Config.new(app, args) + + # create model + model = Model.new(config) + + # create server + Server.run(model, config) + end +end + +Guff.run($0, ARGV) diff --git a/src/guff/config.cr b/src/guff/config.cr new file mode 100644 index 0000000..6021c63 --- /dev/null +++ b/src/guff/config.cr @@ -0,0 +1,98 @@ +require "option_parser" + +module Guff + class Config + DEFAULTS = { + "host": "localhost", + "port": "8989", + "data": "./data", + "public": "./public", + "environment": "development", + } + + DIRS = %w{ + data + public + } + + def initialize(app, args) + @data = defaults + parse_args(app, args) + end + + def [](key : String) : String + @data[key] + end + + def []=(key : String, val : String | Nil) : String + @data[key] = val + end + + private def defaults : Hash(String, String) + DEFAULTS.merge(DEFAULTS.keys.reduce({} of String => String) do |r, key| + env_key = "GUFF_" + key.upcase + r[key] = ENV[env_key] if ENV.has_key?(env_key) + r + end) + end + + private def parse_args(app, args) + OptionParser.parse(args) do |p| + p.on( + "-h HOST", + "--host HOST", + "Host (defaults to \"localhost\")." + ) do |host| + self["host"] = host + end + + p.on( + "-p PORT", + "--port PORT", + "Port (defaults to 8989)." + ) do |port| + self["port"] = port + end + + p.on( + "-d PATH", + "--data-dir PATH", + "Path to data directory (defaults to \"./data\")." + ) do |path| + self["data"] = path + end + + p.on( + "-u PATH", + "--public-dir PATH", + "Path to public directory (defaults to \"./public\")." + ) do |path| + self["public"] = path + end + + p.on( + "-e ENV", + "--environment ENV", + "Environment (defaults to \"development\")." + ) do |path| + self["public"] = path + end + + + p.on( + "--help", + "Print usage" + ) do + puts p.to_s + end + end + + # expand output directory paths and create them + %w{data public}.each do |key| + self[key] = File.expand_path(File.join(File.dirname(app), self[key])) + # puts "DEBUG: %s: %s" % [key, self[key]] + Dir.mkdir(self[key], 0o770) unless File.directory?(self[key]) + end + end + end +end diff --git a/src/guff/version.cr b/src/guff/version.cr new file mode 100644 index 0000000..898a0d6 --- /dev/null +++ b/src/guff/version.cr @@ -0,0 +1,3 @@ +module Guff + VERSION = "0.1.0" +end -- cgit v1.2.3