taski 0.4.2 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +51 -33
  4. data/Steepfile +1 -0
  5. data/docs/GUIDE.md +340 -0
  6. data/examples/README.md +68 -20
  7. data/examples/{context_demo.rb → args_demo.rb} +27 -27
  8. data/examples/clean_demo.rb +204 -0
  9. data/examples/data_pipeline_demo.rb +3 -3
  10. data/examples/group_demo.rb +113 -0
  11. data/examples/nested_section_demo.rb +161 -0
  12. data/examples/parallel_progress_demo.rb +1 -1
  13. data/examples/reexecution_demo.rb +93 -80
  14. data/examples/system_call_demo.rb +56 -0
  15. data/examples/tree_progress_demo.rb +164 -0
  16. data/lib/taski/{context.rb → args.rb} +3 -3
  17. data/lib/taski/execution/execution_context.rb +379 -0
  18. data/lib/taski/execution/executor.rb +538 -0
  19. data/lib/taski/execution/registry.rb +26 -2
  20. data/lib/taski/execution/scheduler.rb +308 -0
  21. data/lib/taski/execution/task_output_pipe.rb +42 -0
  22. data/lib/taski/execution/task_output_router.rb +216 -0
  23. data/lib/taski/execution/task_wrapper.rb +295 -146
  24. data/lib/taski/execution/tree_progress_display.rb +793 -0
  25. data/lib/taski/execution/worker_pool.rb +104 -0
  26. data/lib/taski/section.rb +23 -0
  27. data/lib/taski/static_analysis/analyzer.rb +4 -2
  28. data/lib/taski/static_analysis/visitor.rb +86 -5
  29. data/lib/taski/task.rb +223 -120
  30. data/lib/taski/version.rb +1 -1
  31. data/lib/taski.rb +147 -28
  32. data/sig/taski.rbs +310 -67
  33. metadata +17 -8
  34. data/docs/advanced-features.md +0 -625
  35. data/docs/api-guide.md +0 -509
  36. data/docs/error-handling.md +0 -684
  37. data/lib/taski/execution/coordinator.rb +0 -63
  38. data/lib/taski/execution/parallel_progress_display.rb +0 -201
@@ -1,36 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Taski Context API Example
4
+ # Taski Args API Example
5
5
  #
6
- # This example demonstrates the Context API for accessing runtime information:
6
+ # This example demonstrates the Args API for accessing runtime information:
7
7
  # - working_directory: Where execution started
8
8
  # - started_at: When execution began
9
9
  # - root_task: The first task class that was called
10
- # - User-defined options: Custom values passed via run(context: {...})
10
+ # - User-defined options: Custom values passed via run(args: {...})
11
11
  #
12
- # Run: ruby examples/context_demo.rb
12
+ # Run: ruby examples/args_demo.rb
13
13
 
14
14
  require_relative "../lib/taski"
15
15
 
16
- puts "Taski Context API Example"
16
+ puts "Taski Args API Example"
17
17
  puts "=" * 40
18
18
 
19
- # Task that uses context information for logging
19
+ # Task that uses args information for logging
20
20
  class SetupTask < Taski::Task
21
21
  exports :setup_info
22
22
 
23
23
  def run
24
24
  puts "Setup running..."
25
- puts " Working directory: #{Taski.context.working_directory}"
26
- puts " Started at: #{Taski.context.started_at}"
27
- puts " Root task: #{Taski.context.root_task}"
28
- puts " Environment: #{Taski.context[:env]}"
25
+ puts " Working directory: #{Taski.args.working_directory}"
26
+ puts " Started at: #{Taski.args.started_at}"
27
+ puts " Root task: #{Taski.args.root_task}"
28
+ puts " Environment: #{Taski.args[:env]}"
29
29
 
30
30
  @setup_info = {
31
- directory: Taski.context.working_directory,
32
- timestamp: Taski.context.started_at,
33
- env: Taski.context[:env]
31
+ directory: Taski.args.working_directory,
32
+ timestamp: Taski.args.started_at,
33
+ env: Taski.args[:env]
34
34
  }
35
35
  end
36
36
  end
