├── var ├── name ├── title ├── version ├── created ├── organization ├── authors ├── copyrights ├── repositories ├── summary ├── requirements ├── description └── resources ├── demo ├── applique │ ├── ae.rb │ ├── fixture │ │ └── .ruby │ ├── file.rb │ └── fixture.rb ├── cov.rb ├── 01_config.md ├── 00_concept.md ├── 06_interface.md ├── 02_configuration.md └── 03_import.md ├── pkg └── .gitignore ├── Gemfile ├── .gitignore ├── lib ├── rc │ ├── api.rb │ ├── core_ext │ │ ├── symbol.rb │ │ ├── hash.rb │ │ ├── string.rb │ │ ├── argv.rb │ │ └── kernel.rb │ ├── core_ext.rb │ ├── required.rb │ ├── constants.rb │ ├── tweaks │ │ └── rake.rb │ ├── setup.rb │ ├── dsl.rb │ ├── config_filter.rb │ ├── properties.rb │ ├── config.rb │ ├── interface.rb │ └── configuration.rb ├── rc.rb └── c.rb ├── .yardopts ├── .travis.yml ├── work ├── deprecated │ ├── basic_object.rb │ ├── hash_builder.rb │ ├── current.rb │ ├── processor.rb │ ├── store.rb │ ├── controller.rb │ └── configuration.rb ├── Assembly ├── consider │ ├── rc_by_constant │ └── project.rb └── NOTES.md ├── Rulefile ├── MANIFEST ├── LICENSE.txt ├── .index ├── .rubyrc ├── HISTORY.md ├── README.md └── .gemspec /var/name: -------------------------------------------------------------------------------- 1 | rc 2 | -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | RC 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.4.0 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2011-11-06 2 | -------------------------------------------------------------------------------- /var/organization: -------------------------------------------------------------------------------- 1 | Rubyworks 2 | -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /pkg/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.zip 3 | *.gz 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Trans 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - (c) 2011 Rubyworks (BSD-2-Clause) 3 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/rc.git 3 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | The best way to manage your application's configuration. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .fire/digest 2 | .yardoc 3 | doc 4 | log 5 | tmp 6 | web 7 | *.lock 8 | -------------------------------------------------------------------------------- /lib/rc/api.rb: -------------------------------------------------------------------------------- 1 | # This file is simply a shortcut to `interface.rb`. 2 | 3 | require 'rc/interface' 4 | 5 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - finder 3 | - detroit (build) 4 | - qed (test) 5 | - ae (test) 6 | - test (test) 7 | 8 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "Runtime Configuration" 2 | --readme README.md 3 | --protected 4 | --private 5 | lib 6 | - 7 | [A-Z]*.* 8 | 9 | -------------------------------------------------------------------------------- /lib/rc/core_ext/symbol.rb: -------------------------------------------------------------------------------- 1 | #class Symbol 2 | # 3 | # def /(other) 4 | # "#{self}/#{other}".to_sym 5 | # end 6 | # 7 | #end 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed" 3 | rvm: 4 | - 1.9.2 5 | - 1.9.3 6 | - rbx-19mode 7 | - jruby-19mode 8 | env: 9 | - norc=true 10 | 11 | -------------------------------------------------------------------------------- /demo/cov.rb: -------------------------------------------------------------------------------- 1 | # Usage: qed -r ./spec/cov 2 | require 'simplecov' 3 | SimpleCov.start do 4 | coverage_dir 'log/coverage' 5 | #add_group "Label", "lib/qed/directory" 6 | end 7 | 8 | -------------------------------------------------------------------------------- /lib/rc/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'rc/core_ext/argv' 2 | require 'rc/core_ext/hash' 3 | require 'rc/core_ext/kernel' 4 | require 'rc/core_ext/string' 5 | require 'rc/core_ext/symbol' 6 | 7 | -------------------------------------------------------------------------------- /lib/rc.rb: -------------------------------------------------------------------------------- 1 | # RC - Runtime Configuration 2 | # Copyright (c) 2011 Rubyworks 3 | 4 | # Runtime Configuration for Ruby. 5 | # 6 | module RC 7 | require 'rc/api' 8 | autoconfigure 9 | end 10 | 11 | -------------------------------------------------------------------------------- /work/deprecated/basic_object.rb: -------------------------------------------------------------------------------- 1 | # TODO: Maybe use `backports` for future version. 2 | 3 | unless Object.const_defined?(:BasicObject) 4 | require 'blankslate' 5 | Object::BasicObject = Object::BlankSlate 6 | end 7 | 8 | -------------------------------------------------------------------------------- /demo/applique/fixture/.ruby: -------------------------------------------------------------------------------- 1 | # exmaple configuration file 2 | 3 | config :example do 4 | "example config" 5 | end 6 | 7 | config :example, :profile=>:data do |ex| 8 | ex.name = 'Tommy' 9 | ex.age = 42 10 | end 11 | 12 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | R.C. is a multi-tenant runtime configuration system for Ruby projects. 2 | It can be used to configure almost any Ruby tool or library regardless 3 | of whether that tool or library has dedicated support for RC or not. 4 | -------------------------------------------------------------------------------- /lib/rc/required.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # 4 | # We create a noop method b/c it simplifes implementation. 5 | # When a file is required this method gets called. 6 | # 7 | def self.required(path) 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /lib/rc/constants.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # 4 | # Looking for a config file relative to root of a project, 5 | # these are the files considered to indicate the root directory. 6 | # 7 | ROOT_INDICATORS = %w{.git .hg _darcs .index .rc .ruby} 8 | 9 | end 10 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/rc 3 | docs: http://rubydoc.info/github/rubyworks/rc/master/frames 4 | code: http://github.com/rubyworks/rc 5 | mail: http://groups.google.com/group/rubyworks-mailinglist 6 | chat: http://chat.us.freenode.net/rubyworks 7 | -------------------------------------------------------------------------------- /demo/applique/file.rb: -------------------------------------------------------------------------------- 1 | require 'rc/api' 2 | 3 | When 'configuration file `(((\S+)))` containing' do |slots, text| 4 | RC.clear! #configurations.clear 5 | fname = [slots].flatten.first # temporary transition to new QED 6 | File.open(fname, 'w'){ |f| f << text } 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/c.rb: -------------------------------------------------------------------------------- 1 | # This script is for use in the RUBYOPT, e.g. RUBYOPT="-rc". 2 | # 3 | # My apologies to the author of the `c` gem. If it helps, 4 | # I think it would be better if the functionaility of the 5 | # `c` gem were provided via other gems, e.g. the `github` 6 | # gem. 7 | 8 | require 'rc' 9 | -------------------------------------------------------------------------------- /demo/applique/fixture.rb: -------------------------------------------------------------------------------- 1 | # Setup the fixtures. 2 | 3 | dir = File.dirname(__FILE__) + '/fixture' 4 | Dir.entries(dir).each do |file| 5 | next if file == '.' or file == '..' 6 | path = File.join(dir, file) 7 | next if File.directory?(path) 8 | FileUtils.install(path, Dir.pwd) 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/rc/core_ext/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | 3 | def to_h 4 | dup #rehash 5 | end unless method_defined?(:to_h) 6 | 7 | #def rekey(&block) 8 | # h = {} 9 | # each do |k,v| 10 | # nk = block.call(k) 11 | # h[nk] = v 12 | # end 13 | # h 14 | #end unless method_defined?(:rekey) 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /work/Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | mailto: 4 | - ruby-talk@ruby-lang.org 5 | - rubyworks-mailinglist@googlegroups.com 6 | 7 | gem: 8 | active: true 9 | 10 | github: 11 | folder: web 12 | 13 | dnote: 14 | title: Source Notes 15 | output: log/notes.html 16 | 17 | locat: 18 | output: log/locat.html 19 | 20 | vclog: 21 | output: 22 | - log/history.html 23 | - log/changes.html 24 | 25 | -------------------------------------------------------------------------------- /lib/rc/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | 3 | def tabto(n) 4 | if self =~ /^( *)\S/ 5 | indent(n - $1.length) 6 | else 7 | self 8 | end 9 | end unless method_defined?(:tabto) 10 | 11 | def indent(n, c=' ') 12 | if n >= 0 13 | gsub(/^/, c * n) 14 | else 15 | gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "") 16 | end 17 | end unless method_defined?(:indent) 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /demo/01_config.md: -------------------------------------------------------------------------------- 1 | # Config Class 2 | 3 | The Config class encapsulates a single config entry. Every Config instance has a `command`, `feature`, 4 | and `profile` atribute, as well as a procedure. 5 | 6 | config = RC::Config.new('foo', :profile=>'bar') do 7 | 'example' 8 | end 9 | 10 | config.command #=> 'foo' 11 | config.feature #=> 'foo' 12 | config.profile #=> 'bar' 13 | config.to_proc.call #=> 'example' 14 | 15 | -------------------------------------------------------------------------------- /lib/rc/core_ext/argv.rb: -------------------------------------------------------------------------------- 1 | # 2 | # @deprecated Add to Ruby Facets ? 3 | # 4 | def ARGV.env(*switches) 5 | mapping = (Hash === switches.last ? swithes.pop : {}) 6 | 7 | switches.each do |s| 8 | mapping[s] = s.to_s.sub(/^[-]+/,'') 9 | end 10 | 11 | mapping.each do |switch, envar| 12 | if index = ARGV.index(switch) 13 | ENV[envar] = ARGV[index+1] 14 | elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ } 15 | value = $1 16 | value = value[1..-2] if value.start_with?('"') && value.end_with?('"') 17 | value = value[1..-2] if value.start_with?("'") && value.end_with?("'") 18 | ENV[envar] = value 19 | end 20 | end 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/rc/tweaks/rake.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | RC.configure 'rake' do |config| 4 | Module.new do 5 | extend Rake::DSL 6 | module_eval(&config) 7 | end 8 | end 9 | 10 | module Rake 11 | RC_FILES = '.rubyrc', '.ruby' 12 | 13 | class Application 14 | remove_const(:DEFAULT_RAKEFILES) 15 | DEFAULT_RAKEFILES = [ 16 | 'rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb', 17 | ] + RC_FILES 18 | end 19 | 20 | def self.load_rakefile(path) 21 | case File.basename(path) 22 | when *RC_FILES 23 | # do nothing, RC will do it 24 | else 25 | load(path) 26 | end 27 | end 28 | end 29 | 30 | # Must manually configure tweaked libraries. 31 | #RC.send(:configure_tool, 'rake') 32 | 33 | -------------------------------------------------------------------------------- /demo/00_concept.md: -------------------------------------------------------------------------------- 1 | # R.C. 2 | 3 | The purpose of R.C. is to provide unified configuration management 4 | for Ruby tools. 5 | 6 | R.C. designates a single per-project configuration file, named 7 | either `.config.rb`, `Config.rb` or `config.rb`, looked up in that 8 | order. The structure of this configuration file is very simple. 9 | It is a ruby script sectioned into named `config` and `onload` 10 | blocks: 11 | 12 | config 'rubytest' do 13 | # ... configure rubytest command ... 14 | end 15 | 16 | onload 'rake' do 17 | # ... when rake is required then ... 18 | end 19 | 20 | Utilization of the these configurations may be handled by the consuming 21 | application, but can be used on any Ruby-based tool if `-rc` is added 22 | to RUBYOPT. 23 | 24 | -------------------------------------------------------------------------------- /work/consider/rc_by_constant: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # If we do this in a BasicObject context we could control it all. 4 | # 5 | # So in this case the criteria would be that there were a require `foo` 6 | # for a given `Foo` module, which goes with a `foo` command. 7 | # 8 | # See issue #1. 9 | 10 | module NULL 11 | extend self 12 | def method_missing(*); self; end 13 | end 14 | 15 | def Object.const_missing(name) 16 | cmd = $0.sub('./', '') 17 | p cmd 18 | if cmd == name.to_s.downcase 19 | require name.to_s.downcase 20 | const_get(name) 21 | else 22 | NULL 23 | end 24 | end 25 | 26 | p TryThis.object_id 27 | 28 | 29 | QED.configure do |run| 30 | run.files << 'spec' 31 | end 32 | 33 | Test.run do |run| 34 | run.files << 'test/' 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/rc/core_ext/kernel.rb: -------------------------------------------------------------------------------- 1 | #require 'finder/import' 2 | 3 | module Kernel 4 | 5 | private 6 | 7 | # 8 | # Alias original Kernel#require method. 9 | # 10 | alias_method :require_without_rc, :require 11 | 12 | # 13 | # Redefine Kernel#require with callback. 14 | # 15 | def require(feature, options=nil) 16 | result = require_without_rc(feature) 17 | RC.required(feature) if result 18 | result 19 | end 20 | 21 | class << self 22 | # 23 | # Alias original Kernel.require method. 24 | # 25 | alias_method :require_without_rc, :require 26 | 27 | # 28 | # Redefine Kernel.require with callback. 29 | # 30 | def require(feature) 31 | result = require_without_rc(feature) 32 | RC.required(feature) if result 33 | result 34 | end 35 | end 36 | 37 | end 38 | 39 | -------------------------------------------------------------------------------- /Rulefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ignore 'work', '.yardoc', 'doc', 'log', 'pkg', 'tmp', 'web' 4 | 5 | desc "run specs" 6 | task "spec" do 7 | sh 'qed' 8 | end 9 | 10 | # NOTE: We can't use the qed simplecov profile in the `Config.rb` 11 | # file b/c QED uses RC and SimpleCov must be loaded before the 12 | # code it covers. So we handle here by hand instead. 13 | desc "verify specifications with coverage report" 14 | task 'spec:cov' do 15 | require 'simplecov' 16 | SimpleCov.start do 17 | coverage_dir 'log/coverage' 18 | end 19 | require 'qed/cli' 20 | QED::Session.cli 21 | end 22 | 23 | file 'var/*' do 24 | sh 'dotruby source var' 25 | end 26 | 27 | file 'qed/**/*' do 28 | sh 'qed' 29 | end 30 | 31 | file 'README.md' do 32 | sh 'git add README.md' 33 | sh 'git commit -m "Updated README. [doc]"' 34 | end 35 | 36 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast -x *.lock .index .ruby .rubyrc .yardopts bin demo lib man spec test *.txt *.md 2 | .index 3 | .rubyrc 4 | .yardopts 5 | demo/00_concept.md 6 | demo/01_config.md 7 | demo/02_configuration.md 8 | demo/03_import.md 9 | demo/06_interface.md 10 | demo/applique/ae.rb 11 | demo/applique/file.rb 12 | demo/applique/fixture/.ruby 13 | demo/applique/fixture.rb 14 | demo/cov.rb 15 | lib/c.rb 16 | lib/rc/api.rb 17 | lib/rc/config.rb 18 | lib/rc/config_filter.rb 19 | lib/rc/configuration.rb 20 | lib/rc/constants.rb 21 | lib/rc/core_ext/argv.rb 22 | lib/rc/core_ext/hash.rb 23 | lib/rc/core_ext/kernel.rb 24 | lib/rc/core_ext/string.rb 25 | lib/rc/core_ext/symbol.rb 26 | lib/rc/core_ext.rb 27 | lib/rc/dsl.rb 28 | lib/rc/interface.rb 29 | lib/rc/properties.rb 30 | lib/rc/required.rb 31 | lib/rc/setup.rb 32 | lib/rc/tweaks/rake.rb 33 | lib/rc.rb 34 | LICENSE.txt 35 | HISTORY.md 36 | README.md 37 | -------------------------------------------------------------------------------- /demo/06_interface.md: -------------------------------------------------------------------------------- 1 | # Interface 2 | 3 | The main means of workin with RC's API are the RC class methods, 4 | collectively called the Inteface. 5 | 6 | Let's say we have a configuration file `.ruby` containing: 7 | 8 | config :example do 9 | "example config" 10 | end 11 | 12 | config :example, :profile=>:something do 13 | "example config using profile" 14 | end 15 | 16 | To get the configuration of the current project --relative to the 17 | current working directory, use the `configuration` method. 18 | 19 | RC.configuration 20 | 21 | The configuration properties of the current project can be 22 | had via the `properties` method. 23 | 24 | RC.properties 25 | 26 | The profile names can be looked up for any given tool via the `profile_names` 27 | method. 28 | 29 | RC.profile_names(:example) #=> ['default', 'something'] 30 | 31 | The number of feature configurations in the current project can be 32 | had via the `size` method. 33 | 34 | RC.configuration.size #=> 1 35 | 36 | A list of all configuration entries can be had by calling #to_a. 37 | 38 | RC.configuration.to_a.size #=> 2 39 | 40 | -------------------------------------------------------------------------------- /work/deprecated/hash_builder.rb: -------------------------------------------------------------------------------- 1 | module Confection 2 | 3 | # HashBuilder takes a procedure and builds a Hash out of it. 4 | # 5 | # The procedure must conform to a set of rules to be useful in this respect. 6 | # They must either take an argument and use that argument to set values, or 7 | # if no argument is taken then `#instance_eval` is used to evaluate the 8 | # procedure such that each method represents a key. 9 | # 10 | class HashBuilder < BasicObject 11 | 12 | # 13 | def initialize(hash={}, &block) 14 | @hash = hash 15 | case block.arity 16 | when 0 17 | instance_eval(&block) 18 | else 19 | block.call(self) 20 | end 21 | end 22 | 23 | # 24 | def to_h 25 | @hash 26 | end 27 | 28 | # 29 | def method_missing(s, *a, &b) 30 | m = s.to_s 31 | if a.empty? && !b 32 | @hash[m.to_sym] 33 | else 34 | if b 35 | @hash[m.chomp('=').to_sym] = HashBuilder.new(&b).to_h 36 | else 37 | if a.size > 1 38 | @hash[m.chomp('=').to_sym] = a 39 | else 40 | @hash[m.chomp('=').to_sym] = a.first 41 | end 42 | end 43 | end 44 | end 45 | 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/rc/setup.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # 4 | # Configuration setup is used to customize how a tool handles 5 | # configuration. 6 | # 7 | class Setup 8 | 9 | # 10 | # Intialize new configuration setup. 11 | # 12 | def initialize(command, options={}, &block) 13 | @command = command.to_s 14 | #@command = options[:command] || options[:tool] if options.key?(:command) || options.key?(:tool) 15 | 16 | @profile = options[:profile] if options.key?(:profile) 17 | 18 | @block = block 19 | end 20 | 21 | # 22 | # Command for which this is the configuration setup. 23 | # 24 | attr :command 25 | 26 | # 27 | # Specific profile for which this is the configuration is the setup. 28 | # 29 | attr :profile 30 | 31 | # 32 | # 33 | # 34 | def call(config) 35 | return unless config.command == @command #.to_s if @command 36 | 37 | case profile 38 | when true 39 | return unless RC.profile? 40 | when nil, false 41 | else 42 | return unless config.profile == @profile.to_s #if @profile 43 | end 44 | 45 | @block.call(config) 46 | end 47 | 48 | # 49 | # 50 | # 51 | def to_proc 52 | @block 53 | end 54 | 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /work/NOTES.md: -------------------------------------------------------------------------------- 1 | # Developer Notes 2 | 3 | 4 | ## 2012-04-07 | Toplevel DSLs 5 | 6 | If it were not for Ruby's mixing toplevel definitions into all 7 | objects, I likely would have used the simpler design of 8 | just loading the config files directly (via `#load`). 9 | 10 | It's dissapointing that Ruby continues to insist on mixing toplevel 11 | methods into all objects. It would be much easier to write script 12 | DSLs if it did not, saving a good bit of code. In this case, for 13 | instance, I probably could have shaved off 20% to 40% of the 14 | current code --neither the Config or the Configuration class 15 | would be needed, and the parser could be stripped down to just 16 | enough code to collect a list of profiles since that is all it 17 | would really be useful for then. 18 | 19 | 20 | ## 2012-04-05 | Multiple Configurations 21 | 22 | Should multiple definitions for the same tool and profile be 23 | allowed? 24 | 25 | config :qed, :cov do 26 | ... 27 | end 28 | 29 | config :qed, :cov do 30 | ... 31 | end 32 | 33 | configuration.invoke(:qed, :cov) 34 | 35 | Should both definitions be called, or just the later? I have decide 36 | that both will be called. If this becomes a concern, I may add a `#reconfig` 37 | method which would first clear the list of matching configurations. 38 | 39 | -------------------------------------------------------------------------------- /demo/02_configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The Configuration class handle evaluation of a project configuration file. 4 | 5 | rc = RC::Configuration.new 6 | 7 | We can use the `#instance_eval` method to evaluate a configuration for our 8 | demonstration. 9 | 10 | rc.evaluate(<<-HERE) 11 | config :sample1 do 12 | "block code" 13 | end 14 | HERE 15 | 16 | Evaluation of a configuration file, populate the Confection.config instance. 17 | 18 | sample = rc.configurations.last 19 | sample.command #=> 'sample1' 20 | sample.profile #=> 'default' 21 | sample.class #=> RC::Config 22 | 23 | A profile can be used as a means fo defining multiple configurations 24 | for a single tool. This can be done by setting the second argument to 25 | a Symbol. 26 | 27 | rc.evaluate(<<-HERE) 28 | config :sample2, :profile=>'opt1' do 29 | "block code" 30 | end 31 | HERE 32 | 33 | sample = rc.configurations.last 34 | sample.command #=> 'sample2' 35 | sample.profile #=> 'opt1' 36 | 37 | Or it can be done by using a `profile` block. 38 | 39 | rc.evaluate(<<-HERE) 40 | profile :opt1 do 41 | config :sample2 do 42 | "block code" 43 | end 44 | end 45 | HERE 46 | 47 | sample = rc.configurations.last 48 | sample.command #=> 'sample2' 49 | sample.profile #=> 'opt1' 50 | 51 | 52 | -------------------------------------------------------------------------------- /demo/03_import.md: -------------------------------------------------------------------------------- 1 | # Importing 2 | 3 | ## Configuration Importing 4 | 5 | Configurations can be imported from another project using the `:from` option. 6 | 7 | rc = RC::Configuration.new 8 | 9 | rc.config :qed, :profile=>'example', :from=>'test' 10 | 11 | rc.to_a.size.assert == 1 12 | 13 | The configuration can also be imported from a different profile. 14 | 15 | rc.config :qed, :profile=>:coverage, :from=>['test', :profile=>'simplecov'] 16 | 17 | rc.to_a.size.assert == 2 18 | 19 | Although it will very rarely be of use, it may also be imported from another 20 | feature or command too. 21 | 22 | rc.config :example, :from=>['test', :command=>'sample'] 23 | 24 | Imported configurations can also be augmented via a block. 25 | 26 | rc = RC::Configuration.new 27 | 28 | rc.config :qed, :from=>['test', :profile=>'simplecov'] do 29 | # additional code here 30 | end 31 | 32 | rc.to_a.size.assert == 2 33 | 34 | Technically this last form just creates two configurations for the same 35 | tool and profile, but the ultimate effect is the same. 36 | 37 | ## Script Importing 38 | 39 | Library files can be imported directly into configuration blocks via the 40 | `#import` method. 41 | 42 | rc.config :example do 43 | import "fruitbasket/example.rb" 44 | end 45 | 46 | This looks up the file via the `finder` gem and then evals it in the context 47 | of the config block. 48 | 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause license) 2 | 3 | Ruby Confection (http://rubyworks.github.com/rc) 4 | 5 | Copyright (c) 2011 Rubyworks. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 19 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: Trans 8 | email: transfire@gmail.com 9 | organizations: [] 10 | requirements: 11 | - name: finder 12 | - groups: 13 | - build 14 | development: true 15 | name: detroit 16 | - groups: 17 | - test 18 | development: true 19 | name: qed 20 | - groups: 21 | - test 22 | development: true 23 | name: ae 24 | - groups: 25 | - test 26 | development: true 27 | name: test 28 | conflicts: [] 29 | alternatives: [] 30 | resources: 31 | - type: home 32 | uri: http://rubyworks.github.com/rc 33 | label: Homepage 34 | - type: docs 35 | uri: http://rubydoc.info/github/rubyworks/rc/master/frames 36 | label: Documentation 37 | - type: code 38 | uri: http://github.com/rubyworks/rc 39 | label: Source Code 40 | - type: mail 41 | uri: http://groups.google.com/group/rubyworks-mailinglist 42 | label: Mailing List 43 | - type: chat 44 | uri: http://chat.us.freenode.net/rubyworks 45 | label: IRC Channel 46 | repositories: 47 | - name: upstream 48 | scm: git 49 | uri: git://github.com/rubyworks/rc.git 50 | categories: [] 51 | copyrights: 52 | - holder: Rubyworks 53 | year: '2011' 54 | license: BSD-2-Clause 55 | customs: [] 56 | paths: 57 | lib: 58 | - lib 59 | created: '2011-11-06' 60 | summary: The best way to manage your application's configuration. 61 | title: RC 62 | version: 0.4.0 63 | name: rc 64 | description: ! 'R.C. is a multi-tenant runtime configuration system for Ruby projects. 65 | 66 | It can be used to configure almost any Ruby tool or library regardless 67 | 68 | of whether that tool or library has dedicated support for RC or not.' 69 | date: '2013-01-22' 70 | -------------------------------------------------------------------------------- /lib/rc/dsl.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # Configuration's DSL 4 | # 5 | class DSL < Module 6 | 7 | # 8 | # 9 | # 10 | def initialize(configuration) 11 | @configuration = configuration 12 | @_options = {} 13 | end 14 | 15 | # 16 | def import(glob, opts={}) 17 | @configuration.import(glob, *opts) 18 | end 19 | 20 | # 21 | # 22 | # 23 | #def profile(name, &block) 24 | # raise SyntaxError, "nested profile sections" if @_options[:profile] 25 | # @_options[:profile] = name.to_s 26 | # instance_eval(&block) 27 | # @_options.delete(:profile) 28 | #end 29 | 30 | # 31 | # Profile block. 32 | # 33 | # @param [String,Symbol] name 34 | # A profile name. 35 | # 36 | def profile(name, state={}, &block) 37 | raise SyntaxError, "nested profile sections" if @_options[:profile] 38 | original_state = @_options.dup 39 | @_options.update(state) 40 | @_options[:profile] = name.to_s 41 | 42 | instance_eval(&block) 43 | 44 | @_options = original_state 45 | end 46 | 47 | # 48 | # 49 | def config(command=nil, options={}, &block) 50 | nested_keys = @_options.keys & options.keys.map{|k| k.to_sym} 51 | raise ArgumentError, "nested #{nested_keys.join(', ')}" unless nested_keys.empty? 52 | 53 | options = @_options.merge(options) 54 | 55 | @configuration.config(command, options, &block) 56 | end 57 | 58 | # 59 | # 60 | def onload(feature, options={}, &block) 61 | nested_keys = @_options.keys & options.keys.map{|k| k.to_sym} 62 | raise ArgumentError, "nested #{nested_keys.join(', ')}" unless nested_keys.empty? 63 | 64 | options = @_options.merge(options) 65 | options[:onload] = true 66 | 67 | @configuration.config(feature, options, &block) 68 | end 69 | 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /work/deprecated/current.rb: -------------------------------------------------------------------------------- 1 | module Confection 2 | 3 | # 4 | # Global properties is set for parsing project configuration. 5 | # It is *always* the properties of the current project. 6 | # 7 | $properties = nil 8 | 9 | # Current mixin extends the Confection module. Primarily is provides 10 | # class methods for working with the current project's configurations. 11 | # 12 | module Current 13 | 14 | # 15 | def controller(scope, tool, *options) 16 | params = (Hash === options.last ? options.pop : {}) 17 | params[:profile] = options.shift unless options.empty? 18 | 19 | if from = params[:from] 20 | projects[from] ||= Project.load(from) 21 | projects[from].controller(scope, tool, params) 22 | else 23 | bootstrap if $properties.nil? # TODO: better way to go about this? 24 | current_project.controller(scope, tool, params) 25 | end 26 | end 27 | 28 | # 29 | def bootstrap 30 | $properties = current_project.properties 31 | end 32 | 33 | # 34 | def projects 35 | @projects ||= {} 36 | end 37 | 38 | # 39 | def current_directory 40 | @current_directory ||= Dir.pwd 41 | end 42 | 43 | # 44 | def current_project 45 | projects[current_directory] ||= Project.lookup(current_directory) 46 | end 47 | 48 | # 49 | def clear! 50 | current_project.store.clear! 51 | end 52 | 53 | # 54 | def profiles(tool, options={}) 55 | current_project.profiles(tool) 56 | end 57 | 58 | # 59 | def each(&block) 60 | current_project.each(&block) 61 | end 62 | 63 | # 64 | def size 65 | current_project.size 66 | end 67 | 68 | # 69 | # Project properties. 70 | # 71 | def properties 72 | current_project.properties 73 | end 74 | 75 | end 76 | 77 | extend Current 78 | 79 | end 80 | -------------------------------------------------------------------------------- /.rubyrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Configure QED demo tool. 5 | # 6 | config 'qed' do 7 | puts "QED!" 8 | end 9 | 10 | # 11 | # QED test coverage report using SimpleCov. 12 | # 13 | # Use `$properties.coverage_folder` to set directory in which to store 14 | # coverage report this defaults to `log/coverage`. 15 | # 16 | # IMPORTANT! Unfortunately this will not give us a reliable report 17 | # b/c QED uses the RC gem, so SimpleCov can't differentiate the two. 18 | # 19 | config 'qed', :profile=>'cov' do 20 | puts "QED w/coverage!" 21 | 22 | require 'simplecov' 23 | 24 | dir = $properties.coverage_folder 25 | 26 | SimpleCov.start do 27 | coverage_dir(dir || 'log/coverage') 28 | #add_group "Label", "lib/qed/directory" 29 | end 30 | end 31 | 32 | # 33 | # 34 | # 35 | config 'pry' do 36 | puts "Pry on RC!" 37 | $LOAD_PATH.unshift('lib') 38 | end 39 | 40 | # 41 | # Detroit assembly. 42 | # 43 | config 'detroit' do |asm| 44 | asm.service :email do |s| 45 | s.mailto = ['ruby-talk@ruby-lang.org', 46 | 'rubyworks-mailinglist@googlegroups.com'] 47 | end 48 | 49 | asm.service :gem do |s| 50 | s.gemspec = 'pkg/rc.gemspec' 51 | end 52 | 53 | asm.service :github do |s| 54 | s.folder = 'web' 55 | end 56 | 57 | asm.service :dnote do |s| 58 | s.title = 'Source Notes' 59 | s.output = 'log/notes.html' 60 | end 61 | 62 | asm.service :locat do |s| 63 | s.output = 'log/locat.html' 64 | end 65 | 66 | asm.service :vclog do |s| 67 | s.output = ['log/history.html', 68 | 'log/changes.html'] 69 | end 70 | end 71 | 72 | # 73 | # Rake tasks 74 | # 75 | config 'rake' do 76 | task :default => [:test] 77 | desc 'run unit tests' 78 | task :test do 79 | puts "Rake Boo!" 80 | end 81 | end 82 | 83 | # 84 | # Example configuration. 85 | # 86 | config 'example' do 87 | puts "Configuration Example!" 88 | end 89 | 90 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.4.0 / 2013-01-22 4 | 5 | Tools that have built-in support for RC will have to call `RC.configure` to 6 | configure the tool. The old `configure` method has been renamed to `define_config` 7 | and is used *only* to define how calling `configure` is applied. This was done 8 | so that tools could set `ENV['profile']` internally, say via a command line option. 9 | This is a major change and all tools that it effects need to update to reflect the 10 | change in order to work. 11 | 12 | Changes: 13 | 14 | * Rename `configure` to `define_config` (old `setup` alias is still there). 15 | * Fix missing instance variable in Properties. [bug] 16 | 17 | 18 | ## 0.3.1 / 2012-12-09 19 | 20 | This is bug fix release that addresses a couple of stupid oversights. 21 | 22 | Changes: 23 | 24 | * Fix rake tweak. Use `RC.configure` instead of `court`. 25 | * Fix #autoconfig? method's instance variable. 26 | * Fix #bootstrap_require to override correct hook. 27 | 28 | 29 | ## 0.3.0 / 2012-13-08 30 | 31 | This release is of the project finally begins to settle down the API. 32 | The most significant change since the last release is the use of `.rubyrc`, 33 | or just `.ruby`, as the name for the standard configuration file. It was 34 | changed from the previous `Config.rb` file name in order to avoid any 35 | confusion with Rails `config` directory or any other use of the word. 36 | If you still prefer the old name, simply add a `.rubyrc` file to your 37 | project containing `import "Config.rb"` to get equivalent functionality. 38 | Other than that the majority of changes have been to improve the library 39 | internally. 40 | 41 | Changes: 42 | 43 | * Change default config file name to `.rubyrc`, or just `.ruby`. 44 | * Improve configuration loading code. 45 | * Remove dependency on Courtier. 46 | 47 | 48 | ## 0.2.0 / 2012-04-16 49 | 50 | Major improvements and simplifications to design and API. 51 | Basically, just read the README to see what is new. 52 | 53 | Changes: 54 | 55 | * Add support for require-based configuration setups. 56 | * Overhaul and drastically simplify design. 57 | 58 | 59 | ## 0.1.1 / 2012-04-09 60 | 61 | Initial release of RC. 62 | 63 | Changes: 64 | 65 | * Happy first release. 66 | 67 | -------------------------------------------------------------------------------- /lib/rc/config_filter.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # To be quite honest I have forgotten the purpose of this class 4 | # and it is no longer being used, so it should be deleted. But 5 | # I am holding off a bit, in case I recall why it was here, in 6 | # order to be sure. 7 | # 8 | class ConfigFilter 9 | 10 | include Enumerable 11 | 12 | # 13 | # Initialize new ConfigFilter. 14 | # 15 | # @param [Array] configuration 16 | # List if Config instances. 17 | # 18 | def initialize(configuration, criteria={}) 19 | @configuration = configuration 20 | @criteria = criteria 21 | 22 | @list = [] 23 | 24 | configuration.each do |c| 25 | @list << c if c.match?(criteria) 26 | end 27 | end 28 | 29 | # 30 | # 31 | # 32 | def tool 33 | @criteria[:tool] 34 | end 35 | 36 | # 37 | # 38 | # 39 | def profile 40 | @criteria[:profile] 41 | end 42 | 43 | # 44 | # Returns list of profiles. 45 | # 46 | def profiles 47 | @list.map{ |c| c.profile } 48 | end 49 | 50 | # 51 | # 52 | # 53 | def [](subset) 54 | return method_missing(:[]) if profile 55 | if tool 56 | criteria = @criteria.dup 57 | criteria[:profile] = subset 58 | else 59 | criteria = @criteria.dup 60 | criteria[:tool] = subset 61 | end 62 | self.class.new(@configuration, criteria) 63 | end 64 | 65 | # 66 | # 67 | # 68 | def each(&block) 69 | @list.each do 70 | block 71 | end 72 | end 73 | 74 | # 75 | # 76 | # 77 | def size 78 | @list.size 79 | end 80 | 81 | # 82 | # Call each config. 83 | # 84 | def call(*args) 85 | @list.each do |c| 86 | if c.profile?(RC.current_profile) 87 | c.call(*args) 88 | end 89 | end 90 | end 91 | 92 | # 93 | # Convert to Proc. 94 | # 95 | def to_proc(exec=false) 96 | list = @list 97 | if exec 98 | Proc.new do |*args| 99 | list.each{ |c| instance_exec(*args, &c) } 100 | end 101 | else 102 | Proc.new do |*args| 103 | list.each{ |c| c.call(*args) } 104 | end 105 | end 106 | end 107 | 108 | end 109 | 110 | end 111 | -------------------------------------------------------------------------------- /work/deprecated/processor.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # Mixin for TOPLEVEL scope for processing configurations. 4 | # 5 | module Processor 6 | 7 | # 8 | # 9 | # 10 | def profile(name, &block) 11 | original, @_profile = @_profile, name.to_s 12 | instance_eval(&block) 13 | @_profile = original 14 | end 15 | 16 | # 17 | # Run confgiruation. 18 | # 19 | # @example 20 | # config :qed/:coverage do 21 | # require 'simplecov' 22 | # end 23 | # 24 | def config(tool, *args, &block) 25 | tool = tool.to_s 26 | data = args.shift 27 | opts = (Hash===args.last ? args.pop : {}) 28 | 29 | if tool.index('/') 30 | tool, profile = tool.split('/') 31 | else 32 | profile = nil 33 | end 34 | 35 | raise SyntaxError, "nested profile sections" if profile && @_profile 36 | profile = profile || @_profile || 'default' 37 | 38 | current_tool = RC.current_tool 39 | current_profile = RC.current_profile 40 | 41 | return unless current_tool == tool 42 | return unless current_profile == profile 43 | 44 | begin 45 | require tool 46 | rescue LoadError => e 47 | warn e.message if $VERBOSE 48 | end 49 | 50 | if gem = opts[:from] 51 | # lookup project config file 52 | file = RC.find(gem) 53 | RC.current_tool = opts[:tool] if opts[:tool] 54 | RC.current_profile = opts[:profile] if opts[:profile] 55 | begin 56 | load(file, true) 57 | ensure 58 | RC.current_tool = current_tool 59 | RC.current_profile = current_profile 60 | end 61 | end 62 | 63 | if data = args.shift 64 | raise ArgumentError, "must use data or block, not both" if block 65 | data = data.tabto(0) 66 | block = Proc.new do 67 | YAML.load(data) 68 | end 69 | end 70 | 71 | return unless block 72 | 73 | main = eval('self', TOPLEVEL_BINDING) 74 | if main.respond_to?("rc_#{tool}") 75 | main.__send__("rc_#{tool}", &block) 76 | else 77 | block.call 78 | end 79 | 80 | #if scope = RC.scope(tool) 81 | # if block.arity == 1 82 | # block.call(scope) 83 | # else 84 | # scope.module_eval(&block) 85 | # end 86 | #else 87 | # block.call 88 | #end 89 | end 90 | 91 | end 92 | 93 | end 94 | -------------------------------------------------------------------------------- /lib/rc/properties.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # Project properties make it easy for configurations to utilize 4 | # information about a project for config settings. Properties 5 | # are stored in a global variables called `$properties`. 6 | # 7 | # $properties.name #=> 'rc' 8 | # 9 | # Properties derive from a project's `.index` file and `var/` files. 10 | # This may expand in the future to include a project's gemspec. 11 | # 12 | class Properties 13 | 14 | # 15 | # Initialize new Properties instance. 16 | # 17 | def initialize(path=nil) 18 | @root = find_root(path || Dir.pwd) 19 | @index = load_index 20 | @var = {} 21 | end 22 | 23 | # 24 | # Root directory of project. 25 | # 26 | # @return [String] Full path to project's root directory. 27 | # 28 | def root 29 | @root 30 | end 31 | 32 | # 33 | # Route missing method to index lookup and failing that var file lookup. 34 | # 35 | def method_missing(s) 36 | name = s.to_s.downcase 37 | index(name) || var(name) 38 | end 39 | 40 | private 41 | 42 | # 43 | # 44 | # 45 | def find_root(path) 46 | pwd = File.expand_path(path) 47 | home = File.expand_path('~') 48 | 49 | while pwd != '/' && pwd != home 50 | return pwd if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) } 51 | pwd = File.dirname(pwd) 52 | end 53 | 54 | nil 55 | end 56 | 57 | # 58 | # 59 | # 60 | def index(name) 61 | @index[name] 62 | end 63 | 64 | # 65 | # 66 | # 67 | def var(name) 68 | return @var[name] if @var.key?(name) 69 | 70 | glob = File.join(root, 'var', name) 71 | file = Dir[glob].first 72 | if file 73 | data = File.read(file) 74 | if data =~ /\A(---|%YAML)/ 75 | data = YAML.load(data) 76 | end 77 | @var[name] = data 78 | else 79 | nil 80 | end 81 | end 82 | 83 | # 84 | # 85 | # 86 | def load_index 87 | index = {} 88 | file = File.join(root, '.index') 89 | if File.exist?(file) 90 | begin 91 | index = YAML.load_file(file) 92 | rescue SyntaxError => error 93 | warn error.to_s 94 | end 95 | end 96 | index 97 | end 98 | 99 | # 100 | # @todo Support gemspec as properties source ? 101 | # 102 | def load_gemspec 103 | file = Dir['{*,,pkg/*}.gemspec'].first 104 | # ... 105 | end 106 | 107 | end 108 | 109 | end 110 | -------------------------------------------------------------------------------- /work/consider/project.rb: -------------------------------------------------------------------------------- 1 | module Confection 2 | 3 | # Project configuration. 4 | # 5 | class ProjectConfig 6 | 7 | include Enumerable 8 | 9 | # 10 | # Configuration file pattern. The standard configuration file name is 11 | # `Config.rb`, and that name should be used in most cases. However, 12 | # `.config.rb` can also be use and will take precedence if found. 13 | # Conversely, `config.rb` (lowercase form) can also be used but has 14 | # the least precedence. 15 | # 16 | # Config files looked for in the order or precedence: 17 | # 18 | # * `.config.rb` 19 | # * `Config.rb` 20 | # * `config.rb` 21 | # 22 | PATTERN = '{.,}config{.rb,}' 23 | 24 | # 25 | # Per library cache. 26 | # 27 | def self.cache 28 | @cache ||= {} 29 | end 30 | 31 | # 32 | # Get project configuration from another library. 33 | # 34 | # This method uses the Finder gem. 35 | # 36 | # @param [String] lib 37 | # Library name. 38 | # 39 | # @return [Project,nil] Located project. 40 | # 41 | def self.load(lib=nil) 42 | if lib 43 | lib = lib.to_s 44 | return cache[lib] if cache.key?(lib) 45 | cache[lib] ||= ( 46 | config_path = Find.path(PATTERN, :from=>lib).first 47 | config_path ? new(File.dirname(config_path)) : nil 48 | ) 49 | else 50 | lookup 51 | end 52 | end 53 | 54 | # 55 | # Lookup configuation file. 56 | # 57 | # @param dir [String] 58 | # Optional directory to begin search. 59 | # 60 | # @return [String] file path 61 | # 62 | def self.lookup(dir=nil) 63 | dir = dir || Dir.pwd 64 | home = File.expand_path('~') 65 | while dir != '/' && dir != home 66 | if file = Dir.glob(File.join(dir, PATTERN), File::FNM_CASEFOLD).first 67 | return new(File.dirname(file)) 68 | end 69 | dir = File.dirname(dir) 70 | end 71 | return nil 72 | end 73 | 74 | # 75 | # Initialize new ProjectConfig. 76 | # 77 | # @param [String] root 78 | # Project root directory. 79 | # 80 | def initialize(root) 81 | @root = root 82 | end 83 | 84 | # 85 | # Project root directory. 86 | # 87 | # @return [String] project's root directory 88 | # 89 | attr :root 90 | 91 | # 92 | # Alias for #root. 93 | # 94 | alias :directory :root 95 | 96 | # 97 | # Configuration store tracks a project's confirguration entries. 98 | # 99 | def store 100 | @store ||= Store.new(*source) 101 | end 102 | 103 | # 104 | # The file path of the project's configuration file. 105 | # 106 | # @return [String] path to configuration file 107 | # 108 | def source 109 | Dir.glob(File.join(root, PATTERN), File::FNM_CASEFOLD).first 110 | end 111 | 112 | # 113 | # List of configuration profiles. 114 | # 115 | # @return [Array] profile names 116 | # 117 | def profiles(tool) 118 | store.profiles(tool) 119 | end 120 | 121 | # 122 | # Project properties. 123 | # 124 | # @todo Use cascading class, e.g. Confstruct. 125 | # 126 | def properties 127 | dotruby = File.join(directory,'.ruby') 128 | if File.exist?(dotruby) 129 | data = YAML.load_file(dotruby) 130 | OpenStruct.new(data) 131 | else 132 | OpenStruct.new 133 | end 134 | end 135 | 136 | # 137 | # Create a configuration controller. 138 | # 139 | # @param [Object] scope 140 | # Context for which controller is being created. 141 | # 142 | # @param [Symbol] tool 143 | # The tool of the configuration to select. 144 | # 145 | def controller(scope, tool, options={}) 146 | profile = options[:profile] 147 | configs = store.lookup(tool, profile) 148 | Controller.new(scope, *configs) 149 | end 150 | 151 | # 152 | # Iterate over each configurations. 153 | # 154 | def each(&block) 155 | store.each(&block) 156 | end 157 | 158 | # 159 | # The number of configurations. 160 | # 161 | # @return [Fixnum] config count 162 | # 163 | def size 164 | store.size 165 | end 166 | 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /work/deprecated/store.rb: -------------------------------------------------------------------------------- 1 | module Confection 2 | 3 | class Store 4 | 5 | include Enumerable 6 | 7 | =begin 8 | # 9 | # Bootstrap the system, loading current configurations. 10 | # 11 | def bootstrap 12 | if file 13 | @config[root] = [] 14 | begin 15 | DSL.load_file(file) 16 | rescue => e 17 | raise e if $DEBUG 18 | warn e.message 19 | end 20 | end 21 | @config[root] 22 | end 23 | =end 24 | 25 | # 26 | def initialize(*sources) 27 | @sources = sources 28 | 29 | @list = [] 30 | 31 | sources.each do |source| 32 | if File.file?(source) 33 | parse(source) 34 | else 35 | # ignore directories 36 | end 37 | end 38 | end 39 | 40 | # 41 | attr :sources 42 | 43 | # 44 | def parse(file) 45 | Parser.parse(self, file) 46 | end 47 | 48 | # 49 | # Iterate over each configurations. 50 | # 51 | def each(&block) 52 | @list.each(&block) 53 | end 54 | 55 | # 56 | # The number of configurations. 57 | # 58 | # @return [Fixnum] config count 59 | # 60 | def size 61 | @list.size 62 | end 63 | 64 | # 65 | # Add as configuratio to the store. 66 | # 67 | def <<(conf) 68 | raise TypeError, "not a configuration instance -- `#{conf}'" unless Config === conf 69 | @list << conf 70 | end 71 | 72 | # 73 | # Add a list of configs. 74 | # 75 | def concat(configs) 76 | configs.each{ |c| self << c } 77 | end 78 | 79 | # 80 | # Lookup configuration by tool and profile name. 81 | # 82 | # @todo Future versions should allow this to handle regex and fnmatches. 83 | # 84 | def lookup(tool, profile=nil) 85 | if profile == '*' 86 | select do |c| 87 | c.tool.to_sym == tool.to_sym 88 | end 89 | else 90 | profile = profile.to_sym if profile 91 | 92 | select do |c| 93 | c.tool.to_sym == tool.to_sym && c.profile == profile 94 | end 95 | end 96 | end 97 | 98 | # 99 | # Returns list of profiles collected from all configs. 100 | # 101 | def profiles(tool) 102 | names = [] 103 | each do |c| 104 | names << c.profile if c.tool == tool.to_sym 105 | end 106 | names.uniq.compact 107 | end 108 | 109 | # 110 | # Clear configs. 111 | # 112 | def clear! 113 | @list = [] 114 | end 115 | 116 | # 117 | def first 118 | @list.first 119 | end 120 | 121 | # 122 | def last 123 | @list.last 124 | end 125 | 126 | # 127 | #def config(*args, &block) 128 | # dsl.config(*args, &block) 129 | #end 130 | 131 | # 132 | #def controller(scope, name, profile=nil) 133 | # configs = lookup(name, profile) 134 | # Controller.new(scope, *configs) 135 | #end 136 | 137 | # 138 | # Import configuration from another project. 139 | # 140 | def import(tool, profile, options, &block) 141 | from_tool = options[:tool] || tool 142 | from_profile = options[:profile] || profile 143 | 144 | case from = options[:from] 145 | when String, Symbol 146 | project = Project.load(from.to_s) 147 | store = project ? project.store : nil 148 | else 149 | from = '(self)' 150 | store = self 151 | end 152 | 153 | raise "no configuration found in `#{from}'" unless store 154 | 155 | configs = store.lookup(from_tool, from_profile) 156 | 157 | configs.each do |config| 158 | new_config = config.copy(:tool=>tool, :profile=>profile) 159 | 160 | #new_options = @_options.dup 161 | #new_options[:tool] = tool 162 | #new_options[:profile] = profile 163 | #new_options[:block] = config.block 164 | #new_options[:text] = config.text 165 | 166 | # not so sure about this one 167 | if String === new_config.value 168 | new_config.value += ("\n" + options[:text].to_s) if options[:text] 169 | end 170 | 171 | self << new_config 172 | end 173 | 174 | #if block 175 | # self << Config::Block.new(tool, profile, nil, &block) 176 | #end 177 | end 178 | 179 | end 180 | 181 | end 182 | 183 | -------------------------------------------------------------------------------- /work/deprecated/controller.rb: -------------------------------------------------------------------------------- 1 | module Confection 2 | 3 | # The Controller class is used to encapsulate the various methods of invocation 4 | # that are posible on configuration blocks. It applies those invocations 5 | # across it's set of configurations. 6 | # 7 | class Controller 8 | 9 | include Enumerable 10 | 11 | # 12 | # Initialize new Controller instance. 13 | # 14 | # @param [Object] scope 15 | # 16 | # @param [Array] configs 17 | # 18 | def initialize(scope, *configs) 19 | @scope = scope 20 | @configs = configs 21 | end 22 | 23 | # 24 | # Iterate over each config. 25 | # 26 | def each(&block) 27 | @configs.each(&block) 28 | end 29 | 30 | # 31 | # Number of configs. 32 | # 33 | def size 34 | @configs.size 35 | end 36 | 37 | # 38 | # Execute the configuration code. 39 | # 40 | def call(*args) 41 | result = nil 42 | each do |config| 43 | result = config.call(*args) 44 | end 45 | result 46 | end 47 | 48 | # 49 | # Special configuration call. 50 | # 51 | def configure 52 | result = nil 53 | each do |config| 54 | case config.arity 55 | when 0 56 | exec 57 | when 1 58 | result = config.call(@scope) 59 | end 60 | end 61 | result 62 | end 63 | 64 | # 65 | # Evaluate configuration in the context of the caller. 66 | # 67 | # This is the same as calling: 68 | # 69 | # instance_exec(*args, &config) 70 | # 71 | def exec(*args) 72 | result = nil 73 | each do |config| 74 | if config.respond_to?(:to_proc) 75 | #@scope.extend(config.dsl) # ? 76 | result = @scope.instance_exec(*args, &config) 77 | end 78 | end 79 | result 80 | end 81 | 82 | # 83 | # Load config as script code in context of TOPLEVEL. 84 | # 85 | # This is the same as calling: 86 | # 87 | # main = ::TOPLEVEL_BINDING.eval('self') 88 | # main.instance_exec(*args, &config) 89 | # 90 | def main_exec(*args) 91 | result = nil 92 | main = ::Kernel.eval('self', ::TOPLEVEL_BINDING) # ::TOPLEVEL_BINDING.eval('self') [1.9+] 93 | each do |config| 94 | if config.respond_to?(:to_proc) 95 | #main.extend(config.dsl) 96 | result = main.instance_exec(*args, &config) 97 | end 98 | end 99 | result 100 | end 101 | 102 | # @deprecated Alias for `#main_exec` might be deprecated in future. 103 | alias load main_exec 104 | 105 | # 106 | # Only applicable to script and block configs, this method converts 107 | # a set of code configs into a single block ready for execution. 108 | # 109 | def to_proc 110 | #properties = ::Confection.properties # do these even matter here ? 111 | __configs__ = @configs 112 | block = Proc.new do |*args| 113 | result = nil 114 | #extend dsl # TODO: extend DSL into instance context ? 115 | __configs__.each do |config| 116 | if config.respond_to?(:to_proc) 117 | result = instance_exec(*args, &config) 118 | end 119 | end 120 | result 121 | end 122 | end 123 | 124 | # 125 | # Configurations texts joins together the contents of each 126 | # configuration separated by two newlines (`\n\n`). 127 | # 128 | def to_s 129 | txt = [] 130 | @configs.each do |c| 131 | txt << c.to_s #if c.text 132 | end 133 | txt.join("\n\n") 134 | end 135 | 136 | alias text to_s 137 | 138 | # 139 | # 140 | # 141 | def to_h 142 | hsh = {} 143 | @configs.each do |c| 144 | hsh.merge!(c.to_h) 145 | end 146 | hsh 147 | end 148 | 149 | ## 150 | ## Treat configurations as YAML mappings, load, merge and return. 151 | ## 152 | #def yaml 153 | # @configs.inject({}) do |h, c| 154 | # h.merge(c.yaml) 155 | # end 156 | #end 157 | 158 | # 159 | # Inspection string for controller. 160 | # 161 | def inspect 162 | "#<#{self.class}##{object_id}>" 163 | end 164 | 165 | end 166 | 167 | # 168 | #class NullController 169 | # def exec(*); end 170 | # def call(*); end 171 | # def text; ''; end 172 | # alias to_s text 173 | #end 174 | 175 | end 176 | -------------------------------------------------------------------------------- /work/deprecated/configuration.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # Stores the config definitions. 4 | # 5 | class Configuration 6 | 7 | include Enumerable 8 | 9 | # 10 | # Configuration file pattern. The standard configuration file name is 11 | # `Config.rb`, and that name should be used in most cases. However, 12 | # `.config.rb` can also be use and will take precedence if found. 13 | # Conversely, `config.rb` (lowercase form) can also be used but has 14 | # the least precedence. 15 | # 16 | # Config files looked for in the order or precedence: 17 | # 18 | # * `.config.rb` 19 | # * `Config.rb` 20 | # * `config.rb` 21 | # 22 | CONFIG_FILE = '{.c,C,c}onfig{.rb,}' 23 | 24 | # 25 | # When looking up config file, it one of these is found 26 | # then there is no point to looking further. 27 | # 28 | ROOT_INDICATORS = %w{.git .hg _darcs} #.ruby 29 | 30 | # 31 | # Per library cache. 32 | # 33 | def self.cache 34 | @cache ||= {} 35 | end 36 | 37 | # 38 | # 39 | # 40 | def self.current 41 | name = '(current)' 42 | 43 | return cache[name] if cache.key?(name) 44 | 45 | cache[name] = ( 46 | file = lookup(CONFIG_FILE) 47 | Configuration.new(file) if file 48 | ) 49 | end 50 | 51 | # 52 | # Get configuration for given library +name+. 53 | # 54 | def self.from(name) 55 | name = name.to_s 56 | 57 | return cache[name] if cache.key?(name) 58 | 59 | cache[name] = ( 60 | file = Find.path(CONFIG_FILE, :from=>name).first 61 | Configuration.new(file) if file 62 | ) 63 | end 64 | 65 | # 66 | # Search upward from working directory. 67 | # 68 | def self.lookup(glob, flags=0) 69 | pwd = File.expand_path(Dir.pwd) 70 | home = File.expand_path('~') 71 | while pwd != '/' && pwd != home 72 | if file = Dir.glob(File.join(pwd, glob), flags).first 73 | return file 74 | end 75 | break if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) } 76 | pwd = File.dirname(pwd) 77 | end 78 | return nil 79 | end 80 | 81 | # Initialize new configuration. 82 | # 83 | # @param [String] file 84 | # 85 | def initialize(file) 86 | @file = file 87 | @tool_configs = {} 88 | 89 | parse(@file) 90 | end 91 | 92 | # 93 | # 94 | # 95 | def initialize_copy(other) 96 | @file = other.file 97 | @parser = other.parser 98 | @tool_configs = other.tool_configs 99 | end 100 | 101 | # 102 | # 103 | # 104 | def parser 105 | @parser ||= Parser.new(self) 106 | end 107 | 108 | # 109 | # Parse configuration file. 110 | # 111 | def parse(file) 112 | parser.parse(file) 113 | end 114 | 115 | # 116 | # Iterate over each configurations. 117 | # 118 | def each(&block) 119 | @tool_configs.each(&block) 120 | end 121 | 122 | # 123 | # The number of configurations. 124 | # 125 | # @return [Fixnum] config count 126 | # 127 | def size 128 | @tool_configs.size 129 | end 130 | 131 | # 132 | # Define a new configuration. 133 | # 134 | def config(tool, profile, &block) 135 | tool = tool.to_sym 136 | if @too_configs.key?(tool) 137 | config = @tool_configs[tool] 138 | else 139 | config = ToolConfigs.new(tool) 140 | @tool_configs[tool] = config 141 | end 142 | config.add(profile, &block) 143 | end 144 | 145 | # 146 | # Add a Config instance. 147 | # 148 | def <<(conf) 149 | raise TypeError, "not a Config instance -- `#{conf}'" unless Config === conf 150 | @list << conf 151 | end 152 | 153 | # 154 | # Add a list of Configs. 155 | # 156 | def concat(configs) 157 | configs.each{ |c| self << c } 158 | end 159 | 160 | # 161 | # Lookup configuration by tool and profile name. 162 | # 163 | # @todo Future versions should allow this to handle regex and fnmatches. 164 | # 165 | def lookup(tool, profile=nil) 166 | if profile == '*' 167 | select do |c| 168 | c.tool.to_sym == tool.to_sym 169 | end 170 | else 171 | profile = profile.to_sym if profile 172 | 173 | select do |c| 174 | c.tool.to_sym == tool.to_sym && c.profile == profile 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Reduce configuration by tool name. 181 | # 182 | # @todo Future versions could allow this to handle regex and fnmatches. 183 | # 184 | def [](tool) 185 | copy = dup 186 | copy.list.select! do |c| 187 | c.tool.to_s == tool.to_s 188 | end 189 | copy 190 | end 191 | 192 | # 193 | # Returns list of profiles collected from all configs. 194 | # 195 | def profiles(tool) 196 | names = [] 197 | each do |c| 198 | names << c.profile if c.tool == tool.to_sym 199 | end 200 | names.uniq.compact 201 | end 202 | 203 | # 204 | # Clear configs. 205 | # 206 | def clear! 207 | @list = [] 208 | end 209 | 210 | # 211 | def first 212 | @list.first 213 | end 214 | 215 | # 216 | def last 217 | @list.last 218 | end 219 | 220 | # 221 | # Copy configuration from another project. 222 | # 223 | def import(tool, profile, options) 224 | from_tool = options[:tool] || tool 225 | from_profile = options[:profile] || profile 226 | 227 | case from = options[:from] 228 | when String, Symbol 229 | config = Configuration.from(from) 230 | else 231 | config = self 232 | end 233 | 234 | raise "no configuration found for `#{from}'" unless config 235 | 236 | configs = config.lookup(from_tool, from_profile) 237 | 238 | configs.each do |config| 239 | self << config.copy(:tool=>tool, :profile=>profile) 240 | end 241 | end 242 | 243 | # 244 | def properties 245 | @properties ||= Properties.new 246 | end 247 | 248 | # 249 | def invoke(tool, profile) 250 | each do |c| 251 | c.call if c.match?(tool, profile) 252 | end 253 | end 254 | 255 | protected 256 | 257 | # 258 | # List of configs. 259 | # 260 | def list 261 | @list 262 | end 263 | 264 | end 265 | 266 | end 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RC - Runtime Configuration 2 | 3 | [Homepage](http://rubyworks.github.com/rc) / 4 | [Report Issue](http://github.com/rubyworks/rc/issues) / 5 | [Source Code](http://github.com/rubyworks/rc) 6 | ( [![Build Status](https://secure.travis-ci.org/rubyworks/rc.png)](http://travis-ci.org/rubyworks/rc) 7 | [![Gem Version](https://badge.fury.io/rb/rc.png)](http://badge.fury.io/rb/rc) ) 8 | 9 | 10 | ## About 11 | 12 | RC is a is multi-tenant runtime configuration system for Ruby tools. 13 | It is designed to facilitate Ruby-based configuration for multiple 14 | tools in a single file, and designed to work whether the tool 15 | has built-in support for RC or not. The syntax is simple, universally 16 | applicable, yet flexible. 17 | 18 | RC can be used with any Ruby-based commandline tool or library utilized by 19 | such tool, where there exists some means of configuring it via a toplevel/global 20 | interface; or the tool has been designed to directly support RC, of course. 21 | 22 | 23 | ## Installation 24 | 25 | To use RC with tools that support RC directly, there is likely nothing to 26 | install. Installing the tool should install `rc` via a dependency and 27 | load runtime configurations when the tool is used. 28 | 29 | To use RC with a tool that does not provide built-in support, first install 30 | the RC library, typically via RubyGems: 31 | 32 | gem install rc 33 | 34 | Then add `-rc` to your system's `RUBYOPT` environment variable. 35 | 36 | $ export RUBYOPT='-rc' 37 | 38 | You will want to add that to your `.bashrc`, `.profile` or equivalent configuration 39 | script, so it is always available. 40 | 41 | 42 | ## Instruction 43 | 44 | To use RC in a project create a configuration file called either `.ruby` or `.rubyrc`. 45 | The longer name has precedence if both are present. In this file add configuration blocks 46 | by name of the commandline tool. 47 | 48 | For example, let's demonstrate how we could use this to configure Rake tasks. 49 | (Yes, Rake is not the most obvious choice, since developers are just as happy 50 | to keep using a Rakefile. But using Rake as an example serves to show that it 51 | *can* be done, and also it makes a good tie-in with next example.) 52 | 53 | $ cat .rubyrc 54 | config :rake do 55 | desc 'generate yard docs' 56 | task :yard do 57 | sh 'yard' 58 | end 59 | end 60 | 61 | Now when `rake` is run the tasks defined in this configuration will be available. 62 | 63 | You might wonder why anyone would do this. That's where the *multi-tenancy* 64 | comes into play. Let's add another configuration. 65 | 66 | $ cat .rubyrc 67 | title = "MyApp" 68 | 69 | config :rake do 70 | desc 'generate yard docs' 71 | task :yard do 72 | sh "yard doc --title #{title}" 73 | end 74 | end 75 | 76 | config :qedoc do |doc| 77 | doc.title = "#{title} Demos" 78 | end 79 | 80 | Now we have configuration for both the `rake` tool and the `qedoc` tool in 81 | a single file. Thus we gain the advantage of reducing the file count of our 82 | project while pulling our tool configurations together into one place. 83 | Moreover, these configurations can potentially share settings as demonstrated 84 | here via the `title` local variable. 85 | 86 | Of course, if we want configurations stored in multiple files, that can be done 87 | too. Simple use the `import` method to load them, e.g. 88 | 89 | import 'rc/*.rb' 90 | 91 | RC also supports profiles, either via a `profile` block: 92 | 93 | profile :cov do 94 | config :qed do 95 | require 'simplecov' 96 | ... 97 | end 98 | end 99 | 100 | Or via a keyword parameter: 101 | 102 | config 'qed', profile: 'cov' do 103 | require 'simplecov' 104 | ... 105 | end 106 | 107 | When utilizing the tool, set the profile via an environment variable. 108 | 109 | $ profile=cov qed 110 | 111 | RC also support just `p` as a convenient shortcut. 112 | 113 | $ p=cov qed 114 | 115 | Some tools that support RC out-of-the-box, may support a profile command 116 | line option for specifying the profile. 117 | 118 | $ qed -p cov 119 | 120 | Beyond mere namespacing, some tools might utilize profiles for a more specific 121 | purpose fitting the tool. Consult the tool's documentation for details. 122 | 123 | Configurations can also be pulled in from other gems using the `from` option. 124 | 125 | config :qed, :profile=>'simplecov', :from=>'qed' 126 | 127 | As long as a project includes its `.rubyrc` file (and any imported files) 128 | in it's gem package, it's possible to share configurations in this manner. 129 | 130 | 131 | ## Customization 132 | 133 | A tool can provide dedicated support for RC by loading `rc/api` and using the 134 | `configure` method to define a configuration procedure. For example, 135 | the `detroit` project defines: 136 | 137 | require 'rc/api' 138 | 139 | RC.configure 'detroit' do |config| 140 | if config.command? 141 | Detroit.rc_config << config 142 | end 143 | end 144 | 145 | In our example, when `detroit` is required this configuration will be processed. 146 | The `if config.command?` condition ensures that it only happens if the config's 147 | `command` property matches the current command, i.e. `$0 == 'detroit'`. We see 148 | here that Detroit stores the configuration for later use. When Detroit gets 149 | around to doing it's thing, it checks this `rc_config` setting and evaluates 150 | the configurations found there. 151 | 152 | It is important that RC be required first, ideally before anything else. This 153 | ensures it will pick up all configured features. 154 | 155 | Some tools will want to support a command line option for selecting a 156 | configuration profile. RC has a convenience method to make this very 157 | easy to do. For example, `qed` uses it: 158 | 159 | RC.profile_switch('qed', '-p', '--profile') 160 | 161 | It does not remove the argument from `ARGV`, so the tool's command line option 162 | parser should still account for it. This simply ensures RC will know what the 163 | profile is by setting `ENV['profile']` to the entry following the switch. 164 | 165 | 166 | ## Dependencies 167 | 168 | ### Libraries 169 | 170 | RC depends on the [Finder](http://rubyworks.github.com/finder) library 171 | to provide reliable load path and Gem searching. This is used when importing 172 | configurations from other projects. (It's very much a shame Ruby and RubyGems 173 | does not have this kind of functionality built-in.) 174 | 175 | ### Core Extensions 176 | 177 | RC uses two core extensions, `#to_h`, which applies to a few different 178 | classes, and `String#tabto`. These are *copied* from 179 | [Ruby Facets](http://rubyworks.github.com/facets) to ensure a high 180 | standard of interoperability. 181 | 182 | Both of these methods have been suggested for inclusion in Ruby proper. 183 | Please head over to Ruby Issue Tracker and add your support. 184 | 185 | * http://bugs.ruby-lang.org/issues/749 186 | * http://bugs.ruby-lang.org/issues/6056 187 | 188 | 189 | ## Release Notes 190 | 191 | Please see HISTORY.md file. 192 | 193 | 194 | ## Copyrights 195 | 196 | Copyright (c) 2011 Rubyworks 197 | 198 | Confection is distributable in accordance with the **BSD-2-Clause** license. 199 | 200 | See LICENSE.txt file for details. 201 | 202 | -------------------------------------------------------------------------------- /lib/rc/config.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # Config encapsulates a single configuration entry as defined in a project's 4 | # ruby rc file. A config consists of a possible `command`, `feature`, 5 | # `profile` and `onload` flag. 6 | # 7 | # If `command` or `feature` are nil, then the configuration applies to all 8 | # commands and/or features. 9 | # 10 | # If profile is `nil` it automatically becomes `default`, which is the 11 | # profile used when no profile is specified. 12 | # 13 | class Config 14 | 15 | # 16 | # Initialize Config instance. Config instances are per-configuration, 17 | # which means they are associated with one and only one config entry. 18 | # 19 | # @param [#to_s] target 20 | # The command or feature name. (optional) 21 | # 22 | # @param [Hash] properties 23 | # Any additional properties associated with the config entry. 24 | # 25 | def initialize(*args, &block) 26 | properties = (Hash === args.last ? args.pop : {}) 27 | target = args.first 28 | 29 | @property = {:command=>nil, :feature=>nil, :profile=>'default'} 30 | 31 | if target 32 | @property[:command] = target.to_s 33 | @property[:feature] = target.to_s 34 | end 35 | 36 | @block = block 37 | 38 | properties.each do |k, v| 39 | property(k,v) 40 | end 41 | end 42 | 43 | # 44 | # Get/set property. 45 | # 46 | def property(name, value=ArgumentError) 47 | if value == ArgumentError 48 | get(name) 49 | else 50 | set(name, value) 51 | end 52 | end 53 | 54 | # 55 | # The feature being configured. 56 | # 57 | def feature 58 | @property[:feature] 59 | end 60 | 61 | # 62 | # The name of command being configured. 63 | # 64 | def command 65 | @property[:command] 66 | end 67 | 68 | # @todo Deprecate #tool alias? 69 | alias :tool :command 70 | 71 | # 72 | # The name of the profile to which this configuration belongs. 73 | # 74 | def profile 75 | @property[:profile] 76 | end 77 | 78 | # 79 | # The library from which this configuration derives. 80 | # 81 | def from 82 | @property[:from] 83 | end 84 | 85 | # 86 | # 87 | # 88 | def onload? 89 | @property[:onload] 90 | end 91 | 92 | # 93 | # Most configurations are scripted. In those cases the 94 | # `@block` attributes holds the Proc instance, otherwise 95 | # it is `nil`. 96 | # 97 | attr :block 98 | 99 | # 100 | # IDEA: Presets would be processed first and not require the underlying feature. 101 | # 102 | #def preset? 103 | # @property[:preset] 104 | #end 105 | 106 | # 107 | # The arity of the configuration procedure. 108 | # 109 | # @return [Fixnum] number of arguments 110 | # 111 | def arity 112 | @block ? @block.arity : 0 113 | end 114 | 115 | # 116 | # Require the feature. 117 | # 118 | def require_feature 119 | begin 120 | require feature 121 | rescue LoadError 122 | #warn "No such feature -- `#{feature}'" 123 | end 124 | end 125 | 126 | # 127 | # Call the configuration procedure. 128 | # 129 | def call(*args) 130 | block.call(*args) if block 131 | end 132 | 133 | # 134 | # Returns underlying block. 135 | # 136 | def to_proc 137 | block 138 | end 139 | 140 | ## 141 | ## Convert block into a Hash. 142 | ## 143 | ## @return [Hash] 144 | ## 145 | #def to_h 146 | # HashBuilder.new(&self).to_h 147 | #end 148 | 149 | # 150 | # Copy the configuration with alterations. 151 | # 152 | # @param [Hash] alt 153 | # Alternate values for configuration attributes. 154 | # 155 | # @return [Config] copied config 156 | # 157 | def copy(alt={}) 158 | tool = @property[:feature] || @property[:command] 159 | copy = self.class.new(tool, @property.dup, &@block) 160 | alt.each do |k,v| 161 | copy.property(k, v) 162 | end 163 | copy 164 | end 165 | 166 | # 167 | # Match config against tool and/or profile names. 168 | # 169 | # @return [Boolean] 170 | # 171 | def match?(*args) 172 | props = Hash === args.last ? args.pop : {} 173 | 174 | if target = args.shift 175 | props[:command] = target.to_s 176 | props[:feature] = target.to_s 177 | end 178 | 179 | if props[:profile] 180 | props[:profile] = (props[:profile] || :default).to_s 181 | end 182 | 183 | props.each do |k,v| 184 | pv = property(k) 185 | return false unless (pv.nil? || pv == v) 186 | end 187 | 188 | return true 189 | end 190 | 191 | # 192 | # Does the given `feature` match the config's feature? 193 | # 194 | # @return [Boolean] 195 | # 196 | def feature?(feature=RC.current_feature) 197 | return true if self.feature.nil? 198 | self.feature == feature.to_s 199 | end 200 | 201 | # 202 | # Does the given `command` match the config's command? 203 | # 204 | # @return [Boolean] 205 | # 206 | def command?(command=RC.current_command) 207 | return true if self.command.nil? 208 | self.command == command.to_s 209 | end 210 | alias_method :tool, :command? 211 | 212 | # 213 | # Does the given `profile` match the config's profile? 214 | # 215 | # @return [Boolean] 216 | # 217 | def profile?(profile=RC.current_profile) 218 | self.profile == (profile || 'default').to_s 219 | end 220 | 221 | # 222 | # @todo The feature argument might not be needed. 223 | # 224 | #def configure(feature) 225 | # return false if self.feature != feature 226 | # 227 | # if setup = RC.setup(feature) 228 | # setup.call(self) 229 | # else 230 | # block.call if command? 231 | # end 232 | #end 233 | 234 | ## 235 | ## Ruby 1.9 defines #inspect as #to_s, ugh. 236 | ## 237 | #def inspect 238 | # "#<#{self.class.name}:#{object_id} @tool=%s @profile=%s>" % [tool.inspect, profile.inspect] 239 | #end 240 | 241 | # 242 | # Does the configuration apply? 243 | # 244 | # @return [Boolean] 245 | # 246 | def apply_to_command? 247 | return false if onload? 248 | return false unless command? if command 249 | return false unless profile? if profile 250 | return true 251 | end 252 | alias_method :apply_to_tool?, :apply_to_command? 253 | 254 | def apply_to_feature? 255 | return false unless onload? 256 | return false unless command? if command 257 | return false unless profile? if profile 258 | return true 259 | end 260 | 261 | private 262 | 263 | # 264 | # Get property. 265 | # 266 | def get(name) 267 | @property[name.to_sym] 268 | end 269 | 270 | # 271 | # Set property. 272 | # 273 | def set(name, value) 274 | case name.to_sym 275 | when :command 276 | self.command = value 277 | when :tool # deprecate ? 278 | self.command = value 279 | when :feature 280 | self.feature = value 281 | when :profile 282 | self.profile = value 283 | else 284 | @property[name.to_sym] = value 285 | end 286 | end 287 | 288 | # 289 | def command=(name) 290 | @property[:command] = name ? name.to_s : nil 291 | end 292 | 293 | # 294 | def feature=(path) 295 | @property[:feature] = path ? path.to_s : nil 296 | end 297 | 298 | # 299 | def profile=(name) 300 | @property[:profile] = name ? name.to_s : 'default' 301 | end 302 | 303 | end 304 | 305 | end 306 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /lib/rc/interface.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # External requirements. 4 | require 'yaml' 5 | require 'finder' 6 | #require 'loaded' 7 | 8 | # Internal requirements. 9 | require 'rc/constants' 10 | require 'rc/required' 11 | require 'rc/core_ext' 12 | require 'rc/config' 13 | require 'rc/configuration' 14 | require 'rc/dsl' 15 | #require 'rc/config_filter' 16 | require 'rc/properties' 17 | require 'rc/setup' 18 | 19 | # The Interface module extends the RC module. 20 | # 21 | # A tool can control RC configuration by loading `rc/api` and calling the 22 | # `RC.configure` method with a block that handles the configuration 23 | # for the feature as provided by a project's config file. 24 | # 25 | # The block will often need to be conditioned on the current profile and/or the 26 | # the current command. This is easy enough to do with #profile? and #command? 27 | # methods. 28 | # 29 | # For example, is RSpec wanted to support RC out-of-the-box, the code would 30 | # look something like: 31 | # 32 | # require 'rc/api' 33 | # 34 | # RC.configure('rspec') do |config| 35 | # if config.profile? 36 | # RSpec.configure(&config) 37 | # end 38 | # end 39 | # 40 | module Interface 41 | 42 | # 43 | # The tweaks directory is where special augementation script reside 44 | # the are used to adjust behavior of certain popular tools to work 45 | # with RC that would not otherwise do so. 46 | # 47 | TWEAKS_DIR = File.dirname(__FILE__) + '/tweaks' 48 | 49 | # 50 | # Library configuration cache. Since configuration can be imported from 51 | # other libraries, we keep a cache for each library. 52 | # 53 | # @return [Hash] 54 | # 55 | def cache 56 | @cache ||= {} 57 | end 58 | 59 | # 60 | # Clear the library configuration cache. This is mostly used 61 | # for testing. 62 | # 63 | def clear! 64 | @cache = {} 65 | end 66 | 67 | # 68 | # Load library configuration for a given +gem+. If no +gem+ is 69 | # specified then the current project's configuration is used. 70 | # 71 | # @return [Configuration] 72 | # 73 | def configuration(gem=nil) 74 | key = gem ? gem.to_s : nil #Dir.pwd 75 | cache[key] ||= Configuration.load(:from=>gem) 76 | end 77 | 78 | # 79 | # Return a list of names of defined profiles for a given +tool+. 80 | # 81 | # @param [#to_sym] tool 82 | # Tool for which lookup defined profiles. If none given 83 | # the current tool is used. 84 | # 85 | # @param [Hash] opts 86 | # Options for looking up profiles. 87 | # 88 | # @option opts [#to_s] :gem 89 | # Name of library from which to load the configuration. 90 | # 91 | # @example 92 | # profile_names(:qed) 93 | # 94 | def profile_names(tool=nil, opts={}) 95 | if Hash === tool 96 | opts, tool = tool, nil 97 | end 98 | 99 | tool = tool || current_tool 100 | gem = opts[:from] 101 | 102 | configuration(gem).profile_names(tool) 103 | end 104 | 105 | # 106 | # Get current tool. 107 | # 108 | # @todo Not so sure `ENV['tool']` is a good idea. 109 | # 110 | def current_tool 111 | File.basename(ENV['tool'] || $0) 112 | end 113 | 114 | alias current_command current_tool 115 | 116 | # 117 | # Set current tool. 118 | # 119 | def current_tool=(tool) 120 | ENV['tool'] = tool.to_s 121 | end 122 | 123 | alias current_command= current_tool= 124 | 125 | # 126 | # Get current profile. 127 | # 128 | def current_profile 129 | ENV['profile'] || ENV['p'] || 'default' 130 | end 131 | 132 | # 133 | # Set current profile. 134 | # 135 | def current_profile=(profile) 136 | if profile 137 | ENV['profile'] = profile.to_s 138 | else 139 | ENV['profile'] = nil 140 | end 141 | end 142 | 143 | # 144 | # Properties of the current project. These can be used in a project's config file 145 | # to make configuration more interchangeable. Presently project properties are 146 | # gathered from .index YAML or .gemspec. 147 | # 148 | # It's important to note that properties are not per-gem. Rather they are global 149 | # and belong only the current project. 150 | # 151 | def properties 152 | $properties ||= Properties.new 153 | end 154 | 155 | # 156 | # Configure current tool. 157 | # 158 | def configure(tool=nil) 159 | configure_tool(tool || RC.current_tool) 160 | end 161 | 162 | # 163 | # Define a custom configuration handler. 164 | # 165 | # If the current tool matches the given tool, and autoconfiguration is not being used, 166 | # then configuration is applied immediately. 167 | # 168 | def define_config(tool, options={}, &block) 169 | tool = tool.to_s 170 | 171 | @setup ||= {} 172 | 173 | if block 174 | @setup[tool] = Setup.new(tool, options, &block) 175 | 176 | # REMOVED: Doing this automatically made it impossible for tools to set the profile. 177 | #if tool == current_tool 178 | # configure_tool(tool) unless autoconfig? 179 | #end 180 | end 181 | 182 | @setup[tool] 183 | end 184 | 185 | # 186 | # Original name of `#define_config`. 187 | # 188 | alias :setup :define_config 189 | 190 | # 191 | # Remove a configuration setup. 192 | # 193 | # NOTE: This is probably a YAGNI. 194 | # 195 | def undefine_config(tool) 196 | @setup[tool.to_s] = false 197 | end 198 | 199 | alias :unset :undefine_config 200 | 201 | # 202 | # Set current profile via ARGV switch. This is done immediately, 203 | # setting `ENV['profile']` to the switch value if this setup is 204 | # for the current commandline tool. The reason it is done immediately, 205 | # rather than assigning it in bootstrap, is b/c option parsers somtimes 206 | # consume ARGV as they parse it, and by then it would too late. 207 | # 208 | # @example 209 | # RC.profile_switch('qed', '-p', '--profile') 210 | # 211 | def profile_switch(command, *switches) 212 | return unless command.to_s == RC.current_command 213 | 214 | switches.each do |switch, envar| 215 | if index = ARGV.index(switch) 216 | self.current_profile = ARGV[index+1] 217 | elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ } 218 | value = $1 219 | value = value[1..-2] if value.start_with?('"') && value.end_with?('"') 220 | value = value[1..-2] if value.start_with?("'") && value.end_with?("'") 221 | self.currrent_profile = value 222 | end 223 | end 224 | end 225 | 226 | # 227 | # Set enviroment variable(s) to command line switch value(s). This is a more general 228 | # form of #profile_switch and will probably not get much use in this context. 229 | # 230 | # @example 231 | # RC.switch('qed', '-p'=>'profile', '--profile'=>'profile') 232 | # 233 | def switch(command, switches={}) 234 | return unless command.to_s == RC.current_command 235 | 236 | switches.each do |switch, envar| 237 | if index = ARGV.index(switch) 238 | ENV[envar] = ARGV[index+1] 239 | elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ } 240 | value = $1 241 | value = value[1..-2] if value.start_with?('"') && value.end_with?('"') 242 | value = value[1..-2] if value.start_with?("'") && value.end_with?("'") 243 | ENV[envar] = value 244 | end 245 | end 246 | end 247 | 248 | # 249 | # 250 | # 251 | def autoconfig? 252 | @autoconfig 253 | end 254 | 255 | protected 256 | 257 | # 258 | # 259 | # 260 | def autoconfigure 261 | @autoconfig = true 262 | configure_tool(current_tool) 263 | end 264 | 265 | private 266 | 267 | # 268 | # Configure current commnad. 269 | # 270 | def configure_tool(tool) 271 | tweak(tool) 272 | 273 | configs = RC.configuration[tool] 274 | 275 | return unless configs 276 | 277 | configs.each do |config| 278 | next unless config.apply_to_tool? 279 | config.require_feature if autoconfig? 280 | setup = setup(tool) 281 | next if setup == false # deactivated 282 | setup ? setup.call(config) : config.call 283 | end 284 | end 285 | 286 | # 287 | # Setup the system. 288 | # 289 | def bootstrap 290 | @bootstrap ||= ( 291 | properties # prime global properties 292 | bootstrap_require 293 | true 294 | ) 295 | end 296 | 297 | # 298 | # Tap into require via loaded hook. The hook is only 299 | # triggered on #require, not #load. 300 | # 301 | def bootstrap_require 302 | def RC.required(feature) 303 | config = RC.configuration[feature] 304 | if config 305 | config.each do |config| 306 | next unless config.apply_to_feature? 307 | config.call 308 | end 309 | end 310 | super(feature) if defined?(super) 311 | end 312 | end 313 | 314 | # 315 | # 316 | # 317 | def tweak(command) 318 | tweak = File.join(TWEAKS_DIR, command + '.rb') 319 | if File.exist?(tweak) 320 | require tweak 321 | end 322 | end 323 | 324 | ## 325 | ## IDEA: Preconfigurations occur before other command configs and 326 | ## do not require feature. 327 | ## 328 | #def preconfigure(options={}) 329 | # tool = options[:tool] || current_tool 330 | # profile = options[:profile] || current_profile 331 | # 332 | # preconfiguration.each do |c| 333 | # c.call if c.match?(tool, profile) 334 | # end 335 | #end 336 | end 337 | 338 | # The Interface extends RC module. 339 | extend Interface 340 | 341 | # Prep the system. 342 | bootstrap 343 | 344 | end 345 | -------------------------------------------------------------------------------- /lib/rc/configuration.rb: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | # The Configuration class encapsulates a project/library's tool 4 | # configuration. 5 | # 6 | class Configuration < Module 7 | 8 | # 9 | # Configuration is Enumerable. 10 | # 11 | include Enumerable 12 | 13 | # 14 | # Runtime configuration file glob pattern. 15 | # 16 | CONFIG_FILE = '.ruby{rc,}' 17 | 18 | class << self 19 | # 20 | # Load configuration file from local project or other gem. 21 | # 22 | # @param options [Hash] Load options. 23 | # 24 | # @option options [String] :from 25 | # Name of gem or library. 26 | # 27 | def load(options={}) 28 | if options[:from] 29 | load_from(options[:from]) 30 | else 31 | load_local() 32 | end 33 | end 34 | 35 | # 36 | # Load configuration from another gem. 37 | # 38 | def load_from(gem) 39 | files = Find.path(CONFIG_FILE, :from=>gem) 40 | file = files.find{ |f| File.file?(f) } 41 | new(*file) 42 | 43 | #if file 44 | # paths = [file] 45 | #else 46 | # #paths = Find.path(CONFIG_DIR + '/**/*', :from=>gem) 47 | #end 48 | #files = paths.select{ |path| File.file?(path) } 49 | #new(*files) 50 | end 51 | 52 | # 53 | # Load configuration for current project. 54 | # 55 | def load_local 56 | files = lookup(CONFIG_FILE) 57 | file = files.find{ |f| File.file?(f) } 58 | new(*file) 59 | 60 | #if file 61 | # paths = [file] 62 | #else 63 | # dir = lookup(CONFIG_DIR).find{ |f| File.directory?(f) } 64 | # paths = dir ? Dir.glob(File.join(dir, '**/*')) : [] 65 | #end 66 | #files = paths.select{ |path| File.file?(path) } 67 | end 68 | 69 | private 70 | 71 | # 72 | # Search upward from working directory. 73 | # 74 | def lookup(glob, flags=0) 75 | pwd = File.expand_path(Dir.pwd) 76 | home = File.expand_path('~') 77 | while pwd != '/' && pwd != home 78 | paths = Dir.glob(File.join(pwd, glob), flags) 79 | return paths unless paths.empty? 80 | break if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) } 81 | pwd = File.dirname(pwd) 82 | end 83 | return [] 84 | end 85 | 86 | end 87 | 88 | # 89 | # Initialize new Configuration object. 90 | # 91 | # @param [Array] files 92 | # Configuration files (optional). 93 | # 94 | def initialize(*files) 95 | @files = files 96 | 97 | @_config = Hash.new{ |h,k| h[k]=[] } 98 | #@_onload = Hash.new{ |h,k| h[k]=[] } 99 | 100 | load_files(*files) 101 | end 102 | 103 | # 104 | # Import other runtime configuration files. 105 | # 106 | # @param [String] glob 107 | # File pattern of configutation files to load. 108 | # 109 | # @param opts [Hash] Load options. 110 | # 111 | # @option opts [String] :from 112 | # Name of gem or library. 113 | # 114 | # @option opts [Boolean] :first 115 | # Only match a single file. 116 | # 117 | def import(glob, opts={}) 118 | paths = [] 119 | 120 | glob = glob + '**/*' if glob.end_with?('/') 121 | 122 | if from = opts[:from] 123 | paths = Find.path(glob, :from=>from) 124 | else 125 | if glob.start_with?('/') 126 | if root = lookup_root 127 | glob = File.join(root, glob) 128 | else 129 | raise "no project root for #{glob}" unless root 130 | end 131 | end 132 | paths = Dir.glob(glob) 133 | end 134 | 135 | paths = paths.select{ |path| File.file?(path) } 136 | paths = paths[0..0] if opts[:first] 137 | 138 | load_files(*paths) 139 | 140 | paths.empty? ? nil : paths 141 | end 142 | 143 | # 144 | # Load configuration files. 145 | # 146 | # TODO: begin/rescue around instance_eval? 147 | # 148 | # TODO: Does each file need it's own DSL instance? 149 | # 150 | def load_files(*files) 151 | dsl = DSL.new(self) 152 | files.each do |file| 153 | next unless File.file?(file) # TODO: warn ? 154 | #begin 155 | dsl.instance_eval(File.read(file), file) 156 | #rescue => e 157 | # raise e if $DEBUG 158 | # warn e.message 159 | #end 160 | end 161 | end 162 | 163 | alias :load_file :load_files 164 | 165 | # 166 | # Evaluate configuration code. 167 | # 168 | # @yieldparm cfg 169 | # Configuration code block. 170 | # 171 | def evaluate(*args, &cfg) 172 | dsl = DSL.new(self) 173 | dsl.instance_eval(*args, &cfg) 174 | end 175 | 176 | # 177 | # Configure a commandline tool or feature. 178 | # 179 | # @param [Symbol] target 180 | # The name of the command or feature to configure. 181 | # 182 | # @param [Hash] opts 183 | # Configuration options. 184 | # 185 | # @options opts [String] :command 186 | # Name of command, or false if not a command configuration. 187 | # 188 | # @options opts [String] :feature 189 | # Alternate require if differnt than command name. 190 | # 191 | # @options opts [String] :from 192 | # Library from which to import configuration. 193 | # 194 | # @example 195 | # profile :coverage do 196 | # config :qed, :from=>'qed' 197 | # end 198 | # 199 | def config(target, options={}, &block) 200 | #options[:profile] = (options[:profile] || 'default').to_s 201 | #options[:command] = command.to_s unless options.key?(:command) 202 | #options[:feature] = command.to_s unless options.key?(:feature) 203 | #command = options[:command].to_s 204 | 205 | # IDEA: other import options such as local file? 206 | 207 | configs_from(options).each do |c| 208 | @_config[target.to_s] << c.copy(options) 209 | end 210 | 211 | return unless block 212 | 213 | @_config[target.to_s] << Config.new(target, options, &block) 214 | end 215 | 216 | =begin 217 | # 218 | # 219 | # 220 | def onload(feature, options={}, &block) 221 | #options[:profile] = (options[:profile] || 'default').to_s 222 | 223 | #options[:feature] = feature.to_s unless options.key?(:feature) 224 | #options[:command] = feature.to_s unless options.key?(:command) 225 | 226 | feature = options[:feature].to_s 227 | 228 | # IDEA: what about local file import? 229 | configs_from(options).each do |c| 230 | @_onload[feature] << c.copy(options) 231 | end 232 | 233 | return unless block 234 | 235 | @_onload[feature] << Config.new(feature, options, &block) 236 | end 237 | =end 238 | 239 | # 240 | # 241 | # 242 | def [](feature) 243 | @_config[feature.to_s] 244 | end 245 | 246 | # 247 | # Iterate over each feature config. 248 | # 249 | # @example 250 | # confgiuration.each do |feature, configs| 251 | # configs.each do |config| 252 | # ... 253 | # end 254 | # end 255 | # 256 | def each(&block) 257 | @_config.each(&block) 258 | end 259 | 260 | # 261 | # The number of feature configs. 262 | # 263 | def size 264 | @_config.size 265 | end 266 | 267 | # 268 | # Get a list of the defined configurations. 269 | # 270 | # @return [Array] List of all defined configurations. 271 | # 272 | def to_a 273 | list = [] 274 | @_config.each do |feature, configs| 275 | list.concat(configs) 276 | end 277 | list 278 | end 279 | 280 | # 281 | # @deprecated 282 | # 283 | alias :configurations :to_a 284 | 285 | # 286 | # Get a list of defined profiles names for the given +command+. 287 | # use the current command if no +command+ is given. 288 | # 289 | def profile_names(command=nil) 290 | command = command || RC.current_command 291 | 292 | list = [] 293 | @_config.each do |feature, configs| 294 | configs.each do |c| 295 | if c.command?(command) 296 | list << c.profile 297 | end 298 | end 299 | end 300 | list.uniq 301 | end 302 | 303 | #def inspect 304 | # "#" 305 | #end 306 | 307 | private 308 | 309 | # TODO: other import options such as local file? 310 | 311 | # 312 | # 313 | # 314 | def configs_from(options) 315 | from = options[:from] 316 | list = [] 317 | 318 | return list unless from 319 | 320 | if Array === from 321 | from_name, from_opts = *from 322 | else 323 | from_name, from_opts = from, {} 324 | end 325 | 326 | from_config = RC.configuration(from_name) 327 | 328 | from_opts[:feature] = options[:feature] unless from_opts.key?(:feature) if options[:feature] 329 | from_opts[:command] = options[:command] unless from_opts.key?(:command) if options[:command] 330 | from_opts[:profile] = options[:profile] unless from_opts.key?(:profile) if options[:profile] 331 | 332 | from_opts[:feature] = from_opts[:feature].to_s if from_opts[:feature] 333 | from_opts[:command] = from_opts[:command].to_s if from_opts[:command] 334 | from_opts[:profile] = from_opts[:profile].to_s if from_opts[:profile] 335 | 336 | from_config.each do |ftr, confs| 337 | confs.each_with_index do |c, i| 338 | if c.match?(from_opts) 339 | list << c.copy(options) 340 | end 341 | end 342 | end 343 | 344 | list 345 | end 346 | 347 | # 348 | # Search upward from project root directory. 349 | # 350 | def lookup_root 351 | pwd = File.expand_path(Dir.pwd) 352 | home = File.expand_path('~') 353 | while pwd != '/' && pwd != home 354 | return pwd if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) } 355 | pwd = File.dirname(pwd) 356 | end 357 | return nil 358 | end 359 | 360 | end 361 | 362 | end 363 | --------------------------------------------------------------------------------