taski 0.7.0 → 0.8.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: 0e593418f036752e5adf6bb989cfc800ee249be3b3a7ace71df377a5aa1b73bf
4
- data.tar.gz: 4e69af8247ade3e1e585475dcab88ae73fa42a71a60d4bbcc420456b1b2d9de5
3
+ metadata.gz: f7677c0f3995a7c61b5850fb25c82c60626287584e5c7f918939ededc045e9ea
4
+ data.tar.gz: d470969c88a4df51503487aacdf2c6261b65e6ed75ee3c7cc2ff744dfd750365
5
5
  SHA512:
6
- metadata.gz: 339d64fa997095a68adffa2d7227c5e648dc147edf576824283328a1e655706d6e72666fd56a8d8f934283ddd399eb691467121381f995b8540c798bb7f28c87
7
- data.tar.gz: 3d5d41b708ab1d9f715bc0848d22b7b4233b50b6f922440ec304fb3c32a2b86bddf936839387462370a8b2df0e9de9d8200b9733aa8fdafdf3fa2536e21835ab
6
+ metadata.gz: 5f93d9e7d10f7104851266f0252ec4365d445a90d3a25b885f289df5d8caafbe9f9250ff9236e4cca0791749100c83c46735a8e98e5c1b675662199ba7a0e547
7
+ data.tar.gz: eb4fb9f806cdd5097e0d3321d4cda9e3df183d4850c4aa8161ba09b71666444166f05b5e795b330227076d85f7410adbe2fc4f1e2b19c66d007ff1632d354f97
data/CHANGELOG.md CHANGED
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-01-23
11
+
12
+ ### Added
13
+ - `Taski::Env` class for system-managed execution environment information ([#125](https://github.com/ahogappa/taski/pull/125))
14
+ - Access via `Taski.env.working_directory`, `Taski.env.started_at`, `Taski.env.root_task`
15
+ - `args` and `workers` parameters to `Task.new` for direct task instantiation ([#125](https://github.com/ahogappa/taski/pull/125))
16
+ - `mock_env` helper in `TestHelper` for mocking environment in tests ([#125](https://github.com/ahogappa/taski/pull/125))
17
+
18
+ ### Changed
19
+ - Separate system attributes from `Taski.args` to `Taski.env` ([#125](https://github.com/ahogappa/taski/pull/125))
20
+ - `Taski.args` now holds only user-defined options passed via `run(args: {...})`
21
+ - `Taski.env` holds system-managed execution environment (`root_task`, `started_at`, `working_directory`)
22
+
23
+ ## [0.7.1] - 2026-01-22
24
+
25
+ ### Added
26
+ - `Taski::TestHelper` module for mocking task dependencies in unit tests ([#123](https://github.com/ahogappa/taski/pull/123))
27
+ - `mock_task(TaskClass, key: value)` to mock exported values without running tasks
28
+ - `assert_task_accessed` / `refute_task_accessed` for verifying dependency access
29
+ - Support for both Minitest and RSpec test frameworks
30
+ - Simple one-line progress display mode (`Taski.progress_mode = :simple`) as an alternative to tree display ([#112](https://github.com/ahogappa/taski/pull/112))
31
+ - Configure via `TASKI_PROGRESS_MODE` environment variable or `Taski.progress_mode` API
32
+ - Display captured task output (up to 30 lines) in AggregateError messages for better debugging ([#109](https://github.com/ahogappa/taski/pull/109))
33
+ - Background polling thread in TaskOutputRouter to ensure pipes are drained reliably ([#122](https://github.com/ahogappa/taski/pull/122))
34
+ - `Taski.with_args` helper method for safe argument lifecycle management ([#110](https://github.com/ahogappa/taski/pull/110))
35
+
36
+ ### Changed
37
+ - Progress display now uses alternate screen buffer and shows summary line after completion ([#107](https://github.com/ahogappa/taski/pull/107))
38
+ - Eliminate screen flickering in tree progress display with in-place overwrite rendering ([#121](https://github.com/ahogappa/taski/pull/121))
39
+ - Extract `BaseProgressDisplay` class for shared progress display functionality ([#117](https://github.com/ahogappa/taski/pull/117))
40
+
41
+ ### Fixed
42
+ - Wait for running dependencies in nested executor to prevent deadlock ([#106](https://github.com/ahogappa/taski/pull/106))
43
+ - Preserve namespace path when following method calls in static analysis ([#108](https://github.com/ahogappa/taski/pull/108))
44
+ - Prevent race condition in `Taski.args` lifecycle during concurrent execution ([#110](https://github.com/ahogappa/taski/pull/110))
45
+ - Ensure progress display cleanup on interrupt (Ctrl+C) ([#107](https://github.com/ahogappa/taski/pull/107))
46
+ - Always enable output in PlainProgressDisplay ([#117](https://github.com/ahogappa/taski/pull/117))
47
+
10
48
  ## [0.7.0] - 2025-12-23
11
49
 
12
50
  ### Added
data/README.md CHANGED
@@ -113,6 +113,87 @@ end
113
113
 
114
114
  > **Note**: Nested implementation classes automatically inherit Section's `interfaces` as `exports`.
115
115
 
116
+ ## Best Practices
117
+
118
+ ### Keep Tasks Small and Focused
119
+
120
+ Each task should do **one thing only**. While Taski allows you to write complex logic within a single task, keeping tasks small and focused provides significant benefits:
121
+
122
+ ```ruby
123
+ # ✅ Good: Small, focused tasks
124
+ class FetchData < Taski::Task
125
+ exports :data
126
+ def run
127
+ @data = API.fetch
128
+ end
129
+ end
130
+
131
+ class TransformData < Taski::Task
132
+ exports :result
133
+ def run
134
+ @result = FetchData.data.transform
135
+ end
136
+ end
137
+
138
+ class SaveData < Taski::Task
139
+ def run
140
+ Database.save(TransformData.result)
141
+ end
142
+ end
143
+
144
+ # ❌ Avoid: Monolithic task doing everything
145
+ class DoEverything < Taski::Task
146
+ def run
147
+ data = API.fetch
148
+ result = data.transform
149
+ Database.save(result)
150
+ end
151
+ end
152
+ ```
153
+
154
+ **Why small tasks matter:**
155
+
156
+ - **Parallel Execution**: Independent tasks run concurrently. Large monolithic tasks can't be parallelized
157
+ - **Easier Cleanup**: `Task.clean` works per-task. Smaller tasks mean more granular cleanup control
158
+ - **Better Reusability**: Small tasks can be composed into different workflows
159
+ - **Clearer Dependencies**: The dependency graph becomes explicit and visible with `Task.tree`
160
+
161
+ **Note:** Complex internal logic is perfectly fine. "One thing" means one responsibility, not one line of code. Other tasks only care about the exported results, not how they were computed.
162
+
163
+ ```ruby
164
+ class RawData < Taski::Task
165
+ exports :data
166
+ def run
167
+ @data = API.fetch
168
+ end
169
+ end
170
+
171
+ class ProcessedData < Taski::Task
172
+ exports :result
173
+
174
+ def run
175
+ # Complex internal logic is OK - this task has one responsibility:
176
+ # producing the processed result
177
+ validated = validate_and_clean(RawData.data)
178
+ enriched = enrich_with_metadata(validated)
179
+ normalized = normalize_format(enriched)
180
+ @result = apply_business_rules(normalized)
181
+ end
182
+
183
+ private
184
+
185
+ def validate_and_clean(data)
186
+ # Complex validation logic...
187
+ end
188
+
189
+ def enrich_with_metadata(data)
190
+ # Complex enrichment logic...
191
+ end
192
+
193
+ # ... other private methods
194
+ end
195
+ ```
196
+
116
197
  ## Advanced Usage
117
198
 
118
199
  ### Args - Runtime Information and Options
@@ -122,14 +203,14 @@ Pass custom options and access execution context from any task:
122
203
  ```ruby
123
204
  class DeployTask < Taski::Task
124
205
  def run
125
- # User-defined options
206
+ # User-defined options (Taski.args)
126
207
  env = Taski.args[:env]
127
208
  debug = Taski.args.fetch(:debug, false)
128
209
 
129
- # Runtime information
130
- puts "Working directory: #{Taski.args.working_directory}"
131
- puts "Started at: #{Taski.args.started_at}"
132
- puts "Root task: #{Taski.args.root_task}"
210
+ # Runtime environment information (Taski.env)
211
+ puts "Working directory: #{Taski.env.working_directory}"
212
+ puts "Started at: #{Taski.env.started_at}"
213
+ puts "Root task: #{Taski.env.root_task}"
133
214
  puts "Deploying to: #{env}"
134
215
  end
135
216
  end
@@ -138,13 +219,15 @@ end
138
219
  DeployTask.run(args: { env: "production", debug: true })
139
220
  ```
140
221
 
141
- Args API:
222
+ Args API (user-defined options):
142
223
  - `Taski.args[:key]` - Get option value (nil if not set)
143
224
  - `Taski.args.fetch(:key, default)` - Get with default value
144
225
  - `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
226
+
227
+ Env API (execution environment):
228
+ - `Taski.env.working_directory` - Execution directory
229
+ - `Taski.env.started_at` - Execution start time
230
+ - `Taski.env.root_task` - First task class called
148
231
 
149
232
  ### Execution Model
150
233
 
@@ -191,6 +274,23 @@ WebServer (Task)
191
274
  └── ◻ Server (Task)
192
275
  ```
193
276
 
277
+ **Simple mode** provides a compact single-line display:
278
+
279
+ ```
280
+ ⠹ [3/5] DeployTask | Uploading files...
281
+ ✓ [5/5] All tasks completed (1234ms)
282
+ ```
283
+
284
+ **Configuration:**
285
+
286
+ ```ruby
287
+ # Via API
288
+ Taski.progress_mode = :simple # or :tree (default)
289
+
290
+ # Via environment variable
291
+ TASKI_PROGRESS_MODE=simple ruby your_script.rb
292
+ ```
293
+
194
294
  To disable: `TASKI_PROGRESS_DISABLE=1 ruby your_script.rb`
195
295
 
196
296
  ### Tree Visualization
@@ -203,6 +303,36 @@ puts WebServer.tree
203
303
  # └── Cache (Task)
204
304
  ```
205
305
 
306
+ ## Testing
307
+
308
+ ### Test Helper for Mocking Dependencies
309
+
310
+ Taski provides a test helper to mock task dependencies in your unit tests:
311
+
312
+ ```ruby
313
+ require 'taski/test_helper/minitest'
314
+
315
+ class BuildReportTest < Minitest::Test
316
+ include Taski::TestHelper::Minitest
317
+
318
+ def test_builds_report
319
+ # Mock direct dependencies - their run methods won't execute
320
+ mock_task(FetchData, users: [1, 2, 3])
321
+
322
+ # Task under test uses mocked values
323
+ assert_equal 3, BuildReport.user_count
324
+ end
325
+ end
326
+ ```
327
+
328
+ **Key features:**
329
+ - Mock only direct dependencies; indirect dependencies are automatically isolated
330
+ - Verify which dependencies were accessed with `assert_task_accessed` / `refute_task_accessed`
331
+ - Automatic cleanup after each test
332
+ - Supports both Minitest and RSpec
333
+
334
+ For RSpec, use `include Taski::TestHelper::RSpec` instead.
335
+
206
336
  ## Development
207
337
 
208
338
  ```bash
data/docs/GUIDE.md CHANGED
@@ -287,6 +287,59 @@ After completion:
287
287
  └── Server (Task) 70.2ms
288
288
  ```
289
289
 
290
+ ### Display Modes
291
+
292
+ Taski supports two progress display modes:
293
+
294
+ #### Tree Mode (Default)
295
+
296
+ Full dependency tree visualization with status for each task:
297
+
298
+ ```
299
+ WebServer (Task)
300
+ ├── ⠋ Config (Task) | Reading config.yml...
301
+ │ ├── ✅ Database (Task) 45.2ms
302
+ │ └── ⠙ Cache (Task) | Connecting...
303
+ └── ◻ Server (Task)
304
+ ```
305
+
306
+ #### Simple Mode
307
+
308
+ Compact single-line display showing current progress:
309
+
310
+ ```
311
+ ⠹ [3/5] DeployTask | Uploading files...
312
+ ✓ [5/5] All tasks completed (1234ms)
313
+ ```
314
+
315
+ Format: `[spinner] [completed/total] TaskName | last output...`
316
+
317
+ When multiple tasks run in parallel:
318
+ ```
319
+ ⠹ [2/5] DownloadLayer1, DownloadLayer2 | Downloading...
320
+ ```
321
+
322
+ On failure:
323
+ ```
324
+ ✗ [3/5] DeployTask failed: Connection refused
325
+ ```
326
+
327
+ ### Configuring Progress Mode
328
+
329
+ **Via API:**
330
+
331
+ ```ruby
332
+ Taski.progress_mode = :simple # Use simple mode
333
+ Taski.progress_mode = :tree # Use tree mode (default)
334
+ ```
335
+
336
+ **Via environment variable:**
337
+
338
+ ```bash
339
+ TASKI_PROGRESS_MODE=simple ruby your_script.rb
340
+ TASKI_PROGRESS_MODE=tree ruby your_script.rb
341
+ ```
342
+
290
343
  ### Disabling Progress Display
291
344
 
292
345
  ```bash
@@ -310,6 +363,7 @@ ruby build.rb > build.log 2>&1
310
363
  | Variable | Purpose |
311
364
  |----------|---------|
312
365
  | `TASKI_PROGRESS_DISABLE=1` | Disable progress display |
366
+ | `TASKI_PROGRESS_MODE=simple\|tree` | Set progress display mode (default: tree) |
313
367
  | `TASKI_DEBUG=1` | Enable debug output |
314
368
 
315
369
  ### Dependency Tree Visualization
data/examples/README.md CHANGED
@@ -46,9 +46,9 @@ ruby examples/args_demo.rb
46
46
  - User-defined options via `run(args: {...})`
47
47
  - `Taski.args[:key]` for option access
48
48
  - `Taski.args.fetch(:key, default)` for defaults
49
- - `Taski.args.working_directory`
50
- - `Taski.args.started_at`
51
- - `Taski.args.root_task`
49
+ - `Taski.env.working_directory`
50
+ - `Taski.env.started_at`
51
+ - `Taski.env.root_task`
52
52
 
53
53
  ---
54
54
 
@@ -1,35 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Taski Args API Example
4
+ # Taski Args & Env API Example
5
5
  #
6
- # This example demonstrates the Args API for accessing runtime information:
7
- # - working_directory: Where execution started
8
- # - started_at: When execution began
9
- # - root_task: The first task class that was called
10
- # - User-defined options: Custom values passed via run(args: {...})
6
+ # This example demonstrates the Args and Env APIs:
7
+ # - Taski.env: Execution environment information
8
+ # - working_directory: Where execution started
9
+ # - started_at: When execution began
10
+ # - root_task: The first task class that was called
11
+ # - Taski.args: User-defined options passed via run(args: {...})
11
12
  #
12
13
  # Run: ruby examples/args_demo.rb
13
14
 
14
15
  require_relative "../lib/taski"
15
16
 
16
- puts "Taski Args API Example"
17
+ puts "Taski Args & Env API Example"
17
18
  puts "=" * 40
18
19
 
19
- # Task that uses args information for logging
20
+ # Task that uses env information for logging
20
21
  class SetupTask < Taski::Task
21
22
  exports :setup_info
22
23
 
23
24
  def run
24
25
  puts "Setup running..."
25
- puts " Working directory: #{Taski.args.working_directory}"
26
- puts " Started at: #{Taski.args.started_at}"
27
- puts " Root task: #{Taski.args.root_task}"
26
+ puts " Working directory: #{Taski.env.working_directory}"
27
+ puts " Started at: #{Taski.env.started_at}"
28
+ puts " Root task: #{Taski.env.root_task}"
28
29
  puts " Environment: #{Taski.args[:env]}"
29
30
 
30
31
  @setup_info = {
31
- directory: Taski.args.working_directory,
32
- timestamp: Taski.args.started_at,
32
+ directory: Taski.env.working_directory,
33
+ timestamp: Taski.env.started_at,
33
34
  env: Taski.args[:env]
34
35
  }
35
36
  end
@@ -40,8 +41,8 @@ class FileProcessor < Taski::Task
40
41
  exports :output_path
41
42
 
42
43
  def run
43
- # Use args to determine output location
44
- base_dir = Taski.args.working_directory
44
+ # Use env to determine output location
45
+ base_dir = Taski.env.working_directory
45
46
  env = Taski.args.fetch(:env, "development")
46
47
  @output_path = File.join(base_dir, "tmp", env, "output.txt")
47
48
 
@@ -55,7 +56,7 @@ class TimingTask < Taski::Task
55
56
  exports :duration_info
56
57
 
57
58
  def run
58
- start_time = Taski.args.started_at
59
+ start_time = Taski.env.started_at
59
60
  current_time = Time.now
60
61
  elapsed = current_time - start_time
61
62
 
@@ -76,7 +77,7 @@ class MainTask < Taski::Task
76
77
 
77
78
  def run
78
79
  puts "\nMainTask executing..."
79
- puts " Root task is: #{Taski.args.root_task}"
80
+ puts " Root task is: #{Taski.env.root_task}"
80
81
  puts " Environment: #{Taski.args[:env]}"
81
82
 
82
83
  # Access dependencies
@@ -88,7 +89,7 @@ class MainTask < Taski::Task
88
89
  setup: setup,
89
90
  output_path: output,
90
91
  timing: timing,
91
- root_task: Taski.args.root_task.to_s
92
+ root_task: Taski.env.root_task.to_s
92
93
  }
93
94
 
94
95
  puts "\nExecution Summary:"
@@ -114,5 +115,5 @@ puts "-" * 40
114
115
  puts MainTask.tree
115
116
 
116
117
  puts "\n" + "=" * 40
117
- puts "Args API demonstration complete!"
118
- puts "Note: Args provides runtime information and user options without affecting dependency analysis."
118
+ puts "Args & Env API demonstration complete!"
119
+ puts "Note: Args provides user options, Env provides execution environment info."
@@ -129,7 +129,7 @@ class EnrichWithActivities < Taski::Task
129
129
 
130
130
  activities_by_user = activities.group_by { |a| a[:user_id] }
131
131
  .transform_values do |records|
132
- records.to_h { |r| [r[:action], r[:count]] }
132
+ records.to_h { |r| [r[:action], r[:count]] }
133
133
  end
134
134
 
135
135
  @users_with_activities = users.map do |user|