cl-magic 0.3.9 → 1.2.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.
@@ -0,0 +1,67 @@
1
+ require 'tty-command'
2
+ require 'yaml'
3
+
4
+ class PartsMerger
5
+
6
+ def initialize(working_dir, yaml_arg_munger, help_printer, logger)
7
+ @working_dir = working_dir
8
+ @yaml_arg_munger = yaml_arg_munger
9
+ @help_printer = help_printer
10
+ @logger = logger
11
+ end
12
+
13
+ def merge_parts(compose_hash, dk_parts_hash, args)
14
+ selected_part_keys = []
15
+
16
+ # merge: saved parts
17
+ saved_part_keys = get_saved_parts(dk_parts_hash)
18
+ saved_part_keys.each do |potential_part_key|
19
+ dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
20
+ if dk_part
21
+ compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash)
22
+ selected_part_keys << potential_part_key
23
+ end
24
+ end
25
+
26
+ # merge: arg parts
27
+ while true
28
+ potential_part_key = args.first
29
+ dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
30
+ if dk_part
31
+ unless selected_part_keys.include?(potential_part_key)
32
+ compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash)
33
+ selected_part_keys << potential_part_key
34
+ end
35
+ args.shift # remove part arg
36
+ else
37
+ break
38
+ end
39
+ end
40
+ @logger.puts "" if $stdout.isatty # tailing line break
41
+
42
+ return compose_hash, selected_part_keys, args
43
+ end
44
+
45
+ def get_saved_parts(dk_parts_hash)
46
+
47
+ # get saved parts
48
+ cmd = "cd #{@working_dir} && cl dk parts list"
49
+ out, err = TTY::Command.new(:printer => :null).run("#{cmd}")
50
+ return out.split("\n")
51
+
52
+ end
53
+
54
+ private
55
+
56
+ def print_and_merge_part(part_key, dk_part, compose_hash)
57
+
58
+ # print
59
+ if $stdout.isatty
60
+ help_str = dk_part.fetch('help')
61
+ @help_printer.print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
62
+ end
63
+ # merge
64
+ return @yaml_arg_munger.dk_merge_and_remove(compose_hash, dk_part)
65
+ end
66
+
67
+ end
@@ -0,0 +1,52 @@
1
+ require 'tty-command'
2
+ require 'yaml'
3
+
4
+ class WorldSettings
5
+
6
+ def initialize(working_dir)
7
+ @working_dir = working_dir
8
+ end
9
+
10
+ def get_world_settings_hash()
11
+ filepath = get_world_settings_filepath()
12
+ return File.exist?(filepath) ? YAML.load_file(filepath) : {}
13
+ end
14
+
15
+ def save_world_settings(world_settings_hash)
16
+ filepath = get_world_settings_filepath()
17
+ tempfile = File.new(filepath, 'w')
18
+ tempfile.write(world_settings_hash.to_yaml)
19
+ tempfile.close
20
+ end
21
+
22
+ def get_world_project_path()
23
+ repo_basename = get_repo_basename()
24
+ world_path = get_world_path_from_settings()
25
+ return File.join(world_path, repo_basename) if world_path and repo_basename
26
+ return nil
27
+ end
28
+
29
+ def get_world_path_from_settings()
30
+ world_settings = get_world_settings_hash()
31
+ if world_settings.key?(:world_path) and world_settings.key?(:context)
32
+ return File.join(world_settings[:world_path], world_settings[:context])
33
+ end
34
+ return ""
35
+ end
36
+
37
+ private
38
+
39
+ def get_repo_basename()
40
+ command = "cd #{@working_dir} && basename $(git remote get-url origin 2> /dev/null) .git"
41
+ repo_basename = TTY::Command.new(:printer => :null).run(command).out.gsub('.git', '').strip.chomp
42
+ if repo_basename==".git" or repo_basename==""
43
+ return File.basename(@working_dir)
44
+ end
45
+ return repo_basename
46
+ end
47
+
48
+ def get_world_settings_filepath()
49
+ return File.join(".cl-dk-world.yml")
50
+ end
51
+
52
+ end
@@ -0,0 +1,107 @@
1
+ require 'yaml'
2
+
3
+ class YamlArgMunger
4
+
5
+ def initialize(working_dir, world_settings)
6
+ @working_dir = working_dir
7
+ @world_path = world_settings.get_world_path_from_settings()
8
+ @dk_proj_path = world_settings.get_world_project_path()
9
+ end
10
+
11
+ def get_base_compose_parts_and_make_hashes()
12
+ compose_hash = get_base_compose_hash()
13
+ dk_parts_hash = {}
14
+ dk_make_hash = {}
15
+ if compose_hash
16
+ compose_hash = merge_world_files(compose_hash, show_help=ARGV.include?("--help"))
17
+ dk_parts_hash = compose_hash['x-dk-parts'] ? compose_hash.delete('x-dk-parts') : {}
18
+ dk_make_hash = compose_hash['x-dk-make'] ? compose_hash.delete('x-dk-make') : {}
19
+ end
20
+ return compose_hash, dk_parts_hash, dk_make_hash
21
+ end
22
+
23
+ def dk_merge_and_remove(compose_hash, yml_hash)
24
+ # remove help & merge
25
+ clean_yml = yml_hash.clone
26
+ clean_yml.delete('help')
27
+ return compose_hash.dk_merge(clean_yml).dk_reject! { |k, v| v=='<dk-remove>' }
28
+ end
29
+
30
+ def save_yaml_and_adjust_args(compose_hash, args)
31
+
32
+ # generate final compose file
33
+ tempfile = File.new(File.join(@working_dir, ".cl-dk.yml"), 'w')
34
+ tempfile.write(compose_hash.to_yaml) # write it to the tempfile
35
+
36
+ # remove existing '-f' flag, if needed
37
+ file_flag_index = args.index('-f')
38
+ if file_flag_index==0
39
+ args.delete_at(file_flag_index)
40
+ args.delete_at(file_flag_index)
41
+ end
42
+ args.unshift('-f', tempfile.path) # add new '-f' flag
43
+
44
+ tempfile.close
45
+ return args
46
+ end
47
+
48
+ private
49
+
50
+ def get_base_compose_hash()
51
+ cmd = "cd #{@working_dir} && docker compose config 2> /dev/null"
52
+ return YAML.load(`#{cmd}`)
53
+ end
54
+
55
+ def merge_world_files(compose_hash, show_help=false)
56
+ if @dk_proj_path
57
+ print_dk_help_line("dk-project-path", "#{@dk_proj_path}") if show_help and $stdout.isatty
58
+
59
+ Dir.glob("#{@dk_proj_path}/*.yml").sort.each do |filepath|
60
+ print_dk_help_line("dk-world", "#{filepath}") if show_help and $stdout.isatty
61
+
62
+ # read file and replace
63
+ contents = File.read(filepath)
64
+ contents.gsub!('<dk-world-path>', @world_path)
65
+ contents.gsub!('<dk-project-path>', @dk_proj_path)
66
+ contents.gsub!('<dk-working-path>', @working_dir)
67
+
68
+ # yml merge
69
+ yml_hash = YAML.load(contents)
70
+ compose_hash = dk_merge_and_remove(compose_hash, yml_hash)
71
+ end
72
+ end
73
+ return compose_hash
74
+ end
75
+
76
+ class ::Hash
77
+ def dk_merge(second)
78
+ merger = proc { |_, v1, v2|
79
+ if Hash === v1 && Hash === v2
80
+ v1.merge(v2, &merger)
81
+ else
82
+ if Array === v1 && Array === v2
83
+ if v2.first=="<dk-replace>"
84
+ v2[1..-1] # everything but the first item
85
+ else
86
+ v1 | v2 # union arrays
87
+ end
88
+ else
89
+ if [:undefined, nil, :nil].include?(v2)
90
+ v1
91
+ else
92
+ v2
93
+ end
94
+ end
95
+ end
96
+ }
97
+ merge(second.to_h, &merger)
98
+ end
99
+ def dk_reject!(&blk)
100
+ self.each do |k, v|
101
+ v.dk_reject!(&blk) if v.is_a?(Hash)
102
+ self.delete(k) if blk.call(k, v)
103
+ end
104
+ end
105
+ end
106
+
107
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cl
4
4
  module Magic
