lotusrb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +241 -311
  4. data/bin/lotus +4 -0
  5. data/lib/lotus/application.rb +111 -15
  6. data/lib/lotus/cli.rb +85 -0
  7. data/lib/lotus/commands/console.rb +62 -0
  8. data/lib/lotus/commands/new.rb +32 -0
  9. data/lib/lotus/commands/routes.rb +14 -0
  10. data/lib/lotus/commands/server.rb +65 -0
  11. data/lib/lotus/config/assets.rb +24 -10
  12. data/lib/lotus/config/configure.rb +17 -0
  13. data/lib/lotus/config/framework_configuration.rb +30 -0
  14. data/lib/lotus/config/load_paths.rb +1 -1
  15. data/lib/lotus/config/sessions.rb +97 -0
  16. data/lib/lotus/configuration.rb +698 -40
  17. data/lib/lotus/container.rb +30 -0
  18. data/lib/lotus/environment.rb +377 -0
  19. data/lib/lotus/frameworks.rb +17 -28
  20. data/lib/lotus/generators/abstract.rb +31 -0
  21. data/lib/lotus/generators/application/container/.gitkeep +1 -0
  22. data/lib/lotus/generators/application/container/Gemfile.tt +29 -0
  23. data/lib/lotus/generators/application/container/Rakefile.minitest.tt +10 -0
  24. data/lib/lotus/generators/application/container/config/.env.development.tt +2 -0
  25. data/lib/lotus/generators/application/container/config/.env.test.tt +2 -0
  26. data/lib/lotus/generators/application/container/config/.env.tt +1 -0
  27. data/lib/lotus/generators/application/container/config/environment.rb.tt +7 -0
  28. data/lib/lotus/generators/application/container/config.ru.tt +3 -0
  29. data/lib/lotus/generators/application/container/db/.gitkeep +1 -0
  30. data/lib/lotus/generators/application/container/features_helper.rb.tt +11 -0
  31. data/lib/lotus/generators/application/container/lib/app_name.rb.tt +31 -0
  32. data/lib/lotus/generators/application/container/lib/chirp/entities/.gitkeep +1 -0
  33. data/lib/lotus/generators/application/container/lib/chirp/repositories/.gitkeep +1 -0
  34. data/lib/lotus/generators/application/container/spec_helper.rb.tt +7 -0
  35. data/lib/lotus/generators/application/container.rb +70 -0
  36. data/lib/lotus/generators/slice/.gitkeep.tt +1 -0
  37. data/lib/lotus/generators/slice/action.rb.tt +8 -0
  38. data/lib/lotus/generators/slice/application.rb.tt +182 -0
  39. data/lib/lotus/generators/slice/config/mapping.rb.tt +10 -0
  40. data/lib/lotus/generators/slice/config/routes.rb.tt +8 -0
  41. data/lib/lotus/generators/slice/templates/application.html.erb +9 -0
  42. data/lib/lotus/generators/slice/templates/application.html.erb.tt +9 -0
  43. data/lib/lotus/generators/slice/templates/template.html.erb.tt +2 -0
  44. data/lib/lotus/generators/slice/view.rb.tt +5 -0
  45. data/lib/lotus/generators/slice/views/application_layout.rb.tt +7 -0
  46. data/lib/lotus/generators/slice.rb +103 -0
  47. data/lib/lotus/loader.rb +99 -19
  48. data/lib/lotus/middleware.rb +92 -9
  49. data/lib/lotus/rendering_policy.rb +42 -19
  50. data/lib/lotus/routing/default.rb +1 -1
  51. data/lib/lotus/setup.rb +5 -0
  52. data/lib/lotus/templates/welcome.html +49 -0
  53. data/lib/lotus/version.rb +1 -1
  54. data/lib/lotus/views/default.rb +13 -0
  55. data/lib/lotus/views/default_template_finder.rb +19 -0
  56. data/lib/lotus/welcome.rb +14 -0
  57. data/lib/lotus.rb +1 -0
  58. data/lotusrb.gemspec +9 -5
  59. metadata +122 -36
