appetizer 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +48 -0
- data/Rakefile +2 -0
- data/appetizer.gemspec +18 -0
- data/lib/appetizer/console.rb +3 -0
- data/lib/appetizer/events.rb +15 -0
- data/lib/appetizer/init.rb +3 -0
- data/lib/appetizer/populator.rb +62 -0
- data/lib/appetizer/rack/splash.rb +29 -0
- data/lib/appetizer/rack.rb +2 -0
- data/lib/appetizer/rake.rb +19 -0
- data/lib/appetizer/setup.rb +101 -0
- data/lib/appetizer/tasks/console.rake +5 -0
- data/lib/appetizer/tasks/init.rake +1 -0
- data/lib/appetizer/tasks/test/test.rake +12 -0
- data/lib/appetizer/test.rb +43 -0
- data/lib/appetizer.rb +1 -0
- data/test/appetizer/populator_test.rb +77 -0
- metadata +65 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Appetizer
|
2
|
+
|
3
|
+
A lightweight init process for Rack apps.
|
4
|
+
|
5
|
+
## Assumptions
|
6
|
+
|
7
|
+
* Running Ruby 1.9.2 or better.
|
8
|
+
* Using Bundler.
|
9
|
+
* Logging in all envs except `test` is to `STDOUT`. Logging in `test`
|
10
|
+
is to `tmp/test.log`.
|
11
|
+
* Default internal and external encodings are `Encoding::UTF_8`.
|
12
|
+
|
13
|
+
## Load/Init Lifecycle
|
14
|
+
|
15
|
+
0. Optionally `require "appetizer/{rack,rake}"`, which will
|
16
|
+
1. `require "appetizer/setup"`, which will
|
17
|
+
2. `load "config/env.local.rb"` if it exists, then
|
18
|
+
3. `load "config/env.rb"` if **it** exists.
|
19
|
+
4. `load "config/env/#{App.env}.rb"` if **it** exists, then
|
20
|
+
5. App code is loaded, but not initialized.
|
21
|
+
6. `App.init!` is called. Happens automatically if step 1 occurred.
|
22
|
+
7. Fire the `initializing` event.
|
23
|
+
8. `load "config/init.rb"` if it exists.
|
24
|
+
9. `load "config/{init/**/*.rb"`, then
|
25
|
+
10. Fire the `initialized` event.
|
26
|
+
|
27
|
+
## License (MIT)
|
28
|
+
|
29
|
+
Copyright 2011 Audiosocket (tech@audiosocket.com)
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/appetizer.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Audiosocket"]
|
5
|
+
gem.email = ["tech@audiosocket.com"]
|
6
|
+
gem.description = "A lightweight init process for Rack apps."
|
7
|
+
gem.summary = "Provides Railsish environments and initializers."
|
8
|
+
gem.homepage = "https://github.com/audiosocket/appetizer"
|
9
|
+
|
10
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
13
|
+
gem.name = "appetizer"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = "0.0.0"
|
16
|
+
|
17
|
+
gem.required_ruby_version = ">= 1.9.2"
|
18
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Appetizer
|
2
|
+
|
3
|
+
# Helps assign values from a low-level structure to a rich domain
|
4
|
+
# model. Most useful as an explicit alternative to ActiveRecord's
|
5
|
+
# mass-assignment, with an AR object as the target and a params hash
|
6
|
+
# as the source.
|
7
|
+
|
8
|
+
class Populator
|
9
|
+
attr_reader :target
|
10
|
+
attr_reader :source
|
11
|
+
|
12
|
+
def initialize target, source, &block
|
13
|
+
@target = target
|
14
|
+
@source = source
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def nested key, target = nil, &block
|
20
|
+
source = self.source[key] || self.source[key.intern]
|
21
|
+
target ||= self.target.send key
|
22
|
+
|
23
|
+
Populator.new target, source, &block if source
|
24
|
+
end
|
25
|
+
|
26
|
+
def set key, value = nil, &block
|
27
|
+
value ||= source[key] || source[key.intern]
|
28
|
+
|
29
|
+
return if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
30
|
+
block ? block[value] : target.send("#{key}=", value)
|
31
|
+
end
|
32
|
+
|
33
|
+
module Helpers
|
34
|
+
|
35
|
+
# Call `ctor.new` to create a new model object, then populate,
|
36
|
+
# save, and JSONify as in `update`.
|
37
|
+
|
38
|
+
def create ctor, &block
|
39
|
+
obj = populate ctor.new, &block
|
40
|
+
obj.save!
|
41
|
+
|
42
|
+
halt 201, json(obj)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use a populator to assign values from `params` to `obj`,
|
46
|
+
# returning it when finished. `&block` is passed a populator
|
47
|
+
# instance.
|
48
|
+
|
49
|
+
def populate obj, &block
|
50
|
+
Populator.new(obj, params, &block).target
|
51
|
+
end
|
52
|
+
|
53
|
+
# Populate (see `populate`) an `obj` with `params` data, saving
|
54
|
+
# when finished. Returns JSON for `obj`.
|
55
|
+
|
56
|
+
def update obj, &block
|
57
|
+
populate(obj, &block).save!
|
58
|
+
json obj
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Appetizer
|
2
|
+
module Rack
|
3
|
+
class Splash
|
4
|
+
def initialize root = "public", glob = "**/*", ¬found
|
5
|
+
notfound ||= lambda { |env|
|
6
|
+
[404, { "Content-Type" => "text/plain" }, []]
|
7
|
+
}
|
8
|
+
|
9
|
+
urls = Dir[File.join root, glob].sort.
|
10
|
+
select { |f| File.file? f }.
|
11
|
+
map { |f| f[root.length..-1] }
|
12
|
+
|
13
|
+
@static = ::Rack::Static.new notfound, root: root, urls: urls
|
14
|
+
end
|
15
|
+
|
16
|
+
def call env
|
17
|
+
if env["PATH_INFO"] == "/"
|
18
|
+
env["PATH_INFO"] = "/index.html"
|
19
|
+
end
|
20
|
+
|
21
|
+
@static.call env
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.call env
|
25
|
+
new.call env
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Entry point for Rakefiles.
|
2
|
+
|
3
|
+
require "appetizer/setup"
|
4
|
+
|
5
|
+
# Tasks from Appetizer. Only the first level, since other requires
|
6
|
+
# (like appetizer/rake/test) have their own tasks in subdirs.
|
7
|
+
|
8
|
+
here = File.expand_path "..", __FILE__
|
9
|
+
Dir["#{here}/tasks/*.rake"].sort.each { |f| App.load f }
|
10
|
+
|
11
|
+
# Load test tasks if the app appears to use tests.
|
12
|
+
|
13
|
+
if File.directory? "test"
|
14
|
+
Dir["#{here}/tasks/test/*.rake"].sort.each { |f| App.load f }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Tasks from the app itself.
|
18
|
+
|
19
|
+
Dir["lib/tasks/**/*.rake"].sort.each { |f| App.load f }
|
@@ -0,0 +1,101 @@
|
|
1
|
+
$:.unshift File.expand_path "lib"
|
2
|
+
|
3
|
+
require "appetizer/events"
|
4
|
+
require "fileutils"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
Encoding.default_external = Encoding::UTF_8
|
8
|
+
Encoding.default_internal = Encoding::UTF_8
|
9
|
+
|
10
|
+
module App
|
11
|
+
extend Appetizer::Events
|
12
|
+
|
13
|
+
def self.env
|
14
|
+
(ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development").intern
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.development?
|
18
|
+
:development == env
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.init!
|
22
|
+
return true if defined?(@initialized) && @initialized
|
23
|
+
|
24
|
+
fire :initializing
|
25
|
+
|
26
|
+
load "config/init.rb" if File.exists? "config/init.rb"
|
27
|
+
Dir["config/init/**/*.rb"].sort.each { |f| load f }
|
28
|
+
|
29
|
+
# If the app has an app/models directory, autorequire 'em.
|
30
|
+
|
31
|
+
if File.directory? "app/models"
|
32
|
+
$:.unshift File.expand_path "app/models"
|
33
|
+
Dir["app/models/**/*.rb"].sort.each { |f| require f[11..-4] }
|
34
|
+
end
|
35
|
+
|
36
|
+
fire :initialized
|
37
|
+
|
38
|
+
@initialized = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load file
|
42
|
+
now = Time.now.to_f if ENV["TRACE"]
|
43
|
+
Kernel.load file
|
44
|
+
p :load => { file => (Time.now.to_f - now) } if ENV["TRACE"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.log
|
48
|
+
@log ||= Logger.new test? ? "tmp/test.log" : $stdout
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.production?
|
52
|
+
:production == env
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.require file
|
56
|
+
now = Time.now.to_f if ENV["TRACE"]
|
57
|
+
Kernel.require file
|
58
|
+
p :require => { file => (Time.now.to_f - now) } if ENV["TRACE"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.test?
|
62
|
+
:test == env
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set default log formatter and level. WARN for production, INFO
|
67
|
+
# otherwise. Override severity with the `LOG_LEVEL` env
|
68
|
+
# var. Formatter just prefixes with severity.
|
69
|
+
|
70
|
+
App.log.formatter = lambda do |severity, time, program, message|
|
71
|
+
"[#{severity}] #{message}\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
App.log.level = ENV["LOG_LEVEL"] ?
|
75
|
+
Logger.const_get(ENV["LOG_LEVEL"].upcase) :
|
76
|
+
App.production? ? Logger::WARN : Logger::INFO
|
77
|
+
|
78
|
+
def (App.log).write message
|
79
|
+
self << message
|
80
|
+
end
|
81
|
+
|
82
|
+
# Make sure tmp exists, a bunch of things may use it.
|
83
|
+
|
84
|
+
FileUtils.mkdir_p "tmp"
|
85
|
+
|
86
|
+
# Load the global env files.
|
87
|
+
|
88
|
+
App.load "config/env.local.rb" if File.exists? "config/env.local.rb"
|
89
|
+
App.load "config/env.rb" if File.exists? "config/env.rb"
|
90
|
+
|
91
|
+
# Load the env-specific file.
|
92
|
+
|
93
|
+
envfile = "config/env/#{App.env}.rb"
|
94
|
+
load envfile if File.exists? envfile
|
95
|
+
|
96
|
+
if defined? IRB
|
97
|
+
IRB.conf[:PROMPT_MODE] = :SIMPLE
|
98
|
+
|
99
|
+
App.require "appetizer/console"
|
100
|
+
App.init!
|
101
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
task(:init) { App.init! }
|
@@ -0,0 +1,43 @@
|
|
1
|
+
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = "test"
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
|
5
|
+
module Appetizer
|
6
|
+
class Test < MiniTest::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
self.class.setups.each { |s| instance_eval(&s) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
self.class.teardowns.each { |t| instance_eval(&t) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.setup &block
|
16
|
+
setups << block
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.setups
|
20
|
+
@setups ||= (ancestors - [self]).
|
21
|
+
map { |a| a.respond_to?(:setups) && a.setups }.
|
22
|
+
select { |s| s }.
|
23
|
+
compact.flatten.reverse
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.teardown &block
|
27
|
+
teardowns.unshift block
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.teardowns
|
31
|
+
@teardowns ||= (ancestors - [self]).
|
32
|
+
map { |a| a.respond_to?(:teardowns) && a.teardowns }.
|
33
|
+
select { |t| t}.
|
34
|
+
compact.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.test name, &block
|
38
|
+
define_method "test #{name}", &block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require "appetizer/init"
|
data/lib/appetizer.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "appetizer/setup"
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "appetizer/test"
|
2
|
+
require "appetizer/populator"
|
3
|
+
|
4
|
+
class Appetizer::PopulatorTest < Appetizer::Test
|
5
|
+
def test_initialize
|
6
|
+
p = Appetizer::Populator.new :target, :source
|
7
|
+
|
8
|
+
assert_equal :target, p.target
|
9
|
+
assert_equal :source, p.source
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_nested
|
13
|
+
t2 = mock { expects(:bar=).with "baz" }
|
14
|
+
|
15
|
+
t = mock do
|
16
|
+
expects(:foo).returns t2
|
17
|
+
end
|
18
|
+
|
19
|
+
Appetizer::Populator.new t, foo: { bar: "baz" } do |p|
|
20
|
+
p.nested :foo do |p|
|
21
|
+
p.set :bar
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_populate_target
|
27
|
+
t2 = mock { expects(:bar=).with "baz" }
|
28
|
+
t = mock { expects(:foo).never }
|
29
|
+
|
30
|
+
Appetizer::Populator.new t, foo: { bar: "baz" } do |p|
|
31
|
+
p.populate :foo, t2 do |p|
|
32
|
+
p.set :bar
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_set
|
38
|
+
t = mock { expects(:foo=).with("bar").twice }
|
39
|
+
|
40
|
+
Appetizer::Populator.new t, foo: "bar" do |p|
|
41
|
+
p.set :foo
|
42
|
+
p.set "foo"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_set_missing_source_value
|
47
|
+
t = mock { expects(:foo=).never }
|
48
|
+
|
49
|
+
Appetizer::Populator.new t, Hash.new do |p|
|
50
|
+
p.set :foo
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_set_nil_source_value
|
55
|
+
t = mock { expects(:foo=).never }
|
56
|
+
|
57
|
+
Appetizer::Populator.new t, foo: nil do |p|
|
58
|
+
p.set :foo
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_set_empty_source_value
|
63
|
+
t = mock { expects(:foo=).never }
|
64
|
+
|
65
|
+
Appetizer::Populator.new t, foo: "" do |p|
|
66
|
+
p.set :foo
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_set_with_block
|
71
|
+
t = mock { expects(:foo=).with "BAR" }
|
72
|
+
|
73
|
+
Appetizer::Populator.new t, foo: "bar" do |p|
|
74
|
+
p.set(:foo) { |v| p.target.foo = v.upcase }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appetizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Audiosocket
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-10 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: A lightweight init process for Rack apps.
|
15
|
+
email:
|
16
|
+
- tech@audiosocket.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- appetizer.gemspec
|
26
|
+
- lib/appetizer.rb
|
27
|
+
- lib/appetizer/console.rb
|
28
|
+
- lib/appetizer/events.rb
|
29
|
+
- lib/appetizer/init.rb
|
30
|
+
- lib/appetizer/populator.rb
|
31
|
+
- lib/appetizer/rack.rb
|
32
|
+
- lib/appetizer/rack/splash.rb
|
33
|
+
- lib/appetizer/rake.rb
|
34
|
+
- lib/appetizer/setup.rb
|
35
|
+
- lib/appetizer/tasks/console.rake
|
36
|
+
- lib/appetizer/tasks/init.rake
|
37
|
+
- lib/appetizer/tasks/test/test.rake
|
38
|
+
- lib/appetizer/test.rb
|
39
|
+
- test/appetizer/populator_test.rb
|
40
|
+
homepage: https://github.com/audiosocket/appetizer
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 1.9.2
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.8.11
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Provides Railsish environments and initializers.
|
64
|
+
test_files:
|
65
|
+
- test/appetizer/populator_test.rb
|