tetrahedron 0.0.0.1 → 0.0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f11bb282f14b3b5c27899cdb70fef27f4154dc20
4
- data.tar.gz: a04eafc6d60df291569009131ee564c5fc404b44
3
+ metadata.gz: 5b9d0311bd16dd7fb7b8c35d19fcce8ae77a2036
4
+ data.tar.gz: 9ee81f6f029ba59b260b1e56f49db1dbb0233e87
5
5
  SHA512:
6
- metadata.gz: acb35b1fe3d7a07905a753ff2d8422db1478a097f22d18840230085cf14b8c108e0257a5c71ebc10e5969c7bd8eb3ef565638ed343f883ae8512b4195c47dce6
7
- data.tar.gz: 1c7365409ba0e9bf0d686a849df7d4a445730a23685af903c50bb2193e702960224be195dc8cc0e0265a5d0f33fd39ed3057443cb7f7f834456ef9b97794a2ab
6
+ metadata.gz: 9c2ac0f186880b502265c894461fba56c33e30b931069e614df647627c5fdb8981a65529a3a849a584d9aae7b7dba456f14d48a0a74c5e86a72f826212897b1b
7
+ data.tar.gz: 1a3cb3c6184c50a59bc0b76eff23b72af0717d4b96283646ee90b58286609cccef8386c7587a7cccf0afdc4e52c2e1e30479a8d1646fb01f17b5164d1d69b150
data/Gemfile.lock CHANGED
@@ -1,8 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tetrahedron (0.0.0.1)
4
+ tetrahedron (0.0.1.0)
5
5
  activesupport
6
+ erubis
7
+ mail
8
+ rack
9
+ rack-contrib
10
+ redis
6
11
  sequel
7
12
  sinatra
8
13
  sinatra-contrib
@@ -19,9 +24,14 @@ GEM
19
24
  tzinfo (~> 1.1)
20
25
  backports (3.6.7)
21
26
  coderay (1.1.0)
27
+ erubis (2.7.0)
28
+ git-version-bump (0.15.1)
22
29
  i18n (0.7.0)
23
30
  json (1.8.3)
31
+ mail (2.6.3)
32
+ mime-types (>= 1.16, < 3)
24
33
  method_source (0.8.2)
34
+ mime-types (2.99)
25
35
  minitest (5.8.3)
26
36
  multi_json (1.11.2)
27
37
  pry (0.10.3)
@@ -29,11 +39,15 @@ GEM
29
39
  method_source (~> 0.8.1)
30
40
  slop (~> 3.4)
31
41
  rack (1.6.4)
42
+ rack-contrib (1.4.0)
43
+ git-version-bump (~> 0.15)
44
+ rack (~> 1.4)
32
45
  rack-protection (1.5.3)
33
46
  rack
34
47
  rack-test (0.6.3)
35
48
  rack (>= 1.0)
36
49
  rake (10.4.2)
50
+ redis (3.2.2)
37
51
  sequel (4.28.0)
38
52
  sinatra (1.4.6)
39
53
  rack (~> 1.4)
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
- Tetrahedron
2
- ===========
1
+ # Tetrahedron [![Gem Version](https://img.shields.io/gem/v/tetrahedron.svg)](https://rubygems.org/gems/tetrahedron) [![Build Status](https://img.shields.io/travis/mtwilliams/tetrahedron/master.svg)](https://travis-ci.org/mtwilliams/tetrahedron) [![Code Climate](https://img.shields.io/codeclimate/github/mtwilliams/tetrahedron.svg)](https://codeclimate.com/github/mtwilliams/tetrahedron) [![Dependency Status](https://img.shields.io/gemnasium/mtwilliams/tetrahedron.svg)](https://gemnasium.com/mtwilliams/tetrahedron)
3
2
 