@@ -14,22 +14,47 @@ module Lotus
14
14
  # require 'lotus'
15
15
  #
16
16
  # module Bookshelf
17
- # Application < Lotus::Application
17
+ # class Application < Lotus::Application
18
18
  # end
19
19
  # end
20
20
  class Application
21
- include Lotus::Utils::ClassAttribute
21
+ # Override Ruby's Class#inherited
22
+ #
23
+ # @since 0.2.0
24
+ # @api private
25
+ #
26
+ # @see http://www.ruby-doc.org/core/Class.html#method-i-inherited
27
+ def self.inherited(base)
28
+ super
29
+
30
+ base.class_eval do
31
+ include Lotus::Utils::ClassAttribute
22
32
 
23
- # Application configuration
33
+ class_attribute :configuration
34
+ self.configuration = Configuration.new
35
+ end
36
+
37
+ synchronize do
38
+ applications.add(base)
39
+ end
40
+ end
41
+
42
+ # Registry of Lotus applications in the current Ruby process
24
43
  #
25
- # @since 0.1.0
44
+ # @return [Set] a set of all the registered applications
45
+ #
46
+ # @since 0.2.0
26
47
  # @api private
27
- class_attribute :configuration
28
- self.configuration = Configuration.new
48
+ def self.applications
49
+ synchronize do
50
+ @@applications ||= Set.new
51
+ end
52
+ end
29
53
 
30
54
  # Configure the application.
31
55
  # It yields the given block in the context of the configuration
32
56
  #
57
+ # @param environment [Symbol,nil] the configuration environment name
33
58
  # @param blk [Proc] the configuration block
34
59
  #
35
60
  # @since 0.1.0
@@ -46,8 +71,8 @@ module Lotus
46
71
  # end
47
72
  # end
48
73
  # end
49
- def self.configure(&blk)
50
- configuration.configure(&blk)
74
+ def self.configure(environment = nil, &blk)
75
+ configuration.configure(environment, &blk)
51
76
  end
52
77
 
53
78
  # Return the routes for this application
@@ -67,16 +92,68 @@ module Lotus
67
92
  # @api private
68
93
  attr_writer :routes
69
94
 
95
+ # Rendering policy
96
+ #
97
+ # @param [Lotus::RenderingPolicy]
98
+ #
99
+ # @since 0.2.0
100
+ # @api private
101
+ attr_accessor :renderer
102
+
70
103
  # Initialize and load a new instance of the application
71
104
  #
72
105
  # @return [Lotus::Application] a new instance of the application
73
106
  #
74
107
  # @since 0.1.0
75
108
  def initialize
76
- @loader = Lotus::Loader.new(self)
77
- @loader.load!
109
+ self.class.load!(self)
110
+ end
78
111
 
79
- @rendering_policy = RenderingPolicy.new(configuration)
112
+ # Eager load the application configuration, by activating the framework
113
+ # duplication mechanisms.
114
+ #
115
+ # @param application [Lotus::Application, Class<Lotus::Application>]
116
+ # @return void
117
+ #
118
+ # @since 0.1.1
119
+ #
120
+ # @example
121
+ # require 'lotus'
122
+ #
123
+ # module OneFile
124
+ # class Application < Lotus::Application
125
+ # configure do
126
+ # routes do
127
+ # get '/', to: 'dashboard#index'
128
+ # end
129
+ # end
130
+ #
131
+ # load!
132
+ # end
133
+ #
134
+ # module Controllers::Dashboard
135
+ # include OneFile::Controller
136
+ #
137
+ # action 'Index' do
138
+ # def call(params)
139
+ # self.body = 'Hello!'
140
+ # end
141
+ # end
142
+ # end
143
+ # end
144
+ def self.load!(application = self)
145
+ Lotus::Loader.new(application).load!
146
+ end
147
+
148
+ # Preload all the registered applications
149
+ #
150
+ # @return [void]
151
+ #
152
+ # @since 0.2.0
153
+ def self.preload!
154
+ synchronize do
155
+ applications.each(&:load!)
156
+ end
80
157
  end
