djin 0.6.0 → 0.10.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.
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