flip_the_switch 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Example/.gitignore +2 -1
  4. data/Example/Colors-iOS/ViewControlleriOS.m +27 -0
  5. data/Example/Colors.xcodeproj/project.pbxproj +4 -0
  6. data/Example/Rakefile +1 -0
  7. data/Example/features.yml +3 -2
  8. data/FlipTheSwitch.podspec +1 -1
  9. data/README.md +13 -3
  10. data/bin/flip-the-switch +6 -1
  11. data/flip_the_switch.gemspec +1 -1
  12. data/lib/flip_the_switch/cli.rb +28 -11
  13. data/lib/flip_the_switch/errors.rb +9 -0
  14. data/lib/flip_the_switch/generator/category.rb +1 -0
  15. data/lib/flip_the_switch/generator/plist.rb +1 -0
  16. data/lib/flip_the_switch/generator/settings.rb +121 -0
  17. data/lib/flip_the_switch/generator.rb +1 -1
  18. data/lib/flip_the_switch/reader/defaults.rb +43 -0
  19. data/lib/flip_the_switch/reader/features.rb +53 -0
  20. data/lib/flip_the_switch/reader.rb +2 -1
  21. data/spec/flip_the_switch/cli_spec.rb +12 -5
  22. data/spec/flip_the_switch/generator/category_spec.rb +1 -1
  23. data/spec/flip_the_switch/generator/plist_spec.rb +1 -1
  24. data/spec/flip_the_switch/generator/settings_spec.rb +80 -0
  25. data/spec/flip_the_switch/reader/defaults_spec.rb +96 -0
  26. data/spec/flip_the_switch/reader/{yaml_spec.rb → features_spec.rb} +17 -4
  27. data/spec/resources/ExistingSettingsRoot.plist +33 -0
  28. data/spec/resources/ExpectedSettingsBaseRoot.plist +23 -0
  29. data/spec/resources/ExpectedSettingsFeatures.plist +29 -0
  30. data/spec/resources/ExpectedSettingsMergeExistingRoot.plist +47 -0
  31. data/spec/resources/real/features.yml +6 -2
  32. metadata +20 -6
  33. data/lib/flip_the_switch/reader/yaml.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 619f1abd162d81a6bbad84209c1cea55f2b27aa4
4
- data.tar.gz: 5fe65dd5282fa538e9d7fae127bed002ad3b5b01
3
+ metadata.gz: 1e9b8507bbc66a9479d3fe174c8a42fba3495200
4
+ data.tar.gz: 01f913063a72ab9a2bd670fe7600f0d4b16fc830
5
5
  SHA512:
6
- metadata.gz: a2e7f90c45df40258f8ff75ccae108e4ca54d57f382458b617453d8871eac376846172f232ab1a0a5c2d5832a3cb24d55c4bfa85bc5bb5da26082832f3981b4b
7
- data.tar.gz: 6d7949f226ffec6785cadf0c6008ec653b9d0f28ec266c61db17eb92815ae79e941927dfa11cd98c9ad14eaf91f3e62c312cf7fe34cade0db96a209a7ef2391a
6
+ metadata.gz: 8077a424aec23a34f08139b7213c8f17d8c650d980a3cea097c1d137609f08d5af256823eacf89b9fd67af9b5fd3255a6d7cf70e9a4a6d0a8e62a34bd7256144
7
+ data.tar.gz: a5a65e6aa1a43e8e22a4424a76efd73a90c99106c851ff5cfb2880ee4ea71529d7a159c352b57ebcfa02e34456c439661015f0fa86ebe9599f51dd5190489ce1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.4.0 / 2014-07-16
2
+
3
+ * [FEATURE] #17 Use environments for feature states
4
+ * [FEATURE] #16 Allow reading of CLI defaults from yml file
5
+ * [FEATURE] #14 Auto-create Settings.bundle from feature defaults
6
+
1
7
  # 0.3.0 / 2014-07-02
2
8
 
3
9
  * [FEATURE] #15 Auto-create category for features
data/Example/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  Colors-iOS/Features.plist
2
- Colors-iOS/FlipTheSwitch+Features.*
2
+ Colors-iOS/FlipTheSwitch+Features.*
3
+ Colors-iOS/Settings.bundle/
@@ -12,12 +12,39 @@
12
12
 
13
13
  @implementation ViewControlleriOS
14
14
 
15
+ #pragma mark - Lifecycle
16
+
17
+ - (void)dealloc
18
+ {
19
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
20
+ }
21
+
15
22
  #pragma mark - UIViewController Lifecycle
16
23
 
17
24
  - (void)viewDidLoad
18
25
  {
19
26
  [super viewDidLoad];
20
27
  [self setupView];
28
+ [self setupStateChangeNotifications];
29
+ }
30
+
31
+ - (void)setupStateChangeNotifications
32
+ {
33
+ [self setupNotification:UIApplicationWillEnterForegroundNotification];
34
+ [self setupNotification:UIApplicationDidBecomeActiveNotification];
35
+ }
36
+
37
+ - (void)setupNotification:(NSString *)notification
38
+ {
39
+ [[NSNotificationCenter defaultCenter] addObserver:self
40
+ selector:@selector(applicationStateChanged:)
41
+ name:notification
42
+ object:nil];
43
+ }
44
+
45
+ - (void)applicationStateChanged:(UIApplication *)application
46
+ {
47
+ [self setupView];
21
48
  }