4
3
  [Welcome to the Tet.](https://www.youtube.com/watch?v=lt-udg9zQSE)
@@ -0,0 +1,16 @@
1
+ module Tetrahedron
2
+ class Application
3
+ class Base < Tetrahedron::Base
4
+ def self.inherited(base)
5
+ super(base)
6
+ base.send :define_singleton_method, :inherited do |basee|
7
+ super(basee)
8
+ basee.set :application, Proc.new { basee.to_s.split('::')[0..-2].join('::').constantize }
9
+ basee.set :environment, Proc.new { application.environment.to_sym }
10
+ basee.set :root, Proc.new { application.root }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,16 @@
1
+ module Tetrahedron
2
+ class Application
3
+ class Controller < Tetrahedron::Application::Base
4
+ set :views, Proc.new { "#{root}/app/broadsheet/views" }
5
+
6
+ # Recognize *.html.erb as Erubis templates.
7
+ Tilt.register Tilt::ErubisTemplate, 'html.erb'
8
+
9
+ # Don't recognize *.erb templates.
10
+ def erb(template, options={}, locals={})
11
+ options[:layout] = settings.erb[:layout] if options[:layout].nil?
12
+ render 'html.erb', template, options, locals
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Tetrahedron
2
+ class Application
3
+ class Endpoint < Tetrahedron::Application::Base
4
+ # TODO(mtwilliams): JSON.
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib/all'
3
+
4
+ module Tetrahedron
5
+ class Application < Tetrahedron::Base
6
+ def self.env
7
+ components = self.to_s.upcase.split('::')
8
+ possibilities = (components.size.downto(1).map{|n| components.first(n).join('_')+'_ENV'})
9
+ environments = (possibilities+['RACK_ENV']).map{|possibility| ENV[possibility]}
10
+ @env ||= ::ActiveSupport::StringInquirer.new(environments.reject(&:nil?).first)
11
+ end
12
+
13
+ set :environment, Proc.new { self.env.to_sym }
14
+ set :root, Dir.pwd
15
+
16
+ # Don't use the built-in web-server.
17
+ set :run, false
18
+
19
+ def self.inherited(application)
20
+ super(application)
21
+
22
+ Tetrahedron::Middleware.install(application)
23
+ Tetrahedron::Sessions.install(application)
24
+
25
+ application.const_set('Controller', Class.new(Controller))
26
+ application.const_set('Endpoint', Class.new(Endpoint))
27
+
28
+ Tetrahedron::Database.install(application)
29
+ end
30
+ end
31
+ end
@@ -1,11 +1,8 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib/all'
3
+
1
4
  module Tetrahedron
2
5
  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
6
  # Errors are bubbled so they can be handled by middleware.
10
7
  set :dump_errors, false
11
8
  set :raise_errors, true
@@ -14,21 +11,13 @@ module Tetrahedron
14
11
  # Fuck you, IE9.
15
12
  disable :method_override
16
13
 
17
- configure :development do
18
- # King of iteration, baby.
19
- register Sinatra::Reloader
20
- end
14
+ # TODO(mtwilliams): Re-enable basic protections, like traversal.
15
+ disable :protection
21
16
 
22
- configure do
23
- disable :static
24
- end
17
+ # Let users handle serving static files.
18
+ disable :static
25
19
 
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
20
+ # Hot-reload in development.
21
+ register Sinatra::Reloader
33
22
  end
34
23
  end
@@ -1,113 +1,89 @@
1
- module Tetrahedron
2
- module Database
3
- class Configuration
4
- Options = %i{adapter user password host port database pool}
5
- attr_reader *Options
1
+ require 'sequel'
6
2
 
7
- def initialize(configuration={})
8
- Options.each do |opt|
9
- self.instance_variable_set(:"@#{opt}", configuration[opt])
10
- end
11
- end
3
+ # Everyone needs these.
4
+ Sequel.extension :migration
5
+ Sequel::Model.plugin :timestamps
12
6
 
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
7
+ # The Tet needs this for the magic performed in `Model.db=`.
8
+ Sequel::Model.plugin :subclasses
21
9
 
22
- def self.configured?
23
- !@configuration.nil?
10
+ module Tetrahedron
11
+ class Database < Service
12
+ class Configuration < Service::Configuration
24
13
  end
25
14
 
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)
15
+ class Provider < Service::Provider
16
+ def connection; end
17
+ def connect; raise NotImplementedError; end
18
+ def disconnect; raise NotImplementedError; end
31
19
  end
32
20
 
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
21
+ def self.connected?
22
+ !self.connection.nil?
56
23
  end
57
24
 
