taski 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afd6114f6d0814be687181ddd8e0acfa45af1d1064f14247e2864be53c3103f0
4
- data.tar.gz: 5b76552b037943f8b9555f01eb6b98d5a515be95b8f75e63f6f42314cf388294
3
+ metadata.gz: 0e593418f036752e5adf6bb989cfc800ee249be3b3a7ace71df377a5aa1b73bf
4
+ data.tar.gz: 4e69af8247ade3e1e585475dcab88ae73fa42a71a60d4bbcc420456b1b2d9de5
5
5
  SHA512:
6
- metadata.gz: 6c37d3650e3a1af652e469bad4627964fa62763ad7acc93e54b07bdaa577777dda65f0e34265d7974209ab3b3ab5f5ef5299d4675f6a8810fcdcd5dcbad19306
7
- data.tar.gz: f2d5295608ee67c5a2ecebdfb0416f73fe16569534a4f798252cf5bd101bec3c43507e1c0560be102cfb37c44fcdf603ff2a7cf9a6b0afdfb9b74c3624ff7553
6
+ metadata.gz: 339d64fa997095a68adffa2d7227c5e648dc147edf576824283328a1e655706d6e72666fd56a8d8f934283ddd399eb691467121381f995b8540c798bb7f28c87
7
+ data.tar.gz: 3d5d41b708ab1d9f715bc0848d22b7b4233b50b6f922440ec304fb3c32a2b86bddf936839387462370a8b2df0e9de9d8200b9733aa8fdafdf3fa2536e21835ab
data/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] - 2025-12-23
11
+
12
+ ### Added
13
+ - Group block for organizing progress display messages (`Taski.group`) ([#105](https://github.com/ahogappa/taski/pull/105))
14
+ - Scope-based execution with thread-local registry for independent task execution ([#103](https://github.com/ahogappa/taski/pull/103))
15
+ - `TaskClass::Error` auto-generation for task-specific error handling ([#95](https://github.com/ahogappa/taski/pull/95))
16
+ - `AggregateAware` module for transparent rescue matching with `AggregateError` ([#95](https://github.com/ahogappa/taski/pull/95))
17
+ - `AggregateError#includes?` and `AggregateError#find` methods for searching aggregated errors ([#95](https://github.com/ahogappa/taski/pull/95))
18
+ - Aggregation of multiple errors in parallel execution ([#95](https://github.com/ahogappa/taski/pull/95))
19
+ - `workers` parameter to `Task.run`, `Task.clean`, and `Task.run_and_clean` for configurable parallelism ([#92](https://github.com/ahogappa/taski/pull/92))
20
+
21
+ ### Changed
22
+ - Renamed `context` to `args` for API clarity (BREAKING CHANGE) ([#94](https://github.com/ahogappa/taski/pull/94))
23
+
24
+ ### Fixed
25
+ - Thread-safety improvements in `Registry#get_or_create` ([#90](https://github.com/ahogappa/taski/pull/90))
26
+
27
+ ## [0.6.0] - 2025-12-21
28
+
29
+ ### Added
30
+ - `Task#system` override for capturing subprocess output in progress display
31
+ - `ExecutionContext` with observer pattern for managing execution state
32
+ - Inline task output display in progress tree
33
+ - Clean execution with reverse dependency order via `run_and_clean`
34
+ - Comprehensive documentation for `TaskAbortException`
35
+ - Unit tests for `ExecutionContext`, `WorkerPool`, and `Scheduler`
36
+
37
+ ### Changed
38
+ - Replaced `ThreadOutputCapture` with pipe-based `TaskOutputRouter` for more reliable output capture
39
+ - Split `Executor` into separate `Scheduler` and `WorkerPool` classes for better separation of concerns
40
+ - Centralized tree building logic in `TreeProgressDisplay`
41
+ - Improved `run_and_clean` implementation and display
42
+
43
+ ### Fixed
44
+ - Added mutex protection to `impl_call_order` accessor for thread safety
45
+ - Fixed `ExecutionContext` passing to `TaskWrapper` resolving progress display garbage
46
+ - Improved output capture reliability for progress display
47
+
48
+ ## [0.5.0] - 2025-11-30
49
+
50
+ - Initial release with core task execution functionality
data/README.md CHANGED
@@ -115,7 +115,7 @@ end
115
115
 
116
116
  ## Advanced Usage
117
117
 
118
- ### Context - Runtime Information and Options
118
+ ### Args - Runtime Information and Options
119
119
 
120
120
  Pass custom options and access execution context from any task:
121
121
 
@@ -123,43 +123,62 @@ Pass custom options and access execution context from any task:
123
123
  class DeployTask < Taski::Task
124
124
  def run
125
125
  # User-defined options
126
- env = Taski.context[:env]
127
- debug = Taski.context.fetch(:debug, false)
126
+ env = Taski.args[:env]
127
+ debug = Taski.args.fetch(:debug, false)
128
128
 
129
129
  # Runtime information
130
- puts "Working directory: #{Taski.context.working_directory}"
131
- puts "Started at: #{Taski.context.started_at}"
132
- puts "Root task: #{Taski.context.root_task}"
130
+ puts "Working directory: #{Taski.args.working_directory}"
131
+ puts "Started at: #{Taski.args.started_at}"
132
+ puts "Root task: #{Taski.args.root_task}"
133
133
  puts "Deploying to: #{env}"
134
134
  end
135
135
  end
136
136
 
137
137
  # Pass options when running
138
- DeployTask.run(context: { env: "production", debug: true })
138
+ DeployTask.run(args: { env: "production", debug: true })
139
139
  ```
140
140
 
141
- Context API:
142
- - `Taski.context[:key]` - Get option value (nil if not set)
143
- - `Taski.context.fetch(:key, default)` - Get with default value
144
- - `Taski.context.key?(:key)` - Check if option exists
145
- - `Taski.context.working_directory` - Execution directory
146
- - `Taski.context.started_at` - Execution start time
147
- - `Taski.context.root_task` - First task class called
141
+ Args API:
142
+ - `Taski.args[:key]` - Get option value (nil if not set)
143
+ - `Taski.args.fetch(:key, default)` - Get with default value
144
+ - `Taski.args.key?(:key)` - Check if option exists
145
+ - `Taski.args.working_directory` - Execution directory
146
+ - `Taski.args.started_at` - Execution start time
147
+ - `Taski.args.root_task` - First task class called
148
148
 
149
- ### Re-execution
149
+ ### Execution Model
150
150
 
151
151
  ```ruby
152
- # Cached execution (default)
152
+ # Each class method call creates fresh execution
153
153
  RandomTask.value # => 42
154
- RandomTask.value # => 42 (cached)
154
+ RandomTask.value # => 99 (different value - fresh execution)
155
155
 
156
- # Fresh execution
157
- RandomTask.new.run # => 123 (new instance)
156
+ # Instance-level caching
157
+ instance = RandomTask.new
158
+ instance.run # => 42
159
+ instance.run # => 42 (cached within instance)
160
+ instance.value # => 42
158
161
 
159
- # Reset all caches
160
- RandomTask.reset!
162
+ # Dependencies within same execution share results
163
+ DoubleConsumer.run # RandomTask runs once, both accesses get same value
161
164
  ```
162
165
 
166
+ ### Aborting Execution
167
+
168
+ Stop all pending tasks when a critical error occurs:
169
+
170
+ ```ruby
171
+ class CriticalTask < Taski::Task
172
+ def run
173
+ if fatal_error?
174
+ raise Taski::TaskAbortException, "Cannot continue"
175
+ end
176
+ end
177
+ end
178
+ ```
179
+
180
+ When `TaskAbortException` is raised, no new tasks will start. Already running tasks will complete, then execution stops.
181
+
163
182
  ### Progress Display
164
183
 
165
184
  Tree-based progress visualization is enabled by default:
data/docs/GUIDE.md ADDED
@@ -0,0 +1,340 @@
1
+ # Taski Guide
2
+
3
+ This guide provides detailed documentation beyond the basics covered in the README.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Error Handling](#error-handling)
8
+ - [Lifecycle Management](#lifecycle-management)
9
+ - [Progress Display](#progress-display)
10
+ - [Debugging](#debugging)
11
+
12
+ ---
13
+
14
+ ## Error Handling
15
+
16
+ Taski provides comprehensive error handling for parallel task execution.
17
+
18
+ ### Error Types
19
+
20
+ | Exception | Purpose |
21
+ |-----------|---------|
22
+ | `Taski::AggregateError` | Multiple tasks failed during parallel execution |
23
+ | `Taski::TaskError` | Base class for task-specific errors |
24
+ | `Taski::TaskAbortException` | Intentional abort (stops all tasks immediately) |
25
+ | `Taski::CircularDependencyError` | Circular dependency detected |
26
+ | `TaskClass::Error` | Auto-generated error class for each Task subclass |
27
+
28
+ ### AggregateError
29
+
30
+ When multiple tasks fail during parallel execution, errors are collected into an `AggregateError`:
31
+
32
+ ```ruby
33
+ class DatabaseTask < Taski::Task
34
+ exports :connection
35
+ def run
36
+ raise "Database connection failed"
37
+ end
38
+ end
39
+
40
+ class CacheTask < Taski::Task
41
+ exports :redis_client
42
+ def run
43
+ raise "Cache connection failed"
44
+ end
45
+ end
46
+
47
+ class AppTask < Taski::Task
48
+ def run
49
+ db = DatabaseTask.connection
50
+ cache = CacheTask.redis_client
51
+ end
52
+ end
53
+
54
+ begin
55
+ AppTask.run
56
+ rescue Taski::AggregateError => e
57
+ puts "#{e.errors.size} tasks failed:"
58
+ e.errors.each do |failure|
59
+ puts " - #{failure.task_class.name}: #{failure.error.message}"
60
+ end
61
+ end
62
+ # Output:
63
+ # 2 tasks failed:
64
+ # - DatabaseTask: Database connection failed
65
+ # - CacheTask: Cache connection failed
66
+ ```
67
+
68
+ ### Task-Specific Error Classes
69
+
70
+ Each Task subclass automatically gets an `::Error` class for targeted rescue:
71
+
72
+ ```ruby
73
+ class DatabaseTask < Taski::Task
74
+ exports :connection
75
+ def run
76
+ raise "Connection failed"
77
+ end
78
+ end
79
+
80
+ # Rescue errors from a specific task
81
+ begin
82
+ AppTask.run
83
+ rescue DatabaseTask::Error => e
84
+ puts "Database task failed: #{e.message}"
85
+ # e.task_class returns DatabaseTask
86
+ # e.cause returns the original error
87
+ end
88
+ ```
89
+
90
+ This works transparently with `AggregateError` - when you rescue `DatabaseTask::Error`, it matches an `AggregateError` that contains a `DatabaseTask::Error`.
91
+
92
+ ### TaskAbortException
93
+
94
+ Use `TaskAbortException` to immediately stop all task execution:
95
+
96
+ ```ruby
97
+ class CriticalTask < Taski::Task
98
+ def run
99
+ if critical_condition_met?
100
+ raise Taski::TaskAbortException, "Critical error - aborting"
101
+ end
102
+ end
103
+ end
104
+ ```
105
+
106
+ `TaskAbortException` takes priority over regular errors. Already running tasks will complete, but no new tasks will start.
107
+
108
+ ### Error Handling Best Practices
109
+
110
+ ```ruby
111
+ # 1. Handle errors within the task when recovery is possible
112
+ class ResilientTask < Taski::Task
113
+ exports :data
114
+ def run
115
+ @data = fetch_from_primary
116
+ rescue Timeout::Error
117
+ @data = fetch_from_fallback
118
+ end
119
+ end
120
+
121
+ # 2. Use task-specific errors for clarity
122
+ begin
123
+ AppTask.run
124
+ rescue DatabaseTask::Error => e
125
+ handle_database_failure(e)
126
+ rescue CacheTask::Error => e
127
+ handle_cache_failure(e)
128
+ end
129
+
130
+ # 3. Fail fast with clear messages
131
+ class ValidatingTask < Taski::Task
132
+ def run
133
+ missing = %w[DATABASE_URL API_KEY].select { |v| ENV[v].nil? }
134
+ raise "Missing: #{missing.join(', ')}" if missing.any?
135
+ end
136
+ end
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Lifecycle Management
142
+
143
+ Taski supports resource cleanup with `run`, `clean`, and `run_and_clean` methods.
144
+
145
+ ### Basic Lifecycle
146
+
147
+ ```ruby
148
+ class DatabaseSetup < Taski::Task
149
+ exports :connection
150
+
151
+ def run
152
+ @connection = "postgresql://localhost:5432/myapp"
153
+ puts "Database connected"
154
+ end
155
+
156
+ def clean
157
+ puts "Database disconnected"
158
+ end
159
+ end
160
+
161
+ class WebServer < Taski::Task
162
+ def run
163
+ puts "Server started with #{DatabaseSetup.connection}"
164
+ end
165
+
166
+ def clean
167
+ puts "Server stopped"
168
+ end
169
+ end
170
+
171
+ # Start
172
+ WebServer.run
173
+ # => Database connected
174
+ # => Server started with postgresql://localhost:5432/myapp
175
+
176
+ # Clean (reverse dependency order)
177
+ WebServer.clean
178
+ # => Server stopped
179
+ # => Database disconnected
180
+ ```
181
+
182
+ ### run_and_clean
183
+
184
+ Execute run followed by clean in a single operation:
185
+
186
+ ```ruby
187
+ WebServer.run_and_clean
188
+ # => Database connected
189
+ # => Server started
190
+ # => Server stopped
191
+ # => Database disconnected
192
+ ```
193
+
194
+ ### Idempotent Clean Methods
195
+
196
+ Clean methods should be safe to call multiple times:
197
+
198
+ ```ruby
199
+ class SafeFileTask < Taski::Task
200
+ exports :data_file
201
+
202
+ def run
203
+ @data_file = '/tmp/data.txt'
204
+ File.write(@data_file, 'data')
205
+ end
206
+
207
+ def clean
208
+ # Check before delete
209
+ if @data_file && File.exist?(@data_file)
210
+ File.delete(@data_file)
211
+ end
212
+ end
213
+ end
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Progress Display
219
+
220
+ Taski provides real-time progress visualization during task execution.
221
+
222
+ ### Features
223
+
224
+ - **Spinner Animation**: Animated spinner during execution
225
+ - **Output Capture**: Real-time display of task output (last line)
226
+ - **Status Indicators**: Success/failure icons with execution time
227
+ - **Group Blocks**: Organize output messages into logical phases
228
+ - **TTY Detection**: Clean output when redirected to files
229
+
230
+ ### Group Blocks
231
+
232
+ Use `group` blocks to organize output within a task into logical phases. The current group name is displayed alongside the task's output in the progress display.
233
+
234
+ ```ruby
235
+ class DeployTask < Taski::Task
236
+ def run
237
+ group("Preparing environment") do
238
+ puts "Checking dependencies..."
239
+ puts "Validating config..."
240
+ end
241
+
242
+ group("Building application") do
243
+ puts "Compiling source..."
244
+ puts "Running tests..."
245
+ end
246
+
247
+ group("Deploying") do
248
+ puts "Uploading files..."
249
+ puts "Restarting server..."
250
+ end
251
+ end
252
+ end
253
+ ```
254
+
255
+ Progress display output:
256
+
257
+ ```text
258
+ During execution:
259
+ ⠋ DeployTask (Task) | Deploying: Uploading files...
260
+
261
+ After completion:
262
+ ✓ DeployTask (Task) 520ms
263
+ ```
264
+
265
+ The group name appears as a prefix to the output message: `| GroupName: output...`
266
+
267
+ Groups are useful for:
268
+ - **Logical organization**: Group related operations together
269
+ - **Progress visibility**: See which phase is currently executing
270
+ - **Error context**: Know which phase failed when errors occur
271
+
272
+ ### Example Output
273
+
274
+ ```
275
+ During execution:
276
+ WebServer (Task)
277
+ ├── Config (Task) ...
278
+ │ ├── Database (Task) 45.2ms
279
+ │ └── Cache (Task) ...
280
+ └── Server (Task)
281
+
282
+ After completion:
283
+ WebServer (Task) 120.5ms
284
+ ├── Config (Task) 50.3ms
285
+ │ ├── Database (Task) 45.2ms
286
+ │ └── Cache (Task) 48.1ms
287
+ └── Server (Task) 70.2ms
288
+ ```
289
+
290
+ ### Disabling Progress Display
291
+
292
+ ```bash
293
+ TASKI_PROGRESS_DISABLE=1 ruby your_script.rb
294
+ ```
295
+
296
+ ### File Output Mode
297
+
298
+ When output is redirected, interactive spinners are automatically disabled:
299
+
300
+ ```bash
301
+ ruby build.rb > build.log 2>&1
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Debugging
307
+
308
+ ### Environment Variables
309
+
310
+ | Variable | Purpose |
311
+ |----------|---------|
312
+ | `TASKI_PROGRESS_DISABLE=1` | Disable progress display |
313
+ | `TASKI_DEBUG=1` | Enable debug output |
314
+
315
+ ### Dependency Tree Visualization
316
+
317
+ ```ruby
318
+ puts MyTask.tree
319
+ # MyTask (Task)
320
+ # ├── DatabaseTask (Task)
321
+ # └── CacheTask (Task)
322
+ # └── ConfigTask (Task)
323
+ ```
324
+
325
+ ### Common Issues
326
+
327
+ **Circular Dependencies**
328
+
329
+ ```ruby
330
+ # Detected before execution
331
+ begin
332
+ TaskA.run
333
+ rescue Taski::CircularDependencyError => e
334
+ puts e.cyclic_tasks # [[TaskA, TaskB]]
335
+ end
336
+ ```
337
+
338
+ **Static Analysis Requirements**
339
+
340
+ Tasks must be defined in source files (not dynamically with `Class.new`) because static analysis uses Prism AST parsing which requires actual source files.
data/examples/README.md CHANGED
@@ -34,36 +34,36 @@ ruby examples/section_demo.rb
34
34
 
35
35
  ---
36
36
 
37
- ### 3. context_demo.rb - Runtime Context and Options
37
+ ### 3. args_demo.rb - Runtime Args and Options
38
38
 
39
- Access execution context and pass custom options to tasks.
39
+ Access execution args and pass custom options to tasks.
40
40
 
41
41
  ```bash
42
- ruby examples/context_demo.rb
42
+ ruby examples/args_demo.rb
43
43
  ```
44
44
 
45
45
  **Covers:**
46
- - User-defined options via `run(context: {...})`
47
- - `Taski.context[:key]` for option access
48
- - `Taski.context.fetch(:key, default)` for defaults
49
- - `Taski.context.working_directory`
50
- - `Taski.context.started_at`
51
- - `Taski.context.root_task`
46
+ - User-defined options via `run(args: {...})`
47
+ - `Taski.args[:key]` for option access
48
+ - `Taski.args.fetch(:key, default)` for defaults
49
+ - `Taski.args.working_directory`
50
+ - `Taski.args.started_at`
51
+ - `Taski.args.root_task`
52
52
 
53
53
  ---
54
54
 
55
- ### 4. reexecution_demo.rb - Cache Control
55
+ ### 4. reexecution_demo.rb - Scope-Based Execution
56
56
 
57
- Understand caching behavior and re-execution patterns.
57
+ Understand scope-based execution and caching behavior.
58
58
 
59
59
  ```bash
60
- ruby examples/reexecution_demo.rb
60
+ TASKI_PROGRESS_DISABLE=1 ruby examples/reexecution_demo.rb
61
61
  ```
62
62
 
63
63
  **Covers:**
64
- - Default caching behavior
65
- - `Task.new` for fresh instances
66
- - `Task.reset!` for clearing caches
64
+ - Fresh execution for each class method call
65
+ - Instance-level caching with `Task.new`
66
+ - Scope-based dependency caching
67
67
 
68
68
  ---
69
69
 
@@ -97,16 +97,64 @@ ruby examples/parallel_progress_demo.rb
97
97
 
98
98
  ---
99
99
 
100
+ ### 7. clean_demo.rb - Lifecycle Management
101
+
102
+ Demonstrates resource cleanup with clean methods.
103
+
104
+ ```bash
105
+ ruby examples/clean_demo.rb
106
+ ```
107
+
108
+ **Covers:**
109
+ - Defining `clean` methods for resource cleanup
110
+ - Reverse dependency order execution
111
+ - `run_and_clean` combined operation
112
+
113
+ ---
114
+
115
+ ### 8. system_call_demo.rb - Subprocess Output
116
+
117
+ Capture subprocess output in progress display.
118
+
119
+ ```bash
120
+ ruby examples/system_call_demo.rb
121
+ ```
122
+
123
+ **Covers:**
124
+ - `system()` output capture
125
+ - Streaming output display
126
+ - Parallel subprocess execution
127
+
128
+ ---
129
+
130
+ ### 9. nested_section_demo.rb - Nested Sections
131
+
132
+ Sections that depend on other tasks for implementation selection.
133
+
134
+ ```bash
135
+ TASKI_PROGRESS_DISABLE=1 ruby examples/nested_section_demo.rb
136
+ ```
137
+
138
+ **Covers:**
139
+ - Section inside Section
140
+ - Dynamic implementation selection
141
+ - Complex dependency hierarchies
142
+
143
+ ---
144
+
100
145
  ## Quick Reference
101
146
 
102
147
  | Example | Feature | Complexity |
103
148
  |---------|---------|------------|
104
149
  | quick_start | Exports API | Basic |
105
150
  | section_demo | Section API | Intermediate |
106
- | context_demo | Context API | Intermediate |
107
- | reexecution_demo | Cache Control | Intermediate |
151
+ | args_demo | Args API | Intermediate |
152
+ | reexecution_demo | Scope-Based Execution | Intermediate |
108
153
  | data_pipeline_demo | ETL Pipeline | Advanced |
109
154
  | parallel_progress_demo | Progress Display | Advanced |
155
+ | clean_demo | Lifecycle Management | Intermediate |
156
+ | system_call_demo | Subprocess Output | Advanced |
157
+ | nested_section_demo | Nested Sections | Advanced |
110
158
 
111
159
  ## Running All Examples
112
160