5
- VERSION = "0.3.9"
5
+ VERSION = "1.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cl-magic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Don Najd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-03 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -80,6 +80,48 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-spinner
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tty-progressbar
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: concurrent-ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  - !ruby/object:Gem::Dependency
84
126
  name: pastel
85
127
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +150,20 @@ dependencies:
108
150
  - - ">="
109
151
  - !ruby/object:Gem::Version
110
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: baran
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
111
167
  - !ruby/object:Gem::Dependency
112
168
  name: byebug
113
169
  requirement: !ruby/object:Gem::Requirement
@@ -139,15 +195,25 @@ files:
139
195
  - cl-magic.gemspec
140
196
  - lib/cl/magic.rb
141
197
  - lib/cl/magic/cl
198
+ - lib/cl/magic/cl-ai-chat
199
+ - lib/cl/magic/cl-ai-query
200
+ - lib/cl/magic/cl-ai-store-jira
142
201
  - lib/cl/magic/cl-auth
143
202
  - lib/cl/magic/cl-dk
203
+ - lib/cl/magic/cl-dk-make
204
+ - lib/cl/magic/cl-dk-make-world
205
+ - lib/cl/magic/cl-dk-parts
206
+ - lib/cl/magic/cl-dk-world
144
207
  - lib/cl/magic/cl-envkey
