makit 0.0.144 → 0.0.145

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.
Files changed (165) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/auto.rb +48 -48
  6. data/lib/makit/cli/base.rb +17 -0
  7. data/lib/makit/cli/build_commands.rb +500 -500
  8. data/lib/makit/cli/generators/base_generator.rb +74 -74
  9. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  10. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  11. data/lib/makit/cli/generators/node_generator.rb +50 -50
  12. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  13. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  14. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  15. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  16. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  17. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -40
  18. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  19. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  20. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  21. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  22. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  23. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  24. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  25. data/lib/makit/cli/main.rb +78 -69
  26. data/lib/makit/cli/pipeline_commands.rb +311 -0
  27. data/lib/makit/cli/project_commands.rb +868 -868
  28. data/lib/makit/cli/repository_commands.rb +661 -661
  29. data/lib/makit/cli/strategy_commands.rb +207 -212
  30. data/lib/makit/cli/utility_commands.rb +521 -521
  31. data/lib/makit/commands/factory.rb +359 -359
  32. data/lib/makit/commands/middleware/base.rb +73 -73
  33. data/lib/makit/commands/middleware/cache.rb +248 -248
  34. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  35. data/lib/makit/commands/middleware/validator.rb +269 -269
  36. data/lib/makit/commands/request.rb +316 -316
  37. data/lib/makit/commands/result.rb +323 -323
  38. data/lib/makit/commands/runner.rb +386 -386
  39. data/lib/makit/commands/strategies/base.rb +171 -171
  40. data/lib/makit/commands/strategies/child_process.rb +162 -162
  41. data/lib/makit/commands/strategies/factory.rb +136 -136
  42. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  43. data/lib/makit/commands.rb +50 -50
  44. data/lib/makit/configuration/dotnet_project.rb +48 -48
  45. data/lib/makit/configuration/gitlab_helper.rb +61 -58
  46. data/lib/makit/configuration/project.rb +446 -168
  47. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  48. data/lib/makit/configuration/step.rb +34 -34
  49. data/lib/makit/configuration/timeout.rb +74 -74
  50. data/lib/makit/configuration.rb +21 -16
  51. data/lib/makit/content/default_gitignore.rb +7 -7
  52. data/lib/makit/content/default_gitignore.txt +225 -225
  53. data/lib/makit/content/default_rakefile.rb +13 -13
  54. data/lib/makit/content/gem_rakefile.rb +16 -16
  55. data/lib/makit/context.rb +1 -1
  56. data/lib/makit/data.rb +49 -49
  57. data/lib/makit/directories.rb +140 -140
  58. data/lib/makit/directory.rb +262 -262
  59. data/lib/makit/docs/files.rb +89 -89
  60. data/lib/makit/docs/rake.rb +102 -102
  61. data/lib/makit/dotnet/cli.rb +69 -69
  62. data/lib/makit/dotnet/project.rb +217 -217
  63. data/lib/makit/dotnet/solution.rb +38 -38
  64. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  65. data/lib/makit/dotnet/solution_console.rb +264 -264
  66. data/lib/makit/dotnet/solution_maui.rb +354 -354
  67. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  68. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  69. data/lib/makit/dotnet.rb +102 -102
  70. data/lib/makit/email.rb +90 -90
  71. data/lib/makit/environment.rb +142 -142
  72. data/lib/makit/examples/runner.rb +370 -370
  73. data/lib/makit/exceptions.rb +45 -45
  74. data/lib/makit/fileinfo.rb +32 -24
  75. data/lib/makit/files.rb +43 -43
  76. data/lib/makit/gems.rb +40 -40
  77. data/lib/makit/git/cli.rb +54 -54
  78. data/lib/makit/git/repository.rb +266 -90
  79. data/lib/makit/git.rb +104 -98
  80. data/lib/makit/gitlab/pipeline.rb +857 -0
  81. data/lib/makit/gitlab/pipeline_service_impl.rb +1536 -0
  82. data/lib/makit/gitlab_runner.rb +59 -59
  83. data/lib/makit/humanize.rb +218 -137
  84. data/lib/makit/indexer.rb +47 -47
  85. data/lib/makit/io/filesystem.rb +111 -0
  86. data/lib/makit/io/filesystem_service_impl.rb +337 -0
  87. data/lib/makit/logging/configuration.rb +308 -308
  88. data/lib/makit/logging/format_registry.rb +84 -84
  89. data/lib/makit/logging/formatters/base.rb +39 -39
  90. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  91. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  92. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  93. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  94. data/lib/makit/logging/log_request.rb +119 -119
  95. data/lib/makit/logging/logger.rb +199 -199
  96. data/lib/makit/logging/sinks/base.rb +91 -91
  97. data/lib/makit/logging/sinks/console.rb +72 -72
  98. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  99. data/lib/makit/logging/sinks/structured.rb +123 -123
  100. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  101. data/lib/makit/logging.rb +565 -565
  102. data/lib/makit/markdown.rb +75 -75
  103. data/lib/makit/mp/basic_object_mp.rb +17 -17
  104. data/lib/makit/mp/command_mp.rb +13 -13
  105. data/lib/makit/mp/command_request.mp.rb +17 -17
  106. data/lib/makit/mp/project_mp.rb +199 -199
  107. data/lib/makit/mp/string_mp.rb +205 -199
  108. data/lib/makit/nuget.rb +74 -74
  109. data/lib/makit/podman/podman.rb +458 -0
  110. data/lib/makit/podman/podman_service_impl.rb +1081 -0
  111. data/lib/makit/port.rb +32 -32
  112. data/lib/makit/process.rb +377 -377
  113. data/lib/makit/protoc.rb +112 -107
  114. data/lib/makit/rake/cli.rb +196 -196
  115. data/lib/makit/rake/trace_controller.rb +174 -174
  116. data/lib/makit/rake.rb +81 -81
  117. data/lib/makit/ruby/cli.rb +185 -185
  118. data/lib/makit/ruby.rb +25 -25
  119. data/lib/makit/secrets.rb +51 -51
  120. data/lib/makit/serializer.rb +130 -130
  121. data/lib/makit/services/builder.rb +186 -186
  122. data/lib/makit/services/error_handler.rb +226 -226
  123. data/lib/makit/services/repository_manager.rb +367 -231
  124. data/lib/makit/services/validator.rb +112 -112
  125. data/lib/makit/setup/classlib.rb +101 -101
  126. data/lib/makit/setup/gem.rb +268 -268
  127. data/lib/makit/setup/pages.rb +11 -11
  128. data/lib/makit/setup/razorclasslib.rb +101 -101
  129. data/lib/makit/setup/runner.rb +54 -54
  130. data/lib/makit/setup.rb +5 -5
  131. data/lib/makit/show.rb +110 -110
  132. data/lib/makit/storage.rb +126 -126
  133. data/lib/makit/symbols.rb +175 -170
  134. data/lib/makit/task_info.rb +130 -130
  135. data/lib/makit/tasks/at_exit.rb +15 -15
  136. data/lib/makit/tasks/build.rb +22 -22
  137. data/lib/makit/tasks/clean.rb +13 -13
  138. data/lib/makit/tasks/configure.rb +10 -10
  139. data/lib/makit/tasks/format.rb +10 -10
  140. data/lib/makit/tasks/hook_manager.rb +443 -443
  141. data/lib/makit/tasks/init.rb +49 -49
  142. data/lib/makit/tasks/integrate.rb +29 -29
  143. data/lib/makit/tasks/pull_incoming.rb +13 -13
  144. data/lib/makit/tasks/setup.rb +16 -16
  145. data/lib/makit/tasks/sync.rb +17 -17
  146. data/lib/makit/tasks/tag.rb +16 -16
  147. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  148. data/lib/makit/tasks/test.rb +22 -22
  149. data/lib/makit/tasks/update.rb +18 -18
  150. data/lib/makit/tasks.rb +20 -20
  151. data/lib/makit/test_cache.rb +239 -239
  152. data/lib/makit/tree.rb +37 -37
  153. data/lib/makit/v1/configuration/project_service_impl.rb +371 -0
  154. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -0
  155. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  156. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  157. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -0
  158. data/lib/makit/version.rb +100 -100
  159. data/lib/makit/version_util.rb +21 -21
  160. data/lib/makit/wix.rb +95 -95
  161. data/lib/makit/yaml.rb +29 -29
  162. data/lib/makit/zip.rb +17 -17
  163. data/lib/makit copy.rb +44 -44
  164. data/lib/makit.rb +111 -43
  165. metadata +61 -36