@@ -40,9 +40,9 @@ class FileProcessor < Taski::Task
40
40
  exports :output_path
41
41
 
42
42
  def run
43
- # Use context to determine output location
44
- base_dir = Taski.context.working_directory
45
- env = Taski.context.fetch(:env, "development")
43
+ # Use args to determine output location
44
+ base_dir = Taski.args.working_directory
45
+ env = Taski.args.fetch(:env, "development")
46
46
  @output_path = File.join(base_dir, "tmp", env, "output.txt")
47
47
 
48
48
  puts "FileProcessor: Would write to #{@output_path}"
@@ -55,12 +55,12 @@ class TimingTask < Taski::Task
55
55
  exports :duration_info
56
56
 
57
57
  def run
58
- start_time = Taski.context.started_at
58
+ start_time = Taski.args.started_at
59
59
  current_time = Time.now
60
60
  elapsed = current_time - start_time
61
61
 
62
62
  puts "TimingTask: #{elapsed.round(3)}s since execution started"
63
- puts " Debug mode: #{Taski.context.fetch(:debug, false)}"
63
+ puts " Debug mode: #{Taski.args.fetch(:debug, false)}"
64
64
 
65
65
  @duration_info = {
66
66
  started: start_time,
@@ -76,8 +76,8 @@ class MainTask < Taski::Task
76
76
 
77
77
  def run
78
78
  puts "\nMainTask executing..."
79
- puts " Root task is: #{Taski.context.root_task}"
80
- puts " Environment: #{Taski.context[:env]}"
79
+ puts " Root task is: #{Taski.args.root_task}"
80
+ puts " Environment: #{Taski.args[:env]}"
81
81
 
82
82
  # Access dependencies
83
83
  setup = SetupTask.setup_info
@@ -88,7 +88,7 @@ class MainTask < Taski::Task
88
88
  setup: setup,
89
89
  output_path: output,
90
90
  timing: timing,
91
- root_task: Taski.context.root_task.to_s
91
+ root_task: Taski.args.root_task.to_s
92
92
  }
93
93
 
94
94
  puts "\nExecution Summary:"
@@ -98,15 +98,15 @@ class MainTask < Taski::Task
98
98
  end
99
99
  end
100
100
 
101
- puts "\n1. Running MainTask with context options"
101
+ puts "\n1. Running MainTask with args options"
102
102
  puts "-" * 40
103
- MainTask.run(context: {env: "production", debug: true})
103
+ MainTask.run(args: {env: "production", debug: true})
104
104
 
105
105
  puts "\n" + "=" * 40
106
- puts "\n2. Running SetupTask directly with different context"
106
+ puts "\n2. Running SetupTask directly with different args"
107
107
  puts "-" * 40
108
108
  SetupTask.reset!
109
- SetupTask.run(context: {env: "staging"})
109
+ SetupTask.run(args: {env: "staging"})
110
110
 
111
111
  puts "\n" + "=" * 40
112
112
  puts "\n3. Dependency Tree"
@@ -114,5 +114,5 @@ puts "-" * 40
114
114
  puts MainTask.tree
115
115
 
116
116
  puts "\n" + "=" * 40
