tetrahedron 0.0.0.1 → 0.0.1.0

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 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