├── .gitignore ├── .rspec ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── toggle ├── defaults ├── config.yml.default └── key.yml.default ├── lib ├── toggle.rb └── toggle │ ├── compiler.rb │ ├── parser.rb │ ├── parser │ └── yaml.rb │ └── version.rb ├── script └── ci ├── spec ├── cli_spec.rb ├── parser │ └── yaml_spec.rb ├── parser_spec.rb ├── spec_helper.rb ├── test │ └── fixtures │ │ ├── cli │ │ └── .gitkeep │ │ ├── config.yml │ │ ├── flat-key-local │ │ └── key-local.yml └── toggle_spec.rb └── toggle.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | .rvmrc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you would like to contribute code to Toggle you can do so through GitHub by 5 | forking the repository and sending a pull request. 6 | 7 | When submitting code, please make every effort to follow existing conventions 8 | and style in order to keep the code as readable as possible. 9 | 10 | Before your code can be accepted into the project you must also sign the 11 | [Individual Contributor License Agreement (CLA)][1]. 12 | 13 | 14 | [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in switch.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Square Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toggle 2 | 3 | Toggle provides an organized and flexible framework to set up, manage, and 4 | switch between different configuration settings in your Ruby scripts. 5 | 6 | ## Why? 7 | 8 | Ensuring that a script has the correct configuration settings can become a real 9 | headache. Have you ever had to run a script under different environment 10 | specifications or had to share a script that requires different settings based 11 | on who is running the code? 12 | 13 | You may have resorted to storing configuration information in a hash to the top 14 | of a given script to provide some flexibility. This can work for a script or 15 | two and when your on a small team, but as you write more code or increase your 16 | team's size the need for organization while still maintaining flexibility 17 | quickly arises. 18 | 19 | Having a common pattern around how per-project configurations are handled 20 | becomes a big plus. Projects like [rbenv-vars](https://github.com/sstephenson/rbenv-vars) 21 | came about to help solve issues like these. 22 | 23 | Toggle provides a project with rbenv-vars-like functionality with two main 24 | additions: 25 | 26 | 1. rbenv is not required 27 | 2. you can specify *multiple* environment setups instead of just one on a 28 | per-project basis, each of which is easily switchable to either programmatically 29 | or at runtime. 30 | 31 | Additionally, Toggle provides a command line interface to facilitate setting up 32 | this framework along with a set of options to quickly inspect which variables are 33 | available within a project and what each variable is set to for a given environment 34 | specification. 35 | 36 | ## Installation 37 | 38 | Add this line to your application's Gemfile: 39 | 40 | gem 'toggle' 41 | 42 | And then execute: 43 | 44 | $ bundle 45 | 46 | Or install it yourself as: 47 | 48 | $ gem install toggle 49 | 50 | ## Basic Usage 51 | 52 | To start using Toggle you first need a configuration file. This file 53 | will typically be in YAML format and can contain inline ERB. The file's content 54 | will include all the different configuration sections you want to be able to toggle to. 55 | Each section should be namespaced appropriately. 56 | 57 | As an example let's say we have two different script configurations we'd like to 58 | have available and we name each `alpha` and `beta` respectively. Then our 59 | configuration file might look like: 60 | 61 | ```yaml 62 | # Sample config.yml demonstrating Toggle usage. 63 | # Notice that each section contains the same keys but the values vary. 64 | :alpha: 65 | :name: mr_alpha 66 | :secret: <%= ENV['ALPHA_SECRET'] %> # pretend this is "alpha-secret" 67 | 68 | :beta: 69 | :name: mr_beta 70 | :secret: <%= ENV['BETA_SECRET'] %> # pretend this is "beta-secret" 71 | ``` 72 | 73 | Now in any script we can leverage Toggle to toggle between each configuration 74 | section by setting the `key` attribute, which will load the corresponding 75 | configuration section: 76 | 77 | ```ruby 78 | require 'toggle' 79 | toggle = Toggle.new config_filepath: './config.yml' 80 | 81 | toggle.key = :alpha 82 | puts "#{toggle[:name]} has a secret: #{toggle[:secret]}!" 83 | #=> mr_alpha has a secret: alpha-secret 84 | 85 | toggle.key = :beta 86 | puts "#{toggle[:name]} has a secret: #{toggle[:secret]}!" 87 | #=> mr_beta has a secret: beta-secret 88 | ``` 89 | 90 | Toggle also supports temporary access to a configuration section by passing a 91 | block to `#using`: 92 | 93 | ```ruby 94 | require 'toggle' 95 | 96 | toggle = Toggle.new( 97 | config_filepath: './config.yml', 98 | key: :beta 99 | ) 100 | 101 | toggle.using(:alpha) do |s| 102 | puts "#{s[:name]} has a secret: #{s[:secret]}!" 103 | #=> mr_alpha has a secret: alpha-secret 104 | end 105 | 106 | puts "#{toggle[:name]} has a secret: #{toggle[:secret]}!" 107 | #=> mr_beta has a secret: beta-secret 108 | ``` 109 | 110 | As an alternative to specifying the `key` attribute programmatically, you can 111 | create a key file: 112 | 113 | ```yaml 114 | # sample key.yml file 115 | alpha 116 | ``` 117 | 118 | and then set Toggle's `key_filepath` attribute to specify where the `key`'s 119 | value should be derived from: 120 | 121 | ```ruby 122 | require 'toggle' 123 | 124 | toggle = Toggle.new( 125 | config_filepath: './config.yml', 126 | key_filepath: './key.yml' 127 | ) 128 | 129 | puts "#{toggle[:name]} has a secret: #{toggle[:secret]}!" 130 | #=> mr_alpha has a secret: alpha-secret 131 | ``` 132 | 133 | ## Realworld Use Case: Runtime Toggling 134 | 135 | Let's say there is a developer named Jane and she wants to author a script that 136 | connects to a database server, pulls in data and does some processing, and then 137 | emails the results to her team. 138 | 139 | As she developes the script she wants to pull data from a staging database and 140 | just email the results to herself so she can see how the final product would 141 | look without bothering the whole team until the finished product is ready. 142 | 143 | Once everything is complete she wants to pull data from a production database 144 | and send the email. 145 | 146 | With Toggle, this is easy: 147 | 148 | ```yaml 149 | # Jane's sample config.yml 150 | :development: 151 | :who_to_email: 'jane@company.com' 152 | 153 | :database: 154 | :host: https://staging.data.company.com 155 | :name: some_staging_db 156 | :table: some_staging_table 157 | :username: jane 158 | :password: <%= ENV['DATABASE_PASSWORD'] %> 159 | 160 | :production: 161 | :who_to_email: 'team@company.com' 162 | 163 | :database: 164 | :host: https://prod.data.company.com 165 | :name: some_prod_db 166 | :table: some_prod_table 167 | :username: jane 168 | :password: <%= ENV['DATABASE_PASSWORD'] %> 169 | ``` 170 | 171 | ```ruby 172 | # Jane's sample email_data.rb script 173 | require 'toggle' 174 | 175 | toggle = Toggle.new config_filepath: './config.yml', 176 | key: ENV['key'] 177 | 178 | connection = SomeDBDriver.connect( 179 | host: toggle[:database][:host], 180 | username: toggle[:database][:username], 181 | password: toggle[:database][:password] 182 | ) 183 | 184 | data = connection.get_data_from( 185 | database: toggle[:database][:name], 186 | table: toggle[:database][:table] 187 | ) 188 | 189 | SomeEmailer.send( 190 | to: toggle[:who_to_email], 191 | what: data 192 | ) 193 | ``` 194 | 195 | Now running `email_data.rb` under the development configuration settings is a 196 | snap: 197 | 198 | $ key=development ruby email_data.rb 199 | # => will connect to the staging db + just email jane 200 | 201 | And when it's deemed ready for primetime it can be run with the production 202 | configuration settings via: 203 | 204 | $ key=production ruby email_data.rb 205 | # => will connect to the prod db + email the team 206 | 207 | ## Realworld Use Case: Abstracted Configuration and Sharing 208 | 209 | Continuing with our example from above, let's say that Jane needs to share the 210 | script with John who is another developer on her team so he can work on it 211 | (perhaps he wants to add in logic that does not send an email if no data is 212 | returned so the team doesn't receive an empty email). 213 | 214 | Jane can further abstract her `config.yml` file to faciliate quick sharing 215 | between co-workers: 216 | 217 | ```yaml 218 | # Jane's new sample config.yml 219 | # 220 | # Notice that we have abstracted out the email address, database username and 221 | # password into ENV vars 222 | :development: 223 | :who_to_email: <%= ENV['USER_EMAIL'] %> 224 | 225 | :database: 226 | :host: https://staging.data.company.com 227 | :name: some_staging_db 228 | :table: some_staging_table 229 | :username: <%= ENV['DATABASE_USERNAME'] %> 230 | :password: <%= ENV['DATABASE_PASSWORD'] %> 231 | 232 | :production: 233 | :who_to_email: 'team@company.com' 234 | 235 | :database: 236 | :host: https://prod.data.company.com 237 | :name: some_prod_db 238 | :table: some_prod_table 239 | :username: <%= ENV['DATABASE_USERNAME'] %> 240 | :password: <%= ENV['DATABASE_PASSWORD'] %> 241 | ``` 242 | 243 | John is a `git clone` (or whatever vcs he is using) away from having the 244 | script downloaded locally and ready to run without requiring any configuration 245 | edits. 246 | 247 | In fact, anyone that has `DATABASE_USERNAME`, `DATABASE_PASSWORD` 248 | and `USER_EMAIL` set in their environment can run this script without requiring 249 | any configuration adjustments. 250 | 251 | In general if your team uses any common variables you should consider 252 | abstracting each into environment variables and including them via ERB. Toggle 253 | comes with an easy way to set this up on a per-computer basis. First, run: 254 | 255 | $ toggle --init-local 256 | 257 | This will create `~/.toggle.local`, which you can then edit to `export` any 258 | variables you want to be available in your environment. Finally, make sure 259 | you source this file so your variables are ready to go. 260 | 261 | ## Ignoring the Config and Key Files 262 | 263 | If you can effectively abstract out all configuration settings in environment 264 | variables, you may be able to just commit your `config.yml` and your `key.yml` 265 | files to source control. 266 | 267 | However, consider .gitignore-ing each and providing a `config.yml.default` and 268 | key.yml.default` in their place. With these default files in place you provide 269 | runtime guidance, but allow each developer to make any local adjustments without 270 | running the risk of having these changes committed back to the project's repo 271 | and breaking someone else's settings when they pull in the latest changes. 272 | 273 | Again, borrowing from the above example, if Jane were to instead provide 274 | `config.yml.default` and `key.yml.default` files in her repo, anyone that 275 | downloaded her repo would need to copy each file to their appropriate location 276 | (`config.yml` and `key.yml` respectively) so the script could run. This can be 277 | easily accomplished via: 278 | 279 | $ toggle --copy-defaults project/path 280 | 281 | or you can do this manually via: 282 | 283 | $ cp project/path/config.yml.default project/project/config.yml 284 | $ cp project/path/key.yml.default project/project/key.yml 285 | 286 | ## Toggle CLI 287 | 288 | Toggle comes bundled with a commandline interface: 289 | 290 | ``` 291 | $ toggle --help 292 | Usage: toggle 293 | 294 | Specific arguments: 295 | -g, --init-local [PATH] Adds [PATH]/.toggle.local with var placeholders. Default is $HOME. 296 | -k, --keys file Show available keys for the specified config FILE 297 | --values FILE,KEY Show values for the KEY in the config FILE 298 | --copy-config-defaults [PATH] 299 | Copy all toggle config defaults to actuals in PATH. Default is pwd. 300 | -c, --copy-defaults [PATH] Copy all .toggle.default to .toggle for PATH. Default is pwd. 301 | --ensure-key [PATH] Copies the default key in [PATH] if actual key is not present, does nothing otherwise. Default [PATH] is pwd. 302 | -m, --make-defaults [PATH] Create [PATH]/{config|key}{,.*}.default. Default is pwd. 303 | -v, --version Show version 304 | -h, --help Show this message 305 | ``` 306 | 307 | ## Contributing 308 | 309 | If you would like to contribute code to Toggle you can do so through GitHub by 310 | forking the repository and sending a pull request. 311 | 312 | When submitting code, please make every effort to follow existing conventions 313 | and style in order to keep the code as readable as possible. 314 | 315 | Before your code can be accepted into the project you must also sign the 316 | [Individual Contributor License Agreement (CLA)][1]. 317 | 318 | 319 | [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 320 | 321 | ## License 322 | 323 | Copyright 2013 Square Inc. 324 | 325 | Licensed under the Apache License, Version 2.0 (the "License"); 326 | you may not use this file except in compliance with the License. 327 | You may obtain a copy of the License at 328 | 329 | http://www.apache.org/licenses/LICENSE-2.0 330 | 331 | Unless required by applicable law or agreed to in writing, software 332 | distributed under the License is distributed on an "AS IS" BASIS, 333 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 334 | See the License for the specific language governing permissions and 335 | limitations under the License. 336 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | begin 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) do |t| 7 | t.rspec_opts = '-b' 8 | end 9 | 10 | task default: :spec 11 | rescue LoadError 12 | $stderr.puts "rspec not available, spec task not provided" 13 | end 14 | 15 | begin 16 | require 'cane/rake_task' 17 | 18 | desc "Run cane to check quality metrics" 19 | Cane::RakeTask.new(:quality) do |cane| 20 | cane.abc_max = 10 21 | cane.style_glob = "lib/**/*.rb" 22 | cane.no_doc = true 23 | end 24 | 25 | task :default => :quality 26 | rescue LoadError 27 | warn "cane not available, quality task not provided." 28 | end 29 | -------------------------------------------------------------------------------- /bin/toggle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 4 | 5 | require 'toggle' 6 | require 'optparse' 7 | require 'fileutils' 8 | 9 | def default_key_filepath 10 | File.expand_path('../../defaults/key.yml.default', __FILE__) 11 | end 12 | 13 | def default_config_filepath 14 | File.expand_path('../../defaults/config.yml.default', __FILE__) 15 | end 16 | 17 | def wants_to_force_copy? current_file 18 | respond_yes_to? "File #{current_file} exists. Replace?" 19 | end 20 | 21 | def actual_key_in path 22 | find_file_in path, /^(key(\.yml))?$/ 23 | end 24 | 25 | def actual_config_in path 26 | find_file_in path, /^config\.yml$/ 27 | end 28 | 29 | # finds the first file in "path" arg that matches "regex" pattern 30 | # returns path + filename 31 | def find_file_in path, regex 32 | file = Dir[File.join(path, '*')].map { |filepath| 33 | File.basename filepath 34 | }.find { |filename| 35 | filename =~ regex 36 | } 37 | 38 | file ? File.join(path, file) : nil 39 | end 40 | 41 | # Returns true if response starts with a 'y' or 'Y' (as in 'yes') 42 | # Returns false if response starts with a 'n' or 'N' (as in 'no') 43 | # Aborts if response starts with a 'q' or 'Q' (as in 'quit') 44 | def respond_yes_to? prompt 45 | print "#{prompt} (y/n/q) " 46 | normalized_response = gets[0].chomp.downcase 47 | normalized_response.eql?('q') ? abort('... quitting') : normalized_response.eql?('y') 48 | end 49 | 50 | # The reference file is guaranteed to exist 51 | def identical_files? reference_file, other_file 52 | File.exists?(other_file) && FileUtils.identical?(reference_file, other_file) 53 | end 54 | 55 | # Method requires a path and file_pattern and recursively searchs for matching 56 | # files. 57 | # 58 | # When a match is found, method detects if the default is the same as the 59 | # "actual" (the same filename just without the .default). 60 | # 61 | # If the files are identical, the method does nothing. If the actual file does 62 | # not exist OR the user opts to clobber the existing, the default will replace 63 | # the actual. Otherwise, the actual is left as is. 64 | def recursively_copy_defaults path, file_pattern, options = {} 65 | defaults = {attempt_force_copy: true} 66 | options = defaults.merge(options) 67 | 68 | attempt_force_copy = options[:attempt_force_copy] 69 | 70 | Dir[File.join(path, '/**/', file_pattern)].each do |default_file| 71 | file = default_file.slice(/(.*)\.default$/, 1) 72 | 73 | if identical_files? default_file, file 74 | puts "Default is identical to #{file}, skipping!" 75 | elsif !File.exists?(file) || (attempt_force_copy && wants_to_force_copy?(file)) 76 | puts "Copying #{file} from default" 77 | FileUtils.cp default_file, file 78 | else 79 | puts "Not changing #{file}" 80 | end 81 | end 82 | end 83 | 84 | opt_parser = OptionParser.new do |opts| 85 | opts.banner = "Usage: toggle " 86 | opts.separator "" 87 | opts.separator "Specific arguments:" 88 | 89 | opts.on('-g', '--init-local [PATH]', String, 'Adds [PATH]/.toggle.local with var placeholders. Default is $HOME.') do |path| 90 | path ||= ENV['HOME'] 91 | 92 | if path && path.empty? 93 | raise RuntimeError, 'You must specify a PATH or have HOME env set!' 94 | end 95 | 96 | local_config = File.join(path, '.toggle.local') 97 | sourcing_bash_instructions = "if [ -s ~/.toggle.local ] ; then source ~/.toggle.local ; fi" 98 | 99 | if !File.exists?(local_config) || wants_to_force_copy?(local_config) 100 | content = <<-EOS.strip_heredoc 101 | # Add any variables that you'd like below. 102 | # 103 | # We've included a few suggestions, but please feel free 104 | # to modify as needed. 105 | # 106 | # Make sure that you source this file in your ~/.bash_profile 107 | # or ~/.bashrc (or whereever you'd like) via: 108 | # 109 | # #{sourcing_bash_instructions} 110 | export DATABASE_HOST='' 111 | export DATABASE_NAME='' 112 | export DATABASE_USERNAME='' 113 | export DATABASE_PASSWORD='' 114 | export USER_EMAIL='' 115 | EOS 116 | 117 | %x(echo "#{content}" > #{local_config}) 118 | puts "Local toggle config added at #{local_config}" 119 | puts "Now edit the file and source it from ~/.bash_profile or ~/.bashrc via: #{sourcing_bash_instructions}" 120 | else 121 | puts "Not changing #{local_config}" 122 | end 123 | end 124 | 125 | opts.on("-k", "--keys file", String, "Show available keys for the specified config FILE") do |file| 126 | opts.banner = "Usage: toggle --keys FILE" 127 | 128 | if File.exists? file 129 | toggle = Toggle::Compiler.new(file).parsed_content 130 | puts toggle.keys.map{|k| "- #{k}"}.join("\n") 131 | else 132 | puts "toggle config file not found, please check specified path" 133 | end 134 | end 135 | 136 | # TODO: remove yaml preamble 137 | opts.on("--values FILE,KEY", Array, "Show values for the KEY in the config FILE") do |params| 138 | opts.banner = "Usage: toggle --values FILE,KEY" 139 | 140 | if File.exists?(file = params[0]) 141 | toggle = Toggle::Compiler.new(file).parsed_content 142 | if toggle.keys.include?(key = params[1].to_sym) 143 | puts toggle[key].to_yaml.gsub(/(:password:).+/, "\\1 [redacted]") 144 | else 145 | puts "#{key} not found in #{file}" 146 | end 147 | else 148 | puts "toggle config file not found, please check specified path" 149 | end 150 | end 151 | 152 | opts.on('--copy-config-defaults [PATH]', String, 'Copy all toggle config defaults to actuals in PATH. Default is pwd.') do |path| 153 | path ||= Dir.pwd 154 | recursively_copy_defaults(path, 'config{,.*}.default') 155 | end 156 | 157 | opts.on('-c', '--copy-defaults [PATH]', String, 'Copy all .toggle.default to .toggle for PATH. Default is pwd.') do |path| 158 | path ||= Dir.pwd 159 | recursively_copy_defaults(path, '{key,config}{,.*}.default') 160 | end 161 | 162 | opts.on('--ensure-key [PATH]', String, 'Copies the default key in [PATH] if actual key is not present, does nothing otherwise. Default [PATH] is pwd.') do |path| 163 | path ||= Dir.pwd 164 | recursively_copy_defaults(path, 'key{,.*}.default', attempt_force_copy: false) 165 | end 166 | 167 | opts.on('-m', '--make-defaults [PATH]', String, 'Create [PATH]/{config|key}{,.*}.default. Default is pwd.') do |path| 168 | path ||= Dir.pwd 169 | 170 | default_key = actual_key_in(path) || default_key_filepath 171 | default_config = actual_config_in(path) || default_config_filepath 172 | 173 | key_destination = File.join(path, 'key.yml.default') 174 | config_destination = File.join(path, 'config.yml.default') 175 | 176 | if !File.exists?(key_destination) || wants_to_force_copy?(key_destination) 177 | FileUtils.cp(default_key, key_destination) unless identical_files?(default_key, key_destination) 178 | puts "Default key written to #{key_destination}" 179 | puts "Now go edit it!" 180 | end 181 | 182 | if !File.exists?(config_destination) || wants_to_force_copy?(config_destination) 183 | FileUtils.cp(default_config, config_destination) unless identical_files?(default_config, key_destination) 184 | puts "Default config written to #{config_destination}" 185 | puts "Now go edit it!" 186 | end 187 | end 188 | 189 | opts.on_tail("-v", "--version", "Show version") do 190 | puts "Toggle version #{Toggle::VERSION}" 191 | exit 192 | end 193 | 194 | opts.on_tail("-h", "--help", "Show this message") do 195 | puts opts 196 | exit 197 | end 198 | end 199 | 200 | opt_parser.parse! 201 | -------------------------------------------------------------------------------- /defaults/config.yml.default: -------------------------------------------------------------------------------- 1 | # Copy this file to ./config.yml or run the following command: 2 | # 3 | # $ toggle --copy-defaults [PATH] 4 | # 5 | # which will copy {config,key}{,.*}.default files in the given PATH to 6 | # {config,key}{,.*} (removes .default extension) 7 | # 8 | # We're using the configuration conventions & setup: 9 | # https://git.squareup.com/iacono/toggle#configuration-conventions--setup 10 | # 11 | # If you've set up your local variables, you should be able to copy and go! 12 | # 13 | # Otherwise, run: 14 | # 15 | # $ toggle --init-local 16 | # 17 | # And follow the instructions 18 | :development: 19 | :some: :development_setting 20 | 21 | :production: 22 | :some: :production_setting 23 | # define any other config blocks that you want! 24 | -------------------------------------------------------------------------------- /defaults/key.yml.default: -------------------------------------------------------------------------------- 1 | # Copy this file to ./key{.exts} or run the following command: 2 | # 3 | # $ toggle --copy-defaults [PATH] 4 | # 5 | # which will copy {config,key}{.exts}.default files in the given PATH to 6 | # {config,key}{exts} (removes .default extension) 7 | # 8 | # You can toggle this file to use a particular config block you have 9 | # set up. To view which top level blocks are available for a given key file, 10 | # run: 11 | # 12 | # $ toggle --keys FILENAME 13 | # 14 | # where FILENAME is the name of a given toggle config file. 15 | development # <= change "development" to whatever you'd like 16 | -------------------------------------------------------------------------------- /lib/toggle.rb: -------------------------------------------------------------------------------- 1 | require 'toggle/version' 2 | require 'toggle/compiler' 3 | require 'toggle/parser' 4 | require 'active_support/core_ext/hash/indifferent_access' 5 | require 'active_support/core_ext/string' 6 | 7 | class Toggle 8 | attr_writer :key 9 | attr_accessor :key_parsers 10 | attr_accessor :key_filepath 11 | attr_accessor :config_parsers 12 | attr_accessor :config_filepath 13 | 14 | def initialize attributes = {} 15 | attributes.keys.each do |attribute| 16 | self.send "#{attribute}=", attributes[attribute] 17 | end 18 | end 19 | 20 | def [] attribute 21 | config[attribute] 22 | end 23 | 24 | def config_filepath= value 25 | configs_dirty! unless @config_filepath.eql? value 26 | @config_filepath = value 27 | end 28 | 29 | def key 30 | (@key || key_from_file).try(:to_sym) 31 | end 32 | 33 | def config 34 | configs[key] 35 | end 36 | 37 | def configs 38 | return @configs if defined?(@configs) && configs_clean? 39 | 40 | @configs = HashWithIndifferentAccess.new( 41 | Toggle::Compiler.new( 42 | config_filepath, 43 | config_parsers 44 | ).parsed_content 45 | ) 46 | configs_clean! 47 | @configs 48 | end 49 | 50 | def using temporary_key 51 | return unless block_given? 52 | 53 | previous_key, self.key = self.key, temporary_key 54 | yield self 55 | self.key = previous_key 56 | end 57 | 58 | private 59 | 60 | def key_from_file 61 | Toggle::Compiler.new( 62 | key_filepath, 63 | key_parsers 64 | ).parsed_content if key_filepath 65 | end 66 | 67 | def configs_clean? 68 | @configs_clean 69 | end 70 | 71 | def configs_dirty! 72 | @configs_clean = false 73 | end 74 | 75 | def configs_clean! 76 | @configs_clean = true 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/toggle/compiler.rb: -------------------------------------------------------------------------------- 1 | class Toggle 2 | class Compiler 3 | class FileNotFound < RuntimeError; end 4 | 5 | def initialize file, parsers = nil 6 | @file = file 7 | @parsers = parsers ? [*parsers] : file_extensions 8 | end 9 | 10 | def parsed_content 11 | @parsers.reduce(raw_file_content) do |content, parser| 12 | Toggle::Parser.for(parser).parse content if content 13 | end 14 | end 15 | 16 | private 17 | 18 | def file_extensions 19 | parts = File.basename(@file).split(".") 20 | parts[1, parts.length - 1] 21 | end 22 | 23 | def raw_file_content 24 | begin 25 | File.read(@file).chomp 26 | rescue ::Errno::ENOENT => e 27 | raise FileNotFound.new e.message 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/toggle/parser.rb: -------------------------------------------------------------------------------- 1 | class Toggle 2 | module Parser 3 | class ParserNotFound < StandardError; end 4 | 5 | def self.for type 6 | case type.to_s.downcase 7 | when 'yaml', 'yml' 8 | require 'toggle/parser/yaml' 9 | ::Toggle::Parser::YAML.new 10 | else 11 | raise ParserNotFound, <<-EOS 12 | #{type} is not currently implemented. You should write it! 13 | EOS 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/toggle/parser/yaml.rb: -------------------------------------------------------------------------------- 1 | # YAML Parser handles erb parsing first, then loads the content to yaml 2 | class Toggle 3 | module Parser 4 | class YAML 5 | def parse content 6 | require 'erb' 7 | require 'yaml' 8 | ::YAML.load(::ERB.new(content).result.chomp) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/toggle/version.rb: -------------------------------------------------------------------------------- 1 | class Toggle 2 | VERSION = "1.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /script/ci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bundler_version="1.1.4" 4 | 5 | source "$HOME/.rvm/scripts/rvm" 6 | 7 | function install_ruby_if_needed() { 8 | echo "Checking for $1..." 9 | if ! rvm list rubies | grep $1 > /dev/null; then 10 | echo "Ruby not found $1..." 11 | rvm install $1 12 | fi 13 | } 14 | 15 | function switch_ruby() { 16 | install_ruby_if_needed $1 && rvm use $1 17 | } 18 | 19 | function install_bundler_if_needed() { 20 | echo "Checking for Bundler $bundler_version..." 21 | if ! gem list --installed bundler --version "$bundler_version" > /dev/null; then 22 | gem install bundler --version "$bundler_version" 23 | fi 24 | } 25 | 26 | function update_gems_if_needed() { 27 | echo "Installing gems..." 28 | bundle check || bundle install 29 | } 30 | 31 | function run_tests() { 32 | bundle exec rake 33 | } 34 | 35 | function prepare_and_run() { 36 | switch_ruby $1 && 37 | install_bundler_if_needed && 38 | update_gems_if_needed && 39 | run_tests 40 | } 41 | 42 | prepare_and_run "1.9.3-p194" 43 | -------------------------------------------------------------------------------- /spec/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fileutils' 3 | 4 | def clear_toggle_files_from path 5 | Dir.glob(File.join(path, '*'), File::FNM_DOTMATCH).each do |file| 6 | FileUtils.rm_f file if file =~ /((config|key)(\..*)?$|\.toggle\.local)/ 7 | end 8 | end 9 | 10 | describe "CLI" do 11 | let(:test_dir) { File.join(File.expand_path('..', __FILE__), 'test', 'fixtures', 'cli') } 12 | 13 | describe "--ensure-key" do 14 | let(:default_key_file) { File.join(test_dir, 'key.yml.default') } 15 | let(:key_file) { File.join(test_dir, 'key.yml') } 16 | let(:content) { 'some_key' } 17 | 18 | before :all do 19 | clear_toggle_files_from test_dir 20 | end 21 | 22 | before :each do 23 | %x(echo "#{content}" > #{default_key_file}) 24 | end 25 | 26 | after :each do 27 | clear_toggle_files_from test_dir 28 | end 29 | 30 | describe "when key does not exist" do 31 | it "copies the default key to the actual" do 32 | %x(./bin/toggle --ensure-key #{test_dir}) 33 | FileUtils.identical?(key_file, default_key_file).should == true 34 | end 35 | end 36 | 37 | describe "when key already exists" do 38 | let(:key_content) { 'existing_key' } 39 | 40 | before do 41 | %x(echo "#{key_content}" > #{key_file}) 42 | end 43 | 44 | it "leaves the current key alone" do 45 | %x(./bin/toggle --ensure-key #{test_dir}) 46 | File.read(key_file).chomp.should == key_content 47 | end 48 | end 49 | end 50 | 51 | describe "--copy-config-defaults" do 52 | let(:default_config_file) { File.join(test_dir, 'config.yml.default') } 53 | let(:actual_config_file) { File.join(test_dir, 'config.yml') } 54 | let(:default_config_content) { 'DEFAULT CONFIG CONTENT' } 55 | 56 | before :all do 57 | clear_toggle_files_from test_dir 58 | end 59 | 60 | before :each do 61 | %x(echo "#{default_config_content}" > #{default_config_file}) 62 | end 63 | 64 | after :each do 65 | clear_toggle_files_from test_dir 66 | end 67 | 68 | it "does not copy the key default" do 69 | default_key_file = File.join(test_dir, 'key.yml.default') 70 | actual_key_file = File.join(test_dir, 'key.yml') 71 | default_key_content = 'some_key' 72 | 73 | %x(echo "#{default_key_content}" > #{default_key_file}) 74 | %x(./bin/toggle --copy-config-defaults #{test_dir}) 75 | File.exists?(actual_key_file).should == false 76 | end 77 | 78 | describe "no config exists" do 79 | it "copies the default config to the actual" do 80 | %x(./bin/toggle --copy-config-defaults #{test_dir}) 81 | FileUtils.identical?(actual_config_file, default_config_file).should == true 82 | end 83 | end 84 | 85 | describe "config is identical to default" do 86 | before do 87 | %x(cp #{default_config_file} #{actual_config_file}) 88 | end 89 | 90 | it "leaves current config unchanged" do 91 | %x(./bin/toggle --copy-config-defaults #{test_dir}) 92 | FileUtils.identical?(actual_config_file, default_config_file).should == true 93 | end 94 | end 95 | 96 | describe "actual is present and different from default" do 97 | let(:different_content) { "#{default_config_content} BUT DIFFERENT" } 98 | 99 | before do 100 | %x(echo "#{different_content}" > #{actual_config_file}) 101 | end 102 | 103 | it "leaves current config unchanged when user responds with anything but 'y' words" do 104 | %x(echo 'n' | ./bin/toggle --copy-config-defaults #{test_dir}) 105 | FileUtils.identical?(actual_config_file, default_config_file).should == false 106 | end 107 | 108 | it "replaces current config with default when user responds with 'y' words" do 109 | %x(echo 'y' | ./bin/toggle --copy-config-defaults #{test_dir}) 110 | FileUtils.identical?(actual_config_file, default_config_file).should == true 111 | File.read(actual_config_file).chomp.should == default_config_content 112 | end 113 | end 114 | end 115 | 116 | describe "--copy-defaults" do 117 | let(:config_default_file) { File.join(test_dir, 'config.yml.default') } 118 | let(:config_actual_file) { File.join(test_dir, 'config.yml') } 119 | let(:config_default_content) { 'DEFAULT CONTENT' } 120 | let(:key_default_file) { File.join(test_dir, 'key.yml.default') } 121 | let(:key_actual_file) { File.join(test_dir, 'key.yml') } 122 | let(:key_default_content) { 'DEFAULT CONTENT' } 123 | 124 | before :all do 125 | clear_toggle_files_from test_dir 126 | end 127 | 128 | before :each do 129 | %x(echo "#{config_default_content}" > #{config_default_file}) 130 | %x(echo "#{key_default_content}" > #{key_default_file}) 131 | end 132 | 133 | after :each do 134 | clear_toggle_files_from test_dir 135 | end 136 | 137 | describe "copies default config and key file" do 138 | it "copies each default file over to its appropriate location" do 139 | %x(./bin/toggle --copy-defaults #{test_dir}) 140 | FileUtils.identical?(config_default_file, config_actual_file).should == true 141 | FileUtils.identical?(key_default_file, key_actual_file).should == true 142 | end 143 | end 144 | 145 | describe "actual is identical to default" do 146 | before do 147 | %x(cp #{config_default_file} #{config_actual_file}) 148 | end 149 | 150 | it "leaves current file unchanged" do 151 | %x(./bin/toggle --copy-defaults #{test_dir}) 152 | FileUtils.identical?(config_default_file, config_actual_file).should == true 153 | end 154 | end 155 | 156 | describe "actual is present but has different content from default" do 157 | let(:different_content) { "#{config_default_content} BUT DIFFERENT" } 158 | 159 | before do 160 | %x(echo "#{different_content}" > #{config_actual_file}) 161 | end 162 | 163 | it "leaves current file unchanged when user responds with anything but 'y' words" do 164 | %x(echo 'n' | ./bin/toggle --copy-defaults #{test_dir}) 165 | FileUtils.identical?(config_default_file, config_actual_file).should == false 166 | File.read(config_actual_file).chomp.should == different_content 167 | end 168 | 169 | it "replaces current file with default when user responds with 'y' words" do 170 | %x(echo 'y' | ./bin/toggle --copy-defaults #{test_dir}) 171 | FileUtils.identical?(config_default_file, config_actual_file).should == true 172 | File.read(config_actual_file).chomp.should == config_default_content 173 | end 174 | end 175 | end 176 | 177 | describe "--keys" do 178 | let(:config_file) { File.join(FIXTURES_PATH, 'config.yml') } 179 | 180 | it "alerts the user if the file is not found" do 181 | %x(./bin/toggle --keys /path/to/nothing).chomp.should == "toggle config file not found, please check specified path" 182 | end 183 | 184 | it "can be queried for the available keys from the commandline" do 185 | %x(./bin/toggle --keys #{config_file}).chomp.should == "- local\n- remote" 186 | end 187 | end 188 | 189 | describe "--values" do 190 | let(:config_file) { File.join(FIXTURES_PATH, 'config.yml') } 191 | 192 | it "alerts the user if the file is not found" do 193 | %x(./bin/toggle --values /path/to/nothing).chomp.should == "toggle config file not found, please check specified path" 194 | end 195 | 196 | it "can be queried for the available keys from the commandline" do 197 | %x(./bin/toggle --values #{config_file},local).should == <<-EOS.strip_heredoc 198 | --- 199 | :plain_attribute: local_plain_attribute_value 200 | :erb_attribute: local_erb_attribute_value 201 | EOS 202 | end 203 | end 204 | 205 | describe "--init-local" do 206 | let(:file) { File.join(test_dir, '.toggle.local') } 207 | 208 | before :all do 209 | clear_toggle_files_from test_dir 210 | end 211 | 212 | after :each do 213 | clear_toggle_files_from test_dir 214 | end 215 | 216 | describe "file does not exist" do 217 | it "adds .toggle.local with commons var placeholders" do 218 | %x(./bin/toggle --init-local #{test_dir}) 219 | File.read(file).chomp.should == <<-EOS.strip_heredoc 220 | # Add any variables that you'd like below. 221 | # 222 | # We've included a few suggestions, but please feel free 223 | # to modify as needed. 224 | # 225 | # Make sure that you source this file in your ~/.bash_profile 226 | # or ~/.bashrc (or whereever you'd like) via: 227 | # 228 | # if [ -s ~/.toggle.local ] ; then source ~/.toggle.local ; fi 229 | export DATABASE_HOST='' 230 | export DATABASE_NAME='' 231 | export DATABASE_USERNAME='' 232 | export DATABASE_PASSWORD='' 233 | export USER_EMAIL='' 234 | EOS 235 | end 236 | end 237 | 238 | describe "file already exists" do 239 | before do 240 | %x(echo "SOME CONTENT" > #{file}) 241 | end 242 | 243 | it "leaves .toggle.local unchanged when user responds with anything but 'y' words" do 244 | %x(echo 'n' | ./bin/toggle --init-local #{test_dir}) 245 | File.read(file).chomp.should == 'SOME CONTENT' 246 | end 247 | 248 | it "replaces .toggle.local with default when user responds with 'y' words" do 249 | %x(echo 'y' | ./bin/toggle --init-local #{test_dir}) 250 | File.read(file).chomp.should == <<-EOS.strip_heredoc 251 | # Add any variables that you'd like below. 252 | # 253 | # We've included a few suggestions, but please feel free 254 | # to modify as needed. 255 | # 256 | # Make sure that you source this file in your ~/.bash_profile 257 | # or ~/.bashrc (or whereever you'd like) via: 258 | # 259 | # if [ -s ~/.toggle.local ] ; then source ~/.toggle.local ; fi 260 | export DATABASE_HOST='' 261 | export DATABASE_NAME='' 262 | export DATABASE_USERNAME='' 263 | export DATABASE_PASSWORD='' 264 | export USER_EMAIL='' 265 | EOS 266 | end 267 | end 268 | end 269 | 270 | describe "--make-defaults" do 271 | let(:actual_key_file) { File.join(test_dir, 'key.yml') } 272 | let(:default_key_file) { File.join(test_dir, 'key.yml.default') } 273 | let(:actual_config_file) { File.join(test_dir, 'config.yml') } 274 | let(:default_config_file) { File.join(test_dir, 'config.yml.default') } 275 | 276 | before :all do 277 | clear_toggle_files_from test_dir 278 | end 279 | 280 | after :each do 281 | clear_toggle_files_from test_dir 282 | end 283 | 284 | describe "when user has not created any actual or .default files" do 285 | it "creates a default key + config file in the passed path" do 286 | %x(./bin/toggle --make-defaults #{test_dir}) 287 | File.read(default_key_file).should == <<-EOS.strip_heredoc 288 | # Copy this file to ./key{.exts} or run the following command: 289 | # 290 | # $ toggle --copy-defaults [PATH] 291 | # 292 | # which will copy {config,key}{.exts}.default files in the given PATH to 293 | # {config,key}{exts} (removes .default extension) 294 | # 295 | # You can toggle this file to use a particular config block you have 296 | # set up. To view which top level blocks are available for a given key file, 297 | # run: 298 | # 299 | # $ toggle --keys FILENAME 300 | # 301 | # where FILENAME is the name of a given toggle config file. 302 | development # <= change "development" to whatever you'd like 303 | EOS 304 | 305 | File.read(default_config_file).should == <<-EOS.strip_heredoc 306 | # Copy this file to ./config.yml or run the following command: 307 | # 308 | # $ toggle --copy-defaults [PATH] 309 | # 310 | # which will copy {config,key}{,.*}.default files in the given PATH to 311 | # {config,key}{,.*} (removes .default extension) 312 | # 313 | # We're using the configuration conventions & setup: 314 | # https://git.squareup.com/iacono/toggle#configuration-conventions--setup 315 | # 316 | # If you've set up your local variables, you should be able to copy and go! 317 | # 318 | # Otherwise, run: 319 | # 320 | # $ toggle --init-local 321 | # 322 | # And follow the instructions 323 | :development: 324 | :some: :development_setting 325 | 326 | :production: 327 | :some: :production_setting 328 | # define any other config blocks that you want! 329 | EOS 330 | end 331 | end 332 | 333 | describe "when user has created an actual key file but the corresponding .default file does not exist" do 334 | before do 335 | %x(echo "SOME CONTENT" > #{actual_key_file}) 336 | end 337 | 338 | it "copies the actual user created key file to the corresponding .default" do 339 | %x(./bin/toggle --make-defaults #{test_dir}) 340 | File.read(default_key_file).should == "SOME CONTENT\n" 341 | end 342 | end 343 | 344 | describe "when user has created an actual config file but the corresponding .default file does not exist" do 345 | before do 346 | %x(echo "SOME CONTENT" > #{actual_config_file}) 347 | end 348 | 349 | it "copies the actual user created file to the corresponding .default" do 350 | %x(./bin/toggle --make-defaults #{test_dir}) 351 | File.read(default_config_file).should == "SOME CONTENT\n" 352 | end 353 | end 354 | 355 | describe "when user has created .default key file" do 356 | before do 357 | %x(echo "SOME CONTENT" > #{default_key_file}) 358 | end 359 | 360 | it "leaves the .default file unchanged when user responds with anything but 'y' words" do 361 | %x(echo 'n' | ./bin/toggle --make-defaults #{test_dir}) 362 | File.read(default_key_file).should == "SOME CONTENT\n" 363 | end 364 | 365 | it "replaces the .default file with default when user responds with 'y' words" do 366 | %x(echo 'y' | ./bin/toggle --make-defaults #{test_dir}) 367 | File.read(default_key_file).should == <<-EOS.strip_heredoc 368 | # Copy this file to ./key{.exts} or run the following command: 369 | # 370 | # $ toggle --copy-defaults [PATH] 371 | # 372 | # which will copy {config,key}{.exts}.default files in the given PATH to 373 | # {config,key}{exts} (removes .default extension) 374 | # 375 | # You can toggle this file to use a particular config block you have 376 | # set up. To view which top level blocks are available for a given key file, 377 | # run: 378 | # 379 | # $ toggle --keys FILENAME 380 | # 381 | # where FILENAME is the name of a given toggle config file. 382 | development # <= change "development" to whatever you'd like 383 | EOS 384 | end 385 | end 386 | 387 | describe "when user has created .default config file" do 388 | before do 389 | %x(echo "SOME CONTENT" > #{default_config_file}) 390 | end 391 | 392 | it "leaves current config file unchanged when user responds with anything but 'y' words" do 393 | %x(echo 'n' | ./bin/toggle --make-defaults #{test_dir}) 394 | File.read(default_config_file).should == "SOME CONTENT\n" 395 | end 396 | 397 | it "replaces current config file with default when user responds with 'y' words" do 398 | %x(echo 'y' | ./bin/toggle --make-defaults #{test_dir}) 399 | File.read(default_config_file).should == <<-EOS.strip_heredoc 400 | # Copy this file to ./config.yml or run the following command: 401 | # 402 | # $ toggle --copy-defaults [PATH] 403 | # 404 | # which will copy {config,key}{,.*}.default files in the given PATH to 405 | # {config,key}{,.*} (removes .default extension) 406 | # 407 | # We're using the configuration conventions & setup: 408 | # https://git.squareup.com/iacono/toggle#configuration-conventions--setup 409 | # 410 | # If you've set up your local variables, you should be able to copy and go! 411 | # 412 | # Otherwise, run: 413 | # 414 | # $ toggle --init-local 415 | # 416 | # And follow the instructions 417 | :development: 418 | :some: :development_setting 419 | 420 | :production: 421 | :some: :production_setting 422 | # define any other config blocks that you want! 423 | EOS 424 | end 425 | end 426 | end 427 | end 428 | -------------------------------------------------------------------------------- /spec/parser/yaml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'toggle/parser/yaml' 3 | 4 | describe Toggle::Parser::YAML do 5 | describe "parse" do 6 | let(:parser) { Toggle::Parser::YAML.new } 7 | 8 | it "parses the passed content, converting erb tags" do 9 | parser.parse("Hello world <%= 40 + 2 %>").should == "Hello world 42" 10 | end 11 | 12 | it "parses the passed content, serializing as yaml" do 13 | parser.parse(":hello: world").should == {hello: 'world'} 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Toggle::Parser do 4 | describe "non-found parser" do 5 | it "raises a parser not found error" do 6 | lambda { 7 | Toggle::Parser.for 'nonexistent-parser' 8 | }.should raise_error Toggle::Parser::ParserNotFound 9 | end 10 | end 11 | 12 | describe "'yaml' parser" do 13 | it "returns an instance of the Toggle Yaml parser" do 14 | parser = Toggle::Parser.for 'yaml' 15 | parser.should be_an_instance_of Toggle::Parser::YAML 16 | end 17 | end 18 | 19 | describe "'yml' parser" do 20 | it "returns an instance of the Toggle Yaml parser" do 21 | parser = Toggle::Parser.for 'yml' 22 | parser.should be_an_instance_of Toggle::Parser::YAML 23 | end 24 | end 25 | 26 | describe ":yaml parser" do 27 | it "returns an instance of the Toggle Yaml parser" do 28 | parser = Toggle::Parser.for :yaml 29 | parser.should be_an_instance_of Toggle::Parser::YAML 30 | end 31 | end 32 | 33 | describe ":yml parser" do 34 | it "returns an instance of the Toggle Yaml parser" do 35 | parser = Toggle::Parser.for :yml 36 | parser.should be_an_instance_of Toggle::Parser::YAML 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'toggle' 2 | 3 | FIXTURES_PATH = File.join(File.dirname(__FILE__), 'test', 'fixtures') 4 | -------------------------------------------------------------------------------- /spec/test/fixtures/cli/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/square/toggle/63d4ed664e84cbebc6cfab35fc92d280ccd4f808/spec/test/fixtures/cli/.gitkeep -------------------------------------------------------------------------------- /spec/test/fixtures/config.yml: -------------------------------------------------------------------------------- 1 | <% @local_erb_attribute_value = 'local_erb_attribute_value' %> 2 | <% @remote_erb_attribute_value = 'remote_erb_attribute_value' %> 3 | :local: 4 | :plain_attribute: local_plain_attribute_value 5 | :erb_attribute: <%= @local_erb_attribute_value %> 6 | 7 | :remote: 8 | :plain_attribute: remote_plain_attribute_value 9 | :erb_attribute: <%= @remote_erb_attribute_value %> 10 | -------------------------------------------------------------------------------- /spec/test/fixtures/flat-key-local: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /spec/test/fixtures/key-local.yml: -------------------------------------------------------------------------------- 1 | # here are some comments that should not be parsed 2 | local 3 | -------------------------------------------------------------------------------- /spec/toggle_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Toggle do 4 | let(:config_filepath) { File.join(FIXTURES_PATH, 'config.yml') } 5 | 6 | describe "#configs" do 7 | let(:toggle) { described_class.new config_filepath: config_filepath } 8 | 9 | it "returns compiled content for the specified config file as a hash" do 10 | toggle.configs.should == { 11 | 'local' => { 12 | 'plain_attribute' => 'local_plain_attribute_value', 13 | 'erb_attribute' => 'local_erb_attribute_value' 14 | }, 15 | 'remote' => { 16 | 'plain_attribute' => 'remote_plain_attribute_value', 17 | 'erb_attribute' => 'remote_erb_attribute_value' 18 | } 19 | } 20 | end 21 | end 22 | 23 | describe "#key" do 24 | let(:toggle) { described_class.new } 25 | 26 | it "returns the specified attribute as a symbol" do 27 | toggle.key = 'something' 28 | toggle.key.should == :something 29 | end 30 | 31 | it "returns the flat value when file has no extension" do 32 | toggle.key_filepath = File.join(FIXTURES_PATH, 'flat-key-local') 33 | toggle.key.should == :local 34 | end 35 | 36 | it "returns the compiled key as a symbol from the key file if no key attribute is set" do 37 | toggle.key_filepath = File.join(FIXTURES_PATH, 'key-local.yml') 38 | toggle.key.should == :local 39 | end 40 | 41 | it "returns the set key attribute as a symbol over the compiled key file's key value" do 42 | toggle.key = 'something' 43 | toggle.key_filepath = File.join(FIXTURES_PATH, 'key-local.yml') 44 | toggle.key.should == :something 45 | end 46 | end 47 | 48 | describe "#config" do 49 | let(:toggle) { described_class.new } 50 | 51 | it "returns the config data that corresponds to #key" do 52 | toggle.config_filepath = config_filepath 53 | toggle.key = :local 54 | toggle.config.should == { 55 | 'plain_attribute' => 'local_plain_attribute_value', 56 | 'erb_attribute' => 'local_erb_attribute_value' 57 | } 58 | end 59 | end 60 | 61 | describe "#[]" do 62 | let(:key) { :local } 63 | let(:toggle) { described_class.new config_filepath: config_filepath, key: key } 64 | 65 | it "indifferently returns the value corresponding to the passed argument for the loaded config" do 66 | toggle['plain_attribute'].should == 'local_plain_attribute_value' 67 | toggle[:plain_attribute].should == 'local_plain_attribute_value' 68 | end 69 | end 70 | 71 | describe "#using" do 72 | let(:toggle) { described_class.new config_filepath: config_filepath } 73 | 74 | it "does not raise an exception when a block is not supplied" do 75 | lambda { 76 | toggle.using(:no_block) 77 | }.should_not raise_error 78 | end 79 | 80 | it "allows block access to the specified configs data without changing internal state" do 81 | toggle.key = :local 82 | 83 | toggle.using(:remote) do |config| 84 | config[:plain_attribute].should == 'remote_plain_attribute_value' 85 | config[:erb_attribute].should == 'remote_erb_attribute_value' 86 | end 87 | 88 | toggle.config.should == { 89 | 'plain_attribute' => 'local_plain_attribute_value', 90 | 'erb_attribute' => 'local_erb_attribute_value' 91 | } 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /toggle.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/toggle/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Jeff Iacono"] 6 | gem.email = ["iacono@squareup.com"] 7 | gem.description = %q{Easily control and change the path of your script} 8 | gem.summary = %q{Easily control and change the path of your script} 9 | gem.homepage = "https://github.com/square/toggle" 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "toggle" 15 | gem.require_paths = ["lib"] 16 | gem.version = Toggle::VERSION 17 | 18 | gem.add_runtime_dependency "activesupport", [">= 3.2.3"] 19 | 20 | gem.add_development_dependency "rake" 21 | gem.add_development_dependency "cane" 22 | gem.add_development_dependency "rspec", [">= 2"] 23 | end 24 | --------------------------------------------------------------------------------