flip_the_switch 0.3.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 +7 -0
- data/.gitignore +146 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +14 -0
- data/Classes/FlipTheSwitch/FlipTheSwitch.h +9 -0
- data/Classes/FlipTheSwitch/FlipTheSwitch.m +88 -0
- data/Example/.gitignore +2 -0
- data/Example/Colors-iOS/AppDelegateiOS.h +3 -0
- data/Example/Colors-iOS/AppDelegateiOS.m +4 -0
- data/Example/Colors-iOS/Base.lproj/Main_iPhone.storyboard +76 -0
- data/Example/Colors-iOS/Colors-iOS-Info.plist +49 -0
- data/Example/Colors-iOS/Colors-iOS-Prefix.pch +16 -0
- data/Example/Colors-iOS/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- data/Example/Colors-iOS/Images.xcassets/LaunchImage.launchimage/Contents.json +51 -0
- data/Example/Colors-iOS/ViewControlleriOS.h +2 -0
- data/Example/Colors-iOS/ViewControlleriOS.m +78 -0
- data/Example/Colors-iOS/main.m +8 -0
- data/Example/Colors.xcodeproj/project.pbxproj +358 -0
- data/Example/Colors.xcodeproj/xcshareddata/xcschemes/Colors-iOS.xcscheme +77 -0
- data/Example/Podfile +4 -0
- data/Example/Rakefile +23 -0
- data/Example/features.yml +2 -0
- data/FlipTheSwitch.podspec +16 -0
- data/Gemfile +17 -0
- data/Guardfile.example +15 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/Rakefile +35 -0
- data/Tests/FlipTheSwitchSpec-Mac/Features.plist +8 -0
- data/Tests/FlipTheSwitchSpec-Mac/FlipTheSwitchSpec-Mac-Info.plist +22 -0
- data/Tests/FlipTheSwitchSpec-Mac/FlipTheSwitchSpec-Mac-Prefix.pch +13 -0
- data/Tests/FlipTheSwitchSpec-iOS/Features.plist +8 -0
- data/Tests/FlipTheSwitchSpec-iOS/FlipTheSwitchSpec-iOS-Info.plist +22 -0
- data/Tests/FlipTheSwitchSpec-iOS/FlipTheSwitchSpec-iOS-Prefix.pch +14 -0
- data/Tests/FlipTheSwitchSpec.xcodeproj/project.pbxproj +605 -0
- data/Tests/FlipTheSwitchSpec.xcodeproj/xcshareddata/xcschemes/FlipTheSwitchSpec-Mac.xcscheme +75 -0
- data/Tests/FlipTheSwitchSpec.xcodeproj/xcshareddata/xcschemes/FlipTheSwitchSpec-iOS.xcscheme +75 -0
- data/Tests/Podfile +15 -0
- data/Tests/Rakefile +22 -0
- data/Tests/Spec/Classes/FlipTheSwitch/FlipTheSwitchSpec.m +88 -0
- data/Tests/Spec/Helpers/GcovTestObserver.h +4 -0
- data/Tests/Spec/Helpers/GcovTestObserver.m +12 -0
- data/bin/flip-the-switch +4 -0
- data/flip_the_switch.gemspec +17 -0
- data/lib/flip_the_switch/cli.rb +61 -0
- data/lib/flip_the_switch/errors.rb +12 -0
- data/lib/flip_the_switch/generator/base.rb +13 -0
- data/lib/flip_the_switch/generator/category.rb +71 -0
- data/lib/flip_the_switch/generator/header.h.erb +11 -0
- data/lib/flip_the_switch/generator/implementation.m.erb +26 -0
- data/lib/flip_the_switch/generator/plist.rb +17 -0
- data/lib/flip_the_switch/generator.rb +3 -0
- data/lib/flip_the_switch/reader/yaml.rb +39 -0
- data/lib/flip_the_switch/reader.rb +1 -0
- data/lib/flip_the_switch.rb +3 -0
- data/spec/flip_the_switch/cli_spec.rb +81 -0
- data/spec/flip_the_switch/generator/category_spec.rb +44 -0
- data/spec/flip_the_switch/generator/plist_spec.rb +19 -0
- data/spec/flip_the_switch/reader/yaml_spec.rb +43 -0
- data/spec/resources/ExpectedFeatures.plist +10 -0
- data/spec/resources/expected_header.h +16 -0
- data/spec/resources/expected_implementation.m +46 -0
- data/spec/resources/invalid_layout/features.yml +4 -0
- data/spec/resources/invalid_type/features.yml +7 -0
- data/spec/resources/real/features.yml +2 -0
- data/spec/spec_helper.rb +15 -0
- metadata +166 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<Scheme
|
3
|
+
version = "1.3">
|
4
|
+
<BuildAction
|
5
|
+
parallelizeBuildables = "YES"
|
6
|
+
buildImplicitDependencies = "YES">
|
7
|
+
<BuildActionEntries>
|
8
|
+
<BuildActionEntry
|
9
|
+
buildForTesting = "YES"
|
10
|
+
buildForRunning = "YES"
|
11
|
+
buildForProfiling = "YES"
|
12
|
+
buildForArchiving = "YES"
|
13
|
+
buildForAnalyzing = "YES">
|
14
|
+
<BuildableReference
|
15
|
+
BuildableIdentifier = "primary"
|
16
|
+
BlueprintIdentifier = "5B87AFE9193A80F20082F7A1"
|
17
|
+
BuildableName = "FlipTheSwitchSpec-iOS.xctest"
|
18
|
+
BlueprintName = "FlipTheSwitchSpec-iOS"
|
19
|
+
ReferencedContainer = "container:FlipTheSwitchSpec.xcodeproj">
|
20
|
+
</BuildableReference>
|
21
|
+
</BuildActionEntry>
|
22
|
+
</BuildActionEntries>
|
23
|
+
</BuildAction>
|
24
|
+
<TestAction
|
25
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
26
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
27
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
28
|
+
buildConfiguration = "Debug">
|
29
|
+
<Testables>
|
30
|
+
<TestableReference
|
31
|
+
skipped = "NO">
|
32
|
+
<BuildableReference
|
33
|
+
BuildableIdentifier = "primary"
|
34
|
+
BlueprintIdentifier = "5B87AFE9193A80F20082F7A1"
|
35
|
+
BuildableName = "FlipTheSwitchSpec-iOS.xctest"
|
36
|
+
BlueprintName = "FlipTheSwitchSpec-iOS"
|
37
|
+
ReferencedContainer = "container:FlipTheSwitchSpec.xcodeproj">
|
38
|
+
</BuildableReference>
|
39
|
+
</TestableReference>
|
40
|
+
</Testables>
|
41
|
+
</TestAction>
|
42
|
+
<LaunchAction
|
43
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
44
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
45
|
+
launchStyle = "0"
|
46
|
+
useCustomWorkingDirectory = "NO"
|
47
|
+
buildConfiguration = "Debug"
|
48
|
+
ignoresPersistentStateOnLaunch = "NO"
|
49
|
+
debugDocumentVersioning = "YES"
|
50
|
+
allowLocationSimulation = "YES">
|
51
|
+
<EnvironmentVariables>
|
52
|
+
<EnvironmentVariable
|
53
|
+
key = "SPECTA_REPORTER_CLASS"
|
54
|
+
value = "GcovTestObserver"
|
55
|
+
isEnabled = "YES">
|
56
|
+
</EnvironmentVariable>
|
57
|
+
</EnvironmentVariables>
|
58
|
+
<AdditionalOptions>
|
59
|
+
</AdditionalOptions>
|
60
|
+
</LaunchAction>
|
61
|
+
<ProfileAction
|
62
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
63
|
+
savedToolIdentifier = ""
|
64
|
+
useCustomWorkingDirectory = "NO"
|
65
|
+
buildConfiguration = "Release"
|
66
|
+
debugDocumentVersioning = "YES">
|
67
|
+
</ProfileAction>
|
68
|
+
<AnalyzeAction
|
69
|
+
buildConfiguration = "Debug">
|
70
|
+
</AnalyzeAction>
|
71
|
+
<ArchiveAction
|
72
|
+
buildConfiguration = "Release"
|
73
|
+
revealArchiveInOrganizer = "YES">
|
74
|
+
</ArchiveAction>
|
75
|
+
</Scheme>
|
data/Tests/Podfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
def needed_pods
|
2
|
+
pod 'Specta'
|
3
|
+
pod 'Expecta'
|
4
|
+
pod 'OCMockito'
|
5
|
+
end
|
6
|
+
|
7
|
+
target 'FlipTheSwitchSpec-iOS' do
|
8
|
+
platform :ios, :deployment_target => 6.0
|
9
|
+
needed_pods
|
10
|
+
end
|
11
|
+
|
12
|
+
target 'FlipTheSwitchSpec-Mac' do
|
13
|
+
platform :osx, :deployment_target => 10.8
|
14
|
+
needed_pods
|
15
|
+
end
|
data/Tests/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
|
3
|
+
def pods; 'Pods'; end
|
4
|
+
def podfile_lock; 'Podfile.lock'; end
|
5
|
+
def build; 'build'; end
|
6
|
+
def workspace; 'FlipTheSwitchSpec'; end
|
7
|
+
|
8
|
+
CLOBBER.include(podfile_lock)
|
9
|
+
CLOBBER.include(pods)
|
10
|
+
|
11
|
+
CLEAN.include(build)
|
12
|
+
|
13
|
+
task :default => :test
|
14
|
+
|
15
|
+
file podfile_lock do
|
16
|
+
sh 'pod install'
|
17
|
+
end
|
18
|
+
|
19
|
+
task :test => podfile_lock do
|
20
|
+
sh "xctool test -scheme #{workspace}-Mac -workspace #{workspace}.xcworkspace OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
|
21
|
+
sh "xctool test -scheme #{workspace}-iOS -workspace #{workspace}.xcworkspace -sdk iphonesimulator OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
|
22
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#import "FlipTheSwitch.h"
|
2
|
+
|
3
|
+
@interface FlipTheSwitch (Spec)
|
4
|
+
- (instancetype)initWithUserDefaults:(NSUserDefaults *)userDefaults
|
5
|
+
bundle:(NSBundle *)bundle;
|
6
|
+
@end
|
7
|
+
|
8
|
+
SpecBegin(FlipTheSwitch)
|
9
|
+
__block FlipTheSwitch *subject;
|
10
|
+
|
11
|
+
__block NSUserDefaults *userDefaults;
|
12
|
+
|
13
|
+
__block NSString *standardFeature;
|
14
|
+
__block NSString *plistEnabledFeature;
|
15
|
+
|
16
|
+
before(^{
|
17
|
+
userDefaults = [[NSUserDefaults alloc] init];
|
18
|
+
|
19
|
+
standardFeature = @"standard_feature";
|
20
|
+
plistEnabledFeature = @"plist_enabled_feature";
|
21
|
+
|
22
|
+
subject = [[FlipTheSwitch alloc] initWithUserDefaults:userDefaults
|
23
|
+
bundle:[NSBundle bundleForClass:[self class]]];
|
24
|
+
});
|
25
|
+
|
26
|
+
after(^{
|
27
|
+
for (NSString *key in [userDefaults dictionaryRepresentation]) {
|
28
|
+
[userDefaults removeObjectForKey:key];
|
29
|
+
}
|
30
|
+
[userDefaults synchronize];
|
31
|
+
});
|
32
|
+
|
33
|
+
it(@"is a singleton", ^{
|
34
|
+
expect([FlipTheSwitch sharedInstance]).to.beIdenticalTo([FlipTheSwitch sharedInstance]);
|
35
|
+
});
|
36
|
+
|
37
|
+
context(@"when feature is NOT set", ^{
|
38
|
+
context(@"when feature defaults to enabled through plist", ^{
|
39
|
+
it(@"is enabled", ^{
|
40
|
+
expect([subject isFeatureEnabled:plistEnabledFeature]).to.beTruthy();
|
41
|
+
});
|
42
|
+
});
|
43
|
+
|
44
|
+
context(@"when feature does not default to enabled through plist", ^{
|
45
|
+
it(@"is disabled", ^{
|
46
|
+
expect([subject isFeatureEnabled:standardFeature]).to.beFalsy();
|
47
|
+
});
|
48
|
+
});
|
49
|
+
});
|
50
|
+
|
51
|
+
context(@"when feature is manually enabled", ^{
|
52
|
+
before(^{
|
53
|
+
[subject enableFeature:standardFeature];
|
54
|
+
[subject enableFeature:plistEnabledFeature];
|
55
|
+
});
|
56
|
+
|
57
|
+
it(@"is enabled", ^{
|
58
|
+
expect([subject isFeatureEnabled:standardFeature]).to.beTruthy();
|
59
|
+
expect([subject isFeatureEnabled:plistEnabledFeature]).to.beTruthy();
|
60
|
+
});
|
61
|
+
});
|
62
|
+
|
63
|
+
context(@"when feature is manually enabled", ^{
|
64
|
+
before(^{
|
65
|
+
[subject enableFeature:standardFeature];
|
66
|
+
[subject enableFeature:plistEnabledFeature];
|
67
|
+
[subject disableFeature:standardFeature];
|
68
|
+
[subject disableFeature:plistEnabledFeature];
|
69
|
+
});
|
70
|
+
|
71
|
+
it(@"is enabled", ^{
|
72
|
+
expect([subject isFeatureEnabled:standardFeature]).to.beFalsy();
|
73
|
+
expect([subject isFeatureEnabled:plistEnabledFeature]).to.beFalsy();
|
74
|
+
});
|
75
|
+
});
|
76
|
+
|
77
|
+
context(@"when feature is manually set", ^{
|
78
|
+
before(^{
|
79
|
+
[subject setFeature:standardFeature enabled:YES];
|
80
|
+
[subject setFeature:plistEnabledFeature enabled:YES];
|
81
|
+
});
|
82
|
+
|
83
|
+
it(@"is enabled", ^{
|
84
|
+
expect([subject isFeatureEnabled:standardFeature]).to.beTruthy();
|
85
|
+
expect([subject isFeatureEnabled:plistEnabledFeature]).to.beTruthy();
|
86
|
+
});
|
87
|
+
});
|
88
|
+
SpecEnd
|
data/bin/flip-the-switch
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'flip_the_switch'
|
3
|
+
s.authors = ['Michael England', 'Rob Siwek']
|
4
|
+
s.email = %w(mg.england@gmail.com rob@robsiwek.com)
|
5
|
+
s.summary = 'A simple library to help enabling/disabling features on iOS/Mac applications.'
|
6
|
+
s.homepage = 'https://github.com/michaelengland/FlipTheSwitch'
|
7
|
+
s.version = '0.3.0'
|
8
|
+
s.license = 'MIT'
|
9
|
+
|
10
|
+
s.add_dependency 'activesupport', '~> 3.2'
|
11
|
+
s.add_dependency 'thor', '~> 0.19'
|
12
|
+
s.add_dependency 'plist', '~> 3.1'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split($\)
|
15
|
+
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
16
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'flip_the_switch'
|
3
|
+
|
4
|
+
module FlipTheSwitch
|
5
|
+
class Cli < Thor
|
6
|
+
class_option :input, type: :string, aliases: '-i', default: Dir.pwd, desc: 'Location of the directory containing features.yml file to read'
|
7
|
+
class_option :enabled, type: :array, aliases: '-e', default: [], desc: 'Extra features to be set as enabled'
|
8
|
+
class_option :disabled, type: :array, aliases: '-d', default: [], desc: 'Extra features to be set as disabled'
|
9
|
+
|
10
|
+
desc 'plist', 'Auto-generates a Features.plist file for enabled/disabled features'
|
11
|
+
method_option :output, type: :string, aliases: '-o', default: Dir.pwd, desc: 'Location of the directory in which Features.plist file will be created'
|
12
|
+
def plist
|
13
|
+
plist_generator.generate
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'category', 'Auto-generates .h & .m files for enabled/disabled features'
|
17
|
+
method_option :output, type: :string, aliases: '-o', default: Dir.pwd, desc: 'Location of the directory in which FlipTheSwitch+Features.{h,m} files will be created'
|
18
|
+
def category
|
19
|
+
category_generator.generate
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def plist_generator
|
25
|
+
Generator::Plist.new(output, feature_states)
|
26
|
+
end
|
27
|
+
|
28
|
+
def category_generator
|
29
|
+
Generator::Category.new(output, feature_states)
|
30
|
+
end
|
31
|
+
|
32
|
+
def output
|
33
|
+
options.output
|
34
|
+
end
|
35
|
+
|
36
|
+
def feature_states
|
37
|
+
yaml_reader.feature_states.
|
38
|
+
merge(enabled_states).
|
39
|
+
merge(disabled_states)
|
40
|
+
end
|
41
|
+
|
42
|
+
def yaml_reader
|
43
|
+
Reader::Yaml.new(options.input)
|
44
|
+
end
|
45
|
+
|
46
|
+
def enabled_states
|
47
|
+
states_for(options.enabled, true)
|
48
|
+
end
|
49
|
+
|
50
|
+
def disabled_states
|
51
|
+
states_for(options.disabled, false)
|
52
|
+
end
|
53
|
+
|
54
|
+
def states_for(array, default)
|
55
|
+
array.inject({}) do |h, feature|
|
56
|
+
h[feature] = default
|
57
|
+
h
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module FlipTheSwitch
|
5
|
+
module Generator
|
6
|
+
class Category < Base
|
7
|
+
def generate
|
8
|
+
delete_existing
|
9
|
+
write_files
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def delete_existing
|
15
|
+
delete_file(header_file)
|
16
|
+
delete_file(implementation_file)
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_files
|
20
|
+
write_file(header_file, header)
|
21
|
+
write_file(implementation_file, implementation)
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_file(file, text)
|
25
|
+
File.open(file, 'w') do |f|
|
26
|
+
f.syswrite(text)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_file(file)
|
31
|
+
File.delete(file) if File.exists?(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def header_file
|
35
|
+
File.join(output, 'FlipTheSwitch+Features.h')
|
36
|
+
end
|
37
|
+
|
38
|
+
def implementation_file
|
39
|
+
File.join(output, 'FlipTheSwitch+Features.m')
|
40
|
+
end
|
41
|
+
|
42
|
+
def header
|
43
|
+
render(header_template)
|
44
|
+
end
|
45
|
+
|
46
|
+
def implementation
|
47
|
+
render(implementation_template)
|
48
|
+
end
|
49
|
+
|
50
|
+
def render(template)
|
51
|
+
ERB.new(template).result(binding)
|
52
|
+
end
|
53
|
+
|
54
|
+
def header_template
|
55
|
+
file_template('header.h')
|
56
|
+
end
|
57
|
+
|
58
|
+
def implementation_template
|
59
|
+
file_template('implementation.m')
|
60
|
+
end
|
61
|
+
|
62
|
+
def file_template(name)
|
63
|
+
File.read(File.expand_path("../#{name}.erb", __FILE__))
|
64
|
+
end
|
65
|
+
|
66
|
+
def feature_names
|
67
|
+
feature_states.keys.map(&:to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/* AUTO-GENERATED. DO NOT ALTER */
|
2
|
+
#import <FlipTheSwitch/FlipTheSwitch.h>
|
3
|
+
|
4
|
+
@interface FlipTheSwitch (Features)
|
5
|
+
<% for feature in feature_names %>
|
6
|
+
+ (BOOL)is<%= feature.camelize %>Enabled;
|
7
|
+
+ (void)enable<%= feature.camelize %>;
|
8
|
+
+ (void)disable<%= feature.camelize %>;
|
9
|
+
+ (void)set<%= feature.camelize %>Enabled:(BOOL)enabled;
|
10
|
+
<% end %>
|
11
|
+
@end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
/* AUTO-GENERATED. DO NOT ALTER */
|
2
|
+
#import "FlipTheSwitch+Features.h"
|
3
|
+
|
4
|
+
@implementation FlipTheSwitch (Features)
|
5
|
+
<% for feature in feature_names %>
|
6
|
+
+ (BOOL)is<%= feature.camelize %>Enabled
|
7
|
+
{
|
8
|
+
return [[FlipTheSwitch sharedInstance] isFeatureEnabled:@"<%= feature %>"];
|
9
|
+
}
|
10
|
+
|
11
|
+
+ (void)enable<%= feature.camelize %>
|
12
|
+
{
|
13
|
+
[[FlipTheSwitch sharedInstance] enableFeature:@"<%= feature %>"];
|
14
|
+
}
|
15
|
+
|
16
|
+
+ (void)disable<%= feature.camelize %>
|
17
|
+
{
|
18
|
+
[[FlipTheSwitch sharedInstance] disableFeature:@"<%= feature %>"];
|
19
|
+
}
|
20
|
+
|
21
|
+
+ (void)set<%= feature.camelize %>Enabled:(BOOL)enabled
|
22
|
+
{
|
23
|
+
[[FlipTheSwitch sharedInstance] setFeature:@"<%= feature %>" enabled:enabled];
|
24
|
+
}
|
25
|
+
<% end %>
|
26
|
+
@end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'plist'
|
2
|
+
|
3
|
+
module FlipTheSwitch
|
4
|
+
module Generator
|
5
|
+
class Plist < Base
|
6
|
+
def generate
|
7
|
+
::Plist::Emit.save_plist(feature_states, output_file)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def output_file
|
13
|
+
File.join(output, 'Features.plist')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module FlipTheSwitch
|
4
|
+
module Reader
|
5
|
+
class Yaml
|
6
|
+
def initialize(input)
|
7
|
+
@input = input
|
8
|
+
end
|
9
|
+
|
10
|
+
def feature_states
|
11
|
+
if valid_file?
|
12
|
+
file_states
|
13
|
+
else
|
14
|
+
raise Error::InvalidFile.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
attr_reader :input
|
20
|
+
|
21
|
+
def valid_file?
|
22
|
+
file_states.is_a?(Hash) && file_states.all? { |feature, state|
|
23
|
+
feature.is_a?(String) && !!state == state
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def file_states
|
28
|
+
@file_states ||= YAML.load_file(input_file)
|
29
|
+
rescue SystemCallError => e
|
30
|
+
raise Error::UnreadableFile.new(e)
|
31
|
+
end
|
32
|
+
|
33
|
+
def input_file
|
34
|
+
File.join(input, 'features.yml')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'flip_the_switch/reader/yaml'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FlipTheSwitch::Cli do
|
4
|
+
subject(:cli) { described_class }
|
5
|
+
let(:perform) { subject.start([command] + options, debug: true) }
|
6
|
+
|
7
|
+
context 'when invalid command called' do
|
8
|
+
let(:command) { 'invalid' }
|
9
|
+
let(:options) { [] }
|
10
|
+
|
11
|
+
it 'throws an error' do
|
12
|
+
expect {
|
13
|
+
perform
|
14
|
+
}.to raise_error
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_examples_for 'generator' do
|
19
|
+
let(:yaml_reader) { double(FlipTheSwitch::Reader::Yaml, feature_states: {'something' => true}) }
|
20
|
+
let(:generator) { double(generator_class) }
|
21
|
+
|
22
|
+
context 'when no options given' do
|
23
|
+
let(:options) { [] }
|
24
|
+
|
25
|
+
before do
|
26
|
+
FlipTheSwitch::Reader::Yaml.stub(:new).with(Dir.pwd).and_return(yaml_reader)
|
27
|
+
generator_class.stub(:new).with(Dir.pwd, 'something' => true).and_return(generator)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'generates using default options' do
|
31
|
+
expect(generator).to receive(:generate)
|
32
|
+
perform
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when options given' do
|
37
|
+
before do
|
38
|
+
FlipTheSwitch::Reader::Yaml.stub(:new).with('input').and_return(yaml_reader)
|
39
|
+
generator_class.stub(:new).with('output', {
|
40
|
+
'something' => true,
|
41
|
+
'en' => true,
|
42
|
+
'abled' => true,
|
43
|
+
'dis' => false,
|
44
|
+
'appointing' => false
|
45
|
+
}).and_return(generator)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'using full name' do
|
49
|
+
let(:options) { %w(--input=input --output=output --enabled=en abled --disabled=dis appointing) }
|
50
|
+
|
51
|
+
it 'generates using the options given' do
|
52
|
+
expect(generator).to receive(:generate)
|
53
|
+
perform
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'using aliases' do
|
58
|
+
let(:options) { %w(-i=input -o=output -e=en abled -d=dis appointing) }
|
59
|
+
|
60
|
+
it 'generates using the options given' do
|
61
|
+
expect(generator).to receive(:generate)
|
62
|
+
perform
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when plist command called' do
|
69
|
+
let(:command) { 'plist' }
|
70
|
+
let(:generator_class) { FlipTheSwitch::Generator::Plist }
|
71
|
+
|
72
|
+
it_behaves_like 'generator'
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when category command called' do
|
76
|
+
let(:command) { 'category' }
|
77
|
+
let(:generator_class) { FlipTheSwitch::Generator::Category }
|
78
|
+
|
79
|
+
it_behaves_like 'generator'
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FlipTheSwitch::Generator::Category do
|
4
|
+
subject(:category) { FlipTheSwitch::Generator::Category.new(output, feature_states) }
|
5
|
+
let(:output) { 'spec/resources' }
|
6
|
+
let(:feature_states) { {'first_feature' => true, 'second_feature' => false} }
|
7
|
+
let(:expected_header_file) { File.read('spec/resources/expected_header.h') }
|
8
|
+
let(:expected_implementation_file) { File.read('spec/resources/expected_implementation.m') }
|
9
|
+
|
10
|
+
after do
|
11
|
+
delete_header_if_exists
|
12
|
+
delete_implementation_if_exists
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'writes a FlipTheSwitch+Features.h file with the category header' do
|
16
|
+
subject.generate
|
17
|
+
|
18
|
+
expect(File.read(output_name_with('h'))).to eql(expected_header_file)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'writes a FlipTheSwitch+Features.m file with the category implementation' do
|
22
|
+
subject.generate
|
23
|
+
|
24
|
+
expect(File.read(output_name_with('m'))).to eql(expected_implementation_file)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def delete_header_if_exists
|
30
|
+
delete_output_if_exists_with('h')
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_implementation_if_exists
|
34
|
+
delete_output_if_exists_with('m')
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_output_if_exists_with(suffix)
|
38
|
+
File.delete(output_name_with(suffix)) if File.exists?(output_name_with(suffix))
|
39
|
+
end
|
40
|
+
|
41
|
+
def output_name_with(suffix)
|
42
|
+
"#{output}/FlipTheSwitch+Features.#{suffix}"
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FlipTheSwitch::Generator::Plist do
|
4
|
+
subject(:plist) { FlipTheSwitch::Generator::Plist.new(output, feature_states) }
|
5
|
+
let(:output) { 'spec/resources' }
|
6
|
+
let(:feature_states) { {enabled_feature: true, disabled_feature: false} }
|
7
|
+
let(:output_file) { 'spec/resources/Features.plist' }
|
8
|
+
let(:expected_plist_file) { File.read('spec/resources/ExpectedFeatures.plist') }
|
9
|
+
|
10
|
+
after do
|
11
|
+
File.delete(output_file) if File.exists?(output_file)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'writes a Features.plist file with the enabled features set' do
|
15
|
+
subject.generate
|
16
|
+
|
17
|
+
expect(File.read(output_file)).to eql(expected_plist_file)
|
18
|
+
end
|
19
|
+
end
|