coconut 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +235 -22
- data/Rakefile +29 -0
- data/changelog.md +8 -0
- data/coconut.gemspec +1 -2
- data/features/folder_configuration.feature +52 -0
- data/features/single_file_configuration.feature +16 -1
- data/features/step_definitions/configuration_steps.rb +8 -3
- data/features/support/env.rb +29 -1
- data/lib/coconut.rb +13 -13
- data/lib/coconut/config.rb +43 -13
- data/lib/coconut/dsl/application.rb +35 -0
- data/lib/coconut/dsl/asset.rb +40 -0
- data/lib/coconut/dsl/asset_folder.rb +44 -0
- data/lib/coconut/dsl/blank_slate.rb +33 -0
- data/lib/coconut/dsl/environment.rb +28 -0
- data/lib/coconut/version.rb +1 -1
- data/spec/coconut/coconut_spec.rb +22 -4
- data/spec/coconut/config_spec.rb +22 -22
- data/spec/coconut/dsl/application_spec.rb +24 -0
- data/spec/coconut/dsl/asset_spec.rb +49 -0
- data/spec/coconut/dsl/assets_folder_spec.rb +36 -0
- data/spec/coconut/dsl/blank_slate_spec.rb +9 -0
- data/spec/coconut/dsl/environment_spec.rb +49 -0
- metadata +28 -16
- data/lib/coconut/asset.rb +0 -27
- data/lib/coconut/environment.rb +0 -22
- data/lib/coconut/runner.rb +0 -28
- data/spec/coconut/asset_spec.rb +0 -12
- data/spec/coconut/environment_spec.rb +0 -21
data/README.md
CHANGED
@@ -19,16 +19,16 @@ Or install it yourself as:
|
|
19
19
|
$ gem install coconut
|
20
20
|
|
21
21
|
|
22
|
-
##
|
22
|
+
## Overview
|
23
23
|
Coconut provides **a simple DSL that allows you to configure your application's
|
24
|
-
assets on different environments
|
24
|
+
assets on different environments**.
|
25
25
|
|
26
26
|
```ruby
|
27
27
|
require 'coconut'
|
28
28
|
|
29
29
|
Coconut.configure MyApp do
|
30
30
|
twitter do
|
31
|
-
|
31
|
+
environments :development, :test do
|
32
32
|
consumer_key 'development_key'
|
33
33
|
consumer_secret 'development_secret'
|
34
34
|
end
|
@@ -41,11 +41,8 @@ Coconut.configure MyApp do
|
|
41
41
|
end
|
42
42
|
```
|
43
43
|
|
44
|
-
You only need to require file from your app. Coconut will define a
|
44
|
+
You only need to require the config file from your app. Coconut will define a
|
45
45
|
`config` method on your application's namespace that you can use to query it.
|
46
|
-
**You don't have to specify the environment when querying for configuration
|
47
|
-
values**. Coconut will only run the configuration for the environment it's
|
48
|
-
running on.
|
49
46
|
|
50
47
|
```ruby
|
51
48
|
ENV['RACK_ENV'] = :development
|
@@ -55,28 +52,244 @@ ENV['RACK_ENV'] = :production
|
|
55
52
|
MyApp.config.twitter.consumer_key # => production_key
|
56
53
|
```
|
57
54
|
|
58
|
-
|
59
|
-
|
55
|
+
**You don't have to specify the environment when querying for configuration
|
56
|
+
values. Coconut will only run the configuration for the environment it's
|
57
|
+
running on.**
|
60
58
|
|
61
59
|
|
62
|
-
##
|
63
|
-
|
64
|
-
|
65
|
-
|
60
|
+
## How is the environment detected?
|
61
|
+
Coconut uses the `RACK_ENV` environment variable by default to detect the
|
62
|
+
environment the app is running on. If this variable is not set or is empty it
|
63
|
+
will default to `:development`.
|
66
64
|
|
67
|
-
|
68
|
-
|
65
|
+
If your ecosystem uses something that is not RACK based you can specify how
|
66
|
+
Coconut should find out the environment with the `environment` method on the
|
67
|
+
**Application** level
|
68
|
+
(See the *Specifying how the environment should be found* section on
|
69
|
+
*Application* under *Coconut Anatomy*).
|
69
70
|
|
70
|
-
### List of files
|
71
|
-
TODO
|
72
71
|
|
72
|
+
## Coconut Anatomy
|
73
|
+
Coconut is composed of **3 different parts**:
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
### 1) Application
|
76
|
+
*Application is the top level block* of Coconut. It's composed of the configuration
|
77
|
+
of the *Assets* your application has. It may also contain information about
|
78
|
+
where Coconut should look for *Asset* configuration files and how it should
|
79
|
+
find out which environment the application is running on.
|
80
|
+
|
81
|
+
You will enter this block when you call the `Coconut.configure` method passing
|
82
|
+
in your application's namespace.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
require 'coconut'
|
86
|
+
|
87
|
+
Coconut.configure MyApp do
|
88
|
+
# your application configuration
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
Coconut will then detect the environment,
|
93
|
+
run the configuration and create a `Coconut::Config` object with the assets
|
94
|
+
and it's properties for the current environment. A `config` method will be
|
95
|
+
defined in the namespace passed to `configure`. That method will return the
|
96
|
+
`Coconut::Config` object with your application's configuration.
|
97
|
+
|
98
|
+
That means:
|
99
|
+
* You only need to make sure that you require the config file that includes the
|
100
|
+
*Application* block for your configuration to be run.
|
101
|
+
* Your configuration will be run just once (unless you use `Kernel::load`)
|
102
|
+
* If there is a `config` method on your namespace it will be overriden.
|
103
|
+
* You will be able to access the configuration from anywhere by calling
|
104
|
+
`Namespace::config`.
|
105
|
+
|
106
|
+
#### Specifying how the environment should be found
|
107
|
+
If your application should not use the RACK_ENV environment variable to
|
108
|
+
determine which configuration to use you can tell Coconut how to find out:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
Coconut.configure MyApp do
|
112
|
+
environment { MyApp::find_out_environment }
|
113
|
+
# ...
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Coconut will call the block passed to the `environment` method to find out
|
118
|
+
what environment the application is running on. Therefore **the environment
|
119
|
+
will be the return value of that block**.
|
120
|
+
|
121
|
+
|
122
|
+
#### Coconut flavours (a.k.a. how to structure your configuration).
|
123
|
+
You can structure the configuration the way that suits you best. Maybe it fits
|
124
|
+
in a single file best. Maybe you want to split it and have several configuration
|
125
|
+
files. Coconut offers you three different alternatives.
|
126
|
+
|
127
|
+
#####a) Single file
|
128
|
+
If your configuration is small this is probably the best choice. In this case
|
129
|
+
your application section will only be composed of *Assets*. You will need
|
130
|
+
nothing more. The first example of this README uses this Coconut flavour.
|
131
|
+
|
132
|
+
#####b) Folder
|
133
|
+
If you want to split your configuration in several files and put them in the
|
134
|
+
same folder you will be able to tell Coconut how to find them. Coconut will
|
135
|
+
load **every file in that folder and run them as if they were Coconut scripts**.
|
136
|
+
|
137
|
+
If the file that has the *Application* level configuration is in that folder
|
138
|
+
too it will be run and will cause an error. You have two options to avoid this:
|
139
|
+
|
140
|
+
1. Name that file `config.rb`. By convention Coconut won't run a file in an
|
141
|
+
asset folder if it is called like that.
|
142
|
+
2. Put the configuration file that has the *Application* level configuration
|
143
|
+
in a different folder. In this case the files in the given folder will only
|
144
|
+
have *Asset* configuration.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Coconut.configure MyApp do
|
148
|
+
asset_folder 'path/to/folder'
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
This allows you to write your files focusing only on *Assets*, given that
|
153
|
+
when Coconut will run then within the *Application* block. So you could
|
154
|
+
have the following project structure:
|
155
|
+
|
156
|
+
.
|
157
|
+
..
|
158
|
+
/config
|
159
|
+
config.rb
|
160
|
+
database.rb
|
161
|
+
oauth.rb
|
162
|
+
s3.rb
|
163
|
+
/lib
|
164
|
+
/spec
|
165
|
+
|
166
|
+
You could have the *Application* block on the `config.rb` file like this
|
167
|
+
(assuming that you run the application from the root of the project):
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
Coconut.configure MyApp do
|
171
|
+
asset_folder 'config'
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
And each of the asset files would only include asset configuration:
|
78
176
|
|
79
|
-
|
177
|
+
```ruby
|
178
|
+
database do
|
179
|
+
environment :development do
|
180
|
+
#...
|
181
|
+
end
|
182
|
+
|
183
|
+
environment :staging, :production do
|
184
|
+
#...
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
You would only need to require the `config/config.rb` file to access the
|
190
|
+
configuration.
|
191
|
+
|
192
|
+
#####c) List of files
|
193
|
+
|
194
|
+
*NOT DEVELOPED YET: will be available on next version (0.1.1)*
|
195
|
+
|
196
|
+
This flavour is exactly like the *folder* one with the difference that instead
|
197
|
+
of providing the path to the folder to the asset configuration files you provide
|
198
|
+
the path to every one of them:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
Coconut.configure MyApp do
|
202
|
+
asset_files 'database.rb', 'oauth.rb', 's3.rb'
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
Notice that the paths are relative to where the `config.rb` file is located.
|
207
|
+
|
208
|
+
### 2) Asset
|
209
|
+
Each of this blocks represent one of the assets of your application. Inside
|
210
|
+
each one of them you will include the *Asset* properties on each of the
|
211
|
+
environments your application will run on.
|
212
|
+
|
213
|
+
An *Asset* block consist of the asset name and a Ruby block containing it's
|
214
|
+
configuration in each of the environments:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
asset_name do
|
218
|
+
environment(:production) { property 'value on production' }
|
219
|
+
environment(:development, :test) { property 'value on test and development' }
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
You can specify one or more environments at the same time. You can also use any
|
224
|
+
of the aliases the `environment` method has:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
env(:development, :test) { property 'value' }
|
228
|
+
envs(:development, :test) { property 'value' }
|
229
|
+
environment(:development, :test) { property 'value' }
|
230
|
+
environments(:development, :test) { property 'value' }
|
231
|
+
```
|
232
|
+
|
233
|
+
An *Asset* can have *almost* any name. There are some restrictions that apply to
|
234
|
+
the names. This restrictions apply to both asset names and property names
|
235
|
+
and are explained in the *Environment* section of this README.
|
236
|
+
|
237
|
+
### 3) Environment
|
238
|
+
Environments are the lowest level of a Coconut configuration. Its composed of
|
239
|
+
the configuration properties an asset will have on that environment. So,
|
240
|
+
essentially, it's a series of key-value pairs.
|
241
|
+
|
242
|
+
Properties can have almost any name you can think of. It only has to be a valid
|
243
|
+
Ruby method name and you have to avoid those that will collide with calls on
|
244
|
+
the `Config` object. Coconut will raise an error if you use one of the reserved
|
245
|
+
names:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
environment(:development) { object_id 11 }
|
249
|
+
# => Coconut::Dsl::InvalidName: object_id can't be used as property name: it will collide with Coconut::Config methods
|
250
|
+
```
|
251
|
+
|
252
|
+
You can find out the forbidden property (and asset) names by calling the method:
|
253
|
+
```ruby
|
254
|
+
Coconut::Dsl::BlankSlate.__forbidden_names
|
255
|
+
# => [:respond_to?, :to_hash, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup,
|
256
|
+
# :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze,
|
257
|
+
# :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods,
|
258
|
+
# :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set,
|
259
|
+
# :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send,
|
260
|
+
# :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id,
|
261
|
+
# :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__,
|
262
|
+
# :instance_eval, :__send__, :object_id, :__taken?, :__taken_error_message]
|
263
|
+
```
|
264
|
+
|
265
|
+
If you want to use the methods from `Kernel` that are widely available
|
266
|
+
on Ruby objects you will need to prepend the module to the method call:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
environment(:development) do
|
270
|
+
puts 'here' # declaring a property named "puts"
|
271
|
+
Kernel::puts 'here' # printing "here" to STDOUT
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
Values can be any Ruby object, from a simple integer or string to a lambda.
|
276
|
+
You can even use constant or expressions. Keep in mind that the block passed
|
277
|
+
to environment will be evaluated in a different context:
|
278
|
+
you won't be able to call methods without specifying their receiver.
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
environment :development do
|
282
|
+
four 2 + 2
|
283
|
+
arguments ARGV
|
284
|
+
encodings Encoding.list
|
285
|
+
setup -> do
|
286
|
+
enable :sessions, :logging
|
287
|
+
disable :dump_errors, :some_custom_option
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
## Why coconut?
|
80
293
|
TODO
|
81
294
|
|
82
295
|
## Contributing
|
data/Rakefile
CHANGED
@@ -1 +1,30 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
require 'cucumber/rake/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
namespace :cucumber do
|
6
|
+
Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
|
7
|
+
t.cucumber_opts = ['--strict', '--tags ~@wip', '--format progress']
|
8
|
+
end
|
9
|
+
|
10
|
+
Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
|
11
|
+
t.cucumber_opts = ['--wip', '--tags @wip']
|
12
|
+
end
|
13
|
+
|
14
|
+
Cucumber::Rake::Task.new(:all, 'Run all features')
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :rspec do
|
18
|
+
desc 'Run specs with progress output'
|
19
|
+
RSpec::Core::RakeTask.new(:progress) do |t|
|
20
|
+
t.rspec_opts = '--format progress'
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Run all specs'
|
24
|
+
RSpec::Core::RakeTask.new(:spec)
|
25
|
+
end
|
26
|
+
|
27
|
+
task spec: 'rspec:spec'
|
28
|
+
task cucumber: 'cucumber:ok'
|
29
|
+
task test: ['rspec:progress', 'cucumber:ok']
|
30
|
+
task default: 'test'
|
data/changelog.md
ADDED
data/coconut.gemspec
CHANGED
@@ -17,9 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_runtime_dependency 'hashie'
|
21
|
-
|
22
20
|
gem.add_development_dependency 'cucumber'
|
23
21
|
gem.add_development_dependency 'rake'
|
24
22
|
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'yard'
|
25
24
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Feature: Load configuration from folder
|
2
|
+
|
3
|
+
You can split your configuration into asset files, put those files into a
|
4
|
+
folder and tell Coconut to load the configuration from there. That
|
5
|
+
folder should only include asset files. If it includes some other Ruby file
|
6
|
+
Coconut will try to execute it as such and you will get an error.
|
7
|
+
|
8
|
+
In order to be able to have all the configuration related files in a single
|
9
|
+
folder you can adhere to the convention and put your application configuration
|
10
|
+
in a file called 'config.rb' inside that folder.
|
11
|
+
|
12
|
+
If Coconut will ignore that file and it won't be executed as if it was an
|
13
|
+
asset file.
|
14
|
+
|
15
|
+
You can also have your application related configuration in other folder and
|
16
|
+
use the file name that you want.
|
17
|
+
|
18
|
+
Scenario: Application file and assets file under the same folder
|
19
|
+
Given I have my application config in "/tmp/coconut_config/config.rb" with content:
|
20
|
+
"""
|
21
|
+
Coconut.configure MyApp do
|
22
|
+
asset_folder '/tmp/coconut_config'
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
And I have a "s3.rb" asset file on /tmp/coconut_config with content:
|
26
|
+
"""
|
27
|
+
s3 do
|
28
|
+
env(:development) { key 'xxx' }
|
29
|
+
env(:production) { key 'yyy' }
|
30
|
+
end
|
31
|
+
"""
|
32
|
+
When I run my application on the "development" environment
|
33
|
+
And I query the configuration for "s3.key"
|
34
|
+
Then the configured value should be "xxx"
|
35
|
+
|
36
|
+
Scenario: Application file and assets file in different folders
|
37
|
+
Given I have my application config in "/tmp/coconut.rb" with content:
|
38
|
+
"""
|
39
|
+
Coconut.configure MyApp do
|
40
|
+
asset_folder '/tmp/coconut_config'
|
41
|
+
end
|
42
|
+
"""
|
43
|
+
And I have a "s3.rb" asset file on /tmp/coconut_config with content:
|
44
|
+
"""
|
45
|
+
facebook do
|
46
|
+
env(:development) { key 'xxx' }
|
47
|
+
env(:production) { key 'yyy' }
|
48
|
+
end
|
49
|
+
"""
|
50
|
+
When I run my application on the "development" environment
|
51
|
+
And I query the configuration for "facebook.key"
|
52
|
+
Then the configured value should be "xxx"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Feature: Single file configuration
|
2
2
|
|
3
3
|
Scenario: One environment configuration
|
4
|
-
Given my application
|
4
|
+
Given I have my application config in "/tmp/coconut_config/config.rb" with content:
|
5
5
|
"""
|
6
6
|
Coconut.configure(MyApp) do
|
7
7
|
ftp do
|
@@ -14,3 +14,18 @@ Feature: Single file configuration
|
|
14
14
|
When I run my application on the "development" environment
|
15
15
|
And I query the configuration for "ftp.user"
|
16
16
|
Then the configured value should be "root"
|
17
|
+
|
18
|
+
Scenario: Several environments configuration
|
19
|
+
Given I have my application config in "/tmp/coconut_config/config.rb" with content:
|
20
|
+
"""
|
21
|
+
Coconut.configure(MyApp) do
|
22
|
+
ssh do
|
23
|
+
environment(:development, :staging, :production) do
|
24
|
+
login 'username'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
"""
|
29
|
+
When I run my application on the "development" environment
|
30
|
+
And I query the configuration for "ssh.login"
|
31
|
+
Then the configured value should be "username"
|
@@ -1,10 +1,15 @@
|
|
1
|
-
Given /^my application
|
2
|
-
@
|
1
|
+
Given /^I have my application config in "(.*?)" with content:$/ do |file, content|
|
2
|
+
@config = file
|
3
|
+
File.open(file, 'w+') { |file| file.write(content) }
|
4
|
+
end
|
5
|
+
|
6
|
+
Given /^I have a "(.*?)" asset file on (.*?) with content:$/ do |name, folder, content|
|
7
|
+
File.open(File.join(folder, name), 'w+') { |file| file.write(content) }
|
3
8
|
end
|
4
9
|
|
5
10
|
When /^I run my application on the "(.*?)" environment$/ do |environment|
|
6
11
|
ENV['RACK_ENV']= environment
|
7
|
-
eval @
|
12
|
+
eval "load '#{@config}'"
|
8
13
|
end
|
9
14
|
|
10
15
|
When /^I query the configuration for "(.*?)"$/ do |query|
|
data/features/support/env.rb
CHANGED
@@ -2,4 +2,32 @@ $: << File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
|
2
2
|
|
3
3
|
require 'coconut'
|
4
4
|
|
5
|
-
|
5
|
+
CONFIG_FOLDER = '/tmp/coconut_config/'
|
6
|
+
|
7
|
+
Before do
|
8
|
+
clean_tmp_folder
|
9
|
+
create_folder_for_testing
|
10
|
+
create_namespace_for_testing
|
11
|
+
end
|
12
|
+
|
13
|
+
After do
|
14
|
+
clean_tmp_folder
|
15
|
+
clean_namespace_for_testing
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_namespace_for_testing
|
19
|
+
Object.send(:const_set, :MyApp, Module.new)
|
20
|
+
end
|
21
|
+
|
22
|
+
def clean_namespace_for_testing
|
23
|
+
Object.send(:remove_const, :MyApp)
|
24
|
+
end
|
25
|
+
|
26
|
+
def clean_tmp_folder
|
27
|
+
`rm -rf #{CONFIG_FOLDER}` if File.directory?(CONFIG_FOLDER)
|
28
|
+
`rm -f /tmp/coconut.rb` if File.exists?('/tmp/coconut.rb')
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_folder_for_testing
|
32
|
+
`mkdir #{CONFIG_FOLDER}`
|
33
|
+
end
|
data/lib/coconut.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
require_relative 'coconut/version'
|
2
|
-
require_relative 'coconut/
|
2
|
+
require_relative 'coconut/dsl/application'
|
3
3
|
|
4
4
|
module Coconut
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
include Dsl
|
6
|
+
|
7
|
+
def self.configure(namespace, &config)
|
8
|
+
define_config_method namespace, Application.configure(environment, &config)
|
8
9
|
end
|
9
10
|
|
10
|
-
def self.
|
11
|
+
def self.environment
|
12
|
+
return @__coconut_environment.() unless @__coconut_environment.nil?
|
11
13
|
ENV['RACK_ENV'] || :development
|
12
14
|
end
|
13
15
|
|
16
|
+
def self.take_environment_from(&block)
|
17
|
+
@__coconut_environment = block
|
18
|
+
end
|
19
|
+
|
14
20
|
private
|
15
21
|
|
16
22
|
def self.define_config_method(namespace, configuration)
|
17
|
-
|
18
|
-
define_method
|
19
|
-
@_coconut_configuration ||= configuration
|
20
|
-
end
|
23
|
+
namespace.singleton_class.instance_eval do
|
24
|
+
define_method(:config) { @__coconut_configuration ||= configuration }
|
21
25
|
end
|
22
26
|
end
|
23
|
-
|
24
|
-
def self.singleton_class_of(class_or_module)
|
25
|
-
class << class_or_module; self; end
|
26
|
-
end
|
27
27
|
end
|
data/lib/coconut/config.rb
CHANGED
@@ -1,23 +1,53 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'hashie'
|
3
|
-
require_relative 'asset'
|
4
|
-
require_relative 'runner'
|
5
|
-
|
6
1
|
module Coconut
|
7
|
-
class Config
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
2
|
+
class Config
|
3
|
+
def initialize(properties)
|
4
|
+
@properties = properties
|
5
|
+
@config = config_from(properties)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Coconut::Config objects will respond to methods if its name is
|
9
|
+
# equal to any of the configured properties. This method is redefined to
|
10
|
+
# make its behaviour consistent with Coconut::Config#method_missing
|
11
|
+
#
|
12
|
+
# @return [Boolean] whether a config object knows how to respond to method "name"
|
13
|
+
def respond_to?(name)
|
14
|
+
property?(name) or super
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Hash] a Hash representation of the configuration object
|
18
|
+
def to_hash
|
19
|
+
@properties.dup
|
11
20
|
end
|
12
21
|
|
13
22
|
private
|
14
23
|
|
15
|
-
def
|
16
|
-
|
24
|
+
def config_from(properties)
|
25
|
+
properties.each_with_object({}) do |(property, value), config|
|
26
|
+
config[property]= config_value_for(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def config_value_for(value)
|
31
|
+
return Config.new(value) if value.is_a? Hash
|
32
|
+
value
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(name, *args, &block)
|
36
|
+
if property?(name)
|
37
|
+
define_property_accessor(name)
|
38
|
+
return @config[name]
|
39
|
+
end
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def property?(name)
|
44
|
+
@config.has_key?(name)
|
17
45
|
end
|
18
46
|
|
19
|
-
def
|
20
|
-
|
47
|
+
def define_property_accessor(name)
|
48
|
+
singleton_class.instance_eval do
|
49
|
+
define_method(name) { @config[name] }
|
50
|
+
end
|
21
51
|
end
|
22
52
|
end
|
23
53
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'asset'
|
2
|
+
require_relative 'asset_folder'
|
3
|
+
|
4
|
+
module Coconut
|
5
|
+
module Dsl
|
6
|
+
class Application < BlankSlate
|
7
|
+
def self.configure(current_environment, &config)
|
8
|
+
new(current_environment).run(&config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(current_environment)
|
12
|
+
@current_environment = current_environment
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(&config)
|
16
|
+
@assets_config = {}
|
17
|
+
instance_eval &config
|
18
|
+
Config.new(@assets_config)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def asset_folder(path)
|
24
|
+
instance_eval AssetFolder.config_from(path, IGNORED_FILES)
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(asset, *args, &config)
|
28
|
+
::Kernel::raise InvalidName, __taken_error_message(asset, 'asset name') if __taken?(asset)
|
29
|
+
@assets_config[asset] = Asset.configure(@current_environment, &config)
|
30
|
+
end
|
31
|
+
|
32
|
+
IGNORED_FILES = ['config.rb']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../config'
|
2
|
+
require_relative './environment'
|
3
|
+
|
4
|
+
module Coconut
|
5
|
+
module Dsl
|
6
|
+
class Asset
|
7
|
+
def self.configure(environment, &config)
|
8
|
+
new(environment).run(&config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(current_environment)
|
12
|
+
@current_environment = current_environment
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(&config)
|
16
|
+
@properties = {}
|
17
|
+
instance_eval &config
|
18
|
+
@properties
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def environment(*environments, &config)
|
24
|
+
environments.each { |environment| __configure(environment, config) }
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :env :environment
|
28
|
+
alias :envs :environment
|
29
|
+
alias :environments :environment
|
30
|
+
|
31
|
+
def __configure(environment, config)
|
32
|
+
@properties.merge! Environment.configure(&config) if __current?(environment)
|
33
|
+
end
|
34
|
+
|
35
|
+
def __current?(environment)
|
36
|
+
@current_environment.to_sym == environment.to_sym
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Coconut
|
2
|
+
module Dsl
|
3
|
+
class AssetFolder
|
4
|
+
def self.config_from(path, ignored_files)
|
5
|
+
new(path, ignored_files).assets_config
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(path, ignored_files)
|
9
|
+
@path = path
|
10
|
+
@ignored_files = ignored_files.map(&method(:path_to))
|
11
|
+
end
|
12
|
+
|
13
|
+
def assets_config
|
14
|
+
asset_files_in_folder.map { |file| content(file) }.join "\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def asset_files_in_folder
|
20
|
+
ruby_files_in_folder - @ignored_files
|
21
|
+
end
|
22
|
+
|
23
|
+
def ruby_files_in_folder
|
24
|
+
files_in_folder.select { |file| file.match /\.rb$/ }
|
25
|
+
end
|
26
|
+
|
27
|
+
def files_in_folder
|
28
|
+
folder.entries.map(&method(:path_to)).reject { |path| File.directory? path }
|
29
|
+
end
|
30
|
+
|
31
|
+
def folder
|
32
|
+
Dir.open(@path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def content(file)
|
36
|
+
File.read(file)
|
37
|
+
end
|
38
|
+
|
39
|
+
def path_to(file)
|
40
|
+
File.expand_path(File.join(@path, file))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../config'
|
2
|
+
|
3
|
+
module Coconut
|
4
|
+
module Dsl
|
5
|
+
class InvalidName < Exception; end
|
6
|
+
|
7
|
+
class BlankSlate < BasicObject
|
8
|
+
def self.__forbidden_names
|
9
|
+
Config.instance_methods + PERMANENT_METHODS
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def __taken?(name)
|
15
|
+
Config.instance_methods.include? name
|
16
|
+
end
|
17
|
+
|
18
|
+
def __taken_error_message(name, usage)
|
19
|
+
"#{name} can't be used as #{usage}: it will collide with Coconut::Config methods"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.inherited(subclass)
|
23
|
+
__eraseable_methods.each{ |method_name| undef_method method_name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.__eraseable_methods
|
27
|
+
instance_methods - PERMANENT_METHODS
|
28
|
+
end
|
29
|
+
|
30
|
+
PERMANENT_METHODS = [:instance_eval, :__send__, :object_id, :__taken?, :__taken_error_message, :asset_folder]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'blank_slate'
|
2
|
+
require_relative '../config'
|
3
|
+
|
4
|
+
module Coconut
|
5
|
+
module Dsl
|
6
|
+
class Environment < BlankSlate
|
7
|
+
def self.configure(&config)
|
8
|
+
new.configure(&config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@properties = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def configure(&config)
|
16
|
+
instance_eval &config
|
17
|
+
@properties
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def method_missing(name, *args, &block)
|
23
|
+
::Kernel::raise InvalidName, __taken_error_message(name, 'property name') if __taken?(name)
|
24
|
+
@properties[name] = args.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/coconut/version.rb
CHANGED
@@ -2,7 +2,6 @@ require 'coconut'
|
|
2
2
|
|
3
3
|
class MyClass; end
|
4
4
|
module MyModule; end
|
5
|
-
module MyConfig; def self.config; end; end;
|
6
5
|
|
7
6
|
describe Coconut do
|
8
7
|
it 'defines a config method in the provided namespace' do
|
@@ -12,9 +11,28 @@ describe Coconut do
|
|
12
11
|
MyModule.should respond_to(:config)
|
13
12
|
end
|
14
13
|
|
15
|
-
it
|
16
|
-
|
14
|
+
it "allows the app's configuration to be run twice" do
|
15
|
+
Coconut.configure(MyClass) { asset { env(:development){ property 'initial value' } } }
|
16
|
+
Coconut.configure(MyClass) { asset { env(:development){ property 'latest value' } } }
|
17
|
+
MyClass::config.asset.property.should eq 'latest value'
|
17
18
|
end
|
18
|
-
end
|
19
19
|
|
20
|
+
context 'finding out the environment' do
|
21
|
+
before { Coconut.instance_variable_set(:@__coconut_environment, nil) }
|
22
|
+
|
23
|
+
it 'uses the expression provided by the user if any' do
|
24
|
+
Coconut.take_environment_from { :somewhere }
|
25
|
+
Coconut.environment.should eq :somewhere
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'uses the RACK_ENV environment variable by default' do
|
29
|
+
ENV['RACK_ENV'] = 'rack_env'
|
30
|
+
Coconut.environment.should eq 'rack_env'
|
31
|
+
end
|
20
32
|
|
33
|
+
it 'uses :development as environment if RACK_ENV is not defined' do
|
34
|
+
ENV['RACK_ENV'] = nil
|
35
|
+
Coconut.environment.should eq :development
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/spec/coconut/config_spec.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
require 'coconut/config'
|
2
2
|
|
3
3
|
describe Coconut::Config do
|
4
|
-
|
4
|
+
subject { described_class.new(application) }
|
5
|
+
let(:application) { Hash[twitter: properties] }
|
6
|
+
let(:properties) { Hash[property: 'value', other: 'other'] }
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
ssh {}
|
8
|
+
context 'newly created' do
|
9
|
+
it 'has no property methods' do
|
10
|
+
(subject.methods - Object.instance_methods).should eq [:to_hash]
|
10
11
|
end
|
11
|
-
subject.should have_key 'ftp'
|
12
|
-
subject.should have_key 'ssh'
|
13
|
-
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
ssh_config = lambda {}
|
18
|
-
asset_config.should_receive(:new).with(:current, &ftp_config)
|
19
|
-
asset_config.should_receive(:new).with(:current, &ssh_config)
|
20
|
-
described_class.new(:current) do
|
21
|
-
ftp &ftp_config
|
22
|
-
ssh &ssh_config
|
13
|
+
it 'responds to every property' do
|
14
|
+
subject.should respond_to :twitter
|
23
15
|
end
|
24
16
|
end
|
25
17
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
context 'when a property is queried' do
|
19
|
+
it 'returns the property value' do
|
20
|
+
subject.twitter.property.should eq 'value'
|
21
|
+
subject.twitter.other.should eq 'other'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'defines a new method to access the property' do
|
25
|
+
subject.methods.should_not include(:twitter)
|
26
|
+
subject.twitter
|
27
|
+
subject.methods.should include(:twitter)
|
31
28
|
end
|
32
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can be transformed to a hash' do
|
32
|
+
subject.to_hash.should eq application
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'coconut/dsl/application'
|
2
|
+
|
3
|
+
describe Coconut::Dsl::Application do
|
4
|
+
it 'creates the config for every asset' do
|
5
|
+
config = described_class.new(:current).run do
|
6
|
+
asset { environment(:current) { property 'value' } }
|
7
|
+
end
|
8
|
+
config.asset.property.should eq 'value'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "doesn't allow assets with colliding names" do
|
12
|
+
expect {
|
13
|
+
described_class.configure(:current) { to_s {} }
|
14
|
+
}.to raise_error Coconut::Dsl::InvalidName, /to_s/
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can load the asset configuration from a folder' do
|
18
|
+
path = '.'
|
19
|
+
Coconut::Dsl::AssetFolder.stub(:config_from).with(path, ['config.rb']).
|
20
|
+
and_return("asset { environment(:current) { property 'value' } }")
|
21
|
+
config = described_class.configure(:current) { asset_folder path }
|
22
|
+
config.asset.property.should eq 'value'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'coconut/dsl/asset'
|
2
|
+
|
3
|
+
describe Coconut::Dsl::Asset do
|
4
|
+
it 'creates the asset config for the given environment' do
|
5
|
+
asset_config = described_class.configure(:current) do
|
6
|
+
environment(:other) { property 'value in other' }
|
7
|
+
environment(:current) { property 'value in current' }
|
8
|
+
end
|
9
|
+
asset_config.fetch(:property).should eq 'value in current'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can setup different environments at the same time' do
|
13
|
+
asset_config = described_class.configure(:current) do
|
14
|
+
environment(:other, :current) { property 'value in other and current' }
|
15
|
+
end
|
16
|
+
asset_config.fetch(:property).should eq 'value in other and current'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can setup environments step by step' do
|
20
|
+
asset_config = described_class.configure(:current) do
|
21
|
+
environment(:other, :current) { property 'value in other and current' }
|
22
|
+
environment(:other) { thing 'thing in other' }
|
23
|
+
environment(:current) { thing 'thing in current' }
|
24
|
+
end
|
25
|
+
asset_config.fetch(:property).should eq 'value in other and current'
|
26
|
+
asset_config.fetch(:thing).should eq 'thing in current'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'allows properties to be overriden' do
|
30
|
+
asset_config = described_class.configure(:current) do
|
31
|
+
environment(:other, :current) { property 'value in other and current' }
|
32
|
+
environment(:current) { property 'only in current' }
|
33
|
+
end
|
34
|
+
asset_config.fetch(:property).should eq 'only in current'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has alternate ways of configuring an environment' do
|
38
|
+
asset_config = described_class.configure(:current) do
|
39
|
+
env(:current) { property1 1 }
|
40
|
+
environment(:current) { property2 2 }
|
41
|
+
environments(:current) { property3 3 }
|
42
|
+
envs(:current) { property4 4 }
|
43
|
+
end
|
44
|
+
asset_config.fetch(:property1).should eq 1
|
45
|
+
asset_config.fetch(:property2).should eq 2
|
46
|
+
asset_config.fetch(:property3).should eq 3
|
47
|
+
asset_config.fetch(:property4).should eq 4
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'coconut/dsl/asset_folder'
|
2
|
+
|
3
|
+
describe 'Assets configuration from files in folder', integration: true do
|
4
|
+
let(:path) { '/tmp/coconut-testing/' }
|
5
|
+
let(:ignored) { ['config.rb'] }
|
6
|
+
|
7
|
+
let(:config) { "" }
|
8
|
+
let(:s3_config) { "s3 { environment(:current) { property 'p1' } }" }
|
9
|
+
let(:db_config) { "database { environment(:current) { property 'p2' } }" }
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
`rm -rf #{path}`
|
13
|
+
Dir::mkdir path
|
14
|
+
File.open(File.join(path, 'config.rb'), 'w+') { |f| f.write '' }
|
15
|
+
File.open(File.join(path, 's3.rb'), 'w+') { |f| f.write s3_config }
|
16
|
+
File.open(File.join(path, 'db.rb'), 'w+') { |f| f.write db_config }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'evals every asset file in the folder as application config' do
|
20
|
+
configuration_from_folder.should eq "#{db_config}\n#{s3_config}"
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with other files that are not Ruby files' do
|
24
|
+
before do
|
25
|
+
File.open(File.join(path, 'readme.md'), 'w+') { |f| f.write 'readme' }
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'ignores non Ruby files' do
|
29
|
+
configuration_from_folder.should eq "#{db_config}\n#{s3_config}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def configuration_from_folder
|
34
|
+
Coconut::Dsl::AssetFolder.config_from(path, ignored)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'coconut/dsl/blank_slate'
|
2
|
+
|
3
|
+
describe Coconut::Dsl::BlankSlate do
|
4
|
+
it 'knows the forbidden names' do
|
5
|
+
expected_names = described_class::PERMANENT_METHODS + [:one]
|
6
|
+
Coconut::Config.stub(:instance_methods).and_return([:one])
|
7
|
+
described_class.__forbidden_names.should include(*expected_names)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'coconut/dsl/environment'
|
2
|
+
|
3
|
+
describe Coconut::Dsl::Environment do
|
4
|
+
it 'creates an environment config' do
|
5
|
+
config = described_class.configure do
|
6
|
+
property 'value'
|
7
|
+
other 'other'
|
8
|
+
end
|
9
|
+
config.fetch(:property).should eq 'value'
|
10
|
+
config.fetch(:other).should eq 'other'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "doesn't allow properties with colliding names" do
|
14
|
+
expect {
|
15
|
+
described_class.configure { object_id '11' }
|
16
|
+
}.to raise_error Coconut::Dsl::InvalidName, /object_id/
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can take procs as property values' do
|
20
|
+
config = described_class.configure do
|
21
|
+
property1 Proc.new { 'value' }
|
22
|
+
property2 -> { 'value' }
|
23
|
+
property3 Kernel::lambda { 'value' }
|
24
|
+
end
|
25
|
+
config.fetch(:property1).should be_a_kind_of Proc
|
26
|
+
config.fetch(:property2).should be_a_kind_of Proc
|
27
|
+
config.fetch(:property3).should be_a_kind_of Proc
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can take expressions as property values' do
|
31
|
+
config = described_class.configure do
|
32
|
+
property1 2 + 2
|
33
|
+
property2 'aeiou'[0..2]
|
34
|
+
property3 [:a, :e, :i, :o, :u].take(3)
|
35
|
+
end
|
36
|
+
config.fetch(:property1).should eq 4
|
37
|
+
config.fetch(:property2).should eq 'aei'
|
38
|
+
config.fetch(:property3).should eq [:a, :e, :i]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can take constants and scoped method calls as property values' do
|
42
|
+
config = described_class.configure do
|
43
|
+
arguments Math::PI
|
44
|
+
encodings Encoding.list
|
45
|
+
end
|
46
|
+
config.fetch(:arguments).should eq Math::PI
|
47
|
+
config.fetch(:encodings).should eq Encoding.list
|
48
|
+
end
|
49
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coconut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,17 +9,17 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11
|
12
|
+
date: 2012-12-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: cucumber
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: '0'
|
22
|
-
type: :
|
22
|
+
type: :development
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: rake
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
none: false
|
34
34
|
requirements:
|
@@ -44,7 +44,7 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: rspec
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
@@ -60,7 +60,7 @@ dependencies:
|
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
63
|
+
name: yard
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
66
66
|
requirements:
|
@@ -88,20 +88,27 @@ files:
|
|
88
88
|
- LICENSE.txt
|
89
89
|
- README.md
|
90
90
|
- Rakefile
|
91
|
+
- changelog.md
|
91
92
|
- coconut.gemspec
|
93
|
+
- features/folder_configuration.feature
|
92
94
|
- features/single_file_configuration.feature
|
93
95
|
- features/step_definitions/configuration_steps.rb
|
94
96
|
- features/support/env.rb
|
95
97
|
- lib/coconut.rb
|
96
|
-
- lib/coconut/asset.rb
|
97
98
|
- lib/coconut/config.rb
|
98
|
-
- lib/coconut/
|
99
|
-
- lib/coconut/
|
99
|
+
- lib/coconut/dsl/application.rb
|
100
|
+
- lib/coconut/dsl/asset.rb
|
101
|
+
- lib/coconut/dsl/asset_folder.rb
|
102
|
+
- lib/coconut/dsl/blank_slate.rb
|
103
|
+
- lib/coconut/dsl/environment.rb
|
100
104
|
- lib/coconut/version.rb
|
101
|
-
- spec/coconut/asset_spec.rb
|
102
105
|
- spec/coconut/coconut_spec.rb
|
103
106
|
- spec/coconut/config_spec.rb
|
104
|
-
- spec/coconut/
|
107
|
+
- spec/coconut/dsl/application_spec.rb
|
108
|
+
- spec/coconut/dsl/asset_spec.rb
|
109
|
+
- spec/coconut/dsl/assets_folder_spec.rb
|
110
|
+
- spec/coconut/dsl/blank_slate_spec.rb
|
111
|
+
- spec/coconut/dsl/environment_spec.rb
|
105
112
|
homepage: http://github.com/jacegu/coconut
|
106
113
|
licenses: []
|
107
114
|
post_install_message:
|
@@ -116,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
123
|
version: '0'
|
117
124
|
segments:
|
118
125
|
- 0
|
119
|
-
hash:
|
126
|
+
hash: -443559654264598590
|
120
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
128
|
none: false
|
122
129
|
requirements:
|
@@ -125,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
132
|
version: '0'
|
126
133
|
segments:
|
127
134
|
- 0
|
128
|
-
hash:
|
135
|
+
hash: -443559654264598590
|
129
136
|
requirements: []
|
130
137
|
rubyforge_project:
|
131
138
|
rubygems_version: 1.8.24
|
@@ -134,10 +141,15 @@ specification_version: 3
|
|
134
141
|
summary: Coconut is a simple DSL that allows you to easily write and query your application's
|
135
142
|
configuration with pure Ruby.
|
136
143
|
test_files:
|
144
|
+
- features/folder_configuration.feature
|
137
145
|
- features/single_file_configuration.feature
|
138
146
|
- features/step_definitions/configuration_steps.rb
|
139
147
|
- features/support/env.rb
|
140
|
-
- spec/coconut/asset_spec.rb
|
141
148
|
- spec/coconut/coconut_spec.rb
|
142
149
|
- spec/coconut/config_spec.rb
|
143
|
-
- spec/coconut/
|
150
|
+
- spec/coconut/dsl/application_spec.rb
|
151
|
+
- spec/coconut/dsl/asset_spec.rb
|
152
|
+
- spec/coconut/dsl/assets_folder_spec.rb
|
153
|
+
- spec/coconut/dsl/blank_slate_spec.rb
|
154
|
+
- spec/coconut/dsl/environment_spec.rb
|
155
|
+
has_rdoc:
|
data/lib/coconut/asset.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'hashie'
|
3
|
-
require_relative 'environment'
|
4
|
-
|
5
|
-
module Coconut
|
6
|
-
class Asset < DelegateClass(Hashie::Mash)
|
7
|
-
def initialize(current_environment, &config)
|
8
|
-
super Hashie::Mash.new
|
9
|
-
run config, current_environment if block_given?
|
10
|
-
end
|
11
|
-
|
12
|
-
def environment(environment_name, &config)
|
13
|
-
merge! Environment.new(&config) if current? environment_name
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def run(config, environment)
|
19
|
-
@current_environment = environment
|
20
|
-
instance_eval &config
|
21
|
-
end
|
22
|
-
|
23
|
-
def current?(environment)
|
24
|
-
environment.to_sym == @current_environment.to_sym
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
data/lib/coconut/environment.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
require 'hashie'
|
3
|
-
require_relative 'runner'
|
4
|
-
|
5
|
-
module Coconut
|
6
|
-
class Environment < DelegateClass(Hashie::Mash)
|
7
|
-
def initialize(&config)
|
8
|
-
super Hashie::Mash.new
|
9
|
-
run config
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def run(config)
|
15
|
-
Runner.new run: config, method_missing: method(:configure_property)
|
16
|
-
end
|
17
|
-
|
18
|
-
def configure_property(name, values)
|
19
|
-
self[name] = values.first
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/coconut/runner.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
module Coconut
|
2
|
-
class BlankSlate < BasicObject
|
3
|
-
private
|
4
|
-
|
5
|
-
def self.inherited(subclass)
|
6
|
-
eraseable_methods.each{ |method_name| undef_method method_name }
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.eraseable_methods
|
10
|
-
instance_methods - PERMANENT_METHODS
|
11
|
-
end
|
12
|
-
|
13
|
-
PERMANENT_METHODS = [:instance_eval, :__send__, :object_id]
|
14
|
-
end
|
15
|
-
|
16
|
-
class Runner < BlankSlate
|
17
|
-
def initialize(options)
|
18
|
-
@callback = options[:method_missing]
|
19
|
-
instance_eval &options[:run]
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def method_missing(name, *args, &block)
|
25
|
-
@callback.(name, args, &block)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
data/spec/coconut/asset_spec.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
require 'coconut/asset'
|
2
|
-
|
3
|
-
describe Coconut::Asset do
|
4
|
-
it "knows the asset's config on the current environment" do
|
5
|
-
subject = described_class.new(:current) do
|
6
|
-
environment(:other) { property 'value on other' }
|
7
|
-
environment(:current) { property 'value on current' }
|
8
|
-
end
|
9
|
-
subject.should have_key 'property'
|
10
|
-
subject.fetch('property').should eq 'value on current'
|
11
|
-
end
|
12
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'hashie'
|
2
|
-
require 'coconut/environment'
|
3
|
-
|
4
|
-
describe Coconut::Environment do
|
5
|
-
it 'collects method calls as configuration values' do
|
6
|
-
config = described_class.new { property 'value' }
|
7
|
-
config.should have_key :property
|
8
|
-
config.property.should eq 'value'
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'can have (almost) any property name' do
|
12
|
-
config = described_class.new do
|
13
|
-
__id__ 11
|
14
|
-
equal? 33
|
15
|
-
inspect 'CoconutJuice'
|
16
|
-
end
|
17
|
-
config.fetch('__id__').should eq 11
|
18
|
-
config.fetch('equal?').should eq 33
|
19
|
-
config.fetch('inspect').should eq 'CoconutJuice'
|
20
|
-
end
|
21
|
-
end
|