djin 0.8.0 → 0.11.2

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.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeConfig < Dry::Struct
5
+ attribute :file, Types::String
6
+ attribute :base_directory, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+ attribute :git, Types::String.optional.default(nil)
9
+ attribute :version, Types::String.default('master')
10
+ attribute :missing, Types::Bool.optional.default(nil)
11
+
12
+ using Djin::ObjectExtensions
13
+
14
+ include Dry::Equalizer(:git, :version, :file, :context)
15
+
16
+ def type
17
+ @type ||= git.present? ? :remote : :local
18
+ end
19
+
20
+ def present?
21
+ !missing?
22
+ end
23
+
24
+ def missing?
25
+ missing
26
+ end
27
+
28
+ def full_path
29
+ base_directory_pathname.join(file).expand_path
30
+ end
31
+
32
+ def repository_fetched?
33
+ @repository_fetched ||= base_directory_pathname.join(folder_name).exist?
34
+ end
35
+
36
+ # TODO: Rethink
37
+ def folder_name
38
+ @folder_name ||= "#{git.split('/').last.chomp('.git')}@#{version}"
39
+ end
40
+
41
+ private
42
+
43
+ def base_directory_pathname
44
+ @base_directory_pathname ||= Pathname.new(base_directory)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Base < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Local < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ :local
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,17 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- class FileConfig < Dry::Struct
4
+ class MainConfig < Dry::Struct
5
+ using HashExtensions
6
+
5
7
  attribute :djin_version, Types::String
6
8
  attribute :variables, Types::Hash.optional.default({}.freeze)
7
9
  attribute :tasks, Types::Hash
8
10
  attribute :raw_tasks, Types::Hash
9
11
  # TODO: Add env and args
12
+ attribute :include_configs, Types::Array.of(Djin::IncludeConfig).default([].freeze)
10
13
 
11
14
  include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
12
15
 
13
16
  def version_supported?
14
17
  Vseries::SemanticVersion.new(Djin::VERSION) >= Vseries::SemanticVersion.new(djin_version)
15
18
  end
19
+
20
+ def merge(file_config)
21
+ merged_hash = to_h.merge(file_config.to_h)
22
+
23
+ MainConfig.new(merged_hash)
24
+ end
25
+
26
+ def deep_merge(file_config)
27
+ merged_hash = to_h.deep_merge(file_config.to_h)
28
+
29
+ MainConfig.new(merged_hash)
30
+ end
16
31
  end
17
32
  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
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeResolver
5
+ using ObjectExtensions
6
+ using HashExtensions
7
+
8
+ def initialize(base_directory: '.', remote_directory: '~/.djin/remote', entity_class: Djin::IncludeConfig)
9
+ # TODO: Use chain of responsability
10
+ @base_directory = Pathname.new(base_directory)
11
+ @remote_directory = Pathname.new(remote_directory)
12
+ @entity_class = entity_class
13
+ end
14
+
15
+ def call(params)
16
+ include_config_params = remote_handler(params)
17
+ include_config_params ||= local_handler(params)
18
+
19
+ build_entity(include_config_params)
20
+ end
21
+
22
+ private
23
+
24
+ def remote_handler(params)
25
+ return if params['git'].blank?
26
+
27
+ version = params['version'] || 'master'
28
+ # TODO: Extract RemoteConfig git_folder in IncludeConfig to another place and use it here
29
+ # Maybe create a optional git_folder attribute and fill it in here?
30
+ git_folder = "#{params['git'].split('/').last.chomp('.git')}@#{version}"
31
+
32
+ # TODO: Use RemoteConfigRepository
33
+ remote_file = @remote_directory.join(git_folder).join(params['file']).expand_path
34
+
35
+ missing = !remote_file.exist?
36
+
37
+ params.merge(missing: missing, file: remote_file.to_s, base_directory: @remote_directory.expand_path.to_s)
38
+ end
39
+
40
+ def local_handler(params)
41
+ # TODO: Mark not existing files as missing and handle all the missing files
42
+ missing = !@base_directory.join(params['file']).exist?
43
+ params.merge(missing: missing, base_directory: @base_directory.expand_path.to_s)
44
+ end
45
+
46
+ def build_entity(params)
47
+ @entity_class.new(**params.symbolize_keys)
48
+ end
49
+ end
50
+ end
@@ -12,22 +12,28 @@ module Djin
12
12
  InvalidSyntaxError = Class.new(InvalidConfigurationError)
