djin 0.6.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35900c4ab60e94710d24194ea2192edbf4e806527af87fe9d53e693bcec03704
4
- data.tar.gz: 53a1b1caabf4ff2e7a708fa1e31ccd7b552514cc6eb882d7152ba015bb58db01
3
+ metadata.gz: 5e44393cb2f47b9b3a68d2ce59ae997e5e1d6b252e68778293b5e62a7600374b
4
+ data.tar.gz: 5c02ed7dc956357e31b07c6ade84c5f660cb26469f7434ea0128b42f0ebec74f
5
5
  SHA512:
6
- metadata.gz: 3cb346575e3426446ad0c2ea2043d386a6112b9414d84927b24a5f0e3dec6062c361a9a6f0910c34fb13819d978d1721d882f53fe36fcf4a5aa5231eac4b6cc3
7
- data.tar.gz: 34888b78f7f7af5792fc75f373870792d41986f818bfef643ad8d27f954b9f5e92791226c282933e0f3d7fa57acc42a0f71838df45acff47d48b424d53c24051
6
+ metadata.gz: 1d8d9dbd993757586e35679c12e1d03b98c940e5bca58e6ab2b3b8955c84c78b2fad6ae2053a0430b85aa580217a8540b3196ab3489c0ae76c4426e712142822
7
+ data.tar.gz: be25638d743e46c62a452891298003d3dd93905ea38b6bbe51420d44549ba001785f7b9a631cc4e4b83c61ffa541688420df9bd9339023b1cb86747ad2ae87c4
@@ -14,10 +14,12 @@ jobs:
14
14
  - uses: actions/setup-ruby@v1
15
15
  with:
16
16
  ruby-version: ${{ matrix.ruby }}
17
- - run: |
17
+ - name: Install Gems
18
+ run: |
18
19
  gem install bundler
19
20
  bundle install --jobs 4 --retry 3
20
- bundle exec rake
21
+ - name: Run tests
22
+ run: bundle exec rake
21
23
 
22
24
  lint:
23
25
  runs-on: ubuntu-16.04
@@ -27,8 +29,10 @@ jobs:
27
29
  - uses: actions/setup-ruby@v1
28
30
  with:
29
31
  ruby-version: '2.6'
30
- - run: |
32
+ - name: Install Gems
33
+ run: |
31
34
  gem install bundler
32
35
  bundle install --jobs 4 --retry 3
33
- bundle exec rubocop
36
+ - name: Rubocop
37
+ run: bundle exec rubocop
34
38
 
@@ -1,3 +1,18 @@
1
+ ## 0.10.0 - 08/10/2020
2
+ * [FEATURE] -f command option
3
+
4
+ ## 0.9.0 - 17/09/2020
5
+ * [FEATURE] Include Option
6
+
7
+ ## 0.8.0 - 31/08/2020
8
+ * [FEATURE] Adds aliases option
9
+
10
+ ## 0.7.0 - 20/08/2020
11
+ * [FEATURE] Adds description option
12
+
13
+ ## 0.6.1 - 11/08/2020
14
+ * [TECH] Better Error Handling
15
+
1
16
  ## 0.6.0 - 22/07/2020
2
17
  * [FEATURE] Djin Variables
3
18
 
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- djin (0.6.0)
4
+ djin (0.10.0)
5
5
  dry-cli (~> 0.6.0)
6
+ dry-equalizer (~> 0.3.0)
6
7
  dry-struct (~> 1.3.0)
7
8
  dry-validation (~> 1.5.1)
8
9
  mustache (~> 1.1.1)
@@ -13,7 +14,7 @@ GEM
13
14
  specs:
14
15
  ast (2.4.1)
15
16
  byebug (11.1.1)
16
- concurrent-ruby (1.1.6)
17
+ concurrent-ruby (1.1.7)
17
18
  diff-lcs (1.3)
18
19
  dry-cli (0.6.0)
19
20
  concurrent-ruby (~> 1.0)
@@ -28,12 +29,12 @@ GEM
28
29
  concurrent-ruby (~> 1.0)
29
30
  dry-equalizer (0.3.0)
30
31
  dry-inflector (0.2.0)
31
- dry-initializer (3.0.3)
32
- dry-logic (1.0.6)
32
+ dry-initializer (3.0.4)
33
+ dry-logic (1.0.8)
33
34
  concurrent-ruby (~> 1.0)
34
35
  dry-core (~> 0.2)
