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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.obsidian/app.json +6 -0
  3. data/.obsidian/appearance.json +4 -0
  4. data/.obsidian/community-plugins.json +5 -0
  5. data/.obsidian/core-plugins.json +33 -0
  6. data/.obsidian/plugins/obsidian-minimal-settings/data.json +34 -0
  7. data/.obsidian/plugins/obsidian-minimal-settings/main.js +8 -0
  8. data/.obsidian/plugins/obsidian-minimal-settings/manifest.json +11 -0
  9. data/.obsidian/plugins/obsidian-style-settings/data.json +15 -0
  10. data/.obsidian/plugins/obsidian-style-settings/main.js +165 -0
  11. data/.obsidian/plugins/obsidian-style-settings/manifest.json +10 -0
  12. data/.obsidian/plugins/obsidian-style-settings/styles.css +243 -0
  13. data/.obsidian/plugins/table-editor-obsidian/data.json +6 -0
  14. data/.obsidian/plugins/table-editor-obsidian/main.js +236 -0
  15. data/.obsidian/plugins/table-editor-obsidian/manifest.json +17 -0
  16. data/.obsidian/plugins/table-editor-obsidian/styles.css +78 -0
  17. data/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
  18. data/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
  19. data/.obsidian/themes/Minimal/manifest.json +8 -0
  20. data/.obsidian/themes/Minimal/theme.css +2251 -0
  21. data/.rubocop.yml +231 -0
  22. data/CHANGELOG.md +97 -0
  23. data/LICENSE +21 -0
  24. data/README.md +314 -0
  25. data/Rakefile +13 -0
  26. data/devex-logo.jpg +0 -0
  27. data/docs/developing-tools.md +1000 -0
  28. data/docs/ref/agent-mode.md +46 -0
  29. data/docs/ref/cli-interface.md +60 -0
  30. data/docs/ref/configuration.md +46 -0
  31. data/docs/ref/design-philosophy.md +17 -0
  32. data/docs/ref/error-handling.md +38 -0
  33. data/docs/ref/io-handling.md +88 -0
  34. data/docs/ref/signals.md +141 -0
  35. data/docs/ref/temporal-software-theory.md +790 -0
  36. data/exe/dx +52 -0
  37. data/lib/devex/builtins/.index.rb +10 -0
  38. data/lib/devex/builtins/debug.rb +43 -0
  39. data/lib/devex/builtins/format.rb +44 -0
  40. data/lib/devex/builtins/gem.rb +77 -0
  41. data/lib/devex/builtins/lint.rb +61 -0
  42. data/lib/devex/builtins/test.rb +76 -0
  43. data/lib/devex/builtins/version.rb +156 -0
  44. data/lib/devex/cli.rb +340 -0
  45. data/lib/devex/context.rb +433 -0
  46. data/lib/devex/core/configuration.rb +136 -0
  47. data/lib/devex/core.rb +79 -0
  48. data/lib/devex/dirs.rb +210 -0
  49. data/lib/devex/dsl.rb +100 -0
  50. data/lib/devex/exec/controller.rb +245 -0
  51. data/lib/devex/exec/result.rb +229 -0
  52. data/lib/devex/exec.rb +662 -0
  53. data/lib/devex/loader.rb +136 -0
  54. data/lib/devex/output.rb +257 -0
  55. data/lib/devex/project_paths.rb +309 -0
  56. data/lib/devex/support/ansi.rb +437 -0
  57. data/lib/devex/support/core_ext.rb +560 -0
  58. data/lib/devex/support/global.rb +68 -0
  59. data/lib/devex/support/path.rb +357 -0
  60. data/lib/devex/support.rb +71 -0
  61. data/lib/devex/template_helpers.rb +136 -0
  62. data/lib/devex/templates/debug.erb +24 -0
  63. data/lib/devex/tool.rb +374 -0
  64. data/lib/devex/version.rb +5 -0
  65. data/lib/devex/working_dir.rb +99 -0
  66. data/lib/devex.rb +158 -0
  67. data/ruby-project-template/.gitignore +0 -0
  68. data/ruby-project-template/Gemfile +0 -0
  69. data/ruby-project-template/README.md +0 -0
  70. data/ruby-project-template/docs/README.md +0 -0
  71. data/sig/devex.rbs +4 -0
  72. 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