devex 0.3.5
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 +7 -0
- data/.obsidian/app.json +6 -0
- data/.obsidian/appearance.json +4 -0
- data/.obsidian/community-plugins.json +5 -0
- data/.obsidian/core-plugins.json +33 -0
- data/.obsidian/plugins/obsidian-minimal-settings/data.json +34 -0
- data/.obsidian/plugins/obsidian-minimal-settings/main.js +8 -0
- data/.obsidian/plugins/obsidian-minimal-settings/manifest.json +11 -0
- data/.obsidian/plugins/obsidian-style-settings/data.json +15 -0
- data/.obsidian/plugins/obsidian-style-settings/main.js +165 -0
- data/.obsidian/plugins/obsidian-style-settings/manifest.json +10 -0
- data/.obsidian/plugins/obsidian-style-settings/styles.css +243 -0
- data/.obsidian/plugins/table-editor-obsidian/data.json +6 -0
- data/.obsidian/plugins/table-editor-obsidian/main.js +236 -0
- data/.obsidian/plugins/table-editor-obsidian/manifest.json +17 -0
- data/.obsidian/plugins/table-editor-obsidian/styles.css +78 -0
- data/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
- data/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
- data/.obsidian/themes/Minimal/manifest.json +8 -0
- data/.obsidian/themes/Minimal/theme.css +2251 -0
- data/.rubocop.yml +231 -0
- data/CHANGELOG.md +97 -0
- data/LICENSE +21 -0
- data/README.md +314 -0
- data/Rakefile +13 -0
- data/devex-logo.jpg +0 -0
- data/docs/developing-tools.md +1000 -0
- data/docs/ref/agent-mode.md +46 -0
- data/docs/ref/cli-interface.md +60 -0
- data/docs/ref/configuration.md +46 -0
- data/docs/ref/design-philosophy.md +17 -0
- data/docs/ref/error-handling.md +38 -0
- data/docs/ref/io-handling.md +88 -0
- data/docs/ref/signals.md +141 -0
- data/docs/ref/temporal-software-theory.md +790 -0
- data/exe/dx +52 -0
- data/lib/devex/builtins/.index.rb +10 -0
- data/lib/devex/builtins/debug.rb +43 -0
- data/lib/devex/builtins/format.rb +44 -0
- data/lib/devex/builtins/gem.rb +77 -0
- data/lib/devex/builtins/lint.rb +61 -0
- data/lib/devex/builtins/test.rb +76 -0
- data/lib/devex/builtins/version.rb +156 -0
- data/lib/devex/cli.rb +340 -0
- data/lib/devex/context.rb +433 -0
- data/lib/devex/core/configuration.rb +136 -0
- data/lib/devex/core.rb +79 -0
- data/lib/devex/dirs.rb +210 -0
- data/lib/devex/dsl.rb +100 -0
- data/lib/devex/exec/controller.rb +245 -0
- data/lib/devex/exec/result.rb +229 -0
- data/lib/devex/exec.rb +662 -0
- data/lib/devex/loader.rb +136 -0
- data/lib/devex/output.rb +257 -0
- data/lib/devex/project_paths.rb +309 -0
- data/lib/devex/support/ansi.rb +437 -0
- data/lib/devex/support/core_ext.rb +560 -0
- data/lib/devex/support/global.rb +68 -0
- data/lib/devex/support/path.rb +357 -0
- data/lib/devex/support.rb +71 -0
- data/lib/devex/template_helpers.rb +136 -0
- data/lib/devex/templates/debug.erb +24 -0
- data/lib/devex/tool.rb +374 -0
- data/lib/devex/version.rb +5 -0
- data/lib/devex/working_dir.rb +99 -0
- data/lib/devex.rb +158 -0
- data/ruby-project-template/.gitignore +0 -0
- data/ruby-project-template/Gemfile +0 -0
- data/ruby-project-template/README.md +0 -0
- data/ruby-project-template/docs/README.md +0 -0
- data/sig/devex.rbs +4 -0
- metadata +122 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Devex
|
|
4
|
+
module Exec
|
|
5
|
+
# Result of command execution.
|
|
6
|
+
#
|
|
7
|
+
# All commands (except run?/shell?/exec!) return a Result object.
|
|
8
|
+
# This provides inspectable information about what happened without
|
|
9
|
+
# raising exceptions for non-zero exit codes.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# result = run "bundle", "install"
|
|
13
|
+
# if result.success?
|
|
14
|
+
# puts "Installed!"
|
|
15
|
+
# else
|
|
16
|
+
# puts "Failed: #{result.stderr}"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Exit on failure
|
|
20
|
+
# run("bundle", "install").exit_on_failure!
|
|
21
|
+
#
|
|
22
|
+
# @example Chaining
|
|
23
|
+
# run("lint").then { run("test") }.then { run("build") }.exit_on_failure!
|
|
24
|
+
#
|
|
25
|
+
class Result
|
|
26
|
+
# @return [Array<String>] The command that was executed
|
|
27
|
+
attr_reader :command
|
|
28
|
+
|
|
29
|
+
# @return [Integer, nil] Process ID
|
|
30
|
+
attr_reader :pid
|
|
31
|
+
|
|
32
|
+
# @return [Float, nil] Execution duration in seconds
|
|
33
|
+
attr_reader :duration
|
|
34
|
+
|
|
35
|
+
# @return [Integer, nil] Exit code (0-255), nil if killed by signal
|
|
36
|
+
attr_reader :exit_code
|
|
37
|
+
|
|
38
|
+
# @return [Integer, nil] Signal number if killed by signal
|
|
39
|
+
attr_reader :signal_code
|
|
40
|
+
|
|
41
|
+
# @return [String, nil] Captured stdout (if applicable)
|
|
42
|
+
attr_reader :stdout
|
|
43
|
+
|
|
44
|
+
# @return [String, nil] Captured stderr (if applicable)
|
|
45
|
+
attr_reader :stderr
|
|
46
|
+
|
|
47
|
+
# @return [Exception, nil] Exception if command failed to start
|
|
48
|
+
attr_reader :exception
|
|
49
|
+
|
|
50
|
+
# @return [Hash] Original options passed to the command
|
|
51
|
+
attr_reader :options
|
|
52
|
+
|
|
53
|
+
def initialize(
|
|
54
|
+
command:,
|
|
55
|
+
pid: nil,
|
|
56
|
+
duration: nil,
|
|
57
|
+
exit_code: nil,
|
|
58
|
+
signal_code: nil,
|
|
59
|
+
stdout: nil,
|
|
60
|
+
stderr: nil,
|
|
61
|
+
exception: nil,
|
|
62
|
+
options: {}
|
|
63
|
+
)
|
|
64
|
+
@command = Array(command)
|
|
65
|
+
@pid = pid
|
|
66
|
+
@duration = duration
|
|
67
|
+
@exit_code = exit_code
|
|
68
|
+
@signal_code = signal_code
|
|
69
|
+
@stdout = stdout
|
|
70
|
+
@stderr = stderr
|
|
71
|
+
@exception = exception
|
|
72
|
+
@options = options
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ─────────────────────────────────────────────────────────────
|
|
76
|
+
# Status Predicates
|
|
77
|
+
# ─────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
# @return [Boolean] true if exit code is 0
|
|
80
|
+
def success? = exit_code == 0 && !exception
|
|
81
|
+
|
|
82
|
+
# @return [Boolean] true if exit code is non-zero or there was an exception
|
|
83
|
+
def failed? = !success?
|
|
84
|
+
|
|
85
|
+
# @return [Boolean] true if process was killed by a signal
|
|
86
|
+
def signaled? = !signal_code.nil?
|
|
87
|
+
|
|
88
|
+
# @return [Boolean] true if process was killed due to timeout
|
|
89
|
+
def timed_out? = options[:timed_out] == true
|
|
90
|
+
|
|
91
|
+
# @return [Boolean] true if the process started but we're still waiting
|
|
92
|
+
def running? = pid && exit_code.nil? && signal_code.nil? && !exception
|
|
93
|
+
|
|
94
|
+
# ─────────────────────────────────────────────────────────────
|
|
95
|
+
# Output Access
|
|
96
|
+
# ─────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
# Combined stdout and stderr
|
|
99
|
+
# @return [String, nil]
|
|
100
|
+
def output
|
|
101
|
+
return nil unless stdout || stderr
|
|
102
|
+
|
|
103
|
+
[stdout, stderr].compact.join
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Array<String>] stdout split into lines
|
|
107
|
+
def stdout_lines = stdout&.lines(chomp: true) || []
|
|
108
|
+
|
|
109
|
+
# @return [Array<String>] stderr split into lines
|
|
110
|
+
def stderr_lines = stderr&.lines(chomp: true) || []
|
|
111
|
+
|
|
112
|
+
# ─────────────────────────────────────────────────────────────
|
|
113
|
+
# Monad Operations
|
|
114
|
+
# ─────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
# Exit the process if this result represents failure.
|
|
117
|
+
# Uses the command's exit code, or 1 if there was an exception.
|
|
118
|
+
#
|
|
119
|
+
# @param message [String, nil] Optional message to print before exiting
|
|
120
|
+
# @return [Result] self if successful
|
|
121
|
+
def exit_on_failure!(message: nil)
|
|
122
|
+
return self if success?
|
|
123
|
+
|
|
124
|
+
if message
|
|
125
|
+
warn message
|
|
126
|
+
elsif exception
|
|
127
|
+
warn "Command failed to start: #{exception.message}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
exit(exit_code || 1)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Execute block if this result is successful.
|
|
134
|
+
# Returns self if failed (short-circuit).
|
|
135
|
+
#
|
|
136
|
+
# @yield Block to execute if successful
|
|
137
|
+
# @return [Result] Block's result or self if failed
|
|
138
|
+
def then
|
|
139
|
+
return self if failed?
|
|
140
|
+
|
|
141
|
+
yield
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Transform stdout if successful.
|
|
145
|
+
#
|
|
146
|
+
# @yield [String] Block receives stdout
|
|
147
|
+
# @return [Object, nil] Block's result or nil if failed
|
|
148
|
+
def map
|
|
149
|
+
return nil if failed?
|
|
150
|
+
|
|
151
|
+
yield stdout
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# ─────────────────────────────────────────────────────────────
|
|
155
|
+
# Inspection
|
|
156
|
+
# ─────────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
def to_s
|
|
159
|
+
status = if success?
|
|
160
|
+
"success"
|
|
161
|
+
elsif signaled?
|
|
162
|
+
"signal #{signal_code}"
|
|
163
|
+
elsif exception
|
|
164
|
+
"exception: #{exception.class}"
|
|
165
|
+
else
|
|
166
|
+
"exit #{exit_code}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
"#<Result #{command.first} #{status}>"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def inspect
|
|
173
|
+
parts = ["#<Result"]
|
|
174
|
+
parts << "command=#{command.inspect}"
|
|
175
|
+
parts << "pid=#{pid}" if pid
|
|
176
|
+
parts << "exit_code=#{exit_code}" if exit_code
|
|
177
|
+
parts << "signal_code=#{signal_code}" if signal_code
|
|
178
|
+
parts << "duration=#{'%.3f' % duration}s" if duration
|
|
179
|
+
parts << "stdout=#{stdout.bytesize}b" if stdout
|
|
180
|
+
parts << "stderr=#{stderr.bytesize}b" if stderr
|
|
181
|
+
parts << "exception=#{exception.class}" if exception
|
|
182
|
+
parts << ">"
|
|
183
|
+
parts.join(" ")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @return [Hash] Result as a hash (for JSON serialization, etc.)
|
|
187
|
+
def to_h
|
|
188
|
+
{
|
|
189
|
+
command: command,
|
|
190
|
+
pid: pid,
|
|
191
|
+
exit_code: exit_code,
|
|
192
|
+
signal_code: signal_code,
|
|
193
|
+
duration: duration,
|
|
194
|
+
success: success?,
|
|
195
|
+
stdout: stdout,
|
|
196
|
+
stderr: stderr,
|
|
197
|
+
exception: exception&.message
|
|
198
|
+
}.compact
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# ─────────────────────────────────────────────────────────────
|
|
202
|
+
# Factory Methods
|
|
203
|
+
# ─────────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
class << self
|
|
206
|
+
# Create a Result from Process::Status
|
|
207
|
+
def from_status(status, command:, **)
|
|
208
|
+
new(
|
|
209
|
+
command: command,
|
|
210
|
+
pid: status.pid,
|
|
211
|
+
exit_code: status.exited? ? status.exitstatus : nil,
|
|
212
|
+
signal_code: status.signaled? ? status.termsig : nil,
|
|
213
|
+
**
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create a Result for a failed-to-start command
|
|
218
|
+
def from_exception(exception, command:, **)
|
|
219
|
+
new(
|
|
220
|
+
command: command,
|
|
221
|
+
exception: exception,
|
|
222
|
+
exit_code: 127, # Convention for "command not found"
|
|
223
|
+
**
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|