@@ -1,323 +1,323 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "time"
5
-
6
- module Makit
7
- module Commands
8
- # Structured result objects with comprehensive metadata.
9
- # Uses native Ruby objects instead of protobuf for better performance.
10
- #
11
- # @example Basic result creation
12
- # result = Result.new(
13
- # command: "git --version",
14
- # exit_code: 0,
15
- # stdout: "git version 2.39.5\n",
16
- # stderr: ""
17
- # )
18
- #
19
- # @example Result with full metadata
20
- # result = Result.new(
21
- # command: "bundle install",
22
- # exit_code: 0,
23
- # stdout: "Bundle complete!",
24
- # stderr: "",
25
- # started_at: Time.now - 10,
26
- # finished_at: Time.now,
27
- # metadata: { gems_installed: 42, cache_hit: true }
28
- # )
29
- class Result
30
- # Command execution result with comprehensive metadata.
31
- #
32
- # @!attribute [r] command
33
- # @return [String] the command that was executed
34
- # @!attribute [r] exit_code
35
- # @return [Integer] process exit code
36
- # @!attribute [r] stdout
37
- # @return [String] standard output
38
- # @!attribute [r] stderr
39
- # @return [String] standard error output
40
- # @!attribute [r] started_at
41
- # @return [Time] when execution started
42
- # @!attribute [r] finished_at
43
- # @return [Time, nil] when execution finished
44
- # @!attribute [r] duration
45
- # @return [Float, nil] execution duration in seconds
46
- # @!attribute [r] metadata
47
- # @return [Hash] additional metadata
48
- attr_reader :command, :exit_code, :stdout, :stderr, :started_at, :finished_at, :duration, :metadata
49
-
50
- # Initialize a new command result.
51
- #
52
- # @param command [String] the command that was executed
53
- # @param exit_code [Integer] process exit code (default: nil)
54
- # @param stdout [String] standard output (default: "")
55
- # @param stderr [String] standard error output (default: "")
56
- # @param started_at [Time] when execution started (default: Time.now)
57
- # @param finished_at [Time, nil] when execution finished
58
- # @param metadata [Hash] additional metadata (default: {})
59
- def initialize(command:, **attributes)
60
- @command = command.to_s
61
- @exit_code = attributes[:exit_code]
62
- @stdout = attributes[:stdout] || ""
63
- @stderr = attributes[:stderr] || ""
64
- @started_at = attributes[:started_at] || Time.now
65
- @finished_at = attributes[:finished_at]
66
- @duration = calculate_duration
67
- @metadata = attributes[:metadata] || {}
68
-
69
- # Ensure output is always strings
70
- @stdout = @stdout.to_s
71
- @stderr = @stderr.to_s
72
- end
73
-
74
- # Mark the result as finished.
75
- #
76
- # @param finished_at [Time] when execution finished (default: Time.now)
77
- # @param exit_code [Integer] process exit code
78
- # @param stdout [String] standard output
79
- # @param stderr [String] standard error output
80
- # @return [self] for method chaining
81
- def finish!(finished_at: Time.now, exit_code: nil, stdout: nil, stderr: nil)
82
- @finished_at = finished_at
83
- @duration = calculate_duration
84
- @exit_code = exit_code if exit_code
85
- @stdout = stdout.to_s if stdout
86
- @stderr = stderr.to_s if stderr
87
- self
88
- end
89
-
90
- # Check if the command executed successfully.
91
- #
92
- # @return [Boolean] true if exit code is 0
93
- def success?
94
- exit_code.zero?
95
- end
96
-
97
- # Check if the command execution failed.
98
- #
99
- # @return [Boolean] true if exit code is not 0
100
- def failure?
101
- !success?
102
- end
103
-
104
- # Check if the command execution is complete.
105
- #
106
- # @return [Boolean] true if finished_at is set
107
- def finished?
108
- !finished_at.nil?
109
- end
110
-
111
- # Check if the command is still running.
112
- #
113
- # @return [Boolean] true if execution started but not finished
114
- def running?
115
- !started_at.nil? && finished_at.nil?
116
- end
117
-
118
- # Get combined output (stdout + stderr).
119
- #
120
- # @return [String] combined output
121
- def output
122
- [stdout, stderr].reject(&:empty?).join("\n")
123
- end
124
-
125
- # Get the first line of stdout (useful for simple commands).
126
- #
127
- # @return [String, nil] first line of stdout
128
- def first_line
129
- return nil if stdout.empty?
130
-
131
- stdout.lines.first&.chomp
132
- end
133
-
134
- # Get the last line of stdout (useful for progress indicators).
135
- #
136
- # @return [String, nil] last line of stdout
137
- def last_line
138
- return nil if stdout.empty?
139
-
140
- stdout.lines.last&.chomp
141
- end
142
-
143
- # Check if output contains specific text.
144
- #
145
- # @param text [String] text to search for
146
- # @param case_sensitive [Boolean] whether to perform case-sensitive search
147
- # @return [Boolean] true if text is found in stdout or stderr
148
- def contains?(text, case_sensitive: true)
149
- search_text = case_sensitive ? text : text.downcase
150
- search_stdout = case_sensitive ? stdout : stdout.downcase
151
- search_stderr = case_sensitive ? stderr : stderr.downcase
152
-
153
- search_stdout.include?(search_text) || search_stderr.include?(search_text)
154
- end
155
-
156
- # Convert result to hash representation.
157
- #
158
- # @return [Hash] hash representation of the result
159
- def to_h
160
- {
161
- command: command,
162
- exit_code: exit_code,
163
- stdout: stdout,
164
- stderr: stderr,
165
- started_at: started_at&.iso8601,
166
- finished_at: finished_at&.iso8601,
167
- duration: duration,
168
- success: success?,
169
- metadata: metadata,
170
- }
171
- end
172
-
173
- # Convert result to JSON representation.
174
- #
175
- # @param args [Array] arguments passed to JSON.generate
176
- # @return [String] JSON representation
177
- def to_json(*args)
178
- JSON.generate(to_h, *args)
179
- end
180
-
181
- # Create result from hash representation.
182
- #
183
- # @param hash [Hash] hash containing result information
184
- # @return [Result] new result object
185
- def self.from_hash(hash)
186
- # Parse time strings back to Time objects
187
- started_at = hash[:started_at] || hash["started_at"]
188
- finished_at = hash[:finished_at] || hash["finished_at"]
189
-
190
- started_at = Time.iso8601(started_at) if started_at.is_a?(String)
191
- finished_at = Time.iso8601(finished_at) if finished_at.is_a?(String)
192
-
193
- new(
194
- command: hash[:command] || hash["command"],
195
- exit_code: hash[:exit_code] || hash["exit_code"],
196
- stdout: hash[:stdout] || hash["stdout"] || "",
197
- stderr: hash[:stderr] || hash["stderr"] || "",
198
- started_at: started_at,
199
- finished_at: finished_at,
200
- metadata: hash[:metadata] || hash["metadata"] || {},
201
- )
202
- end
203
-
204
- # Create result from JSON string.
205
- #
206
- # @param json_string [String] JSON string representation
207
- # @return [Result] new result object
208
- # @raise [JSON::ParserError] if JSON is invalid
209
- def self.from_json(json_string)
210
- hash = JSON.parse(json_string)
211
- from_hash(hash)
212
- end
213
-
214
- # Create a failed result with error information.
215
- #
216
- # @param command [String] the command that failed
217
- # @param error [Exception, String] error that occurred
218
- # @param started_at [Time] when execution started
219
- # @return [Result] failed result object
220
- def self.failure(command:, error:, started_at: Time.now)
221
- error_message = error.is_a?(Exception) ? error.message : error.to_s
222
- error_class = error.is_a?(Exception) ? error.class.name : "Error"
223
-
224
- new(
225
- command: command,
226
- exit_code: 1,
227
- stdout: "",
228
- stderr: error_message,
229
- started_at: started_at,
230
- finished_at: Time.now,
231
- metadata: { error_class: error_class, failed: true },
232
- )
233
- end
234
-
235
- # Create a successful result.
236
- #
237
- # @param command [String] the command that succeeded
238
- # @param stdout [String] standard output
239
- # @param started_at [Time] when execution started
240
- # @param duration [Float] execution duration
241
- # @return [Result] successful result object
242
- def self.success(command:, stdout: "", started_at: Time.now, duration: nil)
243
- finished_at = duration ? started_at + duration : Time.now
244
-
245
- new(
246
- command: command,
247
- exit_code: 0,
248
- stdout: stdout,
249
- stderr: "",
250
- started_at: started_at,
251
- finished_at: finished_at,
252
- )
253
- end
254
-
255
- # Add metadata to the result.
256
- #
257
- # @param key [String, Symbol] metadata key
258
- # @param value [Object] metadata value
259
- # @return [self] for method chaining
260
- def add_metadata(key, value)
261
- @metadata[key] = value
262
- self
263
- end
264
-
265
- # Merge additional metadata.
266
- #
267
- # @param additional_metadata [Hash] metadata to merge
268
- # @return [self] for method chaining
269
- def merge_metadata(additional_metadata)
270
- @metadata.merge!(additional_metadata)
271
- self
272
- end
273
-
274
- # Get execution summary for logging/display.
275
- #
276
- # @return [String] human-readable execution summary
277
- def summary
278
- status = success? ? "✓" : "✗"
279
- duration_text = duration ? " (#{format("%.2f", duration)}s)" : ""
280
-
281
- "#{status} #{command}#{duration_text}"
282
- end
283
-
284
- # Get detailed execution information.
285
- #
286
- # @return [String] detailed execution information
287
- def details
288
- lines = []
289
- lines << "Command: #{command}"
290
- lines << "Exit Code: #{exit_code}"
291
- lines << "Started: #{started_at}"
292
- lines << "Finished: #{finished_at}" if finished_at
293
- lines << "Duration: #{format("%.2f", duration)}s" if duration
294
- lines << "Status: #{success? ? "Success" : "Failed"}"
295
-
296
- unless stdout.empty?
297
- lines << "STDOUT:"
298
- lines << stdout.split("\n").map { |line| " #{line}" }
299
- end
300
-
301
- unless stderr.empty?
302
- lines << "STDERR:"
303
- lines << stderr.split("\n").map { |line| " #{line}" }
304
- end
305
-
306
- lines << "Metadata: #{metadata}" unless metadata.empty?
307
-
308
- lines.flatten.join("\n")
309
- end
310
-
311
- private
312
-
313
- # Calculate execution duration.
314
- #
315
- # @return [Float, nil] duration in seconds, or nil if not finished
316
- def calculate_duration
317
- return nil unless started_at && finished_at
318
-
319
- finished_at - started_at
320
- end
321
- end
322
- end
323
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+
6
+ module Makit
7
+ module Commands
8
+ # Structured result objects with comprehensive metadata.
9
+ # Uses native Ruby objects instead of protobuf for better performance.
10
+ #
11
+ # @example Basic result creation
12
+ # result = Result.new(
13
+ # command: "git --version",
14
+ # exit_code: 0,
15
+ # stdout: "git version 2.39.5\n",
16
+ # stderr: ""
17
+ # )
18
+ #
19
+ # @example Result with full metadata
20
+ # result = Result.new(
21
+ # command: "bundle install",
22
+ # exit_code: 0,
23
+ # stdout: "Bundle complete!",
24
+ # stderr: "",
25
+ # started_at: Time.now - 10,
26
+ # finished_at: Time.now,
27
+ # metadata: { gems_installed: 42, cache_hit: true }
28
+ # )
29
+ class Result
30
+ # Command execution result with comprehensive metadata.
31
+ #
32
+ # @!attribute [r] command
33
+ # @return [String] the command that was executed
34
+ # @!attribute [r] exit_code
35
+ # @return [Integer] process exit code
36
+ # @!attribute [r] stdout
37
+ # @return [String] standard output
38
+ # @!attribute [r] stderr
39
+ # @return [String] standard error output
40
+ # @!attribute [r] started_at
41
+ # @return [Time] when execution started
42
+ # @!attribute [r] finished_at
43
+ # @return [Time, nil] when execution finished
44
+ # @!attribute [r] duration
45
+ # @return [Float, nil] execution duration in seconds
46
+ # @!attribute [r] metadata
47
+ # @return [Hash] additional metadata
48
+ attr_reader :command, :exit_code, :stdout, :stderr, :started_at, :finished_at, :duration, :metadata
49
+
50
+ # Initialize a new command result.
51
+ #
52
+ # @param command [String] the command that was executed
53
+ # @param exit_code [Integer] process exit code (default: nil)
54
+ # @param stdout [String] standard output (default: "")
55
+ # @param stderr [String] standard error output (default: "")
56
+ # @param started_at [Time] when execution started (default: Time.now)
57
+ # @param finished_at [Time, nil] when execution finished
58
+ # @param metadata [Hash] additional metadata (default: {})
59
+ def initialize(command:, **attributes)
60
+ @command = command.to_s
61
+ @exit_code = attributes[:exit_code]
62
+ @stdout = attributes[:stdout] || ""
63
+ @stderr = attributes[:stderr] || ""
64
+ @started_at = attributes[:started_at] || Time.now
65
+ @finished_at = attributes[:finished_at]
66
+ @duration = calculate_duration
67
+ @metadata = attributes[:metadata] || {}
68
+
69
+ # Ensure output is always strings
70
+ @stdout = @stdout.to_s
71
+ @stderr = @stderr.to_s
72
+ end
73
+
74
+ # Mark the result as finished.
75
+ #
76
+ # @param finished_at [Time] when execution finished (default: Time.now)
77
+ # @param exit_code [Integer] process exit code
78
+ # @param stdout [String] standard output
79
+ # @param stderr [String] standard error output
80
+ # @return [self] for method chaining
81
+ def finish!(finished_at: Time.now, exit_code: nil, stdout: nil, stderr: nil)
82
+ @finished_at = finished_at
83
+ @duration = calculate_duration
84
+ @exit_code = exit_code if exit_code
85
+ @stdout = stdout.to_s if stdout
86
+ @stderr = stderr.to_s if stderr
87
+ self
88
+ end
89
+
90
+ # Check if the command executed successfully.
91
+ #
92
+ # @return [Boolean] true if exit code is 0
93
+ def success?
94
+ exit_code == 0
95
+ end
96
+
97
+ # Check if the command execution failed.
98
+ #
99
+ # @return [Boolean] true if exit code is not 0
100
+ def failure?
101
+ !success?
102
+ end
103
+
104
+ # Check if the command execution is complete.
105
+ #
106
+ # @return [Boolean] true if finished_at is set
107
+ def finished?
108
+ !finished_at.nil?
109
+ end
110
+
111
+ # Check if the command is still running.
112
+ #
113
+ # @return [Boolean] true if execution started but not finished
114
+ def running?
115
+ !started_at.nil? && finished_at.nil?
116
+ end
117
+
118
+ # Get combined output (stdout + stderr).
119
+ #
120
+ # @return [String] combined output
121
+ def output
122
+ [stdout, stderr].reject(&:empty?).join("\n")
123
+ end
124
+
125
+ # Get the first line of stdout (useful for simple commands).
126
+ #
127
+ # @return [String, nil] first line of stdout
128
+ def first_line
129
+ return nil if stdout.empty?
130
+
131
+ stdout.lines.first&.chomp
132
+ end
133
+
134
+ # Get the last line of stdout (useful for progress indicators).
135
+ #
136
+ # @return [String, nil] last line of stdout
137
+ def last_line
138
+ return nil if stdout.empty?
139
+
140
+ stdout.lines.last&.chomp
141
+ end
142
+
143
+ # Check if output contains specific text.
144
+ #
145
+ # @param text [String] text to search for
146
+ # @param case_sensitive [Boolean] whether to perform case-sensitive search
147
+ # @return [Boolean] true if text is found in stdout or stderr
148
+ def contains?(text, case_sensitive: true)
149
+ search_text = case_sensitive ? text : text.downcase
150
+ search_stdout = case_sensitive ? stdout : stdout.downcase
151
+ search_stderr = case_sensitive ? stderr : stderr.downcase
152
+
153
+ search_stdout.include?(search_text) || search_stderr.include?(search_text)
154
+ end
155
+
156
+ # Convert result to hash representation.
157
+ #
158
+ # @return [Hash] hash representation of the result
159
+ def to_h
160
+ {
161
+ command: command,
162
+ exit_code: exit_code,
163
+ stdout: stdout,
164
+ stderr: stderr,
165
+ started_at: started_at&.iso8601,
166
+ finished_at: finished_at&.iso8601,
167
+ duration: duration,
168
+ success: success?,
169
+ metadata: metadata,
170
+ }
171
+ end
172
+
173
+ # Convert result to JSON representation.
174
+ #
175
+ # @param args [Array] arguments passed to JSON.generate
176
+ # @return [String] JSON representation
177
+ def to_json(*args)
178
+ JSON.generate(to_h, *args)
179
+ end
180
+
181
+ # Create result from hash representation.
182
+ #
183
+ # @param hash [Hash] hash containing result information
184
+ # @return [Result] new result object
185
+ def self.from_hash(hash)
186
+ # Parse time strings back to Time objects
187
+ started_at = hash[:started_at] || hash["started_at"]
188
+ finished_at = hash[:finished_at] || hash["finished_at"]
189
+
190
+ started_at = Time.iso8601(started_at) if started_at.is_a?(String)
191
+ finished_at = Time.iso8601(finished_at) if finished_at.is_a?(String)
192
+
193
+ new(
194
+ command: hash[:command] || hash["command"],
195
+ exit_code: hash[:exit_code] || hash["exit_code"],
196
+ stdout: hash[:stdout] || hash["stdout"] || "",
197
+ stderr: hash[:stderr] || hash["stderr"] || "",
198
+ started_at: started_at,
199
+ finished_at: finished_at,
200
+ metadata: hash[:metadata] || hash["metadata"] || {},
201
+ )
202
+ end
203
+
204
+ # Create result from JSON string.
205
+ #
206
+ # @param json_string [String] JSON string representation
207
+ # @return [Result] new result object
208
+ # @raise [JSON::ParserError] if JSON is invalid
209
+ def self.from_json(json_string)
210
+ hash = JSON.parse(json_string)
211
+ from_hash(hash)
212
+ end
213
+
214
+ # Create a failed result with error information.
215
+ #
216
+ # @param command [String] the command that failed
217
+ # @param error [Exception, String] error that occurred
218
+ # @param started_at [Time] when execution started
219
+ # @return [Result] failed result object
220
+ def self.failure(command:, error:, started_at: Time.now)
221
+ error_message = error.is_a?(Exception) ? error.message : error.to_s
222
+ error_class = error.is_a?(Exception) ? error.class.name : "Error"
223
+
224
+ new(
225
+ command: command,
226
+ exit_code: 1,
227
+ stdout: "",
228
+ stderr: error_message,
229
+ started_at: started_at,
230
+ finished_at: Time.now,
231
+ metadata: { error_class: error_class, failed: true },
232
+ )
233
+ end
234
+
235
+ # Create a successful result.
236
+ #
237
+ # @param command [String] the command that succeeded
238
+ # @param stdout [String] standard output
239
+ # @param started_at [Time] when execution started
240
+ # @param duration [Float] execution duration
241
+ # @return [Result] successful result object
242
+ def self.success(command:, stdout: "", started_at: Time.now, duration: nil)
243
+ finished_at = duration ? started_at + duration : Time.now
244
+
245
+ new(
246
+ command: command,
247
+ exit_code: 0,
248
+ stdout: stdout,
249
+ stderr: "",
250
+ started_at: started_at,
251
+ finished_at: finished_at,
252
+ )
253
+ end
254
+
255
+ # Add metadata to the result.
256
+ #
257
+ # @param key [String, Symbol] metadata key
258
+ # @param value [Object] metadata value
259
+ # @return [self] for method chaining
260
+ def add_metadata(key, value)
261
+ @metadata[key] = value
262
+ self
263
+ end
264
+
265
+ # Merge additional metadata.
266
+ #
267
+ # @param additional_metadata [Hash] metadata to merge
268
+ # @return [self] for method chaining
269
+ def merge_metadata(additional_metadata)
270
+ @metadata.merge!(additional_metadata)
271
+ self
272
+ end
273
+
274
+ # Get execution summary for logging/display.
275
+ #
276
+ # @return [String] human-readable execution summary
277
+ def summary
278
+ status = success? ? "✓" : "✗"
279
+ duration_text = duration ? " (#{format("%.2f", duration)}s)" : ""
280
+
281
+ "#{status} #{command}#{duration_text}"
282
+ end
283
+
284
+ # Get detailed execution information.
285
+ #
286
+ # @return [String] detailed execution information
287
+ def details
288
+ lines = []
289
+ lines << "Command: #{command}"
290
+ lines << "Exit Code: #{exit_code}"
291
+ lines << "Started: #{started_at}"
292
+ lines << "Finished: #{finished_at}" if finished_at
293
+ lines << "Duration: #{format("%.2f", duration)}s" if duration
294
+ lines << "Status: #{success? ? "Success" : "Failed"}"
295
+
296
+ unless stdout.empty?
297
+ lines << "STDOUT:"
298
+ lines << stdout.split("\n").map { |line| " #{line}" }
299
+ end
300
+
301
+ unless stderr.empty?
302
+ lines << "STDERR:"
303
+ lines << stderr.split("\n").map { |line| " #{line}" }
304
+ end
305
+
306
+ lines << "Metadata: #{metadata}" unless metadata.empty?
307
+
308
+ lines.flatten.join("\n")
309
+ end
310
+
311
+ private
312
+
313
+ # Calculate execution duration.
314
+ #
315
+ # @return [Float, nil] duration in seconds, or nil if not finished
316
+ def calculate_duration
317
+ return nil unless started_at && finished_at
318
+
319
+ finished_at - started_at
320
+ end
321
+ end
322
+ end
323
+ end