tetrahedron 0.0.0.1
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 +7 -0
- data/.gitignore +6 -0
- data/AUTHORS +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +65 -0
- data/LICENSE +0 -0
- data/README.md +4 -0
- data/Rakefile +2 -0
- data/bin/tetrahedron +4 -0
- data/config/app.ru +21 -0
- data/config/application.rb +8 -0
- data/config/environment.rb +18 -0
- data/config/puma.rb +29 -0
- data/lib/tetrahedron.rb +59 -0
- data/lib/tetrahedron/app.rb +16 -0
- data/lib/tetrahedron/assets.rb +43 -0
- data/lib/tetrahedron/base.rb +34 -0
- data/lib/tetrahedron/bootfile.rb +43 -0
- data/lib/tetrahedron/bundler.rb +10 -0
- data/lib/tetrahedron/configuration.rb +44 -0
- data/lib/tetrahedron/controller.rb +17 -0
- data/lib/tetrahedron/database.rb +113 -0
- data/lib/tetrahedron/endpoint.rb +4 -0
- data/lib/tetrahedron/environment.rb +28 -0
- data/lib/tetrahedron/error.rb +4 -0
- data/lib/tetrahedron/errors.rb +7 -0
- data/lib/tetrahedron/gem.rb +58 -0
- data/lib/tetrahedron/model.rb +32 -0
- data/lib/tetrahedron/rake.rb +16 -0
- data/lib/tetrahedron/redis.rb +4 -0
- data/lib/tetrahedron/sessions.rb +28 -0
- data/lib/tetrahedron/tasks/tet.rake +5 -0
- data/tetrahedron.gemspec +34 -0
- metadata +174 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f11bb282f14b3b5c27899cdb70fef27f4154dc20
|
4
|
+
data.tar.gz: a04eafc6d60df291569009131ee564c5fc404b44
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acb35b1fe3d7a07905a753ff2d8422db1478a097f22d18840230085cf14b8c108e0257a5c71ebc10e5969c7bd8eb3ef565638ed343f883ae8512b4195c47dce6
|
7
|
+
data.tar.gz: 1c7365409ba0e9bf0d686a849df7d4a445730a23685af903c50bb2193e702960224be195dc8cc0e0265a5d0f33fd39ed3057443cb7f7f834456ef9b97794a2ab
|
data/.gitignore
ADDED
data/AUTHORS
ADDED
File without changes
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
tetrahedron (0.0.0.1)
|
5
|
+
activesupport
|
6
|
+
sequel
|
7
|
+
sinatra
|
8
|
+
sinatra-contrib
|
9
|
+
thor
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
activesupport (4.2.5)
|
15
|
+
i18n (~> 0.7)
|
16
|
+
json (~> 1.7, >= 1.7.7)
|
17
|
+
minitest (~> 5.1)
|
18
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
19
|
+
tzinfo (~> 1.1)
|
20
|
+
backports (3.6.7)
|
21
|
+
coderay (1.1.0)
|
22
|
+
i18n (0.7.0)
|
23
|
+
json (1.8.3)
|
24
|
+
method_source (0.8.2)
|
25
|
+
minitest (5.8.3)
|
26
|
+
multi_json (1.11.2)
|
27
|
+
pry (0.10.3)
|
28
|
+
coderay (~> 1.1.0)
|
29
|
+
method_source (~> 0.8.1)
|
30
|
+
slop (~> 3.4)
|
31
|
+
rack (1.6.4)
|
32
|
+
rack-protection (1.5.3)
|
33
|
+
rack
|
34
|
+
rack-test (0.6.3)
|
35
|
+
rack (>= 1.0)
|
36
|
+
rake (10.4.2)
|
37
|
+
sequel (4.28.0)
|
38
|
+
sinatra (1.4.6)
|
39
|
+
rack (~> 1.4)
|
40
|
+
rack-protection (~> 1.4)
|
41
|
+
tilt (>= 1.3, < 3)
|
42
|
+
sinatra-contrib (1.4.6)
|
43
|
+
backports (>= 2.0)
|
44
|
+
multi_json
|
45
|
+
rack-protection
|
46
|
+
rack-test
|
47
|
+
sinatra (~> 1.4.0)
|
48
|
+
tilt (>= 1.3, < 3)
|
49
|
+
slop (3.6.0)
|
50
|
+
thor (0.19.1)
|
51
|
+
thread_safe (0.3.5)
|
52
|
+
tilt (2.0.1)
|
53
|
+
tzinfo (1.2.2)
|
54
|
+
thread_safe (~> 0.1)
|
55
|
+
|
56
|
+
PLATFORMS
|
57
|
+
ruby
|
58
|
+
|
59
|
+
DEPENDENCIES
|
60
|
+
pry
|
61
|
+
rake
|
62
|
+
tetrahedron!
|
63
|
+
|
64
|
+
BUNDLED WITH
|
65
|
+
1.10.6
|
data/LICENSE
ADDED
File without changes
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/tetrahedron
ADDED
data/config/app.ru
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env rackup
|
2
|
+
|
3
|
+
require_relative 'application'
|
4
|
+
require_relative 'environment'
|
5
|
+
|
6
|
+
# TODO(mtwilliams): Request tracking (make Heroku compatiable).
|
7
|
+
# TODO(mtwilliams): Performance monitoring (make Heroku compatiable).
|
8
|
+
# TODO(mtwilliams): Error handling.
|
9
|
+
|
10
|
+
if Tetrahedron::Sessions.configured?
|
11
|
+
use Rack::Session::Cookie, :key => Tetrahedron::Sessions.config.cookie,
|
12
|
+
:domain => Tetrahedron::Sessions.config.domain,
|
13
|
+
:expire_after => Tetrahedron::Sessions.config.lifetime,
|
14
|
+
:secret => Tetrahedron::Sessions.config.secret
|
15
|
+
else
|
16
|
+
# TODO(mtwilliams): Use a logger.
|
17
|
+
$stderr.puts "Sessions not configured!\n => Use Tetrahedron::Sessions.configure in config/environment.rb!"
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO(mtwilliams): Remove cylcical dependency so we can use a constantized version.
|
21
|
+
run Tetrahedron.config.app.constantize
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'application'
|
2
|
+
|
3
|
+
Tetrahedron::Environment.load
|
4
|
+
|
5
|
+
if Tetrahedron.env.development?
|
6
|
+
# Synchronize STDOUT and STDERR to make debugging easier.
|
7
|
+
STDOUT.sync = STDERR.sync = true
|
8
|
+
end
|
9
|
+
|
10
|
+
require File.join(Tetrahedron.config.root, "config", "environment")
|
11
|
+
|
12
|
+
unless Tetrahedron::Database.configured?
|
13
|
+
$stderr.puts "Database not configured!\n => Use Tetrahedron::Database.configure in config/environment.rb!"
|
14
|
+
end
|
15
|
+
|
16
|
+
# unless Tetrahedron::Redis.configured?
|
17
|
+
# $stderr.puts "Redis not configured!\n => Use Tetrahedron::Redis.configure in config/environment.rb!"
|
18
|
+
# end
|
data/config/puma.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'application'
|
2
|
+
require_relative 'environment'
|
3
|
+
|
4
|
+
rackup File.join(Tetrahedron.root, 'config', 'app.ru')
|
5
|
+
|
6
|
+
environment Tetrahedron.env.to_s
|
7
|
+
bind "tcp://#{ENV['HOST'] || '0.0.0.0'}:#{ENV['PORT'] || 80}"
|
8
|
+
port (ENV['PORT'] || 80).to_i
|
9
|
+
workers (ENV['PUMA_WORKERS'] || 1).to_i
|
10
|
+
threads (ENV['PUMA_MIN_THREADS'] || 1).to_i,
|
11
|
+
(ENV['PUMA_MAX_THREADS'] || 1).to_i
|
12
|
+
|
13
|
+
preload_app!
|
14
|
+
|
15
|
+
before_fork do
|
16
|
+
if Tetrahedron.env.production?
|
17
|
+
# TODO(mtwilliams): Precompile assets.
|
18
|
+
Tetrahedron::Assets.precompile
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
on_worker_boot do
|
23
|
+
Tetrahedron.bootfile.up.call() if Tetrahedron.bootfile.up
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO(mtwilliams): Report low-level errors in production.
|
27
|
+
# lowlevel_error_handler do |e|
|
28
|
+
# ...
|
29
|
+
# end
|
data/lib/tetrahedron.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.require(:default)
|
5
|
+
|
6
|
+
# Some things aren't nicely cut gems, unforunately.
|
7
|
+
require 'base64'
|
8
|
+
require 'erb'
|
9
|
+
require 'net/http'
|
10
|
+
require 'ostruct'
|
11
|
+
require 'securerandom'
|
12
|
+
require 'socket'
|
13
|
+
require 'time'
|
14
|
+
require 'uri'
|
15
|
+
require 'yaml'
|
16
|
+
|
17
|
+
# Smells like Rails...
|
18
|
+
require 'active_support'
|
19
|
+
require 'active_support/core_ext'
|
20
|
+
|
21
|
+
module Tetrahedron
|
22
|
+
def self.root
|
23
|
+
@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'tetrahedron/gem'
|
28
|
+
require 'tetrahedron/bundler'
|
29
|
+
|
30
|
+
require 'tetrahedron/error'
|
31
|
+
require 'tetrahedron/errors'
|
32
|
+
|
33
|
+
require 'tetrahedron/configuration'
|
34
|
+
require 'tetrahedron/environment'
|
35
|
+
require 'tetrahedron/bootfile'
|
36
|
+
|
37
|
+
Tetrahedron::Bundler.require
|
38
|
+
|
39
|
+
require 'sinatra/base'
|
40
|
+
require 'sinatra/contrib'
|
41
|
+
|
42
|
+
require 'sequel'
|
43
|
+
|
44
|
+
Sequel.extension :migration
|
45
|
+
|
46
|
+
Sequel::Model.plugin :timestamps
|
47
|
+
Sequel::Model.plugin :subclasses
|
48
|
+
|
49
|
+
require 'tetrahedron/base'
|
50
|
+
|
51
|
+
require 'tetrahedron/assets'
|
52
|
+
|
53
|
+
require 'tetrahedron/database'
|
54
|
+
require 'tetrahedron/sessions'
|
55
|
+
|
56
|
+
require 'tetrahedron/app'
|
57
|
+
require 'tetrahedron/model'
|
58
|
+
require 'tetrahedron/controller'
|
59
|
+
require 'tetrahedron/endpoint'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
class App < Tetrahedron::Base
|
3
|
+
configure do
|
4
|
+
environment = Tetrahedron::Assets::Environment.new
|
5
|
+
set :assets, environment: environment
|
6
|
+
sprockets_based_server = Tetrahedron::Assets::Server.new(settings.assets[:environment])
|
7
|
+
set :assets, server: sprockets_based_server
|
8
|
+
end
|
9
|
+
|
10
|
+
get %r{^/assets/*} do
|
11
|
+
# Assets are handled by Sprockets.
|
12
|
+
env['PATH_INFO'].sub('/assets', '')
|
13
|
+
settings.assets[:server].call!(env)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
module Assets
|
3
|
+
class Environment < Sprockets::Environment
|
4
|
+
def initialize
|
5
|
+
super(Tetrahedron.config.root)
|
6
|
+
|
7
|
+
self.append_path('vendor/assets/styles')
|
8
|
+
self.append_path('vendor/assets/scripts')
|
9
|
+
self.append_path('vendor/assets/fonts')
|
10
|
+
self.append_path('vendor/assets/images')
|
11
|
+
|
12
|
+
self.append_path('assets/styles')
|
13
|
+
self.append_path('assets/scripts')
|
14
|
+
self.append_path('assets/fonts')
|
15
|
+
self.append_path('assets/images')
|
16
|
+
|
17
|
+
if Tetrahedron.env.production?
|
18
|
+
self.css_compressor = CSSminify.new
|
19
|
+
self.js_compressor = Uglifier.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Server
|
25
|
+
def initialize(environment)
|
26
|
+
@environment = environment
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
@environment.call(env)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Precompiler
|
35
|
+
# Precompile.
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.precompile
|
39
|
+
# TODO(mtwilliams): Implement asset precompilation.
|
40
|
+
$stderr.puts "Asset precompilation is not implemented yet!"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
class Base < Sinatra::Base
|
3
|
+
set :environment, Tetrahedron.env.to_sym
|
4
|
+
set :root, Tetrahedron.config.root
|
5
|
+
|
6
|
+
# Don't use the built-in web-server.
|
7
|
+
set :run, false
|
8
|
+
|
9
|
+
# Errors are bubbled so they can be handled by middleware.
|
10
|
+
set :dump_errors, false
|
11
|
+
set :raise_errors, true
|
12
|
+
set :show_exceptions, false
|
13
|
+
|
14
|
+
# Fuck you, IE9.
|
15
|
+
disable :method_override
|
16
|
+
|
17
|
+
configure :development do
|
18
|
+
# King of iteration, baby.
|
19
|
+
register Sinatra::Reloader
|
20
|
+
end
|
21
|
+
|
22
|
+
configure do
|
23
|
+
disable :static
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/*' do
|
27
|
+
# Try to serve statically first.
|
28
|
+
server = Rack::Static.new(proc {[404, {}, []]}, root: File.join(Tetrahedron.config.root, '/public'))
|
29
|
+
status, headers, response = server.call(env)
|
30
|
+
pass if status == 404
|
31
|
+
[status, headers, response]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
class Bootfile
|
3
|
+
DEFAULT = "config/boot.rb"
|
4
|
+
|
5
|
+
attr_reader :up
|
6
|
+
attr_reader :down
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
# TODO(mtwilliams): Provide reasonable defaults.
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.load(filename=Tetrahedron::Bootfile::DEFAULT)
|
13
|
+
bootfile = Tetrahedron::Bootfile.new
|
14
|
+
require File.join(Tetrahedron.root, "config", "application")
|
15
|
+
DomainSpecificLanguage.for(bootfile).instance_eval(File.read(filename), filename)
|
16
|
+
bootfile
|
17
|
+
end
|
18
|
+
|
19
|
+
class DomainSpecificLanguage
|
20
|
+
def initialize(bootfile)
|
21
|
+
@bootfile = bootfile
|
22
|
+
end
|
23
|
+
|
24
|
+
def up(&block)
|
25
|
+
@bootfile.instance_variable_set(:@up, block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def down(&block)
|
29
|
+
@bootfile.instance_variable_set(:@down, block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.for(bootfile)
|
33
|
+
DomainSpecificLanguage.new(bootfile)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.bootfile(path=Tetrahedron::Bootfile::DEFAULT)
|
39
|
+
@bootfile ||= begin
|
40
|
+
Bootfile.load(path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
class Configuration
|
3
|
+
DEFAULT = "config/tetrahedron.rb"
|
4
|
+
|
5
|
+
attr_reader :app
|
6
|
+
attr_reader :root
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
# TODO(mtwilliams): Best guess @app and @root.
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.load(filename=Tetrahedron::Configuration::DEFAULT)
|
13
|
+
configuration = Tetrahedron::Configuration.new
|
14
|
+
DomainSpecificLanguage.for(configuration).instance_eval(File.read(filename), filename)
|
15
|
+
configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
class DomainSpecificLanguage
|
19
|
+
def initialize(configuration)
|
20
|
+
@configuration = configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
def app(name)
|
24
|
+
@configuration.instance_variable_set(:@app, name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def root(directory)
|
28
|
+
raise Tetrahedron::MisconfiguredError.new(:root, 'the path given does not exist') unless File.exist?(directory)
|
29
|
+
raise Tetrahedron::MisconfiguredError.new(:root, 'the path given is not a directory') unless File.directory?(directory)
|
30
|
+
@configuration.instance_variable_set(:@root, directory)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.for(configuration)
|
34
|
+
DomainSpecificLanguage.new(configuration)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.config(path=Tetrahedron::Configuration::DEFAULT)
|
40
|
+
@configuration ||= begin
|
41
|
+
Configuration.load(path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
class Controller < Tetrahedron::Base
|
3
|
+
set :views, File.join(Tetrahedron.config.root, 'app', Tetrahedron.config.app.underscore, 'views')
|
4
|
+
|
5
|
+
# Use app/<app>/views/_layouts/default as the default layout.
|
6
|
+
set :erb, :layout => :'_layouts/default'
|
7
|
+
|
8
|
+
# Recognize *.html.erb as Erubis templates.
|
9
|
+
Tilt.register Tilt::ErubisTemplate, 'html.erb'
|
10
|
+
|
11
|
+
# Don't recognize *.erb templates.
|
12
|
+
def erb(template, options={}, locals={})
|
13
|
+
options[:layout] = settings.erb[:layout] if options[:layout].nil?
|
14
|
+
render 'html.erb', template, options, locals
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
module Database
|
3
|
+
class Configuration
|
4
|
+
Options = %i{adapter user password host port database pool}
|
5
|
+
attr_reader *Options
|
6
|
+
|
7
|
+
def initialize(configuration={})
|
8
|
+
Options.each do |opt|
|
9
|
+
self.instance_variable_set(:"@#{opt}", configuration[opt])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def override(overrides={})
|
14
|
+
configuration = self.dup
|
15
|
+
Options.each do |opt|
|
16
|
+
configuration.instance_variable_set(:"@#{opt}", overrides[opt]) if overrides.include? opt
|
17
|
+
end
|
18
|
+
configuration
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configured?
|
23
|
+
!@configuration.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configure(configuration)
|
27
|
+
# TODO(mtwilliams): Validate |configuration|.
|
28
|
+
configuration[:database] ||= Terahedron.config.app.to_s.underscore.gsub(/::/,'_')
|
29
|
+
configuration[:pool] ||= 1
|
30
|
+
@configuration = Configuration.new(configuration)
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO(mtwilliams): Refactor into Terahedron::Service.wait_until_reachable
|
34
|
+
def self.wait_until_reachable(overrides={})
|
35
|
+
# By default, we wait up to 15 seconds.
|
36
|
+
overrides[:timeout] ||= 15
|
37
|
+
|
38
|
+
configuration = @configuration.override(overrides) if configured?
|
39
|
+
configuration ||= Configuration.new(overrides)
|
40
|
+
|
41
|
+
# TODO(mtwilliams): Don't assume TCP.
|
42
|
+
# Look at |configuration.adapter|.
|
43
|
+
Timeout::timeout(overrides[:timeout]) do
|
44
|
+
while true
|
45
|
+
begin
|
46
|
+
TCPSocket.new(configuration.host, configuration.port).close
|
47
|
+
return true
|
48
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
49
|
+
# We're all good citizens, right?
|
50
|
+
Kernel.sleep(1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue Timeout::Error
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.wait_until_reachable!(overrides={})
|
59
|
+
# TODO(mtwilliams): Raise a custom exception type.
|
60
|
+
raise "Database is unreachable!" unless wait_until_reachable
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.connect(overrides={})
|
64
|
+
# Wait some amount of time before assuming the database is down.
|
65
|
+
Tetrahedron::Database.wait_until_reachable!(overrides)
|
66
|
+
|
67
|
+
configuration = @configuration.override(overrides) if configured?
|
68
|
+
configuration ||= Configuration.new(overrides)
|
69
|
+
|
70
|
+
@connection = Sequel.connect(:adapter => configuration.adapter,
|
71
|
+
:user => configuration.user,
|
72
|
+
:password => configuration.password,
|
73
|
+
:host => configuration.host,
|
74
|
+
:port => configuration.port,
|
75
|
+
:database => configuration.database,
|
76
|
+
:test => true,
|
77
|
+
:sslmode => :prefer,
|
78
|
+
:max_connections => configuration.pool)
|
79
|
+
|
80
|
+
# Reback our models with the new connection pool.
|
81
|
+
Tetrahedron::Model.db = @connection
|
82
|
+
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.disconnect
|
87
|
+
connection = @connection
|
88
|
+
|
89
|
+
# TODO(mtwilliams): Ensure this is thread safe.
|
90
|
+
# Make sure no one tries to use the disconnected connection pool.
|
91
|
+
Tetrahedron::Model.db = Sequel.mock
|
92
|
+
@connection = nil
|
93
|
+
|
94
|
+
connection.disconnect unless connection.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.connection
|
98
|
+
@connection
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.reset
|
102
|
+
# TODO(mtwilliams): Drop and recreate database.
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.migrate
|
106
|
+
Sequel::IntegerMigrator.run(connection, File.join(Tetrahedron.config.root, 'db', 'migrations'))
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.seed
|
110
|
+
Kernel.load(File.join(Tetrahedron.config.root, 'db', 'seed.rb'))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
module Environment
|
3
|
+
# Loads `ENV` from `.env` if available.
|
4
|
+
def self.load
|
5
|
+
# TODO(mtwilliams): Use a proper logger.
|
6
|
+
$stdout.puts "Loading environment from `.env` if available..."
|
7
|
+
require 'dotenv'
|
8
|
+
Dotenv.load! File.join(Tetrahedron.config.root, '.env')
|
9
|
+
$stdout.puts " => Loaded from `.env`."
|
10
|
+
rescue LoadError => _
|
11
|
+
$stderr.puts " => The 'dotenv' Gem is not available on this system."
|
12
|
+
false
|
13
|
+
rescue Errno::ENOENT
|
14
|
+
$stderr.puts " => No `.env' file."
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.env
|
20
|
+
env = ENV["#{Tetrahedron.config.app.upcase.gsub(/::/,'_')}_ENV"] || ENV['TETRAHEDRON_ENV'] || ENV['RACK_ENV']
|
21
|
+
@env ||= ::ActiveSupport::StringInquirer.new(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.env=(new_environment)
|
25
|
+
ENV["#{Tetrahedron.config.app.upcase.gsub(/::/,'_')}_ENV"] = ENV['TETRAHEDRON_ENV'] = new_environment
|
26
|
+
@env = ::ActiveSupport::StringInquirer.new(new_environment)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Tetrahedron
|
4
|
+
module Gem
|
5
|
+
# The name of this Gem.
|
6
|
+
def self.name
|
7
|
+
"tetrahedron"
|
8
|
+
end
|
9
|
+
|
10
|
+
# The name and email address of the primary author.
|
11
|
+
def self.author
|
12
|
+
self.authors.first
|
13
|
+
end
|
14
|
+
|
15
|
+
# The name and email addresses of all authors.
|
16
|
+
def self.authors
|
17
|
+
[["Michael Williams", "m.t.williams@live.com"]].map do |author|
|
18
|
+
name, email = author
|
19
|
+
OpenStruct.new(name: name, email: email)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This Gem's homepage URL.
|
24
|
+
def self.homepage
|
25
|
+
"http://github.com/mtwilliams/tetrahedron"
|
26
|
+
end
|
27
|
+
|
28
|
+
# This Gem's URL.
|
29
|
+
def self.url
|
30
|
+
"https://rubygems.org/gems/#{self.name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# A short summary of this Gem.
|
34
|
+
def self.summary
|
35
|
+
"Opinionated Sinatra."
|
36
|
+
end
|
37
|
+
|
38
|
+
# A full description of this Gem.
|
39
|
+
def self.description
|
40
|
+
"Tetrahedron is an opinionated web development framework."
|
41
|
+
end
|
42
|
+
|
43
|
+
module VERSION #:nodoc:
|
44
|
+
MAJOR, MINOR, PATCH, PRE = [0, 0, 0, 1]
|
45
|
+
STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
46
|
+
end
|
47
|
+
|
48
|
+
# The semantic version of the this Gem.
|
49
|
+
def self.version
|
50
|
+
Gem::VERSION::STRING
|
51
|
+
end
|
52
|
+
|
53
|
+
# The license covering Tetrahedron.
|
54
|
+
def self.license
|
55
|
+
"Public Domain"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
Model = Class.new(Sequel::Model)
|
3
|
+
|
4
|
+
# Stop Sequel from bitching if it's subclassed before the first database
|
5
|
+
# connection is established.
|
6
|
+
Model.db = Sequel.mock if Sequel::DATABASES.empty?
|
7
|
+
|
8
|
+
class Model
|
9
|
+
def self::db=(db)
|
10
|
+
super(db)
|
11
|
+
|
12
|
+
# All the way down, boys.
|
13
|
+
self.descendents.each do |subclass|
|
14
|
+
subclass.db = db
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self::Model(source)
|
20
|
+
unless Sequel::Model::ANONYMOUS_MODEL_CLASSES.key?(source)
|
21
|
+
anonymous_model_class = nil
|
22
|
+
if source.is_a?(Sequel::Database)
|
23
|
+
anonymous_model_class = Class.new(Tetrahedron::Model)
|
24
|
+
anonymous_model_class.db = source
|
25
|
+
else
|
26
|
+
anonymous_model_class = Class.new(Tetrahedron::Model).set_dataset(source)
|
27
|
+
end
|
28
|
+
Sequel::Model::ANONYMOUS_MODEL_CLASSES[source] = anonymous_model_class
|
29
|
+
end
|
30
|
+
return Sequel::Model::ANONYMOUS_MODEL_CLASSES[source]
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
module Rake
|
3
|
+
def self.install
|
4
|
+
root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
5
|
+
require_relative 'configuration'
|
6
|
+
Tetrahedron::Configuration.load
|
7
|
+
require_relative 'environment'
|
8
|
+
Tetrahedron::Environment.load
|
9
|
+
require File.join(root, 'config', 'application')
|
10
|
+
require File.join(root, 'config', 'environment')
|
11
|
+
Dir.glob(File.join(root, 'lib', 'tetrahedron', 'tasks', '**.rake')).each do |path|
|
12
|
+
Kernel.load(path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tetrahedron
|
2
|
+
module Sessions
|
3
|
+
class Configuration
|
4
|
+
Options = %i{cookie domain lifetime secret}
|
5
|
+
attr_reader *Options
|
6
|
+
|
7
|
+
def initialize(configuration={})
|
8
|
+
Options.each do |opt|
|
9
|
+
self.instance_variable_set(:"@#{opt}", configuration[opt])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configured?
|
19
|
+
!@configuration.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure(configuration)
|
23
|
+
# TODO(mtwilliams): Validate |configuration|.
|
24
|
+
configuration[:cookie] ||= "#{Tetrahedron.config.app.to_s.downcase.gsub(/::/,'.')}.session"
|
25
|
+
@configuration = Sessions::Configuration.new(configuration)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/tetrahedron.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$:.push File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
require 'tetrahedron/gem'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = Tetrahedron::Gem.name
|
6
|
+
s.version = Tetrahedron::Gem.version
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.author = Tetrahedron::Gem.author.name
|
9
|
+
s.email = Tetrahedron::Gem.author.email
|
10
|
+
s.homepage = Tetrahedron::Gem.homepage
|
11
|
+
s.summary = Tetrahedron::Gem.summary
|
12
|
+
s.description = Tetrahedron::Gem.description
|
13
|
+
s.license = Tetrahedron::Gem.license
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 2.2.3'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
|
21
|
+
s.require_paths = %w(lib)
|
22
|
+
|
23
|
+
s.add_development_dependency("rake")
|
24
|
+
s.add_development_dependency("pry")
|
25
|
+
|
26
|
+
s.add_dependency("activesupport")
|
27
|
+
|
28
|
+
s.add_dependency("sinatra")
|
29
|
+
s.add_dependency("sinatra-contrib")
|
30
|
+
|
31
|
+
s.add_dependency("sequel")
|
32
|
+
|
33
|
+
s.add_dependency("thor")
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tetrahedron
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sinatra
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sinatra-contrib
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sequel
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: thor
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Tetrahedron is an opinionated web development framework.
|
112
|
+
email: m.t.williams@live.com
|
113
|
+
executables:
|
114
|
+
- tetrahedron
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- AUTHORS
|
120
|
+
- Gemfile
|
121
|
+
- Gemfile.lock
|
122
|
+
- LICENSE
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- bin/tetrahedron
|
126
|
+
- config/app.ru
|
127
|
+
- config/application.rb
|
128
|
+
- config/environment.rb
|
129
|
+
- config/puma.rb
|
130
|
+
- lib/tetrahedron.rb
|
131
|
+
- lib/tetrahedron/app.rb
|
132
|
+
- lib/tetrahedron/assets.rb
|
133
|
+
- lib/tetrahedron/base.rb
|
134
|
+
- lib/tetrahedron/bootfile.rb
|
135
|
+
- lib/tetrahedron/bundler.rb
|
136
|
+
- lib/tetrahedron/configuration.rb
|
137
|
+
- lib/tetrahedron/controller.rb
|
138
|
+
- lib/tetrahedron/database.rb
|
139
|
+
- lib/tetrahedron/endpoint.rb
|
140
|
+
- lib/tetrahedron/environment.rb
|
141
|
+
- lib/tetrahedron/error.rb
|
142
|
+
- lib/tetrahedron/errors.rb
|
143
|
+
- lib/tetrahedron/gem.rb
|
144
|
+
- lib/tetrahedron/model.rb
|
145
|
+
- lib/tetrahedron/rake.rb
|
146
|
+
- lib/tetrahedron/redis.rb
|
147
|
+
- lib/tetrahedron/sessions.rb
|
148
|
+
- lib/tetrahedron/tasks/tet.rake
|
149
|
+
- tetrahedron.gemspec
|
150
|
+
homepage: http://github.com/mtwilliams/tetrahedron
|
151
|
+
licenses:
|
152
|
+
- Public Domain
|
153
|
+
metadata: {}
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 2.2.3
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
requirements: []
|
169
|
+
rubyforge_project:
|
170
|
+
rubygems_version: 2.4.5.1
|
171
|
+
signing_key:
|
172
|
+
specification_version: 4
|
173
|
+
summary: Opinionated Sinatra.
|
174
|
+
test_files: []
|