117
- puts "Context API demonstration complete!"
118
- puts "Note: Context provides runtime information and user options without affecting dependency analysis."
117
+ puts "Args API demonstration complete!"
118
+ puts "Note: Args provides runtime information and user options without affecting dependency analysis."
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Taski Clean Demo
5
+ #
6
+ # This example demonstrates the clean functionality:
7
+ # - Defining clean methods for resource cleanup
8
+ # - Reverse dependency order execution (dependents cleaned before dependencies)
9
+ # - Parallel clean execution when possible
10
+ #
11
+ # Run: ruby examples/clean_demo.rb
12
+
13
+ require_relative "../lib/taski"
14
+
15
+ puts "Taski Clean Demo"
16
+ puts "=" * 40
17
+
18
+ # Simulated build artifact paths
19
+ BUILD_DIR = "/tmp/taski_clean_demo"
20
+
21
+ # Base task: creates a build directory
22
+ class SetupBuildDir < Taski::Task
23
+ exports :build_path
24
+
25
+ ##
26
+ # Prepares the task's build directory and simulates its creation.
27
+ #
28
+ # Sets the task's exported `build_path` to the "build" subdirectory under `BUILD_DIR`
29
+ # and performs a simulated creation step with informational output.
30
+ def run
31
+ @build_path = "#{BUILD_DIR}/build"
32
+ puts "[SetupBuildDir] Creating build directory: #{@build_path}"
33
+ # Simulated directory creation
34
+ sleep 0.8
35
+ end
36
+
37
+ ##
38
+ # Removes the directory referenced by @build_path.
39
+ # Cleans up build artifacts created by this task.
40
+ def clean
41
+ puts "[SetupBuildDir] Removing build directory: #{@build_path}"
42
+ # Simulated directory removal
43
+ sleep 0.8
44
+ end
45
+ end
46
+
47
+ # Compiles source code (depends on build directory)
48
+ class CompileSource < Taski::Task
49
+ exports :binary_path
50
+
51
+ ##
52
+ # Compiles source artifacts and sets the compiled binary path for downstream tasks.
53
+ # This method sets @binary_path to the build directory's "app.bin" file and emits a console message indicating the compilation target.
54
+ def run
55
+ build_dir = SetupBuildDir.build_path
56
+ @binary_path = "#{build_dir}/app.bin"
57
+ puts "[CompileSource] Compiling to: #{@binary_path}"
58
+ sleep 1.5
59
+ end
60
+
61
+ ##
62
+ # Removes the compiled binary produced by this task.
63
+ # Performs the task's cleanup step and simulates the deletion process.
64
+ def clean
65
+ puts "[CompileSource] Removing compiled binary: #{@binary_path}"
66
+ sleep 0.6
67
+ end
68
+ end
69
+
70
+ # Generates documentation (depends on build directory)
71
+ class GenerateDocs < Taski::Task
72
+ exports :docs_path
73
+
74
+ ##
75
+ # Generates documentation for the build and records the output path.
76
+ #
77
+ # Sets the task's `@docs_path` to the "docs" subdirectory under `SetupBuildDir.build_path`
78
+ # and prints a progress message to STDOUT.
79
+ def run
80
+ build_dir = SetupBuildDir.build_path
81
+ @docs_path = "#{build_dir}/docs"
82
+ puts "[GenerateDocs] Generating docs to: #{@docs_path}"
83
+ sleep 1.2
84
+ end
85
+
86
+ ##
87
+ # Removes the generated documentation at the task's docs_path.
88
+ # Prints a removal message for @docs_path and simulates its deletion.
89
+ def clean
90
+ puts "[GenerateDocs] Removing generated docs: #{@docs_path}"
91
+ sleep 0.5
92
+ end
93
+ end
94
+
95
+ # Creates release package (depends on both compiled source and docs)
96
+ class CreateRelease < Taski::Task
97
+ exports :release_path
98
+
99
+ ##
100
+ # Creates the release package path and announces the included artifacts.
101
+ # Uses CompileSource.binary_path and GenerateDocs.docs_path to determine the package inputs, sets @release_path to "#{BUILD_DIR}/release.zip", and prints the binary, docs, and output paths.
102
+ def run
103
+ binary = CompileSource.binary_path
104
+ docs = GenerateDocs.docs_path
105
+ @release_path = "#{BUILD_DIR}/release.zip"
106
+ puts "[CreateRelease] Creating release package with:"
107
+ puts " - Binary: #{binary}"
108
+ puts " - Docs: #{docs}"
109
+ puts " - Output: #{@release_path}"
110
+ sleep 0.7
111
+ end
112
+
113
+ ##
114
+ # Removes the release package produced by this task.
115
+ # Uses the task's `@release_path` as the target for cleanup.
116
+ def clean
117
+ puts "[CreateRelease] Removing release package: #{@release_path}"
118
+ sleep 0.5
119
+ end
120
+ end
121
+
122
+ puts "\n--- Task Tree Structure ---"
123
+ puts CreateRelease.tree
124
+ puts
125
+
126
+ puts "--- Running build process ---"
127
+ CreateRelease.run
128
+ puts
129
+
130
+ puts "Build completed!"
131
+ puts " Release: #{CreateRelease.release_path}"
132
+ puts
133
+
134
+ puts "--- Cleaning up (reverse dependency order) ---"
135
+ puts "Note: CreateRelease cleans first, then CompileSource/GenerateDocs in parallel,"
136
+ puts " and finally SetupBuildDir cleans last."
137
+ puts
138
+
139
+ # Reset to allow clean execution
140
+ Taski::Task.reset!
141
+
142
+ # Re-run to set up state for clean
143
+ CreateRelease.run
144
+
145
+ puts "\nNow cleaning..."
146
+ CreateRelease.clean
147
+
148
+ puts
149
+ puts "Clean completed! All artifacts removed."
150
+
151
+ puts
152
+ puts "=" * 40
153
+ puts "run_and_clean Demo"
154
+ puts "=" * 40
155
+ puts
156
+ puts "The run_and_clean method executes both phases in a single operation."
157
+ puts "Key benefits:"
158
+ puts " - Single progress display session for both phases"
159
+ puts " - Clean always runs, even if run fails (resource release)"
160
+ puts " - Cleaner API for the common use case"
161
+ puts
162
+
163
+ # Reset for new demonstration
164
+ Taski::Task.reset!
165
+
166
+ puts "--- Using run_and_clean ---"
167
+ puts "This is equivalent to calling run followed by clean, but in a single operation."
168
+ puts
169
+
170
+ CreateRelease.run_and_clean
171
+
172
+ puts
173
+ puts "Both run and clean phases completed in a single call!"
174
+ puts
175
+
176
+ # Demonstrate error handling
177
+ Taski::Task.reset!
178
+
179
+ puts "--- Error Handling Demo ---"
180
+ puts "When run fails, clean is still executed for resource release."
181
+ puts
182
+
183
+ # Task that fails during run
184
+ class FailingBuild < Taski::Task
185
+ exports :result
186
+
187
+ def run
188
+ puts "[FailingBuild] Starting build..."
189
+ raise StandardError, "Build failed: missing dependencies"
190
+ end
191
+
192
+ def clean
193
+ puts "[FailingBuild] Cleaning up partial build artifacts..."
194
+ end
195
+ end
196
+
197
+ begin
198
+ FailingBuild.run_and_clean
199
+ rescue => e
200
+ puts "[Error caught] #{e.message}"
201
+ end
202
+
203
+ puts
204
+ puts "Notice: clean was still executed despite run failing!"
@@ -9,7 +9,7 @@
9
9
  # - Data transformation and aggregation