58
- def self.wait_until_reachable!(overrides={})
59
- # TODO(mtwilliams): Raise a custom exception type.
60
- raise "Database is unreachable!" unless wait_until_reachable
25
+ def self.connection
26
+ configured!
27
+ self.class_variable_get(:@@provider).connection
61
28
  end
62
29
 
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
30
+ def self.connect
31
+ configured!
32
+ connected = self.class_variable_get(:@@provider).connect
33
+ # Back our models with the new connection.
34
+ model = self.class_variable_get(:@@application).const_get("Model")
35
+ model.db = self.connection if connected
36
+ connected
84
37
  end
85
38
 
86
39
  def self.disconnect
87
- connection = @connection
40
+ configured!
41
+ # TODO(mtwilliams): Verify that this is thread-safe.
42
+ model = self.class_variable_get(:@@application).const_get("Model")
43
+ model.db = Sequel.mock
44
+ self.class_variable_get(:@@provider).disconnect
45
+ end
88
46
 
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
47
+ def self.install(application)
48
+ super(application)
93
49
 
94
- connection.disconnect unless connection.nil?
95
- end
50
+ # TODO(mtwilliams): Refactor out this horrid mess.
96
51
 
97
- def self.connection
98
- @connection
99
- end
52
+ # We can't build an inheritence heirarchy.
53
+ # See https://groups.google.com/d/msg/sequel-talk/OG5ti9JAJIE/p1iqO57cwqwJ.
54
+ application.class_eval("Model = Class.new(Sequel::Model);")
55
+ model = application.const_get("Model")
100
56
 
101
- def self.reset
102
- # TODO(mtwilliams): Drop and recreate database.
103
- end
57
+ # Stop Sequel from bitching when we users subclass models before the
58
+ # database connection is established.
59
+ model.db = Sequel.mock
104
60
 
105
- def self.migrate
106
- Sequel::IntegerMigrator.run(connection, File.join(Tetrahedron.config.root, 'db', 'migrations'))
107
- end
61
+ # Back descendents.
62
+ model.send :define_singleton_method, :db= do |db|
63
+ super(db)
64
+ # All the way down, boys.
65
+ self.descendents.each do |descendent|
66
+ descendent.db = db
67
+ end
68
+ end
108
69
 
109
- def self.seed
110
- Kernel.load(File.join(Tetrahedron.config.root, 'db', 'seed.rb'))
70
+ # Custom names are cool, m'kay.
71
+ application.send :define_singleton_method, :Model do |source|
72
+ anonymous_model_class = model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize do
73
+ model::ANONYMOUS_MODEL_CLASSES[source]
74
+ end
75
+ unless anonymous_model_class
76
+ anonymous_model_class = if source.is_a?(Sequel::Database)
77
+ Class.new(model).db = source
78
+ else
79
+ Class.new(model).set_dataset(source)
80
+ end
81
+ model::ANONYMOUS_MODEL_CLASSES_MUTEX.synchronize do
82
+ model::ANONYMOUS_MODEL_CLASSES[source] = anonymous_model_class
83
+ end
84
+ end
85
+ anonymous_model_class
86
+ end
111
87
  end
112
88
  end
113
89
  end
@@ -0,0 +1,48 @@
1
+ module Tetrahedron
2
+ module Databases
3
+ class Postgres < Database::Provider
4
+ class Configuration < Database::Configuration
5
+ attr_accessor :host,
6
+ :port,
7
+ :user,
8
+ :password,
9
+ :database,
10
+ :pool
11
+ end
12
+
13
+ def initialize
14
+ @configuration = Configuration.new
15
+ yield @configuration if block_given?
16
+ end
17
+
18
+ def connection
19
+ @connection
20
+ end
21
+
22
+ def connect
23
+ # Wait some amount of time before assuming the database is down.
24
+ Service.wait_until_reachable!(:protocol => :tcp,
25
+ :host => @configuration.host,
26
+ :port => @configuration.port,
27
+ :timeout => 15)
28
+
29
+ @connection = Sequel.postgres(:host => @configuration.host,
30
+ :port => @configuration.port,
31
+ :user => @configuration.user,
32
+ :password => @configuration.password,
33
+ :database => @configuration.database,
34
+ :test => true,
35
+ :sslmode => :prefer,
36
+ :max_connections => @configuration.pool)
37
+
38
+ true
39
+ end
40
+
41
+ def disconnect
42
+ @connection.disconnect if @connection
43
+ @connection = nil
44
+ true
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ module Tetrahedron
2
+ module Databases
3
+ class SQLite3 < Database::Provider
4
+ class Configuration < Database::Configuration
5
+ attr_accessor :path
6
+ end
7
+
8
+ def initialize
9
+ @configuration = Configuration.new
10
+ yield @configuration if block_given?
11
+ end
12
+
13
+ def connection
14
+ @connection
15
+ end
16
+
17
+ def connect
18
+ # If no path was specified, default to a transient in-memory database.
19
+ @connection = Sequel.sqlite(:database => (@configuration.path || ':memory:'))
20
+ true
21
+ end
22
+
23
+ def disconnect
24
+ @connection.disconnect if @connection
25
+ @connection = nil
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -32,7 +32,7 @@ module Tetrahedron
32
32
 
