├── var ├── name ├── title ├── version ├── created ├── organizations ├── authors ├── copyrights ├── summary ├── requirements ├── repositories ├── description └── resources ├── lib ├── dotruby.yml ├── dotruby │ ├── connects.rb │ ├── connects │ │ ├── rspec.rb │ │ └── rubytest.rb │ ├── module.rb │ ├── tweaks │ │ └── rake.rb │ ├── constant.rb │ ├── configs │ │ ├── feature.rb │ │ ├── base.rb │ │ ├── command.rb │ │ └── constant.rb │ ├── profile.rb │ ├── api.rb │ ├── api-old.rb │ ├── dsl-old.rb │ └── dsl.rb └── dotruby.rb ├── Gemfile ├── spec ├── helper.rb └── spec_api.rb ├── demo ├── fixture │ └── example.rb ├── applique │ ├── require.rb │ └── battery.rb └── 99_battery │ └── battery.md ├── .gitignore ├── .travis.yml ├── .yardopts ├── HISTORY.md ├── .opts ├── .ruby ├── .index ├── LICENSE.txt ├── .gemspec └── README.md /var/name: -------------------------------------------------------------------------------- 1 | dotruby 2 | -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | DotRuby 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /lib/dotruby.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2013-01-19 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /spec/helper.rb: -------------------------------------------------------------------------------- 1 | require 'dotruby/api' 2 | -------------------------------------------------------------------------------- /var/organizations: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - trans 3 | -------------------------------------------------------------------------------- /demo/fixture/example.rb: -------------------------------------------------------------------------------- 1 | module Example 2 | end 3 | 4 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - 2013 Rubyworks (BSD-2-Clause) 3 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Transparent Runtime Configuration for Ruby 2 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - qed (test) 3 | - ae (test) 4 | 5 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git@github.com:rubyworks/dotruby.git 3 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | DotRuby is a universal runtime configuration system for Ruby tools. 2 | -------------------------------------------------------------------------------- /lib/dotruby/connects.rb: -------------------------------------------------------------------------------- 1 | require_relative 'connects/rspec' 2 | require_relative 'connects/rubytest' 3 | 4 | -------------------------------------------------------------------------------- /spec/spec_api.rb: -------------------------------------------------------------------------------- 1 | # You need tests! No shit, Sherlock. 2 | 3 | describe DotRuby do 4 | 5 | end 6 | 7 | -------------------------------------------------------------------------------- /lib/dotruby/connects/rspec.rb: -------------------------------------------------------------------------------- 1 | DotRuby.connect(:constant=>:RSpec, :command=>'rspec', :feature=>'rspec/core') 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | doc 4 | log 5 | pkg 6 | tmp 7 | web 8 | QED.rdoc 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /lib/dotruby/connects/rubytest.rb: -------------------------------------------------------------------------------- 1 | DotRuby.connect(:constant=>:Test, :command=>'rubytest', :feature=>'rubytest') 2 | 3 | -------------------------------------------------------------------------------- /demo/applique/require.rb: -------------------------------------------------------------------------------- 1 | # test assertions 2 | require 'ae' 3 | 4 | # what we are testing 5 | require 'dotruby/api' 6 | 7 | -------------------------------------------------------------------------------- /.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 | 9 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title DotRuby 2 | --readme README.md 3 | --output-dir doc 4 | --protected 5 | --private 6 | lib 7 | - 8 | *.md 9 | *.txt 10 | 11 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.1.0 / 2013-01-23 4 | 5 | First release of DotRuby. 6 | 7 | Changes: 8 | 9 | * It's Your Birthday! 10 | 11 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/dotruby 3 | code: http://github.com/rubyworks/dotruby 4 | mail: http://groups.google.com/group/rubyworks-mailinglist 5 | 6 | -------------------------------------------------------------------------------- /.opts: -------------------------------------------------------------------------------- 1 | yardoc 2 | yard doc 3 | --title DotRuby 4 | --readme README.md 5 | --output-dir doc 6 | --protected 7 | --private 8 | lib 9 | - 10 | *.md 11 | *.txt 12 | 13 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | puts "DotRuby in Action!" 2 | 3 | RSpec.configure do |c| 4 | p "Hello from RSpec Config!" 5 | end 6 | 7 | Test.run do 8 | p "HELLO!" 9 | end 10 | 11 | Test.run('coverage') do 12 | p "HELLO AGAIN!" 13 | end 14 | 15 | Rake.file do 16 | task :default => [:test] 17 | 18 | desc "Run unit tests" 19 | task :test do 20 | sh 'rubytest' 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /demo/99_battery/battery.md: -------------------------------------------------------------------------------- 1 | # Battery 2 | 3 | ## Basic example 4 | 5 | Given a `.ruby` file containing: 6 | 7 | Example.configure do 8 | $result = "Example configured!" 9 | end 10 | 11 | When we run the command: 12 | 13 | $ example 14 | 15 | Which requires the file `example.rb`. 16 | 17 | Then the result should contain: 18 | 19 | Example configured! 20 | 21 | -------------------------------------------------------------------------------- /lib/dotruby/module.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | # By extending Module with a configure class method we ensure 3 | # that we can always configure a program through this interface, 4 | # even if it lacks any other interface with which to do so. 5 | # 6 | # This is a bit experimental. 7 | # 8 | def configure(&block) 9 | block.call # module_eval(&block) 10 | end 11 | end 12 | 13 | -------------------------------------------------------------------------------- /lib/dotruby/tweaks/rake.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | module Rake 4 | 5 | # TODO: While probably a complete YAGNI, how might we load a .ruby rake config into a Rakefile? 6 | 7 | # TODO: rake -T doesn't work, why? 8 | 9 | # Use this method to add tasks to Rake. 10 | def self.file(&config) 11 | Module.new do 12 | extend Rake::DSL 13 | module_eval(&config) 14 | end 15 | end 16 | 17 | class Application 18 | remove_const(:DEFAULT_RAKEFILES) 19 | DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb', '.ruby'] 20 | #DEFAULT_RAKEFILES << '.ruby' 21 | end 22 | 23 | def self.load_rakefile(path) 24 | case File.basename(path) 25 | when '.ruby' 26 | # do nothing, DotRuby will do it 27 | else 28 | load(path) 29 | end 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /demo/applique/battery.rb: -------------------------------------------------------------------------------- 1 | When 'iven a `.ruby` file containing' do |text| 2 | $:.unshift File.expand_path('../fixture', File.dirname(__FILE__)) 3 | 4 | ENV.replace({}) 5 | ARGV.replace([]) 6 | 7 | # set configuration 8 | $test_config = DotRuby::DSL.new(:text=>text) 9 | end 10 | 11 | When 'we run the command' do |text| 12 | command = text.strip.sub('$ ','').strip 13 | 14 | args = command.split(/\s+/) 15 | 16 | while (/=/ =~ args.first) 17 | name, value = args.shift.split('=') 18 | ENV[name] = value 19 | end 20 | 21 | #ENV['exec'] = args.shift 22 | $0 = args.shift 23 | ARGV.replace(args) 24 | 25 | DotRuby.configure!($test_config) 26 | end 27 | 28 | When 'requires the file `/(.*?)/`' do |file| 29 | require file 30 | end 31 | 32 | When 'the result should contain' do |text| 33 | $result.to_s.assert.include?(text.strip) 34 | end 35 | 36 | -------------------------------------------------------------------------------- /lib/dotruby.rb: -------------------------------------------------------------------------------- 1 | # DotRuby - Universal Runtime Configuration for Ruby Tools 2 | # 3 | # Q. What if a program needs to be configured, but it does not provide 4 | # an interface via a constant? For example, what if it uses a global 5 | # variable? 6 | # 7 | # A. Most likely the program has a toplevel namespace module. You 8 | # can always call module_eval to handle it if there is no other means. 9 | # You ccould alos define a *make-shift* extension method on it that does 10 | # the dirty deed. If there is no such module, just make a *make-shift* 11 | # module for it too. 12 | # 13 | # module ProgramWithoutNamespace 14 | # def self.configure(settings) 15 | # $dirty_program_settings = settings 16 | # end 17 | # end 18 | # 19 | # We call this *tweaking*. 20 | 21 | require 'dotruby/api' 22 | 23 | DotRuby.configure! 24 | 25 | # DotRuby Copyright (c) 2012 Rubyworks. All rights reserved. (BSD-2-Clause License) 26 | -------------------------------------------------------------------------------- /.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 | - name: Rubyworks 11 | requirements: 12 | - groups: 13 | - test 14 | development: true 15 | name: qed 16 | - groups: 17 | - test 18 | development: true 19 | name: ae 20 | conflicts: [] 21 | alternatives: [] 22 | resources: 23 | - type: home 24 | uri: http://rubyworks.github.com/dotruby 25 | label: Homepage 26 | - type: code 27 | uri: http://github.com/rubyworks/dotruby 28 | label: Source Code 29 | - type: mail 30 | uri: http://groups.google.com/group/rubyworks-mailinglist 31 | label: Mailing List 32 | repositories: 33 | - name: upstream 34 | scm: git 35 | uri: git@github.com:rubyworks/dotruby.git 36 | categories: [] 37 | copyrights: 38 | - holder: Rubyworks 39 | year: '2013' 40 | license: BSD-2-Clause 41 | customs: [] 42 | paths: 43 | lib: 44 | - lib 45 | created: '2013-01-19' 46 | summary: Transparent Runtime Configuration for Ruby 47 | title: DotRuby 48 | version: 0.2.0 49 | name: dotruby 50 | description: DotRuby is a universal runtime configuration system for Ruby tools. 51 | date: '2013-01-26' 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /lib/dotruby/constant.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | # The virtual constant class is a simple recorder. Every method 4 | # called on it is recorded for later recall on the actual constant 5 | # given by name. 6 | # 7 | # To invoke the recordeed calls on the real constant use `to_proc.call`. 8 | # 9 | class VirtualConstant < BasicObject 10 | 11 | # Initialize configuration. 12 | # 13 | # @param [Symbol] Name of constant. 14 | # 15 | def initialize(name) 16 | @name = name 17 | @calls = [] 18 | end 19 | 20 | # 21 | def name 22 | @name 23 | end 24 | 25 | # An inspection string for the Configuration class. 26 | # 27 | # @return [String] 28 | def inspect 29 | "#" 30 | end 31 | 32 | # 33 | def method_missing(s, *a, &b) 34 | @calls << [s, a, b] 35 | end 36 | 37 | # TODO: Add support for const_missing? But these need 38 | # to be recalled in order with method_missing. 39 | 40 | #def const_missing(name) 41 | # #@calls << Configuration.new("#{@name}::#{name}") 42 | # @calls << VirtualConstant.new("#{@name}::#{name}") 43 | #end 44 | 45 | # Create a Proc instance that will recall the method 46 | # invocations on the actual constant. 47 | # 48 | # @return [Proc] 49 | def to_proc 50 | name, calls = @name, @calls 51 | ::Proc.new do 52 | const = ::Object.const_get(name) 53 | calls.each do |s, a, b| 54 | const.public_send(s, *a, &b) 55 | end 56 | end 57 | end 58 | 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /lib/dotruby/configs/feature.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | module Configuration 4 | 5 | # A Feature configuration is the simplist type of configuration. 6 | # It is applied whenever a paticular feature has been loaded. 7 | # 8 | # IMPORTANT: This kind of configuration may be deprecated b/c it is 9 | # so low-level and broad. 10 | # 11 | class Feature < Base 12 | # Setup new Feature instance. 13 | # 14 | # @param [String] fname 15 | # The feature's name. 16 | # 17 | def initialize(fname, options={}, &block) 18 | @feature = fname 19 | @block = block 20 | end 21 | 22 | # The feature's name. 23 | # 24 | # @return [Sting] The feature name. 25 | def feature 26 | @feature 27 | end 28 | 29 | # A feature is prematched if it has already been required. 30 | # 31 | # @param [Hash] state 32 | # @return [Boolean] 33 | def prematch?(state) 34 | previously_loaded?(feature) 35 | end 36 | 37 | # A feature is postmatched once the feature has been required. 38 | # 39 | # @param [Hash] state 40 | # @option state [String] :exec 41 | # @option state [Array] :argv 42 | # @return [Boolean] 43 | def postmatch?(state) 44 | state[:exec] ||= DotRuby.exec 45 | state[:argv] ||= DotRuby.argv 46 | matching_feature?(feature) 47 | end 48 | 49 | # Run configuration procedure. 50 | # 51 | # TODO: instance_eval at toplevel ? 52 | def call 53 | @block.call 54 | end 55 | 56 | end 57 | 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /lib/dotruby/configs/base.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | module Configuration 4 | 5 | class Base 6 | 7 | # Check if a command matches against criteria. 8 | # 9 | # @return [Boolean] 10 | def matching_command?(command, state={}) 11 | exe, *argv = *command.split(/\s+/) 12 | exe == state[:exec] && argv == state[:argv][0,argv.size] 13 | end 14 | 15 | # Check if a feature matches against criteria. 16 | # 17 | # @todo Need to be more versitile with extensions. 18 | # 19 | # @return [Boolean] 20 | def matching_feature?(feature, state={}) 21 | feature == state[:feature].to_s.chomp('.rb') 22 | end 23 | 24 | # Check if a feature has been require already. 25 | # 26 | # FIXME: This is not a perfect solution. Is it even possible with Ruby? 27 | # Since Ruby provides no way to ask if a feature has been required or not, 28 | # we can only condition application of pre-extisting constants on a 29 | # matching command. 30 | # 31 | # @return Boolean 32 | def previously_loaded?(feature) 33 | feature_path = feature + '.rb' unless feature.end_with?('.rb') 34 | $LOADED_FEATURES.any? do |path| 35 | path.end_with?(feature_path) 36 | end 37 | end 38 | 39 | # Lookup command feature. 40 | # 41 | # @return [String] Feature name. 42 | def command_feature(command) 43 | exename = command.split(/\s+/).first 44 | command_connection(command) || command_connection(exename) || exename 45 | end 46 | 47 | # Commands are connected to one and only one feature name. 48 | # 49 | # @return [String] Feature name. 50 | def command_connection(command) 51 | DSL.command_connections(command) 52 | end 53 | 54 | # Constants can be connected to multiple-commands. 55 | # 56 | # @return [Array] List of commands. 57 | def constant_connections(constant) 58 | DSL.constant_connections(constant) 59 | end 60 | 61 | # Return configuration procedure. 62 | # 63 | # @return [Proc] 64 | def to_proc 65 | @block 66 | end 67 | 68 | end 69 | 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /lib/dotruby/configs/command.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | module Configuration 4 | 5 | # Command configurations are use when a command is explicitly 6 | # configured in the .ruby file. 7 | # 8 | class Command < Base 9 | # Setup new command configuration. 10 | # 11 | # @param [Hash] options 12 | # @option options [String] :feature 13 | # @return nothing 14 | def initialize(command, options={}, &block) 15 | @command = command.to_s 16 | @feature = options[:feature] 17 | @block = block 18 | end 19 | 20 | # A command is typically just the single word, called the *exec*. 21 | # But it can also have subcommand arguments separated by whitespace. 22 | # 23 | # @example 'yard' 24 | # @example 'yard doc' 25 | # @return [String] 26 | def command 27 | @command 28 | end 29 | 30 | # Commands can be connected to one and only one feature. 31 | # 32 | # @return [String] feature name 33 | def feature 34 | @feature ||= command_feature(command) 35 | end 36 | 37 | # A command is prematched if the current command and subcommands are the same 38 | # and the connected feature has been previously required. 39 | # 40 | # @param [Hash] state 41 | # @option state [String] :exec 42 | # @option state [Array] :argv 43 | # @return [Booolean] 44 | def prematch?(state) 45 | matching_command?(command, state) && previously_loaded?(feature) 46 | end 47 | 48 | # A command is postmatched if the current command and subcommands are the same 49 | # and the connected feature is being required. 50 | # 51 | # @param [Hash] state 52 | # @option state [String] :exec 53 | # @option state [Array] :argv 54 | # @option state [String] :feature 55 | # @return [Boolean] 56 | def postmatch?(state) 57 | matching_command?(command, state) && matching_feature?(feature, state) 58 | end 59 | 60 | # Run configuration procedure. 61 | # 62 | # TODO: instance_eval at toplevel ? 63 | def call 64 | @block.call 65 | end 66 | 67 | end 68 | 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /lib/dotruby/configs/constant.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | module Configuration 4 | 5 | # Constant-based configurations are used when a constant is a receiver 6 | # at the toplevel of the .ruby file. 7 | # 8 | class Constant < Base 9 | 10 | # Setup new constant configuration. 11 | # 12 | # @param [VirtualConstant] constant 13 | # @return nothing 14 | def initialize(constant) 15 | @constant = constant 16 | end 17 | 18 | # The constant. 19 | # 20 | # @return [VirtualConstant] 21 | def constant 22 | @constant 23 | end 24 | 25 | # The constant's name. 26 | # 27 | # @return [String] 28 | def name 29 | @constant.name 30 | end 31 | 32 | # Constants can have multiple connections. But they can only have a single feature-only 33 | # connection. Among the other connections, it the connection is only to a command, then 34 | # that commands feature connection is also the constants feature connection. 35 | # 36 | # @return [Array] 37 | def connections 38 | constant_connections(name) || [{:command=>name.to_s.downcase, :feature=>name.to_s.downcase}] 39 | end 40 | 41 | # A constant configuraiton is prematched if the current command and subcommands match one 42 | # of the commands connected to the constant and the connected feature of that command has been 43 | # previously required. 44 | # 45 | # @return [Booolean] 46 | def prematch?(state) 47 | connections.find do |connection| 48 | command = connection[:command] || constant.to_s.downcase 49 | feature = connection[:feature] || command_feature(command) || command 50 | matching_command?(command, state) && previously_loaded?(feature) 51 | end 52 | end 53 | 54 | # A constant configuraiton is postmatched if the current command and subcommands match one 55 | # of the commands connected to the constant and the connected feature of that command is 56 | # being required. 57 | # 58 | # @return [Booolean] 59 | def postmatch?(state) 60 | connections.find do |connection| 61 | command = connection[:command] || constant.to_s.downcase 62 | feature = connection[:feature] || command_feature(command) || command 63 | matching_command?(command, state) && matching_feature?(feature, state) 64 | end 65 | end 66 | 67 | # Run configuration procedure. 68 | # 69 | def call 70 | @constant.to_proc.call 71 | end 72 | 73 | end 74 | 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /lib/dotruby/profile.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | # 4 | class Profile 5 | 6 | # Setup new Profile instance. 7 | # 8 | # @param [String,Symbol] name 9 | # Profile name. 10 | # 11 | # @param [Hash] environment 12 | # Environment settings. 13 | # 14 | def initialize(name, env=nil) 15 | if Hash === name 16 | env, name = name, nil 17 | else 18 | env = {} 19 | end 20 | 21 | @name, @env = name, {} 22 | 23 | env.each do |k,v| 24 | @env[k.to_s] = v.to_s 25 | end 26 | 27 | @consts = {} 28 | @configs = [] 29 | end 30 | 31 | # 32 | def applicable?(env=ENV) 33 | (@name === (ENV['profile'] || ENV['p'])) && 34 | @env.all? do |name, value| 35 | env[name] == value 36 | end 37 | end 38 | 39 | # Name of the profile, if not an environment profile. 40 | # 41 | # @return [String] 42 | def name 43 | @name 44 | end 45 | 46 | # 47 | # 48 | # @return [Hash] 49 | def env 50 | @env 51 | end 52 | 53 | alias :environment :env 54 | 55 | # 56 | # 57 | # @return [Array] 58 | def configs 59 | @configs 60 | end 61 | 62 | alias :configurations :configs 63 | 64 | # Add a configuration instance to the profile. 65 | # 66 | # @return [Array] 67 | def <<(configuration) 68 | @configs << configuration 69 | end 70 | 71 | # 72 | # 73 | # @return [Configuration::Command] 74 | def command(command, options={}, &block) 75 | self << Configuration::Command.new(command, options, &block) 76 | end 77 | 78 | # 79 | # @return [Configuration::Feature] 80 | def feature(feature, options={}, &block) 81 | self << Configuration::Feature.new(feature, options, &block) 82 | end 83 | 84 | # Get the virtual constant for the given constant name. 85 | # 86 | # @return [VirtualConstant] 87 | def constant(const_name) 88 | return @consts[const_name.to_sym] if constant?(const_name) 89 | 90 | constant = VirtualConstant.new(const_name) 91 | 92 | self << Configuration::Constant.new(constant) 93 | 94 | @consts[const_name.to_sym] = constant 95 | end 96 | 97 | # 98 | # Check the constants table to see if the profile has a 99 | # virtual constant by the given name. 100 | # 101 | # @param [#to_sym] name 102 | # The constants name. 103 | # 104 | # @return [Boolean] 105 | # 106 | def constant?(name) 107 | @consts.key?(name.to_sym) 108 | end 109 | 110 | end 111 | 112 | end 113 | -------------------------------------------------------------------------------- /lib/dotruby/api.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | require 'dotruby/module' 3 | require 'dotruby/profile' 4 | require 'dotruby/constant' 5 | require 'dotruby/dsl' 6 | 7 | # 8 | # 9 | def self.configuration 10 | @configuration ||= DSL.load 11 | end 12 | 13 | # 14 | def self.profiles 15 | configuration.profiles 16 | end 17 | 18 | # 19 | def self.connect(*args) 20 | configuration.connect(*args) 21 | end 22 | 23 | # Configure the system. 24 | # 25 | # @return nothing 26 | def self.configure!(conf=nil) 27 | return unless DotRuby.file unless conf 28 | 29 | @configuration = conf if conf 30 | 31 | require_relative 'connects' 32 | 33 | configuration = DotRuby.configuration 34 | profiles = configuration.profiles 35 | 36 | begin 37 | require_relative "tweaks/#{DotRuby.exec}" 38 | rescue LoadError 39 | end 40 | 41 | state = {:exec => self.exec, :argv => self.argv} 42 | 43 | profiles.each do |profile| 44 | next unless profile.applicable? 45 | profile.configurations.each do |configuration| 46 | # If connected feature is already required then apply the configuration. 47 | if configuration.prematch?(state) 48 | configuration.call 49 | end 50 | end 51 | end 52 | 53 | # If the constant doesn't already exist, wait until it is required. 54 | ::Kernel.module_eval { 55 | alias _require require 56 | 57 | def require(fname) 58 | _require(fname) 59 | 60 | state = {:exec=>DotRuby.exec, :argv=>DotRuby.argv, :feature=>fname} 61 | 62 | DotRuby.profiles.each do |profile| 63 | next unless profile.applicable? 64 | profile.configurations.each do |configuration| 65 | if configuration.postmatch?(state) 66 | configuration.call 67 | end 68 | end 69 | end 70 | end 71 | 72 | module_function :require 73 | } 74 | end 75 | 76 | # Current command name. 77 | # 78 | # @return [String] 79 | def self.exec 80 | ENV['exec'] || File.basename($0) 81 | end 82 | 83 | # Current command arguments. 84 | # 85 | # @return [String] 86 | def self.argv 87 | ARGV 88 | end 89 | 90 | # Execute the configuration. 91 | # 92 | # @return nothing 93 | #def self.execute(&config) 94 | # config.call 95 | #end 96 | 97 | # Returns the `.ruby` file of the current project. 98 | # 99 | # @return {String] The .ruby file of the project. 100 | def self.file 101 | if project_root 102 | file = File.join(project_root, '.ruby') 103 | return nil unless File.exist?(file) 104 | return file 105 | end 106 | end 107 | 108 | # Find the root directory of the current project. 109 | # 110 | # @return [String,nil] The root directory of the project. 111 | def self.project_root(start_dir=Dir.pwd) 112 | dir = start_dir 113 | home = File.expand_path('~') 114 | until dir == home || dir == '/' 115 | if Dir[File.join(dir, '{.ruby,.git,.hg}')].first 116 | return dir 117 | end 118 | dir = File.dirname(dir) 119 | end 120 | nil 121 | end 122 | 123 | end 124 | -------------------------------------------------------------------------------- /lib/dotruby/api-old.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | require 'dotruby/dsl' 3 | require 'dotruby/constant' 4 | 5 | # 6 | def self.configuration 7 | @configuration ||= DSL.new(dotruby_file) 8 | end 9 | 10 | # This is a convenience interfact to the configuration domain, which 11 | # is useful for tweaks to redefine the default tag. 12 | def self.default_tag(cname, command, feature=nil) 13 | configuration.default_tag(cname, command, feature) 14 | end 15 | 16 | # Configure the system. 17 | # 18 | # @return nothing 19 | def self.configure! 20 | return unless dotruby_file 21 | 22 | dotruby = DotRuby.configuration 23 | 24 | begin 25 | require_relative "tweaks/#{DotRuby.command}" 26 | rescue LoadError 27 | end 28 | 29 | # If the constant already exists, apply the configuration. 30 | # 31 | # Since Ruby provides no way to ask if a feature has been required or not, 32 | # we can only condition application of pre-extisting constants on a 33 | # matching command. 34 | dotruby.tags.each do |cname, tags| 35 | tags.each do |tag| 36 | next unless Object.const_defined?(cname) 37 | next unless DotRuby.command == tag.first # command of the tag 38 | if config = dotruby.constants[name] 39 | execute(&config) 40 | end 41 | end 42 | end 43 | 44 | # If the constant doesn't already exist, wait until it is required. 45 | ::Kernel.module_eval { 46 | alias _require require 47 | 48 | def require(fname) 49 | _require(fname) 50 | 51 | dotruby = DotRuby.configuration 52 | command = DotRuby.command 53 | dotruby.tags.each do |cname, tags| 54 | tags.each do |tag| 55 | next unless fname == tag.last # feature of the tag 56 | next unless command == tag.first # command of the tag 57 | if config = dotruby.constants[cname] # FIXME: This can run a config more than once ? 58 | DotRuby.execute(&config) 59 | end 60 | end 61 | end 62 | end 63 | 64 | module_function :require 65 | } 66 | end 67 | 68 | # Current command. 69 | # 70 | # @return [String] 71 | def self.command 72 | ENV['command'] || File.basename($0) 73 | end 74 | 75 | # Execute the configuration. 76 | # 77 | # @return nothing 78 | def self.execute(&config) 79 | config.call 80 | end 81 | 82 | # Returns the `.ruby` file of the current project. 83 | # 84 | # @return {String] The .ruby file of the project. 85 | def self.dotruby_file 86 | @dotruby_file ||= ( 87 | dir = start_dir 88 | home = File.expand_path('~') 89 | mark = project_root 90 | until dir == root || dir == home || dir == '/' 91 | file = File.join(dir, '.ruby') 92 | return file if File.exist?(file) 93 | # try parent directory 94 | dir = File.dirname(dir) 95 | end 96 | ) 97 | end 98 | 99 | # Find the root directory of the current project. 100 | # 101 | # @return [String,nil] The root directory of the project. 102 | def self.project_root(start_dir=Dir.pwd) 103 | dir = start_dir 104 | home = File.expand_path('~') 105 | mark = '{.git,.hg}' 106 | until dir == home || dir == '/' 107 | return dir if Dir[File.join(dir, mark)].first 108 | # try parent directory 109 | dir = File.dirname(dir) 110 | end 111 | nil 112 | end 113 | 114 | end 115 | -------------------------------------------------------------------------------- /lib/dotruby/dsl-old.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | 3 | # DotRuby DSL class is used to evaluate the `.ruby` configuration file. 4 | # 5 | class DSL < BasicObject #Module 6 | include ::Kernel 7 | 8 | # Initialize new DSL instance. 9 | # 10 | def initialize(file) 11 | @file = file 12 | 13 | @@default_tags ||= {} 14 | @@defined_tags ||= {} 15 | 16 | @@contants ||= {} 17 | 18 | instance_eval(::File.read(file), file) 19 | end 20 | 21 | # The path of the current project's `.ruby` file. 22 | # 23 | # @return [String] 24 | attr :file 25 | 26 | # Return recognizes tags. 27 | # 28 | # @return [Hash] 29 | def tags 30 | keys = @@default_tags.keys - @@defined_tags.keys 31 | tags = @@defined_tags.dup 32 | keys.each do |key| 33 | tags[key] = [@@default_tags[key]] 34 | end 35 | tags 36 | end 37 | 38 | # Defined tags map constant names to a list of `[command, feature]` pairs. 39 | # 40 | # @return [Hash] 41 | #def defined_tag 42 | #end 43 | 44 | # Table of constant configurations. 45 | # 46 | # @return [Hash] 47 | def constants 48 | @@contants 49 | end 50 | 51 | # Tag a constant to a command and/or feature. 52 | # 53 | def tag(constant, *target) 54 | options = (::Hash === target.last ? target.pop : {}) 55 | target = target.first 56 | 57 | command = options[:command] || target 58 | feature = options[:feature] || target 59 | 60 | tag = [command.to_s, feature.to_s] 61 | 62 | @@defined_tags[constant] ||= [] 63 | @@defined_tags[constant] << tag unless @@defined_tags[constant].include?(tag) 64 | @@defined_tags 65 | end 66 | 67 | # Set the default tag for a constant. 68 | # Unlike defined tags, there can be only one associate for a default tag. 69 | # 70 | # @return [Hash] 71 | def default_tag(cname, command, feature=nil) 72 | @@default_tags[cname.to_sym] = [command.to_s, (feature || command).to_s] 73 | end 74 | 75 | # Only configure if profile matches. 76 | # 77 | # @param [#===] match 78 | # A String or Regexp or any other object that can 79 | # check a match to a String via #===. 80 | # 81 | # @return nothing 82 | def profile(match, &block) 83 | if match === (ENV['profile'] || ENV['p']) 84 | block.call 85 | end 86 | end 87 | 88 | # Only configure if environment matches. 89 | # 90 | # @param [Hash] matches 91 | # A Hash of String or Regexp or any other object that can 92 | # check a match to a String via #===. 93 | # 94 | # @todo Should it be logical-or or logical-and? 95 | # 96 | # @return nothing 97 | def environment(matches={}, &block) 98 | if matches.any?{ |e, m| m === ENV[e] } 99 | block.call 100 | end 101 | end 102 | 103 | # Import configuration from an external source. 104 | # 105 | # @param [Symbol,String] Name of constant or file path. 106 | # 107 | # @return nothing 108 | def import(name, options={}) 109 | raise 'import is not implemented yet' 110 | end 111 | 112 | # Constants provide configuration. 113 | # 114 | # @param [Symbol,String] cname 115 | # 116 | # @return [Constant] 117 | def self.const_missing(cname) 118 | @@default_tags[cname.to_sym] = [cname.to_s.downcase, cname.to_s.downcase] 119 | 120 | @@contants[cname] ||= Constant.new(cname) 121 | end 122 | 123 | end 124 | 125 | end 126 | -------------------------------------------------------------------------------- /lib/dotruby/dsl.rb: -------------------------------------------------------------------------------- 1 | module DotRuby 2 | require_relative 'configs/base' 3 | require_relative 'configs/feature' 4 | require_relative 'configs/command' 5 | require_relative 'configs/constant' 6 | 7 | # 8 | # 9 | class DSL < BasicObject 10 | include ::Kernel 11 | 12 | # 13 | class NestingError < ::SyntaxError 14 | end 15 | 16 | # 17 | def self.command_connections(command) 18 | @@command_connections[command] 19 | end 20 | 21 | # 22 | def self.constant_connections(constant) 23 | @@constant_connections[constant] 24 | end 25 | 26 | # 27 | def self.load(io=nil) 28 | case io 29 | when nil 30 | file = DotRuby.file 31 | file ? new(:file=>file) : new 32 | when ::File 33 | new(:text=>io, :file=>io.path) 34 | when ::String 35 | new(:text=>io) 36 | else 37 | new(:text=>io.read) 38 | end 39 | end 40 | 41 | # 42 | def self.load_file(file) 43 | new(:file=>file) 44 | end 45 | 46 | # 47 | def initialize(options={}) 48 | @file = options[:file] 49 | 50 | # What a crazy awesome line of code! 51 | @profiles = [@@_profile = @default_profile = ::DotRuby::Profile.new(nil)] 52 | 53 | @@command_connections = {} 54 | @@constant_connections = {} 55 | 56 | if text = options[:text] 57 | @file ? instance_eval(text, @file) : instance_eval(text) 58 | else 59 | instance_eval(File.read(@file), @file) if @file 60 | end 61 | end 62 | 63 | # List of profiles. 64 | # 65 | # @return [Array] 66 | def profiles 67 | @profiles 68 | end 69 | 70 | # Connect a constant to the feature from which it derives. 71 | # 72 | # connect :constant=>:RSpec, :feature=>'rspec/core' 73 | # 74 | # Connect a constant to a command that utilizes it for configuration. 75 | # 76 | # connect :constant=>:RSpec, :command=>'rspec' 77 | # 78 | # Or connect both at the same time. 79 | # 80 | # connect :constant=>:RSpec, :feature=>'rspec/core', :command=>'rspec' 81 | # 82 | # Repeated calls for the same constant will *add* additional commands and features. 83 | # 84 | # A command can also be connected to a feature, without any constant. 85 | # 86 | # connect :command=>'rspec', :feature=>'rspec/core' 87 | # 88 | # Finally, the `:to` option can be used to assign whatever is not explicitly stated. 89 | # For example, 90 | # 91 | # connenct :constant=>:QED, :to=>'qed' 92 | # 93 | # is equivalent to 94 | # 95 | # connenct :constant=>:QED, :command=>'qed', :feature=>'qed' 96 | # 97 | def connect(*target) 98 | options = (::Hash === target.last ? target.pop : {}) 99 | 100 | constant = target.first || options[:constant] 101 | 102 | command = options[:command] || options[:to] 103 | feature = options[:feature] || options[:to] 104 | 105 | if constant 106 | connections = (@@constant_connections[constant] ||= []) 107 | if feature && !command 108 | old = connections.find{ |c| c[:feature] && !c[:command] } 109 | connections.delete(old) 110 | connections << {:feature => feature} 111 | elsif command && !feature 112 | connections << {:command=>command} 113 | else 114 | connections << {:feature=>feature, :command=>command} 115 | end 116 | else 117 | @@command_connections[command] = feature 118 | end 119 | end 120 | 121 | # Import configuration from an external source. 122 | # 123 | # @param [Symbol,String] Name of constant or file path. 124 | # 125 | # @return nothing 126 | def import(name, options={}) 127 | raise 'import is not implemented yet' 128 | end 129 | 130 | # 131 | # 132 | def profile(name, env={}, &block) 133 | raise NestingError if @@_profile != @default_profile 134 | @@_profile = Profile.new(name, env) 135 | @profiles << @@_profile 136 | begin 137 | block.call 138 | ensure 139 | @@_profile = @default_profile 140 | end 141 | end 142 | 143 | # 144 | # 145 | def environment(env={}, &block) 146 | raise NestingError if @@profile 147 | @@_profile = Profile.new(env) 148 | @profiles << @@_profile 149 | begin 150 | block.call 151 | ensure 152 | @@_profile = @default_profile 153 | end 154 | end 155 | 156 | # 157 | # 158 | def command(command, options={}, &block) 159 | @@_profile.command(command, options, &block) 160 | end 161 | 162 | # @deprecate ? 163 | # 164 | def feature(name, options={}, &block) 165 | @@_profile.feature(name, options, &block) 166 | end 167 | 168 | # 169 | # 170 | def self.const_missing(const_name) 171 | @@_profile.constant(const_name) 172 | end 173 | 174 | end 175 | 176 | end 177 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotRuby 2 | 3 | **Universal Runtime Configuration for Ruby Tools** 4 | 5 | [Homepage](http://rubyworks.github.com/dotruby) / 6 | [Report Issue](http://github.com/rubyworks/dotruby/issues) / 7 | [Source Code](http://github.com/rubyworks/dotruby) 8 | ( [![Build Status](https://secure.travis-ci.org/rubyworks/dotruby.png)](http://travis-ci.org/rubyworks/dotruby) ) 9 | 10 | 11 | ## [About](#about) 12 | 13 | DotRuby is a is multi-tenant runtime configuration system for Ruby tools. 14 | It is designed to facilitate Ruby-based configuration for multiple 15 | tools in a single file, and designed to work whether the tool has 16 | built-in support for DotRuby or not. The syntax is simple, universally 17 | applicable, and... oh yeah, damn clever. 18 | 19 | DotRuby can be used with any Ruby-based commandline tool or library utilized by 20 | such tool, where there exists some means of configuring it via a toplevel or global 21 | interface; or the tool has been designed to directly support DotRuby, of course. 22 | 23 | 24 | ## [Installation](#installation) 25 | 26 | To use DotRuby with any tool, including those that do not in themselves have a 27 | built-in dependency on DotRuby, first install the DotRuby library, typically 28 | via RubyGems: 29 | 30 | gem install dotruby 31 | 32 | Then add `-rdotruby` to your system's `RUBYOPT` environment variable. 33 | 34 | $ export RUBYOPT='-rdotruby' 35 | 36 | You will want to add that to your `.bashrc`, `.profile` or equivalent configuration 37 | script, so it is always available. 38 | 39 | To use DotRuby with tools that support DotRuby directly, there is likely nothing 40 | to install. Installing the tool should install `dotruby` via a dependency and 41 | load runtime configurations when the tool is used. 42 | 43 | 44 | ## [Instruction](#instruction) 45 | 46 | ### Configuring 47 | 48 | To use DotRuby in a project create a configuration file called `.ruby`. 49 | (Hey, now you know why it has the name, *DotRuby*!). In this file add 50 | configuration code as you would normally do for a given library. The 51 | only caveat is that all such configurations must be against a constant. 52 | 53 | For example, let's say we need to configure RSpec. In the `.ruby` file we can 54 | add the following. 55 | 56 | RSpec.configure do |config| 57 | config.color_enabled = true 58 | config.tty = true 59 | config.formatter = :documentation 60 | end 61 | 62 | This might seems pretty ordinary, but consider that the RSpec library hasn't 63 | necessarily been loaded when this is evaluated! Think about that a bit 64 | and we'll explain how it works below. 65 | 66 | For another example, let's demonstrate how we could use this to configure Rake 67 | tasks. Rake is not the most obvious choice, since developers are just as happy 68 | to keep using a Rakefile. That's fine. But using Rake as an example serves to 69 | show that it *can* be done, and also it makes a good tie-in with next example. 70 | 71 | Rake.file do 72 | desc 'generate yard docs' 73 | task :yard do 74 | sh 'yard' 75 | end 76 | end 77 | 78 | Now when `rake` is run the tasks defined in this configuration will be available. 79 | 80 | Getting back to our Rake example, you might wonder why anyone would want to do 81 | this. That's where the *multi-tenancy* comes into play. Let's add another 82 | configuration. 83 | 84 | title = "MyApp" 85 | 86 | Rake.file do 87 | desc 'generate yard docs' 88 | task :yard do 89 | sh "yard doc --title #{title}" 90 | end 91 | end 92 | 93 | QED.config do |c| 94 | c.title = "#{title} Demos" 95 | end 96 | 97 | Now we have configuration for both the `rake` tool and the `qed` tool in 98 | a single file. Thus we gain the advantage of reducing the file count of our 99 | project while pulling our tool configurations together into one place. 100 | Moreover, these configurations can potentially share settings as demonstrated 101 | here via the `title` local variable. 102 | 103 | ### Tagging 104 | 105 | Some commands don't correspond to their API namespaces. For example, in the 106 | above example we configured QED's title option. But that is actually of 107 | use when running the `qedoc` command, which belongs to the same library. 108 | We can easily tell DotRuby to expect this by adding a `tag`. 109 | 110 | tag :QED, :command=>'qedoc' 111 | 112 | DotRuby's configurations are triggered by three criteria: a constant, 113 | a command and a feature. In the above tag example, the constant is 114 | `QED`, the command is `qedoc` and the feature is not given so it defaults 115 | to the constant name downcased, i.e. `qed`. If need be we can specify a 116 | different feature via the `:feature` option. 117 | 118 | ### Profiles 119 | 120 | Sometimes you need to configure a tool with different settings for different 121 | circumstances. If the tool doesn't have built in support for this, DotRuby 122 | provides some convenience methods for handling this via environment variables. 123 | 124 | A `profile` block can be used to only run if `ENV['profile']`, or as a nice 125 | shortcut `ENV['p']` is set to the given name. 126 | 127 | profile :doc do 128 | RSpec.configure do |config| 129 | config.color_enabled = true 130 | config.tty = true 131 | config.formatter = :documentation 132 | end 133 | end 134 | 135 | To be clear why this is just a convenience method, it is essentially the same 136 | as doing: 137 | 138 | if 'doc' === (ENV['profile'] || ENV['p']) 139 | ... 140 | end 141 | 142 | When utilizing the tool, set the `profile` via an environment variable. 143 | 144 | $ profile=cov rspec 145 | 146 | Or for additional convenience just `p`: 147 | 148 | $ p=cov rspec 149 | 150 | ### Environments 151 | 152 | DotRuby also provided the `environment` convenience method, which is along 153 | the same lines a profile, but allows any environment variable to be used. 154 | 155 | environment :testing => 'yes' do 156 | ... 157 | end 158 | 159 | Again, this is just a shortcut for: 160 | 161 | if 'yes' === ENV['testing'] 162 | ... 163 | end 164 | 165 | It is recommended that you use the `profile` instead of `environment` unless their 166 | is specific reason not to do so. This makes it easier for others to utilize, instead 167 | of having to recollect which environment variables where used for what configurations. 168 | 169 | ### Tweaks 170 | 171 | In the Rake example, you might notice that `Rake.file` isn't an official method 172 | of the Rake API. This is called a *tweak* and is built-in with DotRuby. Some 173 | tools that we might wish to use with DotRuby don't have an interface that 174 | suffices, in these cases a tweak can be used to give it one. 175 | 176 | If there is a tool you would like to configure with DotRuby, but it doesn't 177 | provided a means for it, and a reasonably simple tweak can make it viable, 178 | please submit a patch and it will be added to DotRuby. And let the tool creator 179 | know about it! Hopefully, in time tool developers will make the tweak unnecessary. 180 | 181 | ### Importing 182 | 183 | **(Comming soon)** 184 | 185 | Configurations can also be pulled in from other gems using the `import` command. 186 | For instance, if we wanted to reuse the Rake configurations as defined in 187 | the `QED` gem: 188 | 189 | import :Rake, :from=>'qed' 190 | 191 | If a particular profile or environment is needed, these can specified as options. 192 | 193 | import :RSpec, :from=>'rspec', :profile=>'simplecov' 194 | 195 | As long as a project includes its `.ruby` file (and any local imported files) 196 | in it's gem package, it's possible to share configurations in this manner. 197 | 198 | Generally we want all our configurations stored in a single file, but if need be 199 | the `import` method can be used to place configuration in multiple files. 200 | Simple use a local path `import` method to load them, e.g. 201 | 202 | import './config/*.dotrb' 203 | 204 | DotRuby translates the initial dot into a path relative to the file itself, 205 | i.e. `__dir__`. Why can't we leave off the initial dot? If we did import 206 | would work like require and try to load the file from a gem --however, 207 | there is an issue with implementing this that needs to be resolved with 208 | Ruby itself (autoload), so this feature is on hold for the time being. 209 | 210 | ### Third Party Support 211 | 212 | To support DotRuby, all developers have to do is make sure their tools 213 | have a way of being configured via a toplevel namespace constant. 214 | 215 | It is also helps when the the library name to be required is the the same 216 | as the library's namespace downcased. When it's not the same a `tag` entry 217 | is needed to tell DotRuby from which feature to expect the constant. For 218 | popular tools that have such a discrepancy, DotRuby provides *tweaks* that 219 | take care of it automatically. But it's always best to follow the general 220 | good practice that the gem name is the same as the lib name which is the same 221 | as the namespace downcased. 222 | 223 | Finally a third party tool can take the final step of full support by using 224 | DotRuby as it preferred means of configuration. In that case, just make 225 | sure to `require 'dotruby'`. 226 | 227 | 228 | ## [How It Works](#howitworks) 229 | 230 | The design of DotRuby is actually quite clever. What it does is proxy all 231 | calls to *virtual constants*, keeping a record of the messages sent to them. 232 | When it is time to apply these configurations, it finds the ones that apply 233 | to the given command and sends the recorded messages on to the real constants. 234 | If those constants haven't been loaded yet, it adds a hook to `require` and 235 | waits for the matching feature to load, at which time it applies the configuration. 236 | In this way, DotRuby can actually be required before or after the library that 237 | it will configure and it works regardless. 238 | 239 | There is a minor caveat here though. Luckily it will rarely be a real issue, 240 | but it is possible for `autoload` to fowl up the works, b/c it does not call out 241 | the standard require method. So there is no way override it and insert the necessary 242 | hook. Again, this is not likely to be a problem, especially if good naming practices 243 | are used, but it's a good thing to know just in case you run into some unexpected 244 | behavior. 245 | 246 | 247 | ## [Dependencies](#dependencies) 248 | 249 | ### Libraries 250 | 251 | DotRuby depends on the [Finder](http://rubyworks.github.com/finder) library 252 | to provide reliable load path and Gem searching. This is used when importing 253 | configurations from other projects. (It's very much a shame Ruby and RubyGems 254 | does not have this kind of functionality built-in.) 255 | 256 | ### Core Extensions 257 | 258 | DotRuby uses two core extensions, `#to_h`, which applies to a few different 259 | classes, and `String#tabto`. These are *copied* from 260 | [Ruby Facets](http://rubyworks.github.com/facets) to ensure a high 261 | standard of interoperability. 262 | 263 | Both of these methods have been suggested for inclusion in Ruby proper. 264 | Please head over to Ruby Issue Tracker and add your support. 265 | 266 | * http://bugs.ruby-lang.org/issues/749 267 | * http://bugs.ruby-lang.org/issues/6056 268 | 269 | 270 | ## [Release Notes](#releasenotes) 271 | 272 | Please see HISTORY.md file. 273 | 274 | 275 | ## [Copyrights & Licensing](#copyrights) 276 | 277 | DotRuby is copyrighted open-source software. 278 | 279 | Copyright (c) 2011 Rubyworks. All rights reserved. 280 | 281 | It is modifiable and redistributable in accordance with the **BSD-2-Clause** license. 282 | 283 | See LICENSE.txt file for details. 284 | --------------------------------------------------------------------------------