22
49
 
23
50
  #pragma mark - Actions
@@ -8,6 +8,7 @@
8
8
 
9
9
  /* Begin PBXBuildFile section */
10
10
  35D6E35AEAC9473FAAAB5BE5 /* libPods-Colors-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F7C4D132FE24A06ACC52E5F /* libPods-Colors-iOS.a */; };
11
+ 5B13CA4919721395001D008A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B13CA4819721395001D008A /* Settings.bundle */; };
11
12
  5B20701A194237BF00D07667 /* Features.plist in Resources */ = {isa = PBXBuildFile; fileRef = F80321679CBE499FFA387688 /* Features.plist */; };
12
13
  5B909898193A235E004C12B3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B909897193A235E004C12B3 /* Foundation.framework */; };
13
14
  5B90989C193A235E004C12B3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B90989B193A235E004C12B3 /* UIKit.framework */; };
@@ -20,6 +21,7 @@
20
21
  /* End PBXBuildFile section */
21
22
 
22
23
  /* Begin PBXFileReference section */
24
+ 5B13CA4819721395001D008A /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
23
25
  5B909894193A235E004C12B3 /* Colors-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Colors-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
24
26
  5B909897193A235E004C12B3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
25
27
  5B90989B193A235E004C12B3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
@@ -93,6 +95,7 @@
93
95
  F803243E8A66F29C3EA37B65 /* AppDelegateiOS.m */,
94
96
  F8032EBDB675B3B788B1297F /* FlipTheSwitch+Features.h */,
95
97
  F803287440254AB051D00DDB /* FlipTheSwitch+Features.m */,
98
+ 5B13CA4819721395001D008A /* Settings.bundle */,
96
99
  );
97
100
  path = "Colors-iOS";
98
101
  sourceTree = "<group>";
@@ -162,6 +165,7 @@
162
165
  isa = PBXResourcesBuildPhase;
163
166
  buildActionMask = 2147483647;