35
36
  dry-equalizer (~> 0.2)
36
- dry-schema (1.5.2)
37
+ dry-schema (1.5.5)
37
38
  concurrent-ruby (~> 1.0)
38
39
  dry-configurable (~> 0.8, >= 0.8.3)
39
40
  dry-core (~> 0.4)
@@ -53,13 +54,13 @@ GEM
53
54
  dry-equalizer (~> 0.3)
54
55
  dry-inflector (~> 0.1, >= 0.1.2)
55
56
  dry-logic (~> 1.0, >= 1.0.2)
56
- dry-validation (1.5.2)
57
+ dry-validation (1.5.6)
57
58
  concurrent-ruby (~> 1.0)
58
59
  dry-container (~> 0.7, >= 0.7.1)
59
60
  dry-core (~> 0.4)
60
61
  dry-equalizer (~> 0.2)
61
62
  dry-initializer (~> 3.0)
62
- dry-schema (~> 1.5)
63
+ dry-schema (~> 1.5, >= 1.5.2)
63
64
  ice_nine (0.11.2)
64
65
  mustache (1.1.1)
65
66
  parallel (1.19.2)
data/README.md CHANGED
@@ -26,7 +26,7 @@ If you use Rbenv you can install djin only once and create a alias in your .basr
26
26
  To use djin first you need to create a djin.yml file:
27
27
 
28
28
  ```yaml
29
- djin_version: '0.6.0'
29
+ djin_version: '0.10.0'
30
30
 
31
31
  tasks:
32
32
  # With a docker image
@@ -40,18 +40,20 @@ tasks:
40
40
 
41
41
  # Using a docker-compose service
42
42
  test:
43
- docker-compose:
44
- service: app
45
- run:
46
- commands: rspec
47
- options: "--rm"
43
+ docker-compose:
44
+ service: app
45
+ run:
46
+ commands: rspec
47
+ options: "--rm"
48
+ aliases: # Optional Array of strings
49
+ - rspec
48
50
  ```
49
51
 
50
52
  You can also set task dependencies with depends_on option:
51
53
 
52
54
 
53
55
  ```yaml
54
- djin_version: '0.6.0'
56
+ djin_version: '0.10.0'
55
57
 
56
58
  _default_run_options: &default_run_options
57
59
  options: "--rm"
@@ -80,7 +82,7 @@ tasks:
80
82
  Or mix local commands and docker/docker-compose commands:
81
83
 
82
84
  ```yaml
83
- djin_version: '0.6.0'
85
+ djin_version: '0.10.0'
84
86
 
85
87
  _default_run_options: &default_run_options
86
88
  options: "--rm"
@@ -119,7 +121,7 @@ After that you can run `djin {{task_name}}`, like `djin script` or `djin test`
119
121
  You can also use environment variables using the '{{YOUR_ENV_HERE}}' syntax, like so:
120
122
 
121
123
  ```yaml
122
- djin_version: '0.6.0'
124
+ djin_version: '0.10.0'
123
125
 
124
126
  _default_run_options: &default_run_options
125
127
  options: "--rm"
@@ -136,7 +138,7 @@ tasks:
136
138
 
137
139
  Or define some variables to use in multiple locations
138
140
  ```yaml
139
- djin_version: '0.6.0'
141
+ djin_version: '0.10.0'
140
142
 
141
143
  _default_run_options: &default_run_options
142
144
  options: "--rm"
@@ -160,7 +162,7 @@ tasks:
160
162
  It's also possible to pass custom arguments to the command, which means is possible to make a djin task act like the command itself:
161
163
 
162
164
  ```yaml
163
- djin_version: '0.6.0'
165
+ djin_version: '0.10.0'
164
166
 
165
167
  _default_run_options: &default_run_options
166
168
  options: "--rm"
@@ -172,12 +174,117 @@ tasks:
172
174
  run:
173
175
  commands: rubocop {{args}}
174
176
  <<: *default_run_options
177
+ aliases:
178
+ - lint
175
179
 
176
180
  ```
177
181
 
178
182
  With that you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which wil make djin runs `rubocop --parallel` inside the service `app`.
179
183
 