13
13
 
14
14
  class << self
15
+ # rubocop:disable Metrics/AbcSize
15
16
  def load!(file_config)
16
- contract = TaskContract.new
17
+ # TODO: Move task validation to ConfigLoader and add variables/include validations
18
+ task_contract = TaskContract.new
17
19
 
18
20
  file_config.tasks.map do |task_name, options|
19
- result = contract.call(options)
21
+ result = task_contract.call(options)
20
22
 
21
23
  raise InvalidSyntaxError, { task_name.to_sym => result.errors.to_h } if result.failure?
22
24
 
23
25
  command, build_command = build_commands(options, task_name: task_name)
24
26
 
25
- raw_command, = build_commands(file_config.raw_tasks[task_name], task_name: task_name)
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
26
31
 
27
32
  task_params = {
28
33
  name: task_name,
29
34
  build_command: build_command,
30
- description: options['description'] || "Runs: #{raw_command}",
35
+ # TODO: Remove `|| command` after FIXME(1)
36
+ description: options['description'] || "Runs: #{raw_command || command}",
31
37
  command: command,
32
38
  raw_command: raw_command,
33
39
  aliases: options['aliases'],
@@ -37,6 +43,7 @@ module Djin
37
43
  Djin::Task.new(**task_params)
38
44
  end
39
45
  end
46
+ # rubocop:enable Metrics/AbcSize
40
47
 
41
48
  private
42
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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class RemoteConfigRepository
5
+ attr_accessor :base_path
6
+
7
+ def initialize(remote_configs, base_path: Pathname.new('~/.djin/remote'), stderr: Djin.stderr)
8
+ @remote_configs = remote_configs
9
+ @base_path = base_path
10
+ @stderr = stderr
11
+ end
12
+
13
+ def add(remote_config)
14
+ remote_configs << remote_config
15
+ end
16
+
17
+ def find(**filters)
18
+ remote_configs.select do |remote_config|
19
+ filters.reduce(true) do |memo, (filter_key, filter_value)|
20
+ memo && remote_config.public_send(filter_key) == filter_value
21
+ end
22
+ end
23
+ end
24
+
25
+ def fetch_all
26
+ remote_configs_by_folders.each do |rc|
27
+ git_folder = base_path.join(rc.folder_name).expand_path
28
+
29
+ # TODO: Extract STDEER Output, maybe publishing events and subscribing a observer for the logs.
30
+ stderr.puts "Remote Path: #{base_path.expand_path}"
31
+
32
+ git_repo = rc.missing? ? clone_repo(git_folder, rc) : fetch_repo(git_folder, rc)
33
+
34
+ stderr.puts "Checking out to '#{rc.version}'"
35
+ git_repo.checkout(rc.version)
36
+ git_repo.pull('origin', rc.version)
37
+ end
38
+ end
39
+
40
+ def clear
41
+ remote_configs_by_folders.each do |rc|
42
+ git_folder = base_path.join(rc.folder_name)
43
+
44
+ stderr.puts "Removing #{rc.folder_name} repository..."
45
+ `rm -rf #{git_folder}`
46
+ end
47
+ end
48
+
49
+ def clear_all
50
+ remove_remote_folder
51
+ end
52
+
53
+ def remote_configs
54
+ @remote_configs ||= []
55
+ end
56
+
57
+ private
58
+
59
+ attr_accessor :stderr
60
+
61
+ def remote_configs_by_folders
62
+ @remote_configs_by_folders ||= remote_configs
63
+ .group_by(&:folder_name)
64
+ .values
65
+ .map(&:first)
66
+ end
67
+
68
+ def remove_remote_folder
69
+ stderr.puts "Removing #{base_path}..."
70
+ `rm -rf #{base_path}`
71
+ end
72
+
73
+ def clone_repo(git_folder, remote_config)
74
+ stderr.puts "Missing #{remote_config.folder_name} repository, cloning in #{git_folder}"
75
+ Git.clone(remote_config.git.to_s, git_folder.to_s, branch: remote_config.version)
76
+ end
77
+
78
+ def fetch_repo(git_folder, remote_config)
79
+ stderr.puts "#{remote_config.git} repository already cloned, fetching..."
80
+ git_repo = Git.open(git_folder)
81
+ git_repo.fetch
82
+
83
+ git_repo
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,46 @@
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
+
26
+ opts.on('--all') do
27
+ throw :root_cli_exit
28
+ end
29
+ end.parse(args)
30
+ end
31
+
32
+ remove_file_args!(args)
33
+ options
34
+ end
35
+
36
+ def remove_file_args!(args)
37
+ file_option = ['-f', '--file']
38
+ args_indexes_to_remove = args.each_with_index.map do |value, index|
39
+ index if (file_option.include?(args[index - 1]) && index.positive?) || file_option.include?(value)
40
+ end.compact
41
+
42
+ args_indexes_to_remove.reverse.each { |index| args.delete_at(index) }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.8.0'
4
+ VERSION = '0.11.2'
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.8.0
4
+ version: 0.11.2
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-08-31 00:00:00.000000000 Z
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.5.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: git
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.8.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: mustache
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,20 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.17.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.17.0
167
195
  description:
168
196
  email:
169
197
  - carlos.atks@gmail.com
@@ -174,6 +202,7 @@ extra_rdoc_files: []
174
202
  files:
175
203
  - ".github/workflows/ruby.yml"
176
204
  - ".gitignore"
205
+ - ".irbrc"
177
206
  - ".rspec"
178
207
  - ".rubocop.yml"
179
208
  - ".rubocop_todo.yml"
@@ -191,22 +220,35 @@ files:
191
220
  - djin.gemspec
192
221
  - djin.yml
193
222
  - docker-compose.yml
223
+ - docker-entrypoint.sh
194
224
  - examples/djin.yml
225
+ - examples/djin_lib/test.yml
226
+ - examples/local_tasks/.djin/server_tasks.yml
227
+ - examples/local_tasks/djin.yml
228
+ - examples/remote_tasks/djin.yml
195
229
  - exe/djin
196
230
  - lib/djin.rb
197
231
  - lib/djin/cli.rb
198
232
  - lib/djin/config_loader.rb
199
- - lib/djin/entities/file_config.rb
233
+ - lib/djin/entities/include_config.rb
234
+ - lib/djin/entities/include_config/base.rb
235
+ - lib/djin/entities/include_config/local.rb
236
+ - lib/djin/entities/main_config.rb
200
237
  - lib/djin/entities/task.rb
201
238
  - lib/djin/entities/types.rb
202
239
  - lib/djin/executor.rb
203
240
  - lib/djin/extensions/hash_extensions.rb
241
+ - lib/djin/extensions/object_extensions.rb
242
+ - lib/djin/include_resolver.rb
204
243
  - lib/djin/interpreter.rb
205
244
  - lib/djin/interpreter/base_command_builder.rb
206
245
  - lib/djin/interpreter/docker_command_builder.rb
207
246
  - lib/djin/interpreter/docker_compose_command_builder.rb
208
247
  - lib/djin/interpreter/local_command_builder.rb
248
+ - lib/djin/memory_cache.rb
249
+ - lib/djin/repositories/remote_config_repository.rb
209
250
  - lib/djin/repositories/task_repository.rb
251
+ - lib/djin/root_cli_parser.rb
210
252
  - lib/djin/task_contract.rb
211
253
  - lib/djin/version.rb
212
254
  homepage: https://github.com/catks/djin