33
33
  # A short summary of this Gem.
34
34
  def self.summary
35
- "Opinionated Sinatra."
35
+ "Welcome to the Tet."
36
36
  end
37
37
 
38
38
  # A full description of this Gem.
@@ -41,7 +41,7 @@ module Tetrahedron
41
41
  end
42
42
 
43
43
  module VERSION #:nodoc:
44
- MAJOR, MINOR, PATCH, PRE = [0, 0, 0, 1]
44
+ MAJOR, MINOR, PATCH, PRE = [0, 0, 1, 0]
45
45
  STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
46
46
  end
47
47
 
@@ -0,0 +1,25 @@
1
+ module Tetrahedron
2
+ class Middleware
3
+ def self.use(middleware, *args, &block)
4
+ stack = self.class_variable_get(:@@stack)
5
+ stack << proc { |app| middleware.new(app, *args, &block) }
6
+ self.class_variable_set(:@@stack, stack)
7
+ end
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ middlewares = self.class.class_variable_get(:@@stack).reverse
15
+ wrapped = middlewares.inject(@app) {|_, middleware| middleware[_]}
16
+ wrapped.call(env)
17
+ end
18
+
19
+ def self.install(application)
20
+ middleware = Class.new(self)
21
+ application.const_set('Middleware', middleware)
22
+ middleware.class_variable_set(:@@stack, [])
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ module Tetrahedron
2
+ class Service
3
+ def self.install(application)
4
+ service = Class.new(self)
5
+ service.class_variable_set(:@@application, application)
6
+ application.const_set(self.to_s.split('::').last, service)
7
+ end
8
+
9
+ Unconfigured = Class.new(Tetrahedron::Error)
10
+ Misconfigured = Class.new(Tetrahedron::Error)
11
+ AlreadyConfigured = Class.new(Tetrahedron::Error)
12
+
13
+ class Configuration
14
+ end
15
+
16
+ def self.configured?
17
+ self.class_variable_defined?(:@@provider)
18
+ end
19
+
20
+ def self.configured!
21
+ raise Unconfigured unless self.configured?
22
+ end
23
+
24
+ def self.configure(&configurator)
25
+ raise AlreadyConfigured if self.configured?
26
+ provider = configurator.call()
27
+ raise Misconfigured unless provider.is_a? Provider
28
+ self.class_variable_set(:@@provider, provider)
29
+ end
30
+
31
+ Unreachable = Class.new(Tetrahedron::Error)
32
+
33
+ class Provider
34
+ end
35
+
36
+ def self.wait_until_reachable(opts={})
37
+ Timeout::timeout(opts.fetch(:timeout, 15)) do
38
+ while true
39
+ begin
40
+ case opts[:protocol]
41
+ when :tcp
42
+ TCPSocket.new(opts[:host], opts[:port]).close
43
+ return true
44
+ when :udp
45
+ raise "This makes no sense!"
46
+ end
47
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
48
+ # We're all good citizens, right?
49
+ Kernel.sleep(1)
50
+ end
51
+ end
52
+ end
53
+ rescue Timeout::Error
54
+ false
55
+ end
56
+
57
+ def self.wait_until_reachable!(opts={})
58
+ raise Unreachable unless self.wait_until_reachable(opts)
59
+ end
60
+ end
61
+ end
@@ -1,28 +1,37 @@
1
1
  module Tetrahedron