10
10
  #
11
11
  # Run: ruby examples/data_pipeline_demo.rb
12
- # With progress: TASKI_FORCE_PROGRESS=1 ruby examples/data_pipeline_demo.rb
12
+ # Disable progress: TASKI_PROGRESS_DISABLE=1 ruby examples/data_pipeline_demo.rb
13
13
 
14
14
  require_relative "../lib/taski"
15
15
 
@@ -227,5 +227,5 @@ puts "\nKey concepts demonstrated:"
227
227
  puts " - DataSourceSection: Switch between production/test data"
228
228
  puts " - ReportFormatSection: Switch output format (text/JSON)"
229
229
  puts " - Parallel execution of independent transforms"
230
- puts "\nTry with progress display:"
231
- puts " TASKI_FORCE_PROGRESS=1 ruby examples/data_pipeline_demo.rb"
230
+ puts "\nTo disable progress display:"
231
+ puts " TASKI_PROGRESS_DISABLE=1 ruby examples/data_pipeline_demo.rb"
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/taski"
5
+
6
+ # Demo for group blocks in progress display
7
+ # Run with: ruby examples/group_demo.rb
8
+ #
9
+ # Groups organize output messages within a task into logical phases,
10
+ # displayed as children of the task in the progress tree.
11
+
12
+ class ConfigTask < Taski::Task
13
+ exports :config
14
+
15
+ def run
16
+ group("Loading configuration") do
17
+ puts "Reading config file..."
18
+ sleep(0.2)
19
+ puts "Parsing YAML..."
20
+ sleep(0.1)
21
+ puts "Config loaded"
22
+ end
23
+
24
+ @config = {
25
+ database_url: "postgresql://localhost/myapp",
26
+ redis_url: "redis://localhost:6379"
27
+ }
28
+ end
29
+ end
30
+
31
+ class BuildTask < Taski::Task
32
+ exports :artifacts
33
+
34
+ def run
35
+ ConfigTask.config # Ensure config is loaded
36
+
37
+ group("Compiling source") do
38
+ puts "Analyzing dependencies..."
39
+ sleep(0.2)
40
+ puts "Compiling 42 files..."
41
+ sleep(0.3)
42
+ puts "Compilation complete"
43
+ end
44
+
45
+ group("Running tests") do
46
+ puts "Running unit tests..."
47
+ sleep(0.2)
48
+ puts "Running integration tests..."
49
+ sleep(0.2)
50
+ puts "All 128 tests passed"
51
+ end
52
+
53
+ group("Bundling assets") do
54
+ puts "Minifying JavaScript..."
55
+ sleep(0.1)
56
+ puts "Optimizing images..."
57
+ sleep(0.2)
58
+ puts "Assets bundled"
59
+ end
60
+
61
+ @artifacts = ["app.js", "app.css", "images/"]
62
+ end
63
+ end
64
+
65
+ class DeployTask < Taski::Task
66
+ exports :deploy_url
67
+
68
+ def run
69
+ artifacts = BuildTask.artifacts
70
+
71
+ group("Preparing deployment") do
72
+ puts "Creating deployment package..."
73
+ sleep(0.2)
74
+ puts "Package size: 12.5 MB"
75
+ end
76
+
77
+ group("Uploading to server") do
78
+ puts "Connecting to server..."
79
+ sleep(0.1)
80
+ puts "Uploading #{artifacts.size} artifacts..."
81
+ sleep(0.3)
82
+ puts "Upload complete"
83
+ end
84
+
85
+ group("Starting application") do
86
+ puts "Stopping old instance..."
87
+ sleep(0.1)
88
+ puts "Starting new instance..."
89
+ sleep(0.2)
90
+ puts "Health check passed"
91
+ end
92
+
93
+ @deploy_url = "https://app.example.com"
94
+ end
95
+ end
96
+
97
+ # Show tree structure before execution
98
+ puts "Task Tree Structure:"
99
+ puts "=" * 60
100
+ puts DeployTask.tree
101
+ puts "=" * 60
102
+ puts
103
+
104
+ # Reset for execution
105
+ DeployTask.reset!
106
+
107
+ # Execute with progress display
108
+ url = DeployTask.deploy_url
109
+
110
+ puts "\n"
111
+ puts "=" * 60
112
+ puts "Deployment completed!"
113
+ puts "Application URL: #{url}"
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/taski"
5
+
6
+ # Demo for nested Sections (Section inside Section)
7
+ # Run with: ruby examples/nested_section_demo.rb
8
+
9
+ # Configuration loader task - determines which storage to use
10
+ class StorageConfig < Taski::Task
11
+ exports :use_s3
12
+
13
+ def run
14
+ puts "Loading storage configuration..."
15
+ sleep(0.4)
16
+ puts "Checking cloud credentials..."
17
+ sleep(0.3)
18
+ puts "Validating storage policies..."
19
+ sleep(0.3)
20
+ # Simulate config loading - use local storage by default
21
+ @use_s3 = ENV["USE_S3"] == "1"
22
+ end
23
+ end
24
+
25
+ # Inner Section: Storage backend selection (depends on StorageConfig)
26
+ class StorageSection < Taski::Section
27
+ interfaces :storage_client
28
+
29
+ def impl
30
+ # Use config task result to decide implementation
31
+ if StorageConfig.use_s3
32
+ S3Storage
33
+ else
34
+ LocalStorage
35
+ end
36
+ end
37
+
38
+ class S3Storage < Taski::Task
39
+ def run
40
+ puts "Connecting to S3..."
41
+ sleep(1.0)
42
+ @storage_client = "s3://bucket/data"
43
+ end
44
+ end
45
+
46
+ class LocalStorage < Taski::Task
47
+ def run
48
+ puts "Initializing local filesystem..."
49
+ sleep(0.5)
50
+ puts "Mounting volumes..."
51
+ sleep(0.5)
52
+ puts "Local storage ready"
53
+ sleep(0.3)
54
+ @storage_client = "/var/data"
55
+ end
56
+ end
57
+ end
58
+
59
+ # Database configuration loader - determines which database to use
60
+ class DatabaseConfig < Taski::Task
61
+ exports :use_prod
62
+
63
+ def run
64
+ puts "Loading database configuration..."
65
+ sleep(0.3)
66
+ puts "Checking environment variables..."
67
+ sleep(0.3)
68
+ puts "Resolving database endpoints..."
69
+ sleep(0.4)
70
+ # Simulate config loading - use dev database by default
71
+ @use_prod = ENV["USE_PROD_DB"] == "1"
72
+ end
73
+ end
74
+
75
+ # Outer Section: Database selection (depends on Storage and DatabaseConfig)
76
+ class DatabaseSection < Taski::Section
77
+ interfaces :connection_string
78
+
79
+ def impl
80
+ # Use config task result to decide implementation
81
+ if DatabaseConfig.use_prod
82
+ ProductionDB
83
+ else
84
+ DevelopmentDB
85
+ end
86
+ end
87
+
88
+ class ProductionDB < Taski::Task
89
+ def run
90
+ # Production DB also needs storage for backups
91
+ storage = StorageSection.storage_client
92
+ puts "Connecting to production database (backup: #{storage})..."
93
+ sleep(0.8)
94
+ @connection_string = "postgresql://prod-server:5432/myapp"
95
+ end
96
+ end
97
+
98
+ class DevelopmentDB < Taski::Task
99
+ def run
100
+ # Dev DB uses storage for test data
101
+ storage = StorageSection.storage_client
102
+ puts "Initializing dev database connection..."
103
+ sleep(0.4)
104
+ puts "Loading test fixtures from #{storage}..."
105
+ sleep(0.5)
106
+ puts "Dev database ready"
107
+ sleep(0.3)
108
+ @connection_string = "postgresql://localhost:5432/myapp_dev"
109
+ end
110
+ end
111
+ end
112
+
113
+ # Task that uses the database
114
+ class FetchData < Taski::Task
115
+ exports :data
116
+
117
+ def run
118
+ db = DatabaseSection.connection_string
119
+ puts "Connecting to #{db}..."
120
+ sleep(0.3)
121
+ puts "Querying records..."
122
+ sleep(0.4)
123
+ puts "Processing results..."
124
+ sleep(0.3)
125
+ @data = ["item1", "item2", "item3"]
126
+ end
127
+ end
128
+
129
+ # Main application task
130
+ class Application < Taski::Task
131
+ exports :result
132
+
133
+ def run
134
+ data = FetchData.data
135
+ puts "Validating #{data.size} items..."
136
+ sleep(0.3)
137
+ puts "Transforming data..."
138
+ sleep(0.4)
139
+ puts "Finalizing results..."
140
+ sleep(0.3)
141
+ @result = "Processed: #{data.join(", ")}"
142
+ end
143
+ end
144
+
145
+ # Show tree structure before execution
146
+ puts "Nested Section Tree Structure:"
147
+ puts "=" * 70
148
+ puts Application.tree
149
+ puts "=" * 70
150
+ puts
151
+
152
+ # Reset for execution
153
+ Application.reset!
154
+
155
+ # Execute
156
+ result = Application.result
157
+
158
+ puts "\n"
159
+ puts "=" * 70
160
+ puts "Execution completed!"
161
+ puts "Result: #{result}"
@@ -4,7 +4,7 @@
4
4
  require_relative "../lib/taski"
5
5
 
6
6
  # Demo tasks that simulate parallel execution with progress display
7
- # Run with: TASKI_FORCE_PROGRESS=1 ruby examples/parallel_progress_demo.rb
7
+ # Run with: ruby examples/parallel_progress_demo.rb
8
8
 
9
9
  class DownloadLayer1 < Taski::Task
10
10
  exports :layer1_data