rc 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,192 @@
1
+ module RC
2
+
3
+ # Configuration
4
+ #
5
+ class Configuration < Module
6
+
7
+ #
8
+ # Configuration file pattern. The standard configuration file name is
9
+ # `Config.rb`, and that name should be used in most cases. However,
10
+ # `.config.rb` can also be use and will take precedence if found.
11
+ # Conversely, `config.rb` (lowercase form) can also be used but has
12
+ # the least precedence.
13
+ #
14
+ # Config files looked for in the order or precedence:
15
+ #
16
+ # * `.config.rb`
17
+ # * `Config.rb`
18
+ # * `config.rb`
19
+ #
20
+ CONFIG_FILE = '{.c,C,c}onfig{.rb,}'
21
+
22
+ #
23
+ # When looking up config file, it one of these is found
24
+ # then there is no point to looking further.
25
+ #
26
+ ROOT_INDICATORS = %w{.git .hg _darcs} #.ruby
27
+
28
+ #
29
+ # Load configuration file from local project or other gem.
30
+ #
31
+ # @param options [Hash] Load options.
32
+ #
33
+ # @option options [String] :from
34
+ # Name of gem or library.
35
+ #
36
+ def self.load(options={})
37
+ if from = options[:from]
38
+ file = Find.path(CONFIG_FILE, :from=>from).first
39
+ else
40
+ file = lookup(CONFIG_FILE)
41
+ end
42
+ new(file)
43
+ end
44
+
45
+ #
46
+ # Initialize new Configuration object.
47
+ #
48
+ # @param [String] file
49
+ # Configuration file (optional).
50
+ #
51
+ def initialize(file=nil)
52
+ @file = file
53
+
54
+ @_list = []
55
+ @_state = {}
56
+
57
+ # TODO: does this rescue make sense here?
58
+ begin
59
+ import_relative(@file) if @file
60
+ rescue => e
61
+ raise e if $DEBUG
62
+ warn e.message
63
+ end
64
+ end
65
+
66
+ #
67
+ # Profile block.
68
+ #
69
+ # @param [String,Symbol] name
70
+ # A profile name.
71
+ #
72
+ def profile(name, &block)
73
+ raise SyntaxError, "nested profile sections" if @_state[:profile]
74
+ original_state = @_state.dup
75
+ @_state[:profile] = name.to_s
76
+ instance_eval(&block)
77
+ @_state = original_state
78
+ end
79
+
80
+ #
81
+ # Configure a tool.
82
+ #
83
+ # @param [Symbol] tool
84
+ # The name of the tool to configure.
85
+ #
86
+ # @param [Hash] opts
87
+ # Configuration options.
88
+ #
89
+ # @options opts [String] :from
90
+ # Library from which to extract configuration.
91
+ #
92
+ # @example
93
+ # profile :coverage do
94
+ # config :qed, :from=>'qed'
95
+ # end
96
+ #
97
+ # @todo Clean this code up.
98
+ #
99
+ def config(tool, *args, &block)
100
+ options = (Hash===args.last ? args.pop : {})
101
+
102
+ # @todo Might we have an option to lockdown tool
103
+ # So that we do without ToolConfiguration?
104
+
105
+ case args.first
106
+ when Symbol
107
+ profile = args.shift
108
+ when String
109
+ profile = args.shift unless args.first.index("\n")
110
+ end
111
+
112
+ data = args.shift
113
+ raise ArgumentError, "must use data or block, not both" if data && block
114
+ if data
115
+ data = data.tabto(0)
116
+ block = Proc.new do
117
+ YAML.load(data)
118
+ end
119
+ end
120
+
121
+ from = options[:from]
122
+
123
+ raise ArgumentError, "too many arguments" if args.first
124
+ raise SyntaxError, "nested profile sections" if profile && @_state[:profile]
125
+ #raise ArgumentError, "use block or :from setting" if options[:from] && block
126
+
127
+ profile = @_state[:profile] unless profile
128
+
129
+ if from
130
+ from_config = RC.configuration(from)
131
+ from_tool = options[:tool] || tool
132
+ from_profile = options[:profile] || profile
133
+ from_config.each do |c|
134
+ if c.match?(from_tool, from_profile)
135
+ @_list << Config.new(tool, profile, &c)
136
+ end
137
+ end
138
+
139
+ return unless block
140
+ end
141
+
142
+ @_list << Config.new(tool, profile, &block)
143
+ end
144
+
145
+ #
146
+ # @return [Hash] Defined configurations.
147
+ #
148
+ def configurations
149
+ @_list
150
+ end
151
+
152
+ #
153
+ # @return [ToolConfiguration] Subset of Configuration.
154
+ #
155
+ def [](tool)
156
+ ToolConfiguration.new(tool, self)
157
+ end
158
+
159
+ # Configuration is Enumerable.
160
+ include Enumerable
161
+
162
+ #
163
+ def each(&block)
164
+ @_list.each(&block)
165
+ end
166
+
167
+ #
168
+ def size
169
+ @_list.size
170
+ end
171
+
172
+ private
173
+
174
+ #
175
+ # Search upward from working directory.
176
+ #
177
+ def self.lookup(glob, flags=0)
178
+ pwd = File.expand_path(Dir.pwd)
179
+ home = File.expand_path('~')
180
+ while pwd != '/' && pwd != home
181
+ if file = Dir.glob(File.join(pwd, glob), flags).first
182
+ return file
183
+ end
184
+ break if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) }
185
+ pwd = File.dirname(pwd)
186
+ end
187
+ return nil
188
+ end
189
+
190
+ end
191
+
192
+ end
@@ -0,0 +1,49 @@
1
+ module Kernel
2
+ #
3
+ # Evaluate script directly into current scope.
4
+ #
5
+ def import(feature)
6
+ file = Find.load_path(feature).first
7
+ raise LoadError, "no such file -- #{feature}" unless file
8
+ instance_eval(::File.read(file), file) if file
9
+ end
10
+
11
+ #
12
+ # Evaluate script directly into current scope.
13
+ #
14
+ def import_relative(file)
15
+ raise LoadError, "no such file -- #{file}" unless File.file?(file)
16
+ instance_eval(::File.read(file), file) if file
17
+ end
18
+ end
19
+
20
+ class Hash
21
+ def to_h
22
+ dup #rehash
23
+ end unless method_defined?(:to_h)
24
+ end
25
+
26
+ class String
27
+ def tabto(n)
28
+ if self =~ /^( *)\S/
29
+ indent(n - $1.length)
30
+ else
31
+ self
32
+ end
33
+ end unless method_defined?(:tabto)
34
+
35
+ def indent(n, c=' ')
36
+ if n >= 0
37
+ gsub(/^/, c * n)
38
+ else
39
+ gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
40
+ end
41
+ end unless method_defined?(:indent)
42
+ end
43
+
44
+ #class Symbol
45
+ # def /(other)
46
+ # "#{self}/#{other}".to_sym
47
+ # end
48
+ #end
49
+
@@ -0,0 +1,163 @@
1
+ module RC
2
+ # External requirements.
3
+ require 'yaml'
4
+ require 'finder'
5
+
6
+ # Internal requirements.
7
+ require 'rc/core_ext'
8
+ require 'rc/config'
9
+ require 'rc/configuration'
10
+ require 'rc/tool_configuration'
11
+ require 'rc/properties'
12
+
13
+ #
14
+ # Configuration file pattern. The standard configuration file name is
15
+ # `Config.rb`, and that name should be used in most cases. However,
16
+ # `.config.rb` can also be use and will take precedence if found.
17
+ # Conversely, `config.rb` (lowercase form) can also be used but has
18
+ # the least precedence.
19
+ #
20
+ # Config files looked for in the order or precedence:
21
+ #
22
+ # * `.config.rb`
23
+ # * `Config.rb`
24
+ # * `config.rb`
25
+ #
26
+ FILE_PATTERN = '{.c,C,c}onfig{.rb,}'
27
+
28
+ #
29
+ def self.cache
30
+ @cache ||= {}
31
+ end
32
+
33
+ #
34
+ def self.clear!
35
+ @cache = {}
36
+ end
37
+
38
+ #
39
+ def self.configuration(gem=nil)
40
+ key = gem ? gem.to_s : nil #Dir.pwd
41
+ cache[key] ||= Configuration.load(:from=>gem)
42
+ end
43
+
44
+ #
45
+ # @return [Array] List of profiles for given `tool`.
46
+ #
47
+ def self.profiles(tool, options={})
48
+ tool = tool.to_s
49
+ gem = options[:from]
50
+ configuration(gem).map{ |c| c.tool.to_s }
51
+ end
52
+
53
+ #
54
+ # Get current tool.
55
+ #
56
+ def self.current_tool
57
+ File.basename(ENV['tool'] || $0)
58
+ end
59
+
60
+ #
61
+ # Set current tool.
62
+ #
63
+ def self.current_tool=(tool)
64
+ ENV['tool'] = tool.to_s
65
+ end
66
+
67
+ #
68
+ # Get current profile.
69
+ #
70
+ def self.current_profile
71
+ ENV['profile']
72
+ end
73
+
74
+ #
75
+ # Set current profile.
76
+ #
77
+ def self.current_profile=(profile)
78
+ if profile
79
+ ENV['profile'] = profile.to_s
80
+ else
81
+ ENV['profile'] = nil
82
+ end
83
+ end
84
+
85
+ #
86
+ # Project properties.
87
+ #
88
+ def self.properties
89
+ $properties ||= Properties.new
90
+ end
91
+
92
+ #
93
+ # Get/set configuration processor. Tools can use this
94
+ # to gain control over the configuration procedure.
95
+ #
96
+ # The block should take a single argument of the current
97
+ # Configuration.
98
+ #
99
+ # This might be used to save the configuration for
100
+ # a later execution, or to evaluate the procedures
101
+ # in a special scope, or both.
102
+ #
103
+ # Keep in mind that if configurations are evaluated in
104
+ # a different scope, they may not be able to utilize
105
+ # any shared methods defined in the config file.
106
+ #
107
+ # @example
108
+ # RC.processor('qed') do |config|
109
+ # if i = ARGV.index('--profile') || ARGV.index('-p')
110
+ # ENV['profile'] = ARGV[i+1]
111
+ # end
112
+ # RC.configure
113
+ # end
114
+ #
115
+ def self.processor(tool, &block)
116
+ @processors ||= {}
117
+ @processors[tool.to_s] = block if block
118
+ @processors[tool.to_s]
119
+ end
120
+
121
+ #
122
+ # Setup configuration.
123
+ #
124
+ def self.configure(options={})
125
+ tool = options[:tool] || current_tool
126
+ profile = options[:profile] || current_profile
127
+
128
+ configuration.each do |c|
129
+ c.call if c.match?(tool, profile)
130
+ end
131
+ end
132
+
133
+ #
134
+ # Start RC.
135
+ #
136
+ def self.bootstrap
137
+ properties # prime global properties
138
+
139
+ tweak = File.join(File.dirname(__FILE__), 'tweaks', current_tool + '.rb')
140
+ if File.exist?(tweak)
141
+ require tweak
142
+ else
143
+ begin
144
+ require current_tool
145
+ rescue LoadError
146
+ end
147
+ end
148
+
149
+ if proc = processor(current_tool)
150
+ tool_config = ToolConfiguration.new(current_tool, configuration)
151
+ proc.call(tool_config)
152
+ else
153
+ configure
154
+ end
155
+ end
156
+
157
+ # @todo: I'm sure this, #bootstrap and #processor can be simplifed.
158
+ def self.run(tool, &block)
159
+ processor(tool, &block)
160
+ require 'rc'
161
+ end
162
+
163
+ end
@@ -0,0 +1,44 @@
1
+ module RC
2
+
3
+ # Currently properties derive from a project's .ruby file.
4
+ # This will be expanded upon in future version to allow
5
+ # additional customization.
6
+ #
7
+ # @todo Lookup project root directory.
8
+ #
9
+ class Properties
10
+
11
+ #
12
+ #
13
+ #
14
+ DATA_FILE = '.ruby'
15
+
16
+ #
17
+ #
18
+ #
19
+ def initialize
20
+ @data = {}
21
+
22
+ if file = Dir[DATA_FILE].first
23
+ @data.update(YAML.load_file(file))
24
+ end
25
+ end
26
+
27
+ #
28
+ #
29
+ #
30
+ def method_missing(s)
31
+ @data[s.to_s]
32
+ end
33
+
34
+ private
35
+
36
+ # @todo Support gemspec as properties source ?
37
+ def import_gemspec
38
+ file = Dir['{*,,pkg/*}.gemspec'].first
39
+ # ...
40
+ end
41
+
42
+ end
43
+
44
+ end