81
158
 
82
159
  # Return the configuration for this application
@@ -89,6 +166,14 @@ module Lotus
89
166
  self.class.configuration
90
167
  end
91
168
 
169
+ # Return the application name
170
+ #
171
+ # @since 0.2.0
172
+ # @api private
173
+ def name
174
+ self.class.name
175
+ end
176
+
92
177
  # Process a request.
93
178
  # This method makes Lotus applications compatible with the Rack protocol.
94
179
  #
@@ -101,9 +186,8 @@ module Lotus
101
186
  # @see http://rack.github.io
102
187
  # @see Lotus::Application#middleware
103
188
  def call(env)
104
- middleware.call(env).tap do |response|
105
- @rendering_policy.render(response)
106
- end
189
+ renderer.render(env,
190
+ middleware.call(env))
107
191
  end
108
192
 
109
193
  # Rack middleware stack
@@ -115,7 +199,19 @@ module Lotus
115
199
  #
116
200
  # @see Lotus::Middleware
117
201
  def middleware
118
- @middleware ||= Lotus::Middleware.new(self)
202
+ @middleware ||= configuration.middleware
203
+ end
204
+
205
+ private
206
+
207
+ # Yields the given block in a critical section
208
+ #
209
+ # @since 0.2.0
210
+ # @api private
211
+ def self.synchronize
212
+ Mutex.new.synchronize do
213
+ yield
214
+ end
119
215
  end
120
216
  end
121
217
  end
