coconut 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|