180
- Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: `{{#args?} {{args}} --and-other-thing{{/args?}}`), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
184
+ Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: `{{#args?}} {{args}} --and-other-thing{{/args?}}`), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
185
+
186
+ ### Reusing tasks
187
+
188
+ If you have multiple tasks with similar behaviour and with small differences you can use the `include` keyword, so this:
189
+
190
+ ```yaml
191
+ djin_version: '0.10.0'
192
+
193
+ tasks:
194
+ "host1:ssh":
195
+ local:
196
+ run:
197
+ - ssh my_user@host1.com.br
198
+
199
+ "host1:restart":
200
+ local:
201
+ run:
202
+ - ssh -t my_user@host1.com.br restart
203
+
204
+ "host1:logs":
205
+ local:
206
+ run:
207
+ - ssh -t my_user@host1.com.br tail -f /var/log/my_log
208
+
209
+ "host2:ssh":
210
+ local:
211
+ run:
212
+ - ssh my_user@host2.com.br
213
+
214
+ "host2:restart":
215
+ local:
216
+ run:
217
+ - ssh -t my_user@host2.com.br restart
218
+
219
+ "host2:logs":
220
+ local:
221
+ run:
222
+ - ssh -t my_user@host2.com.br tail -f /var/log/my_file
223
+
224
+ ```
225
+
226
+ can become this:
227
+
228
+ ```yaml
229
+ # djin.yml
230
+ djin_version: '0.10.0'
231
+
232
+ include:
233
+ - file: '.djin/server_tasks.yml'
234
+ context:
235
+ variables:
236
+ namespace: host1
237
+ host: host1.com
238
+ ssh_user: my_user
239
+
240
+ - file: '.djin/server_tasks.yml'
241
+ context:
242
+ variables:
243
+ namespace: host2
244
+ host: host2.com
245
+ ssh_user: my_user
246
+
247
+ ```
248
+
249
+
250
+ ```yaml
251
+ # .djin/server_tasks.yml
252
+ djin_version: '0.10.0'
253
+
254
+ tasks:
255
+ "{{namespace}}:ssh":
256
+ local:
257
+ run:
258
+ - ssh {{ssh_user}}@{{host}}
259
+
260
+ "{{namespace}}:restart":
261
+ local:
262
+ run:
263
+ - ssh -t {{ssh_user}}@{{host}} restart
264
+
265
+ "{{namespace}}:logs":
266
+ local:
267
+ run:
268
+ - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
269
+ ```
270
+
271
+ ### Loading custom files
272
+
273
+ You can also specify a file to be read by djin with `-f`, eg:
274
+
275
+ ```bash
276
+ djin -f my_file.yml # Returns the help for all tasks in my_file
277
+ djin -f my_file.yml build # Execute the build task defined in my_file.yml
278
+ ```
279
+
280
+ You can also specify multiple files to join tasks between files:
281
+
282
+ ```bash
283
+ # Mix the tasks
284
+ djin -f my_file.yml -f my_file2.yml # Returns the help for all tasks in my_file
285
+ djin -f my_file.yml -f my_file2.yml build # Execute the build task defined in my_file.yml or my_file2.yml
286
+ ```
287
+
181
288
 
182
289
  ## Development
183
290
 
@@ -185,9 +292,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
185
292
 
186
293
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, run `djin release -- {{increment_option}}` (where {{incremment_option}} can be `--patch`, `--minor` or `major`), which will change version, update the CHANGELOG.md, create a new commit, create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
187
294
 
188
- ## TODO:
189
295
 
190
- 1. Adds a `-f` option to load custom djin files
296
+ ## TODO
297
+
298
+ 1. Enable multiple -f options to merge configuration between files
299
+ 2. Option to export tasks to Makefile
300
+ 3. djin-export docker image to create and sync makefiles
301
+ 4. include key option to add tasks in git repositories files (maybe with a local cache)
191
302
 
192
303
  ## Contributing
193
304
 