164
167
  files = (
168
+ 5B13CA4919721395001D008A /* Settings.bundle in Resources */,
165
169
  5B20701A194237BF00D07667 /* Features.plist in Resources */,
166
170
  5B9098B3193A235E004C12B3 /* Images.xcassets in Resources */,
167
171
  5B9098AB193A235E004C12B3 /* Main_iPhone.storyboard in Resources */,
data/Example/Rakefile CHANGED
@@ -19,5 +19,6 @@ end
19
19
  task :test => podfile_lock do
20
20
  sh 'bundle exec ../bin/flip-the-switch plist -o Colors-iOS'
21
21
  sh 'bundle exec ../bin/flip-the-switch category -o Colors-iOS'
22
+ sh 'bundle exec ../bin/flip-the-switch settings -o Colors-iOS'
22
23
  sh "xctool build -scheme #{workspace}-iOS -workspace #{workspace}.xcworkspace -sdk iphonesimulator OBJROOT=#{build} SHARED_PRECOMPS_DIR=#{build}"
23
24
  end
data/Example/features.yml CHANGED
@@ -1,2 +1,3 @@
1
- purple_color: Yes
2
- red_color: No
1
+ default:
2
+ purple_color: Yes
3
+ red_color: No
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'FlipTheSwitch'
3
- s.version = '0.3.0'
3
+ s.version = '0.4.0'
4
4
  s.summary = 'A simple library to help enabling/disabling features on iOS/Mac applications.'
5
5
  s.authors = {
6
6
  'Michael England' => 'mg.england@gmail.com',
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A feature switching/toggling/flipping library for ObjectiveC.
4
4
 
5
- # flip_the_switch [![Build Status](https://travis-ci.org/michaelengland/FlipTheSwitch.svg?branch=master)](https://travis-ci.org/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch/coverage.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Gem Version](https://badge.fury.io/rb/flip-the-switch.svg)](http://badge.fury.io/rb/flip-the-switch)
5
+ # flip_the_switch [![Build Status](https://travis-ci.org/michaelengland/FlipTheSwitch.svg?branch=master)](https://travis-ci.org/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Code Climate](https://codeclimate.com/github/michaelengland/FlipTheSwitch/coverage.png)](https://codeclimate.com/github/michaelengland/FlipTheSwitch) [![Gem Version](https://badge.fury.io/rb/flip_the_switch.svg)](http://badge.fury.io/rb/flip_the_switch)
6
6
 
7
7
  A gem command line tool for generating the `Features.plist` & `FlipTheSwitch+Features.{h,m}` categories to help with the corresponding Pod.
8
8
 
@@ -63,6 +63,7 @@ If you install the gem, you will be able to use the Command-Line-Interface.
63
63
  The CLI consists of 2 commands:
64
64
 
65
65
  - `plist` - creates a `Features.plist` file for enabled/disabled features like that mentioned above.
66
+ - `settings` - creates a `Settings.bundle` used by the OS settings. These can then be used to enable/disable the features at runtime.
66
67
  - `category` - creates `FlipTheSwitch+Features.{h,m}` files for features, thus giving compile-time checks for adding/removal of new features.
67
68
  e.g:
68
69
 
@@ -83,7 +84,16 @@ e.g:
83
84
  The features, along with their default enabled/disabled state, are read from a `features.yml` file. e.g.:
84
85
 
85
86
  ```yaml
86
- awesome_feature: Yes
87
+ default:
88
+ awesome_feature: Yes
89
+ ```
90
+
91
+ In order to avoid typing in the same options all the time, you can create a `.flip.yml` file for the default options, e.g.:
92
+
93
+ ```yaml
94
+ input: features
95
+ environment: development
96
+ category_output: Classes/Extensions
87
97
  ```
88
98
 
89
99
  For more information, run `flip-the-switch help`
@@ -92,7 +102,7 @@ For more information, run `flip-the-switch help`
92
102
 
93
103
  Add `pod 'FlipTheSwitch'` to your Podfile
94
104
 
95
- Add `gem 'flip-the-switch'` to your Gemfile
105
+ Add `gem 'flip_the_switch'` to your Gemfile
96
106
 
97
107
  ## Authors
98
108
 
data/bin/flip-the-switch CHANGED
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'flip_the_switch/cli'
3
3
 
4
- FlipTheSwitch::Cli.start
4
+ begin
5
+ FlipTheSwitch::Cli.start
6
+ rescue FlipTheSwitch::Error::Base => e
7
+ $stderr.puts e
8
+ exit(false)
9
+ end
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
4
4
  s.email = %w(mg.england@gmail.com rob@robsiwek.com)
5
5
  s.summary = 'A simple library to help enabling/disabling features on iOS/Mac applications.'
6
6
  s.homepage = 'https://github.com/michaelengland/FlipTheSwitch'
7
- s.version = '0.3.0'
7
+ s.version = '0.4.0'
8
8
  s.license = 'MIT'
9
9
 
10
10
  s.add_dependency 'activesupport', '~> 3.2'
@@ -3,22 +3,35 @@ require 'flip_the_switch'
3
3
 
4
4
  module FlipTheSwitch
5
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'
6
+ private
7
+ def self.defaults
8
+ @defaults ||= Reader::Defaults.new.defaults
9
+ end
10
+
11
+ public
12
+ class_option :input, type: :string, aliases: '-i', default: defaults[:input], desc: 'Location of the directory containing features.yml file to read'
13
+ class_option :environment, type: :string, aliases: '-n', default: defaults[:environment], desc: 'Name of environment to read from in features.yml file'
14
+ class_option :enabled, type: :string, aliases: '-e', default: defaults[:enabled], desc: 'Extra features to be set as enabled'
15
+ class_option :disabled, type: :string, aliases: '-d', default: defaults[:disabled], desc: 'Extra features to be set as disabled'
9
16
 
10
17
  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'
18
+ method_option :output, type: :string, aliases: '-o', default: defaults[:plist_output], desc: 'Location of the directory in which Features.plist file will be created'
12
19
  def plist
13
20
  plist_generator.generate
14
21
  end
15
22
 
16
23
  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'
24
+ method_option :output, type: :string, aliases: '-o', default: defaults[:category_output], desc: 'Location of the directory in which FlipTheSwitch+Features.{h,m} files will be created'
18
25
  def category
19
26
  category_generator.generate
20
27
  end
21
28
 
29
+ desc 'settings', 'Auto-generates settings.bundle files for enabling/disabling features from iOS settings menu'
30
+ method_option :output, type: :string, aliases: '-o', default: defaults[:settings_output], desc: 'Location of the directory in which FlipTheSwitch+Features.{h,m} files will be created'
31
+ def settings
32
+ settings_generator.generate
33
+ end
34
+
22
35
  private
23
36
 
24
37
  def plist_generator
@@ -29,26 +42,30 @@ module FlipTheSwitch
29
42
  Generator::Category.new(output, feature_states)
30
43
  end
31
44
 
45
+ def settings_generator
46
+ Generator::Settings.new(output, feature_states)
47
+ end
48
+
32
49
  def output
33
- options.output
50
+ options['output']
34
51
  end
35
52
 
36
53
  def feature_states
37
- yaml_reader.feature_states.
54
+ feature_reader.feature_states.
38
55
  merge(enabled_states).
39
56
  merge(disabled_states)
40
57
  end
41
58
 
42
- def yaml_reader
43
- Reader::Yaml.new(options.input)
59
+ def feature_reader
60
+ Reader::Features.new(options['input'], options['environment'])
44
61
  end
45
62
 
46
63
  def enabled_states
47
- states_for(options.enabled, true)
64
+ states_for(options['enabled'].split(','), true)
48
65
  end
49
66
 
50
67
  def disabled_states
51
- states_for(options.disabled, false)
68
+ states_for(options['disabled'].split(','), false)
52
69
  end
53
70
 
54
71
  def states_for(array, default)
@@ -7,6 +7,15 @@ module FlipTheSwitch
7
7
  end
8
8
 
9
9
  class InvalidFile < Base
10
+ def initialize(file)
11
+ super("Invalid file - #{file}")
12
+ end
13
+ end
14
+
15
+ class InvalidEnvironment < Base
16
+ def initialize(environment)
17
+ super("Invalid environment - #{environment}")
18
+ end
10
19
  end
11
20
  end
12
21
  end
@@ -1,3 +1,4 @@
1
+ require 'flip_the_switch/generator/base'
1
2
  require 'active_support/inflector'
2
3
  require 'erb'
3
4
 
@@ -1,3 +1,4 @@
1
+ require 'flip_the_switch/generator/base'
1
2
  require 'plist'
2
3
 
3
4
  module FlipTheSwitch
@@ -0,0 +1,121 @@
1
+ require 'flip_the_switch/generator/base'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'plist'
4
+
5
+ module FlipTheSwitch
6
+ module Generator
7
+ class Settings < Base
8
+ def generate
9
+ create_settings_bundle_if_not_exists
10
+ read_settings
11
+ delete_existing_settings_if_exist
12
+ write_settings
13
+ end
14
+
15
+ private
16
+
17
+ def create_settings_bundle_if_not_exists
18
+ unless Dir.exists?(settings_bundle)
19
+ Dir.mkdir(settings_bundle)
20
+ end
21
+ end
22
+
23
+ def read_settings
24
+ current_plist
25
+ end
26
+
27
+ def delete_existing_settings_if_exist
28
+ delete_root_plist_if_exists
29
+ delete_features_plist_if_exists
30
+ end
31
+
32
+ def write_settings
33
+ write_root_plist
34
+ write_features_plist
35
+ end
36
+
37
+ def delete_root_plist_if_exists
38
+ delete_file(root_plist)
39
+ end
40
+
41
+ def delete_features_plist_if_exists
42
+ delete_file(features_plist)
43
+ end
44
+
45
+ def write_root_plist
46
+ ::Plist::Emit.save_plist(root, root_plist)
47
+ end
48
+
49
+ def write_features_plist
50
+ ::Plist::Emit.save_plist(features, features_plist)
51
+ end
52
+
53
+ def delete_file(file)
54
+ File.delete(file) if File.exists?(file)
55
+ end
56
+
57
+ def root
58
+ current_plist.with_indifferent_access.merge(PreferenceSpecifiers: root_preferences)
59
+ end
60
+
61
+ def root_preferences
62
+ existing_root_preferences.delete_if { |root_preference|
63
+ root_preference[:Title] == 'Features'
64
+ } + feature_root_preferences
65
+ end
66
+
67
+ def existing_root_preferences
68
+ current_plist.with_indifferent_access[:PreferenceSpecifiers] || []
69
+ end
70
+
71
+ def feature_root_preferences
72
+ [
73
+ {
74
+ Title: 'Features',
75
+ Type: 'PSGroupSpecifier'
76
+ },
77
+ {
78
+ File: 'Features',
79
+ Title: 'Features',
80
+ Type: 'PSChildPaneSpecifier'
81
+ }
82
+ ]
83
+ end
84
+
85
+ def features
86
+ {PreferenceSpecifiers: feature_toggles}
87
+ end
88
+
89
+ def feature_toggles
90
+ feature_states.map { |feature, state|
91
+ {
92
+ Type: 'PSToggleSwitchSpecifier',
93
+ Title: feature,
94
+ Key: "FTS_FEATURE_#{feature}",
95
+ DefaultValue: state
96
+ }
97
+ }
98
+ end
99
+
100
+ def current_plist
101
+ @current_plist ||= if File.exists?(root_plist)
102
+ ::Plist::parse_xml(root_plist)
103
+ else
104
+ {}
105
+ end
106
+ end
107
+
108
+ def root_plist
109
+ File.join(settings_bundle, 'Root.plist')
110
+ end
111
+
112
+ def features_plist
113
+ File.join(settings_bundle, 'Features.plist')
114
+ end
115
+
116
+ def settings_bundle
117
+ File.join(output, 'Settings.bundle')
118
+ end
119
+ end
120
+ end
121
+ end
@@ -1,3 +1,3 @@
1
- require 'flip_the_switch/generator/base'
2
1
  require 'flip_the_switch/generator/category'
3
2
  require 'flip_the_switch/generator/plist'
3
+ require 'flip_the_switch/generator/settings'
@@ -0,0 +1,43 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module FlipTheSwitch
4
+ module Reader
5
+ class Defaults
6
+ def defaults
7
+ if valid_file?
8
+ base_defaults.merge(file_defaults)
9
+ else
10
+ raise Error::InvalidFile.new(defaults_file)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def valid_file?
17
+ file_defaults.is_a?(Hash)
18
+ end
19
+
20
+ def base_defaults
21
+ {
22
+ input: Dir.pwd,
23
+ environment: 'default',
24
+ enabled: '',
25
+ disabled: '',
26
+ category_output: Dir.pwd,
27
+ plist_output: Dir.pwd,
28
+ settings_output: Dir.pwd
29
+ }.with_indifferent_access
30
+ end
31
+
32
+ def file_defaults
33
+ @file_defaults ||= YAML.load_file(defaults_file)
34
+ rescue SystemCallError
35
+ {}
36
+ end
37
+
38
+ def defaults_file
39
+ '.flip.yml'
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,53 @@
1
+ require 'yaml'
2
+
3
+ module FlipTheSwitch
4
+ module Reader
5
+ class Features
6
+ def initialize(input, environment)
7
+ @input = input
8
+ @environment = environment
9
+ end
10
+
11
+ def feature_states
12
+ if valid_file?
13
+ environment_states
14
+ else
15
+ raise Error::InvalidFile.new(input_file)
16
+ end
17
+ end
18
+
19
+ private
20
+ attr_reader :input, :environment
21
+
22
+ def valid_file?
23
+ file_states.is_a?(Hash) && file_states.all? { |environment, feature|
24
+ environment.is_a?(String) && valid_feature_hash?(feature)
25
+ }
26
+ end
27
+
28
+ def valid_feature_hash?(feature_hash)
29
+ feature_hash.is_a?(Hash) && feature_hash.all? { |feature, state|
30
+ feature.is_a?(String) && !!state == state
31
+ }
32
+ end
33
+
34
+ def environment_states
35
+ if file_states.has_key?(environment)
36
+ file_states[environment]
37
+ else
38
+ raise Error::InvalidEnvironment.new(environment)
39
+ end
40
+ end
41
+
42
+ def file_states
43
+ @file_states ||= YAML.load_file(input_file)
44
+ rescue SystemCallError => e
45
+ raise Error::UnreadableFile.new(e)
46
+ end
47
+
48
+ def input_file
49
+ File.join(input, 'features.yml')
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1 +1,2 @@
1
- require 'flip_the_switch/reader/yaml'
1
+ require 'flip_the_switch/reader/features'
2
+ require 'flip_the_switch/reader/defaults'
@@ -16,14 +16,14 @@ describe FlipTheSwitch::Cli do
16
16
  end
17
17
 
18
18
  shared_examples_for 'generator' do
19
- let(:yaml_reader) { double(FlipTheSwitch::Reader::Yaml, feature_states: {'something' => true}) }
19
+ let(:feature_reader) { double(FlipTheSwitch::Reader::Features, feature_states: {'something' => true}) }
20
20
  let(:generator) { double(generator_class) }
21
21
 
22
22
  context 'when no options given' do
23
23
  let(:options) { [] }
24
24
 
25
25
  before do
26
- FlipTheSwitch::Reader::Yaml.stub(:new).with(Dir.pwd).and_return(yaml_reader)
26
+ FlipTheSwitch::Reader::Features.stub(:new).with(Dir.pwd, 'default').and_return(feature_reader)
27
27
  generator_class.stub(:new).with(Dir.pwd, 'something' => true).and_return(generator)
28
28
  end
29
29
 
@@ -35,7 +35,7 @@ describe FlipTheSwitch::Cli do
35
35
 
36
36
  context 'when options given' do
37
37
  before do
38
- FlipTheSwitch::Reader::Yaml.stub(:new).with('input').and_return(yaml_reader)
38
+ FlipTheSwitch::Reader::Features.stub(:new).with('input', 'environment').and_return(feature_reader)
39
39
  generator_class.stub(:new).with('output', {
40
40
  'something' => true,
41
41
  'en' => true,
@@ -46,7 +46,7 @@ describe FlipTheSwitch::Cli do
46
46
  end
47
47
 
48
48
  context 'using full name' do
49
- let(:options) { %w(--input=input --output=output --enabled=en abled --disabled=dis appointing) }
49
+ let(:options) { %w(--input=input --environment=environment --output=output --enabled=en,abled --disabled=dis,appointing) }
50
50
 
51
51
  it 'generates using the options given' do
52
52
  expect(generator).to receive(:generate)
@@ -55,7 +55,7 @@ describe FlipTheSwitch::Cli do
55
55
  end
56
56
 
57
57
  context 'using aliases' do
58
- let(:options) { %w(-i=input -o=output -e=en abled -d=dis appointing) }
58
+ let(:options) { %w(-i=input -n=environment -o=output -e=en,abled -d=dis,appointing) }
59
59
 
60
60
  it 'generates using the options given' do
61
61
  expect(generator).to receive(:generate)
@@ -78,4 +78,11 @@ describe FlipTheSwitch::Cli do
78
78
 
79
79
  it_behaves_like 'generator'
80
80
  end
81
+
82
+ context 'when settings command called' do
83
+ let(:command) { 'settings' }
84
+ let(:generator_class) { FlipTheSwitch::Generator::Settings }
85
+
86
+ it_behaves_like 'generator'
87
+ end
81
88
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe FlipTheSwitch::Generator::Category do
4
- subject(:category) { FlipTheSwitch::Generator::Category.new(output, feature_states) }
4
+ subject(:category) { described_class.new(output, feature_states) }
5
5
  let(:output) { 'spec/resources' }
6
6
  let(:feature_states) { {'first_feature' => true, 'second_feature' => false} }
7
7
  let(:expected_header_file) { File.read('spec/resources/expected_header.h') }
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe FlipTheSwitch::Generator::Plist do
4
- subject(:plist) { FlipTheSwitch::Generator::Plist.new(output, feature_states) }
4
+ subject(:plist) { described_class.new(output, feature_states) }
5
5
  let(:output) { 'spec/resources' }
6
6
  let(:feature_states) { {enabled_feature: true, disabled_feature: false} }
7
7
  let(:output_file) { 'spec/resources/Features.plist' }
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe FlipTheSwitch::Generator::Settings do
5
+ subject(:settings) { described_class.new(output, feature_states) }
6
+ let(:output) { 'spec/resources' }
7
+ let(:settings_bundle) { File.join(output, 'Settings.bundle') }
8
+ let(:feature_states) { {'first_feature' => true, 'second_feature' => false} }
9
+ let(:root_output_file) { File.read(File.join(settings_bundle, 'Root.plist')) }
10
+ let(:features_output_file) { File.read(File.join(settings_bundle, 'Features.plist')) }
11
+ let(:features_output_file) { File.read(File.join(settings_bundle, 'Features.plist')) }
12
+ let(:expected_features_file) { File.read('spec/resources/ExpectedSettingsFeatures.plist')}
13
+
14
+ after do
15
+ delete_settings_bundle_if_exists
16
+ end
17
+
18
+ context 'when settings already exist' do
19
+ let(:expected_root_file) { File.read('spec/resources/ExpectedSettingsMergeExistingRoot.plist') }
20
+
21
+ before do
22
+ Dir.mkdir(settings_bundle)
23
+ File.write(File.join(settings_bundle, 'Root.plist'), File.read('spec/resources/ExistingSettingsRoot.plist'))
24
+ end
25
+
26
+ it 'adds child page link to existing settings' do
27
+ subject.generate
28
+
29
+ expect(root_output_file).to eql(expected_root_file)
30
+ end
31
+
32
+ it 'creates a features settings page' do
33
+ subject.generate
34
+
35
+ expect(features_output_file).to eql(expected_features_file)
36
+ end
37
+ end
38
+
39
+ context 'when settings already exist and have already got features' do
40
+ let(:expected_root_file) { File.read('spec/resources/ExpectedSettingsMergeExistingRoot.plist') }
41
+
42
+ before do
43
+ Dir.mkdir(settings_bundle)
44
+ File.write(File.join(settings_bundle, 'Root.plist'), File.read('spec/resources/ExpectedSettingsMergeExistingRoot.plist'))
45
+ File.write(File.join(settings_bundle, 'Features.plist'), File.read('spec/resources/ExpectedFeatures.plist'))
46
+ end
47
+
48
+ it 'creates settings with features child page link' do
49
+ subject.generate
50
+
51
+ expect(root_output_file).to eql(expected_root_file)
52
+ end
53
+
54
+ it 'creates a features settings page' do
55
+ subject.generate
56
+
57
+ expect(features_output_file).to eql(expected_features_file)
58
+ end
59
+ end
60
+
61
+ context 'when no settings already exist' do
62
+ let(:expected_root_file) { File.read('spec/resources/ExpectedSettingsBaseRoot.plist') }
63
+
64
+ it 'creates settings with features child page link' do
65
+ subject.generate
66
+
67
+ expect(root_output_file).to eql(expected_root_file)
68
+ end
69
+
70
+ it 'creates a features settings page' do
71
+ subject.generate
72
+
73
+ expect(features_output_file).to eql(expected_features_file)
74
+ end
75
+ end
76
+
77
+ def delete_settings_bundle_if_exists
78
+ FileUtils.rm_rf(settings_bundle) if Dir.exists?(settings_bundle)
79
+ end
80
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe FlipTheSwitch::Reader::Defaults do
4
+ subject(:reader) { described_class.new }
5
+ let(:defaults_file) { '.flip.yml' }
6
+
7
+ after do
8
+ File.delete(defaults_file) if File.exists?(defaults_file)
9
+ end
10
+
11
+ context 'when defaults file exists' do
12
+ context 'when valid' do
13
+ before do
14
+ File.write(defaults_file, YAML.dump({
15
+ input: 'input',
16
+ environment: 'environment',
17
+ enabled: 'enabled',
18
+ disabled: 'disabled',
19
+ category_output: 'category_output',
20
+ plist_output: 'plist_output',
21
+ settings_output: 'settings_output'
22
+ }))
23
+ end
24
+
25
+ it 'returns file input default' do
26
+ expect(subject.defaults[:input]).to eql('input')
27
+ end
28
+
29
+ it 'returns file environment default' do
30
+ expect(subject.defaults[:environment]).to eql('environment')
31
+ end
32
+
33
+ it 'returns file enabled default' do
34
+ expect(subject.defaults[:enabled]).to eql('enabled')
35
+ end
36
+
37
+ it 'returns file disabled default' do
38
+ expect(subject.defaults[:disabled]).to eql('disabled')
39
+ end
40
+
41
+ it 'returns file category output default' do
42
+ expect(subject.defaults[:category_output]).to eql('category_output')
43
+ end
44
+
45
+ it 'returns file plist output default' do
46
+ expect(subject.defaults[:plist_output]).to eql('plist_output')
47
+ end
48
+
49
+ it 'returns file settings output default' do
50
+ expect(subject.defaults[:settings_output]).to eql('settings_output')
51
+ end
52
+ end
53
+
54
+ context 'when invalid' do
55
+ before do
56
+ File.write(defaults_file, 'this is not what we want')
57
+ end
58
+
59
+ specify do
60
+ expect {
61
+ subject.defaults
62
+ }.to raise_error(FlipTheSwitch::Error::InvalidFile)
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'when defaults file does NOT exist' do
68
+ it 'returns base input default' do
69
+ expect(subject.defaults[:input]).to eql(Dir.pwd)
70
+ end
71
+
72
+ it 'returns base environment default' do
73
+ expect(subject.defaults[:environment]).to eql('default')
74
+ end
75
+
76
+ it 'returns base enabled default' do
77
+ expect(subject.defaults[:enabled]).to eql('')
78
+ end
79
+
80
+ it 'returns base disabled default' do
81
+ expect(subject.defaults[:disabled]).to eql('')
82
+ end
83
+
84
+ it 'returns base category output default' do
85
+ expect(subject.defaults[:category_output]).to eql(Dir.pwd)
86
+ end
87
+
88
+ it 'returns base plist output default' do
89
+ expect(subject.defaults[:plist_output]).to eql(Dir.pwd)
90
+ end
91
+
92
+ it 'returns base settings output default' do
93
+ expect(subject.defaults[:settings_output]).to eql(Dir.pwd)
94
+ end
95
+ end
96
+ end
@@ -1,13 +1,26 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe FlipTheSwitch::Reader::Yaml do
4
- subject(:reader) { described_class.new(input) }
3
+ describe FlipTheSwitch::Reader::Features do
4
+ subject(:reader) { described_class.new(input, environment) }
5
+ let(:environment) { 'beta' }
5
6
 
6
7
  context 'when given a real file' do
7
8
  let(:input) { 'spec/resources/real' }
8
9
 
9
- it 'reads the enabled/disabled states of the features' do
10
- expect(subject.feature_states).to eql('enabled_feature' => true, 'disabled_feature' => false)
10
+ context 'when given an valid environment' do
11
+ it 'reads the enabled/disabled states of the features for the environment' do
12
+ expect(subject.feature_states).to eql('enabled_feature' => true, 'disabled_feature' => false)
13
+ end
14
+ end
15
+
16
+ context 'when given an invalid environment' do
17
+ let(:environment) { 'invalid' }
18
+
19
+ specify do
20
+ expect {
21
+ subject.feature_states
22
+ }.to raise_error(FlipTheSwitch::Error::InvalidEnvironment)
23
+ end
11
24
  end
12
25
  end
13
26
 
@@ -0,0 +1,33 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreferenceSpecifiers</key>
6
+ <array>
7
+ <dict>
8
+ <key>Type</key>
9
+ <string>PSSliderSpecifier</string>
10
+ <key>Key</key>
11
+ <string>SomeThing</string>
12
+ <key>DefaultValue</key>
13
+ <integer>0</integer>
14
+ <key>MinimumValue</key>
15
+ <integer>0</integer>
16
+ <key>MaximumValue</key>
17
+ <integer>0</integer>
18
+ </dict>
19
+ <dict>
20
+ <key>Type</key>
21
+ <string>PSToggleSwitchSpecifier</string>
22
+ <key>Title</key>
23
+ <string>On/Off</string>
24
+ <key>Key</key>
25
+ <string>whatever</string>
26
+ <key>DefaultValue</key>
27
+ <false/>
28
+ </dict>
29
+ </array>
30
+ <key>StringsTable</key>
31
+ <string>Root</string>
32
+ </dict>
33
+ </plist>
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreferenceSpecifiers</key>
6
+ <array>
7
+ <dict>
8
+ <key>Title</key>
9
+ <string>Features</string>
10
+ <key>Type</key>
11
+ <string>PSGroupSpecifier</string>
12
+ </dict>
13
+ <dict>
14
+ <key>File</key>
15
+ <string>Features</string>
16
+ <key>Title</key>
17
+ <string>Features</string>
18
+ <key>Type</key>
19
+ <string>PSChildPaneSpecifier</string>
20
+ </dict>
21
+ </array>
22
+ </dict>
23
+ </plist>
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreferenceSpecifiers</key>
6
+ <array>
7
+ <dict>
8
+ <key>DefaultValue</key>
9
+ <true/>
10
+ <key>Key</key>
11
+ <string>FTS_FEATURE_first_feature</string>
12
+ <key>Title</key>
13
+ <string>first_feature</string>
14
+ <key>Type</key>
15
+ <string>PSToggleSwitchSpecifier</string>
16
+ </dict>
17
+ <dict>
18
+ <key>DefaultValue</key>
19
+ <false/>
20
+ <key>Key</key>
21
+ <string>FTS_FEATURE_second_feature</string>
22
+ <key>Title</key>
23
+ <string>second_feature</string>
24
+ <key>Type</key>
25
+ <string>PSToggleSwitchSpecifier</string>
26
+ </dict>
27
+ </array>
28
+ </dict>
29
+ </plist>
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>PreferenceSpecifiers</key>
6
+ <array>
7
+ <dict>
8
+ <key>DefaultValue</key>
9
+ <integer>0</integer>
10
+ <key>Key</key>
11
+ <string>SomeThing</string>
12
+ <key>MaximumValue</key>
13
+ <integer>0</integer>
14
+ <key>MinimumValue</key>
15
+ <integer>0</integer>
16
+ <key>Type</key>
17
+ <string>PSSliderSpecifier</string>
18
+ </dict>
19
+ <dict>
20
+ <key>DefaultValue</key>
21
+ <false/>
22
+ <key>Key</key>
23
+ <string>whatever</string>
24
+ <key>Title</key>
25
+ <string>On/Off</string>
26
+ <key>Type</key>
27
+ <string>PSToggleSwitchSpecifier</string>
28
+ </dict>
29
+ <dict>
30
+ <key>Title</key>
31
+ <string>Features</string>
32
+ <key>Type</key>
33
+ <string>PSGroupSpecifier</string>
34
+ </dict>
35
+ <dict>
36
+ <key>File</key>
37
+ <string>Features</string>
38
+ <key>Title</key>
39
+ <string>Features</string>
40
+ <key>Type</key>
41
+ <string>PSChildPaneSpecifier</string>
42
+ </dict>
43
+ </array>
44
+ <key>StringsTable</key>
45
+ <string>Root</string>
46
+ </dict>
47
+ </plist>
@@ -1,2 +1,6 @@
1
- enabled_feature: Yes
2
- disabled_feature: No
1
+ defaults: &DEFAULT
2
+ enabled_feature: Yes
3
+ disabled_feature: Yes
4
+ beta:
5
+ <<: *DEFAULT
6
+ disabled_feature: No
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flip_the_switch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael England
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-07-01 00:00:00.000000000 Z
12
+ date: 2014-07-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -115,13 +115,21 @@ files:
115
115
  - lib/flip_the_switch/generator/header.h.erb
116
116
  - lib/flip_the_switch/generator/implementation.m.erb
117
117
  - lib/flip_the_switch/generator/plist.rb
118
+ - lib/flip_the_switch/generator/settings.rb
118
119
  - lib/flip_the_switch/reader.rb
119
- - lib/flip_the_switch/reader/yaml.rb
120
+ - lib/flip_the_switch/reader/defaults.rb
121
+ - lib/flip_the_switch/reader/features.rb
120
122
  - spec/flip_the_switch/cli_spec.rb
121
123
  - spec/flip_the_switch/generator/category_spec.rb
122
124
  - spec/flip_the_switch/generator/plist_spec.rb
123
- - spec/flip_the_switch/reader/yaml_spec.rb
125
+ - spec/flip_the_switch/generator/settings_spec.rb
126
+ - spec/flip_the_switch/reader/defaults_spec.rb
127
+ - spec/flip_the_switch/reader/features_spec.rb
128
+ - spec/resources/ExistingSettingsRoot.plist
124
129
  - spec/resources/ExpectedFeatures.plist
130
+ - spec/resources/ExpectedSettingsBaseRoot.plist
131
+ - spec/resources/ExpectedSettingsFeatures.plist
132
+ - spec/resources/ExpectedSettingsMergeExistingRoot.plist
125
133
  - spec/resources/expected_header.h
126
134
  - spec/resources/expected_implementation.m
127
135
  - spec/resources/invalid_layout/features.yml
@@ -148,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
156
  version: '0'
149
157
  requirements: []
150
158
  rubyforge_project:
151
- rubygems_version: 2.1.11
159
+ rubygems_version: 2.0.3
152
160
  signing_key:
153
161
  specification_version: 4
154
162
  summary: A simple library to help enabling/disabling features on iOS/Mac applications.
@@ -156,8 +164,14 @@ test_files:
156
164
  - spec/flip_the_switch/cli_spec.rb
157
165
  - spec/flip_the_switch/generator/category_spec.rb
158
166
  - spec/flip_the_switch/generator/plist_spec.rb
159
- - spec/flip_the_switch/reader/yaml_spec.rb
167
+ - spec/flip_the_switch/generator/settings_spec.rb
168
+ - spec/flip_the_switch/reader/defaults_spec.rb
169
+ - spec/flip_the_switch/reader/features_spec.rb
170
+ - spec/resources/ExistingSettingsRoot.plist
160
171
  - spec/resources/ExpectedFeatures.plist
172
+ - spec/resources/ExpectedSettingsBaseRoot.plist
173
+ - spec/resources/ExpectedSettingsFeatures.plist
174
+ - spec/resources/ExpectedSettingsMergeExistingRoot.plist
161
175
  - spec/resources/expected_header.h
162
176
  - spec/resources/expected_implementation.m
163
177
  - spec/resources/invalid_layout/features.yml
@@ -1,39 +0,0 @@
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
-