145
208
  - lib/cl/magic/cl-gc-sql
146
209
  - lib/cl/magic/cl-gc-tags
147
210
  - lib/cl/magic/cl-glab-commit
148
211
  - lib/cl/magic/cl-history
149
212
  - lib/cl/magic/cl-jira-fetch
150
- - lib/cl/magic/cl-jira-stats
213
+ - lib/cl/magic/cl-jira-fetch-by-epics
214
+ - lib/cl/magic/cl-jira-to-elastic
215
+ - lib/cl/magic/cl-jira-to-markdown
216
+ - lib/cl/magic/cl-jira-to-stats
151
217
  - lib/cl/magic/cl-kube-cp
152
218
  - lib/cl/magic/cl-kube-deployment
153
219
  - lib/cl/magic/cl-kube-ktx
@@ -159,12 +225,20 @@ files:
159
225
  - lib/cl/magic/cl-poll
160
226
  - lib/cl/magic/cl-sandbox
161
227
  - lib/cl/magic/cl-vault
228
+ - lib/cl/magic/common/ai_prompt.rb
229
+ - lib/cl/magic/common/ai_text_splitter.rb
162
230
  - lib/cl/magic/common/common_options.rb
231
+ - lib/cl/magic/common/elastic.rb
163
232
  - lib/cl/magic/common/gcloud.rb
164
233
  - lib/cl/magic/common/jira.rb
165
234
  - lib/cl/magic/common/kubectl.rb
166
235
  - lib/cl/magic/common/logging.rb
236
+ - lib/cl/magic/common/milvus.rb
167
237
  - lib/cl/magic/common/parse_and_pick.rb
238
+ - lib/cl/magic/dk/help_printer.rb
239
+ - lib/cl/magic/dk/parts_merger.rb
240
+ - lib/cl/magic/dk/world_settings.rb
241
+ - lib/cl/magic/dk/yaml_arg_munger.rb
168
242
  - lib/cl/magic/version.rb
169
243
  homepage: https://gitlab.com/beesbot/cl-magic
170
244
  licenses:
