djin 0.8.0 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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