2
- module Sessions
2
+ class Sessions
3
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])
4
+ OPTIONS = [:key, :domain, :path, :expires, :secret]
5
+ attr_accessor *OPTIONS
6
+ def dsl(&block)
7
+ config = self
8
+ dsl = Class.new
9
+ OPTIONS.each do |option|
10
+ dsl.send :define_method, option.to_sym do |value|
11
+ config.instance_variable_set(:"@#{option}", value)
12
+ end
10
13
  end
14
+ dsl.new.instance_eval(&block)
11
15
  end
12
16
  end
13
17
 
14
- def self.config
15
- @configuration
16
- end
17
-
18
- def self.configured?
19
- !@configuration.nil?
18
+ def self.configure(&configurator)
19
+ application = self.class_variable_get(:@@application)
20
+ configuration = Configuration.new
21
+ configuration.key = (application.to_s.underscore.split('::')+['session']).join('.')
22
+ configuration.dsl(&configurator)
23
+ middleware = application.const_get('Middleware')
24
+ middleware.use(Rack::Session::Cookie, :key => configuration.key,
25
+ :domain => configuration.domain,
26
+ :path => configuration.path,
27
+ :expire_after => configuration.expires,
28
+ :secret => configuration.secret)
20
29
  end
21
30
 
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)
31
+ def self.install(application)
32
+ sessions = Class.new(self)
33
+ application.const_set('Sessions', sessions)
34
+ sessions.class_variable_set(:@@application, application)
26
35
  end
27
36
  end
28
37
  end
data/lib/tetrahedron.rb CHANGED
@@ -1,59 +1,31 @@
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
1
  require 'active_support'
19
2
  require 'active_support/core_ext'
20
3
 
21
4
  module Tetrahedron
22
- def self.root
23
- @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
24
- end
5
+ require 'tetrahedron/gem'
6
+ require 'tetrahedron/error'
7
+
8
+ require 'tetrahedron/middleware'
9
+ require 'tetrahedron/sessions'
10
+
11
+ require 'tetrahedron/base'
12
+ require 'tetrahedron/application'
13
+ require 'tetrahedron/application/base'
14
+ require 'tetrahedron/application/controller'
15
+ require 'tetrahedron/application/endpoint'
16
+
17
+ require 'tetrahedron/service'
18
+ require 'tetrahedron/database'
19
+ require 'tetrahedron/databases/sqlite3'
20
+ require 'tetrahedron/databases/postgres'
21
+
22
+ # require 'tetrahedron/cache'
23
+ # require 'tetrahedron/caches/null' #=> ActiveSupport::Cache::NullStore
24
+ # require 'tetrahedron/caches/memory' #=> ActiveSupport::Cache::MemoryStore
25
+ # require 'tetrahedron/caches/memcache' #=> Dalli
26
+ # require 'tetrahedron/caches/redis' #=> Hiredis
27
+ # require 'tetrahedron/queue'
28
+ # require 'tetrahedron/queues/null' #=> .*~~~ The Abyss ~~~*.
29
+ # require 'tetrahedron/queues/spawn' #=> Thread.new
30
+ # require 'tetrahedron/queues/sidekiq' #=> Sidekiq (from Redis, with Love)
25
31
  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'
data/tetrahedron.gemspec CHANGED
@@ -23,12 +23,17 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency("rake")
24
24
  s.add_development_dependency("pry")
25
25
 
26
+ s.add_dependency("thor")
27
+
26
28
  s.add_dependency("activesupport")
27
29
 
30
+ s.add_dependency("rack")
31
+ s.add_dependency("rack-contrib")
28
32
  s.add_dependency("sinatra")
29
33
  s.add_dependency("sinatra-contrib")
34
+ s.add_dependency("erubis")
30
35
 
31
36
  s.add_dependency("sequel")
32
-
33
- s.add_dependency("thor")
37
+ s.add_dependency("redis")
38
+ s.add_dependency("mail")
34
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tetrahedron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.1
4
+ version: 0.0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-27 00:00:00.000000000 Z
11
+ date: 2015-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
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'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: activesupport
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,34 @@ dependencies:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
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: rack-contrib
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'
55
97
  - !ruby/object:Gem::Dependency
56
98
  name: sinatra