@@ -1,180 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Fetch jira issues print stats
3
- require 'optparse'
4
- require 'optparse/subcommand'
5
- require 'tty-command'
6
- require 'tty-prompt'
7
- require 'active_support/all'
8
-
9
- require 'cl/magic/common/common_options.rb'
10
- require 'cl/magic/common/logging.rb'
11
- require 'cl/magic/common/jira.rb'
12
-
13
- require 'net/http'
14
- require 'json'
15
-
16
- require 'byebug'
17
-
18
- @logger = get_logger()
19
- @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
20
-
21
- #
22
- # Features
23
- #
24
-
25
- def get_issues_from_datafile(options)
26
- issues = []
27
- filepath = File.join(@working_dir, options[:data_filepath])
28
- File.foreach(filepath) do |line|
29
- issue = JSON.parse(line)
30
- issuetype = issue["fields"]["issuetype"]["name"]
31
-
32
- # filter if needed
33
- issues << issue unless options[:exclude_issuetypes].include?(issuetype)
34
- end
35
- return issues
36
- end
37
-
38
- def oldest_issue_date(issues)
39
- return issues.collect {|i| Date.parse(i["fields"]["created"])}.sort.first
40
- end
41
-
42
- def do_work(options)
43
- issues = get_issues_from_datafile(options)
44
- oldest_date = oldest_issue_date(issues)
45
- iter_date_range(oldest_date) do |start_date, end_date|
46
- stat_hashes = issues_to_stat_hashes(issues, start_date, end_date, options)
47
- counts = print_stats(stat_hashes, start_date, end_date)
48
- puts counts # print each time range
49
- end
50
- end
51
-
52
- def iter_date_range(start_date)
53
- current_date = start_date.beginning_of_week
54
- while current_date < 1.week.ago.end_of_week
55
- yield current_date, current_date.end_of_week
56
- current_date += 2.weeks # increment
57
- end
58
- end
59
-
60
- def issues_to_stat_hashes(issues, start_date, end_date, options)
61
-
62
- # parse dates for changelogs
63
- issues.each do |issue|
64
- issue["status_changelogs"].each do |l|
65
- date_string = l["created"]
66
- l["created"] = Date.parse(date_string) unless date_string.class == Date
67
- end
68
- end
69
-
70
- # collect stat hashes
71
- return issues.collect do |issue|
72
-
73
- # skip if issue is out of range
74
- issue_created = Date.parse(issue["fields"]["created"])
75
- if issue_created > end_date
76
- nil
77
- else
78
- # get in-range status change
79
- status_changelog = nil
80
- status_changelog = issue["status_changelogs"]
81
- .select { |l| l["created"] > end_date }
82
- .sort_by { |l| l["created"] }.last if start_date && end_date
83
-
84
- # exclude issue types
85
- if status_changelog and options[:exclude_issuetypes].include?(status_changelog["toString"])
86
- nil
87
- else
88
- # yield stat hash
89
- {
90
- issuetype: issue["fields"]["issuetype"]["name"],
91
- status: status_changelog ? status_changelog["toString"] : issue["fields"]["status"]["name"]
92
- }
93
- end
94
- end
95
- end.compact
96
- end
97
-
98
- def print_stats(stat_hashes, start_date, end_date)
99
- counts = {
100
- start_date: start_date.strftime("%m-%d-%Y"),
101
- end_date: end_date.strftime("%m-%d-%Y"),
102
- total_todo: 0,
103
- total_done: 0,
104
- total: stat_hashes ? stat_hashes.count : 0
105
- }
106
- return counts unless stat_hashes.any?
107
- stat_hashes.each do |stat|
108
-
109
- issuetype = stat[:issuetype]
110
- status = stat[:status]
111
-
112
- # count all
113
- increment(counts, "status_#{issuetype.downcase.underscore}")
114
-
115
- # count by status
116
- case status
117
- when "Ready","Rework","In Progress","In QA","Ready For Code Review","In Code Review"
118
- increment(counts, :total_todo)
119
- when "Ready to Deploy","Closed"
120
- increment(counts, :total_done)
121
- # else
122
- # raise "invalid ticket status: #{status}"
123
- end
124
- end
125
- return counts
126
- end
127
-
128
- def increment(hash, key)
129
- if hash.key? key
130
- hash[key] += 1
131
- else
132
- hash[key] = 1
133
- end
134
- end
135
-
136
- #
137
- # Options
138
- #
139
-
140
- options = {
141
- exclude_issuetypes: []
142
- }
143
- global_banner = <<DOC
144
-
145
- Process jira fetch file an return stats
146
-
147
- Usage: #{@cl_cmd_name} [options]
148
-
149
- DOC
150
-
151
- global = OptionParser.new do |g|
152
- g.banner = global_banner
153
- add_help_and_verbose(g)
154
-
155
- g.on("-f", "--data-filepath FILEPATH", "relative path to file produced by 'cl jira fetch' command") do |v|
156
- options[:data_filepath] = v
157
- end
158
-
159
- g.on("-e", "--exclude-issuetypes CSV", "comma separated list of issuetypes you want to exclude") do |v|
160
- options[:exclude_issuetypes] = v.split(',')
161
- options[:exclude_issuetypes] << "Won't Do"
162
- end
163
- end
164
-
165
- #
166
- # Run
167
- #
168
-
169
- @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
170
- global.parse(ARGV)
171
-
172
- # prompt for missing options
173
- ask_and_store_option(options, :data_filepath, "data_filepath: ")
174
-
175
- # display full command
176
- write_history("""#{@cl_cmd_name} \\
177
- --data-filepath=#{options[:data_filepath]}
178
- """)
179
-
180
- do_work(options)