appydave-tools 0.70.0 → 0.71.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 +4 -4
- data/.claude/commands/brainstorming-agent.md +227 -0
- data/.claude/commands/cli-test.md +251 -0
- data/.claude/commands/dev.md +234 -0
- data/.claude/commands/po.md +227 -0
- data/.claude/commands/progress.md +51 -0
- data/.claude/commands/uat.md +321 -0
- data/.rubocop.yml +9 -0
- data/AGENTS.md +43 -0
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +26 -3
- data/README.md +15 -0
- data/bin/dam +21 -1
- data/bin/jump.rb +29 -0
- data/bin/subtitle_processor.rb +54 -1
- data/bin/zsh_history.rb +846 -0
- data/docs/README.md +162 -69
- data/docs/architecture/cli/exe-bin-convention.md +434 -0
- data/docs/architecture/cli-patterns.md +631 -0
- data/docs/architecture/gpt-context/gpt-context-architecture.md +325 -0
- data/docs/architecture/gpt-context/gpt-context-implementation-guide.md +419 -0
- data/docs/architecture/gpt-context/gpt-context-vision.md +179 -0
- data/docs/architecture/testing/testing-patterns.md +762 -0
- data/docs/backlog.md +120 -0
- data/docs/cli-tests/FR-3-jump-location-tool.md +515 -0
- data/docs/specs/fr-002-gpt-context-help-system.md +265 -0
- data/docs/specs/fr-003-jump-location-tool.md +779 -0
- data/docs/specs/zsh-history-tool.md +820 -0
- data/docs/uat/FR-3-jump-location-tool.md +741 -0
- data/exe/jump +11 -0
- data/exe/{subtitle_manager → subtitle_processor} +1 -1
- data/exe/zsh_history +11 -0
- data/lib/appydave/tools/configuration/openai.rb +1 -1
- data/lib/appydave/tools/dam/file_helper.rb +28 -0
- data/lib/appydave/tools/dam/project_listing.rb +4 -30
- data/lib/appydave/tools/dam/s3_operations.rb +2 -1
- data/lib/appydave/tools/dam/ssd_status.rb +226 -0
- data/lib/appydave/tools/dam/status.rb +3 -51
- data/lib/appydave/tools/jump/cli.rb +561 -0
- data/lib/appydave/tools/jump/commands/add.rb +52 -0
- data/lib/appydave/tools/jump/commands/base.rb +43 -0
- data/lib/appydave/tools/jump/commands/generate.rb +153 -0
- data/lib/appydave/tools/jump/commands/remove.rb +58 -0
- data/lib/appydave/tools/jump/commands/report.rb +214 -0
- data/lib/appydave/tools/jump/commands/update.rb +42 -0
- data/lib/appydave/tools/jump/commands/validate.rb +54 -0
- data/lib/appydave/tools/jump/config.rb +233 -0
- data/lib/appydave/tools/jump/formatters/base.rb +48 -0
- data/lib/appydave/tools/jump/formatters/json_formatter.rb +19 -0
- data/lib/appydave/tools/jump/formatters/paths_formatter.rb +21 -0
- data/lib/appydave/tools/jump/formatters/table_formatter.rb +183 -0
- data/lib/appydave/tools/jump/location.rb +134 -0
- data/lib/appydave/tools/jump/path_validator.rb +47 -0
- data/lib/appydave/tools/jump/search.rb +230 -0
- data/lib/appydave/tools/subtitle_processor/transcript.rb +51 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools/zsh_history/command.rb +37 -0
- data/lib/appydave/tools/zsh_history/config.rb +235 -0
- data/lib/appydave/tools/zsh_history/filter.rb +184 -0
- data/lib/appydave/tools/zsh_history/formatter.rb +75 -0
- data/lib/appydave/tools/zsh_history/parser.rb +101 -0
- data/lib/appydave/tools.rb +25 -0
- data/package.json +1 -1
- metadata +51 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Jump
|
|
6
|
+
module Commands
|
|
7
|
+
# Generate command creates shell aliases and help content
|
|
8
|
+
class Generate < Base
|
|
9
|
+
VALID_TARGETS = %w[aliases help all].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :target, :output_path, :output_dir
|
|
12
|
+
|
|
13
|
+
# rubocop:disable Metrics/ParameterLists
|
|
14
|
+
def initialize(config, target, output_path: nil, output_dir: nil, path_validator: PathValidator.new, **options)
|
|
15
|
+
super(config, path_validator: path_validator, **options)
|
|
16
|
+
@target = target
|
|
17
|
+
@output_path = output_path
|
|
18
|
+
@output_dir = output_dir
|
|
19
|
+
end
|
|
20
|
+
# rubocop:enable Metrics/ParameterLists
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
unless VALID_TARGETS.include?(target)
|
|
24
|
+
return error_result(
|
|
25
|
+
"Unknown generate target: #{target}",
|
|
26
|
+
code: 'INVALID_INPUT',
|
|
27
|
+
suggestion: "Valid targets: #{VALID_TARGETS.join(', ')}"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
send("generate_#{target}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def generate_aliases
|
|
37
|
+
content = build_aliases_content
|
|
38
|
+
write_or_return(content, output_path, 'aliases-jump.zsh')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def generate_help
|
|
42
|
+
content = build_help_content
|
|
43
|
+
write_or_return(content, output_path, 'jump-help.txt')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def generate_all
|
|
47
|
+
aliases_content = build_aliases_content
|
|
48
|
+
help_content = build_help_content
|
|
49
|
+
|
|
50
|
+
if output_dir
|
|
51
|
+
aliases_path = File.join(output_dir, 'aliases-jump.zsh')
|
|
52
|
+
help_path = File.join(output_dir, 'data', 'jump-help.txt')
|
|
53
|
+
|
|
54
|
+
FileUtils.mkdir_p(File.dirname(aliases_path))
|
|
55
|
+
FileUtils.mkdir_p(File.dirname(help_path))
|
|
56
|
+
|
|
57
|
+
File.write(aliases_path, aliases_content)
|
|
58
|
+
File.write(help_path, help_content)
|
|
59
|
+
|
|
60
|
+
success_result(
|
|
61
|
+
message: 'Generated all files',
|
|
62
|
+
files: [aliases_path, help_path]
|
|
63
|
+
)
|
|
64
|
+
else
|
|
65
|
+
success_result(
|
|
66
|
+
aliases: aliases_content,
|
|
67
|
+
help: help_content
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_aliases_content
|
|
73
|
+
lines = []
|
|
74
|
+
lines << '# Jump Location Aliases'
|
|
75
|
+
lines << "# Generated by: jump generate aliases (#{Time.now.strftime('%Y-%m-%d %H:%M')})"
|
|
76
|
+
lines << '# Source: ~/.config/appydave/locations.json'
|
|
77
|
+
lines << ''
|
|
78
|
+
lines << '# Usage: source this file in your .zshrc'
|
|
79
|
+
lines << '# source ~/.oh-my-zsh/custom/aliases-jump.zsh'
|
|
80
|
+
lines << ''
|
|
81
|
+
|
|
82
|
+
# Group by brand/client
|
|
83
|
+
grouped = group_locations_for_aliases
|
|
84
|
+
|
|
85
|
+
grouped.each do |group_name, locations|
|
|
86
|
+
lines << "# #{group_name}"
|
|
87
|
+
locations.each do |loc|
|
|
88
|
+
path = path_validator.expand(loc.path)
|
|
89
|
+
desc = loc.description ? " # #{loc.description}" : ''
|
|
90
|
+
lines << "alias #{loc.jump}=\"cd '#{path}'\"#{desc}"
|
|
91
|
+
end
|
|
92
|
+
lines << ''
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
lines.join("\n")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def build_help_content
|
|
99
|
+
lines = []
|
|
100
|
+
lines << '# Jump Location Help'
|
|
101
|
+
lines << "# Generated: #{Time.now.strftime('%Y-%m-%d %H:%M')}"
|
|
102
|
+
lines << ''
|
|
103
|
+
|
|
104
|
+
# Format for fzf/ah integration
|
|
105
|
+
config.locations.each do |loc|
|
|
106
|
+
path = path_validator.expand(loc.path)
|
|
107
|
+
brand_client = loc.brand || loc.client || ''
|
|
108
|
+
type = loc.type || ''
|
|
109
|
+
desc = loc.description || ''
|
|
110
|
+
|
|
111
|
+
# Format: jump_alias | path | brand/client | type | description
|
|
112
|
+
lines << "#{loc.jump}\t#{path}\t#{brand_client}\t#{type}\t#{desc}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
lines.join("\n")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def group_locations_for_aliases
|
|
119
|
+
grouped = {}
|
|
120
|
+
|
|
121
|
+
config.locations.each do |loc|
|
|
122
|
+
group = loc.brand || loc.client || 'Other'
|
|
123
|
+
grouped[group] ||= []
|
|
124
|
+
grouped[group] << loc
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Sort groups and locations within groups
|
|
128
|
+
grouped.sort.to_h.transform_values do |locs|
|
|
129
|
+
locs.sort_by(&:key)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def write_or_return(content, path, default_filename)
|
|
134
|
+
if path
|
|
135
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
136
|
+
File.write(path, content)
|
|
137
|
+
success_result(
|
|
138
|
+
message: "Generated #{default_filename}",
|
|
139
|
+
path: path,
|
|
140
|
+
lines: content.lines.count
|
|
141
|
+
)
|
|
142
|
+
else
|
|
143
|
+
success_result(
|
|
144
|
+
content: content,
|
|
145
|
+
lines: content.lines.count
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Jump
|
|
6
|
+
module Commands
|
|
7
|
+
# Remove command deletes a location entry
|
|
8
|
+
class Remove < Base
|
|
9
|
+
attr_reader :key, :force
|
|
10
|
+
|
|
11
|
+
def initialize(config, key, force: false, path_validator: PathValidator.new, **options)
|
|
12
|
+
super(config, path_validator: path_validator, **options)
|
|
13
|
+
@key = key
|
|
14
|
+
@force = force
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
# Validate key exists
|
|
19
|
+
location = config.find(key)
|
|
20
|
+
unless location
|
|
21
|
+
suggestions = find_suggestions(key)
|
|
22
|
+
suggestion = suggestions.empty? ? nil : "Did you mean: #{suggestions.join(', ')}?"
|
|
23
|
+
return error_result("Location '#{key}' not found", code: 'NOT_FOUND', suggestion: suggestion)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Require --force
|
|
27
|
+
unless force
|
|
28
|
+
return error_result(
|
|
29
|
+
"Use --force to confirm removal of '#{key}'",
|
|
30
|
+
code: 'CONFIRMATION_REQUIRED'
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Remove
|
|
35
|
+
config.remove(key)
|
|
36
|
+
config.save
|
|
37
|
+
|
|
38
|
+
success_result(
|
|
39
|
+
message: "Location '#{key}' removed successfully",
|
|
40
|
+
removed: location.to_h
|
|
41
|
+
)
|
|
42
|
+
rescue ArgumentError => e
|
|
43
|
+
error_result(e.message, code: 'NOT_FOUND')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def find_suggestions(query)
|
|
49
|
+
config.locations
|
|
50
|
+
.map(&:key)
|
|
51
|
+
.select { |k| k.include?(query) || query.include?(k) || k.start_with?(query[0..1]) }
|
|
52
|
+
.first(3)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Jump
|
|
6
|
+
module Commands
|
|
7
|
+
# Report command generates various reports about locations
|
|
8
|
+
class Report < Base
|
|
9
|
+
VALID_REPORTS = %w[
|
|
10
|
+
categories brands clients types tags
|
|
11
|
+
by-brand by-client by-type by-tag
|
|
12
|
+
summary
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :report_type, :filter
|
|
16
|
+
|
|
17
|
+
def initialize(config, report_type, filter: nil, path_validator: PathValidator.new, **options)
|
|
18
|
+
super(config, path_validator: path_validator, **options)
|
|
19
|
+
@report_type = report_type
|
|
20
|
+
@filter = filter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run
|
|
24
|
+
unless VALID_REPORTS.include?(report_type)
|
|
25
|
+
return error_result(
|
|
26
|
+
"Unknown report type: #{report_type}",
|
|
27
|
+
code: 'INVALID_INPUT',
|
|
28
|
+
suggestion: "Valid types: #{VALID_REPORTS.join(', ')}"
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
send("report_#{report_type.gsub('-', '_')}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def report_categories
|
|
38
|
+
categories = config.categories.map do |name, info|
|
|
39
|
+
{
|
|
40
|
+
name: name,
|
|
41
|
+
description: info['description'] || info[:description],
|
|
42
|
+
values: info['values'] || info[:values] || []
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
success_result(
|
|
47
|
+
report: 'categories',
|
|
48
|
+
count: categories.size,
|
|
49
|
+
results: categories
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def report_brands
|
|
54
|
+
brands = config.brands.map do |key, info|
|
|
55
|
+
location_count = config.locations.count { |loc| loc.brand == key }
|
|
56
|
+
{
|
|
57
|
+
key: key,
|
|
58
|
+
description: info['description'] || info[:description],
|
|
59
|
+
aliases: info['aliases'] || info[:aliases] || [],
|
|
60
|
+
location_count: location_count
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
success_result(
|
|
65
|
+
report: 'brands',
|
|
66
|
+
count: brands.size,
|
|
67
|
+
results: brands.sort_by { |b| -b[:location_count] }
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def report_clients
|
|
72
|
+
clients = config.clients.map do |key, info|
|
|
73
|
+
location_count = config.locations.count { |loc| loc.client == key }
|
|
74
|
+
{
|
|
75
|
+
key: key,
|
|
76
|
+
description: info['description'] || info[:description],
|
|
77
|
+
aliases: info['aliases'] || info[:aliases] || [],
|
|
78
|
+
location_count: location_count
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
success_result(
|
|
83
|
+
report: 'clients',
|
|
84
|
+
count: clients.size,
|
|
85
|
+
results: clients.sort_by { |c| -c[:location_count] }
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def report_types
|
|
90
|
+
type_counts = Hash.new(0)
|
|
91
|
+
config.locations.each do |loc|
|
|
92
|
+
type_counts[loc.type || 'untyped'] += 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
types = type_counts.map do |type, count|
|
|
96
|
+
{ type: type, location_count: count }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
success_result(
|
|
100
|
+
report: 'types',
|
|
101
|
+
count: types.size,
|
|
102
|
+
results: types.sort_by { |t| -t[:location_count] }
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def report_tags
|
|
107
|
+
tag_counts = Hash.new(0)
|
|
108
|
+
config.locations.each do |loc|
|
|
109
|
+
loc.tags.each { |tag| tag_counts[tag] += 1 }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
tags = tag_counts.map do |tag, count|
|
|
113
|
+
{ tag: tag, location_count: count }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
success_result(
|
|
117
|
+
report: 'tags',
|
|
118
|
+
count: tags.size,
|
|
119
|
+
results: tags.sort_by { |t| -t[:location_count] }
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def report_by_brand
|
|
124
|
+
grouped = group_by_field(:brand, filter)
|
|
125
|
+
success_result(
|
|
126
|
+
report: 'by-brand',
|
|
127
|
+
filter: filter,
|
|
128
|
+
groups: grouped
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def report_by_client
|
|
133
|
+
grouped = group_by_field(:client, filter)
|
|
134
|
+
success_result(
|
|
135
|
+
report: 'by-client',
|
|
136
|
+
filter: filter,
|
|
137
|
+
groups: grouped
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def report_by_type
|
|
142
|
+
grouped = group_by_field(:type, filter)
|
|
143
|
+
success_result(
|
|
144
|
+
report: 'by-type',
|
|
145
|
+
filter: filter,
|
|
146
|
+
groups: grouped
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def report_by_tag
|
|
151
|
+
grouped = {}
|
|
152
|
+
config.locations.each do |loc|
|
|
153
|
+
loc.tags.each do |tag|
|
|
154
|
+
next if filter && tag != filter
|
|
155
|
+
|
|
156
|
+
grouped[tag] ||= []
|
|
157
|
+
grouped[tag] << location_summary(loc)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
success_result(
|
|
162
|
+
report: 'by-tag',
|
|
163
|
+
filter: filter,
|
|
164
|
+
groups: grouped.sort_by { |_, locs| -locs.size }.to_h
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def report_summary
|
|
169
|
+
success_result(
|
|
170
|
+
report: 'summary',
|
|
171
|
+
total_locations: config.locations.size,
|
|
172
|
+
total_brands: config.brands.size,
|
|
173
|
+
total_clients: config.clients.size,
|
|
174
|
+
by_type: count_by_field(:type),
|
|
175
|
+
by_brand: count_by_field(:brand),
|
|
176
|
+
by_client: count_by_field(:client),
|
|
177
|
+
config_info: config.info
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def group_by_field(field, filter_value)
|
|
182
|
+
grouped = {}
|
|
183
|
+
config.locations.each do |loc|
|
|
184
|
+
value = loc.send(field) || 'unassigned'
|
|
185
|
+
next if filter_value && value != filter_value
|
|
186
|
+
|
|
187
|
+
grouped[value] ||= []
|
|
188
|
+
grouped[value] << location_summary(loc)
|
|
189
|
+
end
|
|
190
|
+
grouped.sort_by { |_, locs| -locs.size }.to_h
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def count_by_field(field)
|
|
194
|
+
counts = Hash.new(0)
|
|
195
|
+
config.locations.each do |loc|
|
|
196
|
+
value = loc.send(field) || 'unassigned'
|
|
197
|
+
counts[value] += 1
|
|
198
|
+
end
|
|
199
|
+
counts.sort_by { |_, count| -count }.to_h
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def location_summary(location)
|
|
203
|
+
{
|
|
204
|
+
key: location.key,
|
|
205
|
+
path: location.path,
|
|
206
|
+
jump: location.jump,
|
|
207
|
+
description: location.description
|
|
208
|
+
}.compact
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Jump
|
|
6
|
+
module Commands
|
|
7
|
+
# Update command modifies an existing location entry
|
|
8
|
+
class Update < Base
|
|
9
|
+
attr_reader :key, :attrs
|
|
10
|
+
|
|
11
|
+
def initialize(config, key, attrs, path_validator: PathValidator.new, **options)
|
|
12
|
+
super(config, path_validator: path_validator, **options)
|
|
13
|
+
@key = key
|
|
14
|
+
@attrs = attrs
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
# Validate key exists
|
|
19
|
+
return error_result("Location '#{key}' not found", code: 'NOT_FOUND') unless config.key_exists?(key)
|
|
20
|
+
|
|
21
|
+
# Validate path if provided
|
|
22
|
+
@path_warning = "Warning: Path '#{attrs[:path]}' does not exist" if attrs[:path] && !path_validator.exists?(attrs[:path])
|
|
23
|
+
|
|
24
|
+
# Update
|
|
25
|
+
config.update(key, attrs)
|
|
26
|
+
config.save
|
|
27
|
+
|
|
28
|
+
updated = config.find(key)
|
|
29
|
+
result = success_result(
|
|
30
|
+
message: "Location '#{key}' updated successfully",
|
|
31
|
+
location: updated.to_h
|
|
32
|
+
)
|
|
33
|
+
result[:warning] = @path_warning if @path_warning
|
|
34
|
+
result
|
|
35
|
+
rescue ArgumentError => e
|
|
36
|
+
error_result(e.message, code: 'INVALID_INPUT')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appydave
|
|
4
|
+
module Tools
|
|
5
|
+
module Jump
|
|
6
|
+
module Commands
|
|
7
|
+
# Validate command checks if location paths exist
|
|
8
|
+
class Validate < Base
|
|
9
|
+
attr_reader :key
|
|
10
|
+
|
|
11
|
+
def initialize(config, key: nil, path_validator: PathValidator.new, **options)
|
|
12
|
+
super(config, path_validator: path_validator, **options)
|
|
13
|
+
@key = key
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
locations_to_validate = if key
|
|
18
|
+
location = config.find(key)
|
|
19
|
+
return error_result("Location '#{key}' not found", code: 'NOT_FOUND') unless location
|
|
20
|
+
|
|
21
|
+
[location]
|
|
22
|
+
else
|
|
23
|
+
config.locations
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
results = locations_to_validate.map do |loc|
|
|
27
|
+
{
|
|
28
|
+
key: loc.key,
|
|
29
|
+
path: loc.path,
|
|
30
|
+
expanded_path: path_validator.expand(loc.path),
|
|
31
|
+
valid: path_validator.exists?(loc.path),
|
|
32
|
+
jump: loc.jump
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
valid_count = results.count { |r| r[:valid] }
|
|
37
|
+
invalid_count = results.count { |r| !r[:valid] }
|
|
38
|
+
|
|
39
|
+
# Update validation timestamp
|
|
40
|
+
config.touch_validated
|
|
41
|
+
config.save
|
|
42
|
+
|
|
43
|
+
success_result(
|
|
44
|
+
count: results.size,
|
|
45
|
+
valid_count: valid_count,
|
|
46
|
+
invalid_count: invalid_count,
|
|
47
|
+
results: results
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|