57
99
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +122,20 @@ dependencies:
80
122
  - - ">="
81
123
  - !ruby/object:Gem::Version
82
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: erubis
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: sequel
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -95,7 +151,21 @@ dependencies:
95
151
  - !ruby/object:Gem::Version
96
152
  version: '0'
97
153
  - !ruby/object:Gem::Dependency
98
- name: thor
154
+ name: redis
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: mail
99
169
  requirement: !ruby/object:Gem::Requirement
100
170
  requirements:
101
171
  - - ">="
@@ -123,29 +193,20 @@ files:
123
193
  - README.md
124
194
  - Rakefile
125
195
  - bin/tetrahedron
126
- - config/app.ru
127
- - config/application.rb
128
- - config/environment.rb
129
- - config/puma.rb
130
196
  - lib/tetrahedron.rb
131
- - lib/tetrahedron/app.rb
132
- - lib/tetrahedron/assets.rb
197
+ - lib/tetrahedron/application.rb
198
+ - lib/tetrahedron/application/base.rb
199
+ - lib/tetrahedron/application/controller.rb
200
+ - lib/tetrahedron/application/endpoint.rb
133
201
  - 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
202
  - lib/tetrahedron/database.rb
139
- - lib/tetrahedron/endpoint.rb
140
- - lib/tetrahedron/environment.rb
203
+ - lib/tetrahedron/databases/postgres.rb
204
+ - lib/tetrahedron/databases/sqlite3.rb
141
205
  - lib/tetrahedron/error.rb
142
- - lib/tetrahedron/errors.rb
143
206
  - lib/tetrahedron/gem.rb
144
- - lib/tetrahedron/model.rb
145
- - lib/tetrahedron/rake.rb
146
- - lib/tetrahedron/redis.rb
207
+ - lib/tetrahedron/middleware.rb
208
+ - lib/tetrahedron/service.rb
147
209
  - lib/tetrahedron/sessions.rb
148
- - lib/tetrahedron/tasks/tet.rake
149
210
  - tetrahedron.gemspec
150
211
  homepage: http://github.com/mtwilliams/tetrahedron
151
212
  licenses:
@@ -170,5 +231,5 @@ rubyforge_project:
170
231
  rubygems_version: 2.4.5.1
171
232
  signing_key:
172
233
  specification_version: 4
173
- summary: Opinionated Sinatra.
234
+ summary: Welcome to the Tet.
174
235
  test_files: []
data/config/app.ru DELETED
@@ -1,21 +0,0 @@
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
@@ -1,8 +0,0 @@
1
- # TODO(mtwilliams): Better multitenancy.
2
-
3
- $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
- require 'tetrahedron'
5
-
6
- require File.join(Tetrahedron.config.root, "config", "application")
7
-
8
- Tetrahedron::Configuration.load
@@ -1,18 +0,0 @@
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 DELETED
@@ -1,29 +0,0 @@
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
@@ -1,16 +0,0 @@
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
@@ -1,43 +0,0 @@
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
@@ -1,43 +0,0 @@
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
@@ -1,10 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
-
4
- module Tetrahedron
5
- module Bundler
6
- def self.require
7
- ::Bundler.require(:default, :assets, :db, :redis, Tetrahedron.env.to_sym)
8
- end
9
- end
10
- end
@@ -1,44 +0,0 @@
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
@@ -1,17 +0,0 @@
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
@@ -1,4 +0,0 @@
1
- module Tetrahedron
2
- class Endpoint < Tetrahedron::Base
3
- end
4
- end
@@ -1,28 +0,0 @@
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
@@ -1,7 +0,0 @@
1
- module Tetrahedron
2
- class MisconfiguredError < Error
3
- def initialize(setting, reason)
4
- super("The setting `#{setting}` is misconfigured: #{reason}!")
5
- end
6
- end
7
- end
@@ -1,32 +0,0 @@
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
@@ -1,16 +0,0 @@
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
@@ -1,4 +0,0 @@
1
- module Tetrahedron
2
- module Redis
3
- end
4
- end
@@ -1,5 +0,0 @@
1
- namespace :tet do
2
- task :up do
3
- exec "puma -C #{File.join(Tetrahedron.root, 'config', 'puma.rb')}"
4
- end
5
- end