pinball_wizard 0.0.1.pre → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitignore +9 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +21 -0
- data/README.md +336 -0
- data/Rakefile +4 -0
- data/bower.json +14 -0
- data/dist/css_tagger.js +42 -0
- data/dist/css_tagger.min.js +3 -0
- data/dist/pinball_wizard.js +227 -0
- data/gulpfile.js +40 -0
- data/karma.conf.js +66 -0
- data/lib/pinball_wizard.rb +5 -0
- data/lib/pinball_wizard/configuration.rb +17 -0
- data/lib/pinball_wizard/dsl.rb +38 -0
- data/lib/pinball_wizard/feature.rb +52 -0
- data/lib/pinball_wizard/helpers/hash.rb +26 -0
- data/lib/pinball_wizard/null_feature.rb +7 -0
- data/lib/pinball_wizard/railtie.rb +9 -0
- data/lib/pinball_wizard/registry.rb +44 -0
- data/lib/pinball_wizard/version.rb +3 -0
- data/lib/pinball_wizard/view_helpers/rails.rb +13 -0
- data/lib/pinball_wizard/view_helpers/sinatra/slim.rb +15 -0
- data/package.json +45 -0
- data/pinball_wizard.gemspec +20 -0
- data/spec/pinball_wizard/dsl_spec.rb +73 -0
- data/spec/pinball_wizard/feature_spec.rb +89 -0
- data/spec/pinball_wizard/registry_spec.rb +56 -0
- data/src/css_tagger.coffee +44 -0
- data/src/pinball_wizard.coffee +157 -0
- data/test/spec/css_tagger_spec.coffee +50 -0
- data/test/spec/pinball_wizard_spec.coffee +268 -0
- data/test/test-main.coffee +25 -0
- metadata +47 -10
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'pinball_wizard/helpers/hash'
|
2
|
+
|
3
|
+
module PinballWizard
|
4
|
+
class Feature
|
5
|
+
attr_reader :name, :active, :options
|
6
|
+
attr_reader :disabled, :message
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
@name = name.to_s
|
10
|
+
@options = options
|
11
|
+
@active = ensure_callable(options.fetch(:active, false))
|
12
|
+
@options = Helpers::Hash.without(options, :name, :active)
|
13
|
+
@disabled = false
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :to_s, :name
|
17
|
+
|
18
|
+
def active?
|
19
|
+
active.call
|
20
|
+
end
|
21
|
+
|
22
|
+
def disabled?
|
23
|
+
determine_state # Called here for Registry#disabled?
|
24
|
+
disabled
|
25
|
+
end
|
26
|
+
|
27
|
+
def disable(message = 'No reason given.')
|
28
|
+
@disabled = true
|
29
|
+
@message = message
|
30
|
+
end
|
31
|
+
|
32
|
+
def determine_state
|
33
|
+
# noop: use defaults
|
34
|
+
end
|
35
|
+
|
36
|
+
def state
|
37
|
+
if disabled?
|
38
|
+
"disabled: #{message}"
|
39
|
+
elsif active?
|
40
|
+
'active'
|
41
|
+
else
|
42
|
+
'inactive'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def ensure_callable(object)
|
49
|
+
object.respond_to?(:call) ? object : proc { object }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Further Reading: https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along
|
2
|
+
module PinballWizard
|
3
|
+
module Helpers
|
4
|
+
module Hash
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Convert [:a, :b, { c: 'd' }] => { a: true, b: true, c: 'd' }
|
8
|
+
def normalize_options(options)
|
9
|
+
options.each_with_object({}) do |opt, memo|
|
10
|
+
if opt.is_a?(::Hash)
|
11
|
+
memo.merge!(opt)
|
12
|
+
else
|
13
|
+
memo[opt] = true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def without(hash, *keys)
|
19
|
+
keys.each do |key|
|
20
|
+
hash.delete(key)
|
21
|
+
end
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'pinball_wizard/helpers/hash'
|
2
|
+
require 'pinball_wizard/null_feature'
|
3
|
+
|
4
|
+
# Further Reading: https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along
|
5
|
+
module PinballWizard
|
6
|
+
module Registry
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def add(feature)
|
10
|
+
collection[feature.name] = feature
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(name)
|
14
|
+
collection.fetch(name.to_s) { null_feature }
|
15
|
+
end
|
16
|
+
|
17
|
+
def disabled?(name)
|
18
|
+
get(name).disabled?
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection
|
22
|
+
@collection ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
@collection = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
pairs = collection.map do |name, feature|
|
31
|
+
[feature.to_s, feature.state]
|
32
|
+
end
|
33
|
+
::Hash[pairs]
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :to_hash, :to_h
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def null_feature
|
41
|
+
@null_feature ||= NullFeature.new 'null'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module PinballWizard
|
2
|
+
module ViewHelpers
|
3
|
+
module Rails
|
4
|
+
def feature(name, options = {})
|
5
|
+
unless PinballWizard::Registry.disabled?(name)
|
6
|
+
partial_name = options.fetch(:partial) { name }
|
7
|
+
options[:partial] = "features/#{partial_name}"
|
8
|
+
render options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module PinballWizard
|
2
|
+
module ViewHelpers
|
3
|
+
module Sinatra
|
4
|
+
module Slim
|
5
|
+
def feature(name, opts_or_locals = {})
|
6
|
+
unless PinballWizard::Registry.disabled?(name)
|
7
|
+
partial_name = opts_or_locals.fetch(:partial) { name }
|
8
|
+
locals = opts_or_locals.tap { |h| h.delete(:partial) }
|
9
|
+
partial "features/#{partial_name}", locals
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/package.json
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"name": "pinball_wizard",
|
3
|
+
"version": "0.2.0",
|
4
|
+
"description": "pinball_wizard",
|
5
|
+
"homepage": "https://github.com/primedia/pinball_wizard",
|
6
|
+
"license": "MIT",
|
7
|
+
"author": {
|
8
|
+
"name": "RentPath"
|
9
|
+
},
|
10
|
+
"repository": {
|
11
|
+
"type": "git",
|
12
|
+
"url": "https://github.com/primedia/pinball_wizard.git"
|
13
|
+
},
|
14
|
+
"github": "https://github.com/primedia/pinball_wizard.git",
|
15
|
+
"devDependencies": {
|
16
|
+
"del": "^1.1.0",
|
17
|
+
"gulp": "^3.8.10",
|
18
|
+
"gulp-coffee": "^2.2.0",
|
19
|
+
"gulp-sourcemaps": "~1.2.0",
|
20
|
+
"gulp-util": "^3.0.1",
|
21
|
+
"karma": "~0.12.31",
|
22
|
+
"karma-jasmine": "~0.3.4",
|
23
|
+
"karma-cli": "0.0.4",
|
24
|
+
"karma-chrome-launcher": "~0.1.7",
|
25
|
+
"requirejs": "~2.1.15",
|
26
|
+
"karma-requirejs": "~0.2.2",
|
27
|
+
"gulp": "~3.8.10"
|
28
|
+
},
|
29
|
+
"scripts": {
|
30
|
+
"compile": "gulp build",
|
31
|
+
"build": "gulp dist",
|
32
|
+
"livereload": "gulp",
|
33
|
+
"watch": "gulp",
|
34
|
+
"watch:test": "karma start",
|
35
|
+
"test": "karma start --single-run",
|
36
|
+
"test:clean": "npm run clean && npm run compile && npm test",
|
37
|
+
"clean": "rm -rf .tmp",
|
38
|
+
"preclean:all": "npm run clean",
|
39
|
+
"clean:all": "rm -rf node_modules",
|
40
|
+
"prereset": "npm run clean:all",
|
41
|
+
"reset": "npm install",
|
42
|
+
"postreset": "npm run compile",
|
43
|
+
"preversion": "npm run reset && npm test && npm run build"
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/pinball_wizard/version', __FILE__)
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Caleb Wright", "Mark Herman", "RentPath Team"]
|
7
|
+
gem.email = ["cwright@rentpath.com", "markherman@rentpath.com"]
|
8
|
+
gem.homepage = 'https://github.com/primedia/pinball_wizard'
|
9
|
+
gem.description = 'Build flippable features.'
|
10
|
+
gem.summary = "Lib to build flippable features."
|
11
|
+
gem.date = Date.today.to_s
|
12
|
+
gem.licenses = ['MIT']
|
13
|
+
gem.executables = []
|
14
|
+
gem.files = `git ls-files | grep -v myapp`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
16
|
+
gem.name = "pinball_wizard"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = PinballWizard::VERSION
|
19
|
+
gem.required_ruby_version = '>= 1.9'
|
20
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'pinball_wizard'
|
2
|
+
|
3
|
+
describe PinballWizard::DSL do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
PinballWizard::Registry.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.build' do
|
10
|
+
context 'without builder class flags' do
|
11
|
+
before(:each) do
|
12
|
+
PinballWizard::DSL.build do
|
13
|
+
feature :example_a
|
14
|
+
feature :example_b, :active
|
15
|
+
feature :example_c, active: proc { false }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'adds to the registry' do
|
20
|
+
expect(PinballWizard::Registry.to_h).to eq({
|
21
|
+
'example_a' => 'inactive',
|
22
|
+
'example_b' => 'active',
|
23
|
+
'example_c' => 'inactive'
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'creates a Feature class instance' do
|
28
|
+
expect(PinballWizard::Registry.get('example_a')).to be_a(PinballWizard::Feature)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with a builder class flag' do
|
33
|
+
|
34
|
+
class MyCustomFeature < PinballWizard::Feature; end
|
35
|
+
|
36
|
+
let(:config) do
|
37
|
+
PinballWizard::Configuration.new my_custom_feature: MyCustomFeature
|
38
|
+
end
|
39
|
+
|
40
|
+
before(:each) do
|
41
|
+
PinballWizard::DSL.build(config) do
|
42
|
+
feature :example_a, :my_custom_feature
|
43
|
+
feature :example_b, :foo, my_custom_feature: { b: true }
|
44
|
+
feature :example_c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'adds to the registry' do
|
49
|
+
expect(PinballWizard::Registry.to_h).to eq({
|
50
|
+
'example_a' => 'inactive',
|
51
|
+
'example_b' => 'inactive',
|
52
|
+
'example_c' => 'inactive'
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'creates a custom class instance when given a symbol' do
|
57
|
+
expect(PinballWizard::Registry.get('example_a')).to be_a(MyCustomFeature)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'creates a custom class instance when given a hash' do
|
61
|
+
expect(PinballWizard::Registry.get('example_b')).to be_a(MyCustomFeature)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'createa a Feature class instance for the non-specified' do
|
65
|
+
expect(PinballWizard::Registry.get('example_c')).to be_a(PinballWizard::Feature)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'passes on symbols and hash into Feature options' do
|
69
|
+
expect(PinballWizard::Registry.get('example_b').options).to eq({ foo: true, my_custom_feature: { b: true } })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'pinball_wizard'
|
2
|
+
|
3
|
+
describe PinballWizard::Feature do
|
4
|
+
describe '#state' do
|
5
|
+
context 'with defaults' do
|
6
|
+
subject { PinballWizard::Feature.new 'example' }
|
7
|
+
|
8
|
+
it 'should not be active' do
|
9
|
+
expect(subject.state).to eq('inactive')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when setting active to a boolean' do
|
14
|
+
subject do
|
15
|
+
PinballWizard::Feature.new 'example', active: true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should be activate' do
|
19
|
+
expect(subject.state).to eq('active')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when setting active to a proc' do
|
24
|
+
subject do
|
25
|
+
PinballWizard::Feature.new 'example', active: proc { true }
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be activate' do
|
29
|
+
expect(subject.state).to eq('active')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when disabled' do
|
34
|
+
subject do
|
35
|
+
PinballWizard::Feature.new 'example'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should be disabled with a message' do
|
39
|
+
subject.disable 'reason'
|
40
|
+
expect(subject.state).to eq('disabled: reason')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#active?' do
|
46
|
+
context 'without setting a value' do
|
47
|
+
subject { PinballWizard::Feature.new 'example' }
|
48
|
+
|
49
|
+
it 'should not be active' do
|
50
|
+
expect(subject).not_to be_active
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with a boolean value' do
|
55
|
+
subject do
|
56
|
+
PinballWizard::Feature.new 'example', active: true
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should not be activated' do
|
60
|
+
expect(subject).to be_active
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with a proc' do
|
65
|
+
subject do
|
66
|
+
PinballWizard::Feature.new 'example', active: proc { true }
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not be activated' do
|
70
|
+
expect(subject).to be_active
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#name' do
|
76
|
+
subject do
|
77
|
+
PinballWizard::Feature.new('my_super_duper').name
|
78
|
+
end
|
79
|
+
|
80
|
+
it { should eq('my_super_duper') }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.new' do
|
84
|
+
it 'takes a hash of options' do
|
85
|
+
feature = PinballWizard::Feature.new 'example', foo: 'bar'
|
86
|
+
expect(feature.options).to eq({ foo: 'bar' })
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'pinball_wizard'
|
2
|
+
|
3
|
+
describe PinballWizard::Registry do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
PinballWizard::Registry.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:default_feature) do
|
10
|
+
PinballWizard::Feature.new 'default'
|
11
|
+
end
|
12
|
+
|
13
|
+
class DisabledFeature < PinballWizard::Feature
|
14
|
+
def determine_state
|
15
|
+
disable 'Reason'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:disabled_feature) do
|
20
|
+
DisabledFeature.new 'disabled'
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.add' do
|
24
|
+
it 'should add to the registry with defaults' do
|
25
|
+
PinballWizard::Registry.add(default_feature)
|
26
|
+
expect(PinballWizard::Registry.collection).to eq({
|
27
|
+
'default' => default_feature
|
28
|
+
})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.disabled?' do
|
33
|
+
it 'should be true for a disabled feature' do
|
34
|
+
PinballWizard::Registry.add(disabled_feature)
|
35
|
+
expect(PinballWizard::Registry.disabled?('disabled')).to eq(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should be true for a non-existant feature' do
|
39
|
+
expect(PinballWizard::Registry.disabled?('foo')).to eq(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should be false for a active feature' do
|
43
|
+
PinballWizard::Registry.add(default_feature)
|
44
|
+
expect(PinballWizard::Registry.disabled?('default')).to eq(false)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.to_h' do
|
49
|
+
it 'should build a hash' do
|
50
|
+
PinballWizard::Registry.add(default_feature)
|
51
|
+
expect(PinballWizard::Registry.to_h).to eq({
|
52
|
+
'default' => 'inactive'
|
53
|
+
})
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|