data/Vertofile CHANGED
@@ -28,12 +28,15 @@ context(branch('master')) {
28
28
  file('CHANGELOG.md').prepend(version_changes)
29
29
  git!('add CHANGELOG.md')
30
30
 
31
- file('lib/djin/version.rb').replace(latest_version.to_s, new_version.to_s)
32
- file('djin.yml').replace(latest_version.to_s, new_version.to_s)
33
- file('examples/djin.yml').replace(latest_version.to_s, new_version.to_s)
31
+ files_to_change_version_once = %w[lib/djin/version.rb djin.yml] + Dir['examples/**/*.yml'] + Dir['spec/support/fixtures/**/*.yml']
32
+
33
+ files_to_change_version_once.each do |filename|
34
+ file(filename).replace(latest_version.to_s, new_version.to_s)
35
+ end
36
+
34
37
  file('README.md').replace_all(latest_version.to_s, new_version.to_s)
35
38
 
36
- git!('add lib/djin/version.rb djin.yml examples/djin.yml README.md')
39
+ git!("add #{files_to_change_version_once.join(' ')} README.md")
37
40
 
38
41
  sh!('bundle install')
39
42
  sh!('rake install')
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.require_paths = ['lib']
29
29
 
30
30
  spec.add_dependency 'dry-cli', '~> 0.6.0'
31
+ spec.add_dependency 'dry-equalizer', '~> 0.3.0'
31
32
  spec.add_dependency 'dry-struct', '~> 1.3.0'
32
33
  spec.add_dependency 'dry-validation', '~> 1.5.1'
33
34
  spec.add_dependency 'mustache', '~> 1.1.1'
data/djin.yml CHANGED
@@ -1,17 +1,21 @@
1
- djin_version: '0.6.0'
1
+ djin_version: '0.10.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
5
5
 
6
6
  tasks:
7
7
  test:
8
+ description: Runs Specs
8
9
  docker-compose:
9
10
  service: app
10
11
  run:
11
12
  commands: "cd /usr/src/djin && rspec {{args}}"
12
13
  <<: *default_run_options
14
+ aliases:
15
+ - rspec
13
16
 
14
17
  sh:
18
+ description: Enter app service shell
15
19
  docker-compose:
16
20
  service: app
17
21
  run:
@@ -1,5 +1,17 @@
1
1
  ---
2
- djin_version: '0.6.0'
2
+ djin_version: '0.10.0'
3
+
4
+ include:
5
+ - file: 'djin_lib/test.yml'
6
+ context:
7
+ variables:
8
+ namespace: 'test:'
9
+
10
+ - file: 'djin_lib/test.yml'
11
+ context:
12
+ variables:
13
+ namespace: 'test2:'
14
+
3
15
 
4
16
  tasks:
5
17
  default:
@@ -7,12 +19,6 @@ tasks:
7
19
  image: "ruby:2.5"
8
20
  run:
9
21
  - "ruby -e 'puts \\\" Hello\\\"'"
10
- test:
11
- docker-compose:
12
- service: app
13
- run:
14
- commands: rspec
15
- options: "--rm"
16
22
 
17
23
  script:
18
24
  docker:
@@ -0,0 +1,12 @@
1
+ djin_version: '0.10.0'
2
+
3
+ _default_run_options: &default_run_options
4
+ options: "--rm --entrypoint=''"
5
+
6
+ tasks:
7
+ "{{namespace}}unit":
8
+ docker-compose:
9
+ service: app
10
+ run:
11
+ commands: "cd /usr/src/djin && rspec {{args}}"
12
+ <<: *default_run_options
@@ -0,0 +1,17 @@
1
+ djin_version: '0.8.0'
2
+
3
+ tasks:
4
+ "{{namespace}}:ssh":
5
+ local:
6
+ run:
7
+ - ssh {{ssh_user}}@{{host}}
8
+
9
+ "{{namespace}}:restart":
10
+ local:
11
+ run:
12
+ - ssh -t {{ssh_user}}@{{host}} restart
13
+
14
+ "{{namespace}}:logs":
15
+ local:
16
+ run:
17
+ - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
@@ -0,0 +1,22 @@
1
+ djin_version: '0.10.0'
2
+
3
+ include:
4
+ - file: '.djin/server_tasks.yml'
5
+ context:
6
+ variables:
7
+ namespace: host1
8
+ host: host1.com
9
+ ssh_user: my_user
10
+
11
+ - file: '.djin/server_tasks.yml'
12
+ context:
13
+ variables:
14
+ namespace: host2
15
+ host: host2.com
16
+ ssh_user: my_user
17
+
18
+ tasks:
19
+ hello_command:
20
+ local:
21
+ run:
22
+ - echo 'Hello Djin'
@@ -8,9 +8,12 @@ require 'dry-validation'
8
8
  require 'vseries'
9
9
  require 'dry/cli'
10
10
  require 'mustache'
11
+ require 'optparse'
11
12
  require 'djin/extensions/hash_extensions'
13
+ require 'djin/extensions/object_extensions'
12
14
  require 'djin/entities/types'
13
15
  require 'djin/entities/task'
16
+ require 'djin/entities/file_config'
14
17
  require 'djin/interpreter/base_command_builder'
15
18
  require 'djin/interpreter/docker_command_builder'
16
19
  require 'djin/interpreter/docker_compose_command_builder'
@@ -18,25 +21,30 @@ require 'djin/interpreter/local_command_builder'
18
21
  require 'djin/interpreter'
19
22
  require 'djin/config_loader'
20
23
  require 'djin/executor'
24
+ require 'djin/root_cli_parser'
21
25
  require 'djin/cli'
22
26
  require 'djin/task_contract'
23
27
  require 'djin/repositories/task_repository'
28
+ require 'djin/memory_cache'
24
29
 
25
30
  module Djin
26
31
  class Error < StandardError; end
27
32
 
28
- def self.load_tasks!(path = Pathname.getwd.join('djin.yml'))
29
- abort 'Error: djin.yml not found' unless path.exist?
33
+ using Djin::ObjectExtensions
30
34
 
31
- djin_config = ConfigLoader.load!(path.read)
35
+ def self.load_tasks!(*file_paths)
36
+ files = file_paths.presence || RootCliParser.parse![:files] || ['djin.yml']
37
+
38
+ file_config = ConfigLoader.load_files!(*files)
32
39
 
33
40
  # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
34
- tasks = Interpreter.load!(djin_config)
41
+ tasks = Interpreter.load!(file_config)
35
42
 
36
43
  @task_repository = TaskRepository.new(tasks)
37
44
  CLI.load_tasks!(tasks)
38
45
  rescue Djin::Interpreter::InvalidConfigurationError => e
39
- abort(e.message)
46
+ error_name = e.class.name.split('::').last
47
+ abort("[#{error_name}] #{e.message}")
40
48
  end
41
49
 
42
50
  def self.tasks
@@ -46,4 +54,12 @@ module Djin
46
54
  def self.task_repository
47
55
  @task_repository ||= TaskRepository.new
48
56
  end
57
+
58
+ def self.cache
59
+ @cache ||= MemoryCache.new
60
+ end
61
+
62
+ def self.root_path
63
+ Pathname.new File.expand_path(File.dirname(__FILE__) + '/..')
64
+ end
49
65
  end
@@ -7,7 +7,7 @@ module Djin
7
7
  def self.load_tasks!(tasks)
8
8
  tasks.each do |task|
9
9
  command = Class.new(Dry::CLI::Command) do
10
- desc "Runs: #{task.command}"
10
+ desc task.description
11
11
 
12
12
  define_method(:task) { task }
13
13
 
@@ -16,7 +16,7 @@ module Djin
16
16
  end
17
17
  end
18
18
 
19
- register task.name, command
19
+ register(task.name, command, aliases: task.aliases)
20
20
  end
21
21
  end
22
22
 
@@ -28,6 +28,16 @@ module Djin
28
28
  end
29
29
  end
30
30
 
31
+ class File < Dry::CLI::Command
32
+ desc 'Specify a djin file to load (default: djin.yml)'
33
+ argument :filepath, required: true, desc: 'The file path to load'
34
+
35
+ def call(filename:, **)
36
+ # The actual behaviour is on RootCliParser
37
+ end
38
+ end
39
+
40
+ register '-f', File, aliases: ['--file']
31
41
  register '--version', Version, aliases: ['-v']
32
42
  end
33
43
  end
@@ -6,39 +6,46 @@ module Djin
6
6
  # a TaskLoader
7
7
  class ConfigLoader
8
8
  using Djin::HashExtensions
9
- RESERVED_WORDS = %w[djin_version variables tasks].freeze
9
+ RESERVED_WORDS = %w[djin_version variables tasks include].freeze
10
10
 
11
- def self.load!(template_file)
12
- new(template_file).load!
11
+ # CHange Base Error
12
+ FileNotFoundError = Class.new(Interpreter::InvalidConfigurationError)
13
+
14
+ def self.load_files!(*files, runtime_config: {}, base_directory: '.')
15
+ files.map do |file_path|
16
+ ConfigLoader.load!(file_path, runtime_config: runtime_config, base_directory: base_directory)
17
+ end&.reduce(:deep_merge)
18
+ end
19
+
20
+ def self.load!(template_file_path, runtime_config: {}, base_directory: '.')
21
+ new(template_file_path, runtime_config: runtime_config, base_directory: base_directory).load!
13
22
  end
14
23
 
15
- def initialize(template_file)
16
- @template_file = template_file
24
+ def initialize(template_file_path, runtime_config: {}, base_directory: '.')
25
+ @base_directory = Pathname.new(base_directory)
26
+ @template_file = @base_directory.join(template_file_path)
27
+
28
+ raise FileNotFoundError, "File '#{@template_file}' not found" unless @template_file.exist?
29
+
30
+ @template_file_content = Djin.cache.fetch(@template_file.realpath.to_s) { @template_file.read }
31
+ @runtime_config = runtime_config
17
32
  end
18
33
 
19
34
  def load!
20
35
  validate_version!
21
36
 
22
- # TODO: Return a DjinConfig Entity
23
- tasks
37
+ file_config
24
38
  end
25
39
 
26
40
  private
27
41
 
28
- def raw_djin_config
29
- @raw_djin_config ||= yaml_load(@template_file)
30
- end
31
-
32
- def rendered_djin_config
33
- @rendered_djin_config ||= begin
34
- locals = env.merge(variables)
35
-
36
- rendered_yaml = Mustache.render(@template_file,
37
- args: args.join(' '),
38
- args?: args.any?,
39
- **locals)
40
- yaml_load(rendered_yaml)
41
- end
42
+ def file_config
43
+ FileConfig.new(
44
+ djin_version: version,
45
+ variables: variables,
46
+ tasks: tasks,
47
+ raw_tasks: raw_tasks
48
+ )
42
49
  end
43
50
 
44
51
  def version
@@ -47,11 +54,15 @@ module Djin
47
54
  end
48
55
 
49
56
  def variables
50
- @variables ||= raw_djin_config['variables']&.symbolize_keys || {}
57
+ @variables ||= included_variables.merge(raw_djin_config['variables']&.symbolize_keys || {})
51
58
  end
52
59
 
53
60
  def tasks
54
- rendered_djin_config['tasks'] || legacy_tasks
61
+ included_tasks.merge(rendered_djin_config['tasks'] || legacy_tasks)
62
+ end
63
+
64
+ def raw_tasks
65
+ included_raw_tasks.merge(raw_djin_config['tasks'] || legacy_raw_tasks)
55
66
  end
56
67
 
57
68
  def legacy_tasks
@@ -61,6 +72,35 @@ module Djin
61
72
  rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
62
73
  end
63
74
 
75
+ def legacy_raw_tasks
76
+ raw_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
77
+ end
78
+
79
+ def included_variables
80
+ return {} unless included_config
81
+
82
+ included_config.variables
83
+ end
84
+
85
+ def included_tasks
86
+ return {} unless included_config
87
+
88
+ included_config.tasks
89
+ end
90
+
91
+ def included_raw_tasks
92
+ return {} unless included_config
93
+
94
+ included_config.raw_tasks
95
+ end
96
+
97
+ def included_config
98
+ @included_config ||= raw_djin_config['include']&.map do |tasks_reference|
99
+ ConfigLoader.load!(tasks_reference['file'], base_directory: @template_file.dirname,
100
+ runtime_config: tasks_reference['context'] || {})
101
+ end&.reduce(:deep_merge)
102
+ end
103
+
64
104
  def args
65
105
  index = ARGV.index('--')
66
106
 
@@ -73,18 +113,32 @@ module Djin
73
113
  @env ||= ENV.to_h.symbolize_keys
74
114
  end
75
115
 
76
- def yaml_load(text)
77
- YAML.safe_load(text, [], [], true)
116
+ def raw_djin_config
117
+ @raw_djin_config ||= yaml_load(@template_file_content).deep_merge(@runtime_config)
118
+ rescue Psych::SyntaxError => e
119
+ raise Interpreter::InvalidConfigFileError, "File: #{@template_file.realpath}\n #{e.message}"
78
120
  end
79
121
 
80
- def version_supported?
81
- Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(version)
122
+ def rendered_djin_config
123
+ @rendered_djin_config ||= begin
124
+ locals = env.merge(variables)
125
+
126
+ rendered_yaml = Mustache.render(@template_file_content,
127
+ args: args.join(' '),
128
+ args?: args.any?,
129
+ **locals)
130
+ yaml_load(rendered_yaml).merge(@runtime_config)
131
+ end
132
+ end
133
+
134
+ def yaml_load(text)
135
+ YAML.safe_load(text, [], [], true)
82
136
  end
83
137
 
84
138
  def validate_version!
85
139
  raise Interpreter::MissingVersionError, 'Missing djin_version' unless version
86
140
 
87
- return if version_supported?
141
+ return if file_config.version_supported?
88
142
 
89
143
  raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
90
144
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class FileConfig < Dry::Struct
5
+ using HashExtensions
6
+
7
+ attribute :djin_version, Types::String
8
+ attribute :variables, Types::Hash.optional.default({}.freeze)
9
+ attribute :tasks, Types::Hash
10
+ attribute :raw_tasks, Types::Hash
11
+ # TODO: Add env and args
12
+
13
+ include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
14
+
15
+ def version_supported?
16
+ Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
17
+ end
18
+
19
+ def merge(file_config)
20
+ merged_hash = to_h.merge(file_config.to_h)
21
+
22
+ FileConfig.new(merged_hash)
23
+ end
24
+
25
+ def deep_merge(file_config)
26
+ merged_hash = to_h.deep_merge(file_config.to_h)
27
+
28
+ FileConfig.new(merged_hash)
29
+ end
30
+ end
31
+ end
@@ -6,12 +6,10 @@ module Djin
6
6
  attribute :description, Types::String.optional.default(nil)
7
7
  attribute :build_command, Types::String.optional.default(nil)
8
8
  attribute :command, Types::String.optional.default(nil)
9
+ attribute :raw_command, Types::String.optional.default(nil)
10
+ attribute :aliases, Types::Array.of(Types::String).optional.default([].freeze)
9
11
  attribute :depends_on, Types::Array.of(Types::String).optional.default([].freeze)
10
12
 
11
- def ==(other)
12
- name == other.name &&
13
- command == other.command &&
14
- build_command == other.build_command
15
- end
13
+ include Dry::Equalizer(:name, :command, :build_command)
16
14
  end
17
15
  end
@@ -10,6 +10,20 @@ module Djin
10
10
  def symbolize_keys
11
11
  map { |key, value| [key.to_sym, value] }.to_h
12
12
  end
13
+
14
+ def deep_merge(other_hash)
15
+ dup.deep_merge!(other_hash)
16
+ end
17
+
18
+ def deep_merge!(other_hash)
19
+ merge!(other_hash) do |_, this_val, other_val|
20
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
21
+ this_val.deep_merge(other_val)
22
+ else
23
+ other_val
24
+ end
25
+ end
26
+ end
13
27
  end
14
28
  end
15
29
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module ObjectExtensions
5
+ refine Object do
6
+ def presence(default = nil)
7
+ present? ? self : default
8
+ end
9
+
10
+ def present?
11
+ !blank?
12
+ end
13
+
14
+ def blank?
15
+ return true unless self
16
+
17
+ # TODO: Improve Validations
18
+ return empty? if respond_to?(:empty?)
19
+
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
@@ -6,31 +6,44 @@ module Djin
6
6
 
7
7
  # TODO: Move Errors to ConfigLoader
8
8
  InvalidConfigurationError = Class.new(StandardError)
9
+ InvalidConfigFileError = Class.new(InvalidConfigurationError)
9
10
  MissingVersionError = Class.new(InvalidConfigurationError)
10
11
  VersionNotSupportedError = Class.new(InvalidConfigurationError)
11
12
  InvalidSyntaxError = Class.new(InvalidConfigurationError)
12
13
 
13
14
  class << self
14
- def load!(tasks_params)
15
- contract = TaskContract.new
15
+ # rubocop:disable Metrics/AbcSize
16
+ def load!(file_config)
17
+ # TODO: Move task validation to ConfigLoader and add variables/include validations
18
+ task_contract = TaskContract.new
16
19
 
17
- tasks_params.map do |task_name, options|
18
- result = contract.call(options)
20
+ file_config.tasks.map do |task_name, options|
21
+ result = task_contract.call(options)
19
22
 
20
23
  raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
21
24
 
22
25
  command, build_command = build_commands(options, task_name: task_name)
23
26
 
27
+ # FIXME(1): Handle dynamic named tasks, eg: {{namespace}}unit and remove condition
28
+ if file_config.raw_tasks[task_name]
29
+ raw_command, = build_commands(file_config.raw_tasks[task_name], task_name: task_name)
30
+ end
31
+
24
32
  task_params = {
25
33
  name: task_name,
26
34
  build_command: build_command,
35
+ # TODO: Remove `|| command` after FIXME(1)
36
+ description: options['description'] || "Runs: #{raw_command || command}",
27
37
  command: command,
38
+ raw_command: raw_command,
39
+ aliases: options['aliases'],
28
40
  depends_on: options['depends_on']
29
41
  }.compact
30
42
 
31
43
  Djin::Task.new(**task_params)
32
44
  end
33
45
  end
46
+ # rubocop:enable Metrics/AbcSize
34
47
 
35
48
  private
36
49
 
@@ -17,6 +17,7 @@ module Djin
17
17
  run_options = run_params['options']
18
18
  end
19
19
 
20
+ # TODO: Remove empty values
20
21
  run_command = run_command.join(' && ') if run_command.is_a?(Array)
21
22
 
22
23
  [run_command, run_options]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class MemoryCache
5
+ def initialize(hash_store = {})
6
+ @hash_store = hash_store
7
+ end
8
+
9
+ def fetch(key)
10
+ @hash_store[key] || @hash_store[key] = yield
11
+ end
12
+
13
+ def clear
14
+ @hash_store = {}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ ## This class is responsible to handle options that must be evaluated
5
+ # before the load of tasks in djin file(eg: djin.yml)
6
+ class RootCliParser
7
+ class << self
8
+ def parse!(args = ARGV)
9
+ options = {}
10
+
11
+ # TODO: Find a better way to handle -f/--file option,
12
+ # throw, catch and delete in ARGV are necessary
13
+ # to only remove the -f/--file option
14
+ # and bypass everything else to Dry::CLI
15
+ catch(:root_cli_exit) do
16
+ OptionParser.new do |opts|
17
+ opts.on('-f FILE', '--file FILE') do |v|
18
+ options[:files] ||= []
19
+ options[:files] << v
20
+ end
21
+
22
+ opts.on('-h', '--help') do
23
+ throw :root_cli_exit
24
+ end
25
+ end.parse(args)
26
+ end
27
+
28
+ remove_file_args!(args)
29
+ options
30
+ end
31
+
32
+ def remove_file_args!(args)
33
+ file_option = ['-f', '--file']
34
+ args_indexes_to_remove = args.each_with_index.map do |value, index|
35
+ index if (file_option.include?(args[index - 1]) && index.positive?) || file_option.include?(value)
36
+ end.compact
37
+
38
+ args_indexes_to_remove.reverse.each { |index| args.delete_at(index) }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -49,6 +49,7 @@ module Djin
49
49
  hash(LocalSchema)
50
50
  end
51
51
 
52
+ optional(:aliases).each(:str?)
52
53
  optional(:depends_on).each(:str?)
53
54
  end
54
55
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.6.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: djin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Atkinson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-23 00:00:00.000000000 Z
11
+ date: 2020-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-equalizer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: dry-struct
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -178,20 +192,27 @@ files:
178
192
  - djin.yml
179
193
  - docker-compose.yml
180
194
  - examples/djin.yml
195
+ - examples/djin_lib/test.yml
196
+ - examples/local_tasks/.djin/server_tasks.yml
197
+ - examples/local_tasks/djin.yml
181
198
  - exe/djin
182
199
  - lib/djin.rb
183
200
  - lib/djin/cli.rb
184
201
  - lib/djin/config_loader.rb
202
+ - lib/djin/entities/file_config.rb
185
203
  - lib/djin/entities/task.rb
186
204
  - lib/djin/entities/types.rb
187
205
  - lib/djin/executor.rb
188
206
  - lib/djin/extensions/hash_extensions.rb
207
+ - lib/djin/extensions/object_extensions.rb
189
208
  - lib/djin/interpreter.rb
190
209
  - lib/djin/interpreter/base_command_builder.rb
191
210
  - lib/djin/interpreter/docker_command_builder.rb
192
211
  - lib/djin/interpreter/docker_compose_command_builder.rb
193
212
  - lib/djin/interpreter/local_command_builder.rb
213
+ - lib/djin/memory_cache.rb
194
214
  - lib/djin/repositories/task_repository.rb
215
+ - lib/djin/root_cli_parser.rb
195
216
  - lib/djin/task_contract.rb
196
217
  - lib/djin/version.rb
197
218
  homepage: https://github.com/catks/djin
@@ -213,7 +234,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
234
  - !ruby/object:Gem::Version
214
235
  version: '0'
215
236
  requirements: []
216
- rubygems_version: 3.0.3
237
+ rubyforge_project:
238
+ rubygems_version: 2.7.6
217
239
  signing_key:
218
240
  specification_version: 4
219
241
  summary: djin is a make-like utility for docker containers