data/lib/lotus/cli.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'thor'
2
+ require 'lotus/environment'
3
+ require 'lotus/version'
4
+
5
+ module Lotus
6
+ class Cli < Thor
7
+ include Thor::Actions
8
+
9
+ desc 'version', 'prints Lotus version'
10
+ def version
11
+ puts Lotus::VERSION
12
+ end
13
+
14
+ desc 'server', 'starts a lotus server'
15
+ method_option :port, aliases: '-p', desc: 'The port to run the server on, '
16
+ method_option :server, desc: 'choose a specific Rack::Handler, e.g. webrick, thin etc'
17
+ method_option :rackup, desc: 'a rackup configuration file path to load (config.ru)'
18
+ method_option :host, desc: 'the host address to bind to'
19
+ method_option :debug, desc: 'turn on debug output'
20
+ method_option :warn, desc: 'turn on warnings'
21
+ method_option :daemonize, desc: 'if true, the server will daemonize itself (fork, detach, etc)'
22
+ method_option :pid, desc: 'path to write a pid file after daemonize'
23
+ method_option :environment, desc: 'path to environment configuration (config/environment.rb)'
24
+ method_option :code_reloading, desc: 'code reloading', type: :boolean, default: true
25
+ method_option :help, aliases: '-h', desc: 'displays the usage message'
26
+
27
+ def server
28
+ if options[:help]
29
+ invoke :help, ['server']
30
+ else
31
+ require 'lotus/commands/server'
32
+ Lotus::Commands::Server.new(environment).start
33
+ end
34
+ end
35
+
36
+ desc 'console', 'starts a lotus console'
37
+ method_option :environment, desc: 'path to environment configuration (config/environment.rb)'
38
+ method_option :engine, desc: 'choose a specific console engine: irb/pry/ripl (irb)'
39
+ method_option :help, aliases: '-h', desc: 'displays the usage method'
40
+
41
+ def console
42
+ if options[:help]
43
+ invoke :help, ['console']
44
+ else
45
+ require 'lotus/commands/console'
46
+ Lotus::Commands::Console.new(environment).start
47
+ end
48
+ end
49
+
50
+ desc 'routes', 'prints routes'
51
+ method_option :environment, desc: 'path to environment configuration (config/environment.rb)'
52
+ method_option :help, aliases: '-h', desc: 'displays the usage method'
53
+
54
+ def routes
55
+ if options[:help]
56
+ invoke :help, ['routes']
57
+ else
58
+ require 'lotus/commands/routes'
59
+ Lotus::Commands::Routes.new(environment).start
60
+ end
61
+ end
62
+
63
+ desc 'new', 'generates a new application'
64
+ method_option :architecture, aliases: '-a', desc: 'application architecture', type: :string, default: 'container'
65
+ method_option :application, desc: 'application name', type: :string, default: 'web'
66
+ method_option :application_base_url, desc: 'application base url', type: :string, default: '/'
67
+ method_option :lotus_head, desc: 'use Lotus HEAD', type: :boolean, default: false
68
+ method_option :help, aliases: '-h', desc: 'displays the usage method'
69
+
70
+ def new(name = nil)
71
+ if options[:help]
72
+ invoke :help, ['new']
73
+ else
74
+ require 'lotus/commands/new'
75
+ Lotus::Commands::New.new(name, environment, self).start
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def environment
82
+ Lotus::Environment.new(options)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,62 @@
1
+ module Lotus
2
+ module Commands
3
+ class Console
4
+ module Methods
5
+ def reload!
6
+ puts 'Reloading...'
7
+ Kernel.exec "#{$0} console"
8
+ end
9
+ end
10
+
11
+ ENGINES = {
12
+ 'pry' => 'Pry',
13
+ 'ripl' => 'Ripl',
14
+ 'irb' => 'IRB'
15
+ }.freeze
16
+
17
+ attr_reader :options
18
+
19
+ def initialize(environment)
20
+ @environment = environment
21
+ @options = environment.to_options
22
+ end
23
+
24
+ def start
25
+ # Clear out ARGV so Pry/IRB don't attempt to parse the rest
26
+ ARGV.shift until ARGV.empty?
27
+ require @environment.env_config.to_s
28
+
29
+ # Add convenience methods to the main:Object binding
30
+ TOPLEVEL_BINDING.eval('self').send(:include, Methods)
31
+ Lotus::Application.preload!
32
+
33
+ engine.start
34
+ end
35
+
36
+ def engine
37
+ load_engine options.fetch(:engine) { engine_lookup }
38
+ end
39
+
40
+ private
41
+
42
+ def engine_lookup
43
+ (ENGINES.find { |_, klass| Object.const_defined?(klass) } || default_engine).first
44
+ end
45
+
46
+ def default_engine
47
+ ENGINES.to_a.last
48
+ end
49
+
50
+ def load_engine(engine)
51
+ require engine
52
+ rescue LoadError
53
+ ensure
54
+ return Object.const_get(
55
+ ENGINES.fetch(engine) {
56
+ raise ArgumentError.new("Unknown console engine: #{ engine }")
57
+ }
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,32 @@
1
+ require 'pathname'
2
+ require 'lotus/utils/string'
3
+ require 'lotus/utils/class'
4
+
5
+ module Lotus
6
+ module Commands
7
+ class New
8
+ GENERATORS_NAMESPACE = "Lotus::Generators::Application::%s".freeze
9
+
10
+ attr_reader :app_name, :source, :target, :cli, :options
11
+
12
+ def initialize(app_name, environment, cli)
13
+ @app_name = app_name
14
+ @options = environment.to_options
15
+ @arch = @options.fetch(:architecture)
16
+
17
+ @target = Pathname.pwd.join(@app_name)
18
+ @source = Pathname.new(@options.fetch(:source) { ::File.dirname(__FILE__) + '/../generators/application/' }).join(@arch)
19
+
20
+ @cli = cli
21
+
22
+ require "lotus/generators/application/#{ @arch }"
23
+ command = Utils::String.new(@arch).classify
24
+ @command = Utils::Class.load!(GENERATORS_NAMESPACE % command).new(self)
25
+ end
26
+
27
+ def start
28
+ @command.start
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ module Lotus
2
+ module Commands
3
+ class Routes
4
+ def initialize(environment)
5
+ @environment = environment
6
+ end
7
+
8
+ def start
9
+ require @environment.env_config
10
+ puts Lotus::Container.new.routes.inspector.to_s
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ require 'rack'
2
+
3
+ module Lotus
4
+ module Commands
5
+ # Rack compatible server.
6
+ #
7
+ # It is run with:
8
+ #
9
+ # `bundle exec lotus server`
10
+ #
11
+ # It runs the application, by using the server specified in your `Gemfile`
12
+ # (eg. Puma or Unicorn).
13
+ #
14
+ # It enables code reloading by default.
15
+ # This feature is implemented via process fork and requires `shotgun` gem.
16
+ #
17
+ # @since 0.1.0
18
+ # @api private
19
+ class Server < ::Rack::Server
20
+ attr_reader :options
21
+
22
+ def initialize(env)
23
+ @_env = env
24
+ @options = _extract_options(@_env)
25
+
26
+ if code_reloading?
27
+ require 'shotgun'
28
+ @app = Shotgun::Loader.new(@_env.rackup.to_s)
29
+ end
30
+ end
31
+
32
+ # Primarily this removes the ::Rack::Chunked middleware
33
+ # which is the cause of Safari content-length bugs.
34
+ def middleware
35
+ mw = Hash.new { |e, m| e[m] = [] }
36
+ mw["deployment"].concat([::Rack::ContentLength, ::Rack::CommonLogger])
37
+ mw["development"].concat(mw["deployment"] + [::Rack::ShowExceptions, ::Rack::Lint])
38
+ mw
39
+ end
40
+
41
+ def start
42
+ if code_reloading?
43
+ Shotgun.enable_copy_on_write
44
+ Shotgun.preload
45
+ end
46
+
47
+ super
48
+ end
49
+
50
+ private
51
+ def _extract_options(env)
52
+ env.to_options.merge(
53
+ config: env.rackup.to_s,
54
+ Host: env.host,
55
+ Port: env.port,
56
+ AccessLog: []
57
+ )
58
+ end
59
+
60
+ def code_reloading?
61
+ @_env.code_reloading?
62
+ end
63
+ end
64
+ end
65
+ end
@@ -4,26 +4,40 @@ module Lotus
4
4
  #
5
5
  # @since 0.1.0
6
6
  # @api private
7
- class Assets
7
+ class Assets < Utils::LoadPaths
8
8
  DEFAULT_DIRECTORY = 'public'.freeze
9
9
 
10
- def initialize(root, directory)
11
- @path = root.join(directory || DEFAULT_DIRECTORY)
10
+ # @since 0.1.0
11
+ # @api private
12
+ def initialize(root)
13
+ @root = root
14
+ @paths = Array(DEFAULT_DIRECTORY)
12
15
  end
13
16
 
17
+ # @since 0.1.0
18
+ # @api private
14
19
  def entries
15
- if @path.exist?
16
- @path.children.map {|child| "/#{ child.basename }" }
17
- else
18
- []
20
+ hash = Hash.new { |k, v| k[v] = [] }
21
+ each do |path|
22
+ if path.exist?
23
+ hash[path.to_s] = path.children.map { |child| "/#{ child.basename }" }
24
+ end
19
25
  end
26
+ hash
20
27
  end
21
28
 
22
- def to_s
23
- @path.to_s
29
+ # @since 0.2.0
30
+ # @api private
31
+ def any?
32
+ @paths.any?
24
33
  end
25
34
 
26
- alias_method :to_str, :to_s
35
+ protected
36
+ # @since 0.1.0
37
+ # @api private
38
+ def realpath(path)
39
+ @root.join(path).realpath
40
+ end
27
41
  end
28
42
  end
29
43
  end
@@ -0,0 +1,17 @@
1
+ require 'lotus/config/mapper'
2
+
3
+ module Lotus
4
+ module Config
5
+ # Define configuration of application of
6
+ # a specific environment
7
+ #
8
+ # @since 0.2.0
9
+ # @api private
10
+ class Configure < Mapper
11
+ private
12
+ def error_message
13
+ 'You must specify a block or a file for configuration definition'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Lotus
2
+ module Config
3
+ # Collects all the settings for a given framework configuration and then
4
+ # forwards them when the application is loaded.
5
+ #
6
+ # @since 0.2.0
7
+ # @api private
8
+ class FrameworkConfiguration < BasicObject
9
+ # @since 0.2.0
10
+ # @api private
11
+ def initialize
12
+ @settings = []
13
+ end
14
+
15
+ # @since 0.2.0
16
+ # @api private
17
+ def __apply(configuration)
18
+ @settings.each do |(m, args, blk)|
19
+ configuration.public_send(m, *args, &blk)
20
+ end
21
+ end
22
+
23
+ # @since 0.2.0
24
+ # @api private
25
+ def method_missing(m, *args, &blk)
26
+ @settings.push([m, args, blk])
27
+ end
28
+ end
29
+ end
30
+ end
@@ -13,7 +13,7 @@ module Lotus
13
13
  @root = root
14
14
 
15
15
  each do |path|
16
- Dir.glob(path.join(PATTERN)).each {|file| require file }
16
+ Dir.glob(path.join(PATTERN)).each { |file| require file }
17
17
  end
18
18
  end
19
19
 
@@ -0,0 +1,97 @@
1
+ require 'ipaddr'
2
+ require 'lotus/utils/string'
3
+
4
+ module Lotus
5
+ module Config
6
+ # Sessions configuration
7
+ #
8
+ # @since 0.2.0
9
+ # @api private
10
+ class Sessions
11
+
12
+ # Ruby namespace for Rack session adapters
13
+ #
14
+ # @since 0.2.0
15
+ # @api private
16
+ RACK_NAMESPACE = 'Rack::Session::%s'.freeze
17
+
18
+ # Localhost string for detecting localhost host configuration
19
+ #
20
+ # @since x.x.x
21
+ # @api private
22
+ BLACKLISTED_DOMAINS = %w(localhost).freeze
23
+
24
+ # HTTP sessions configuration
25
+ #
26
+ # @param adapter [Symbol,String,Class] the session adapter
27
+ # @param options [Hash] the optional session options
28
+ # @param configuration [Lotus::Configuration] the application configuration
29
+ #
30
+ # @since 0.2.0
31
+ # @api private
32
+ #
33
+ # @see http://www.rubydoc.info/github/rack/rack/Rack/Session/Abstract/ID
34
+ def initialize(adapter = nil, options = {}, configuration = nil)
35
+ @adapter = adapter
36
+ @options = options
37
+ @configuration = configuration
38
+ end
39
+
40
+ # Check if the sessions are enabled
41
+ #
42
+ # @return [FalseClass,TrueClass] the result of the check
43
+ #
44
+ # @since 0.2.0
45
+ # @api private
46
+ def enabled?
47
+ !!@adapter
48
+ end
49
+
50
+ # Returns the Rack middleware and the options
51
+ #
52
+ # @return [Array] Rack middleware and options
53
+ #
54
+ # @since 0.2.0
55
+ # @api private
56
+ def middleware
57
+ middleware = case @adapter
58
+ when Symbol
59
+ RACK_NAMESPACE % Utils::String.new(@adapter).classify
60
+ else
61
+ @adapter
62
+ end
63
+
64
+ [middleware, options]
65
+ end
66
+
67
+ private
68
+
69
+ # @since 0.2.0
70
+ # @api private
71
+ def options
72
+ default_options.merge(@options)
73
+ end
74
+
75
+ # @since 0.2.0
76
+ # @api private
77
+ def default_options
78
+ if @configuration
79
+ { domain: domain, secure: @configuration.ssl? }
80
+ else
81
+ {}
82
+ end
83
+ end
84
+
85
+ def domain
86
+ domain = @configuration.host
87
+ if !BLACKLISTED_DOMAINS.include?(domain) && !ip_address?(domain)
88
+ domain
89
+ end
90
+ end
91
+
92
+ def ip_address?(string)
93
+ !!IPAddr.new(string) rescue false
94
+ end
95
+ end
96
+ end
97
+ end