taski 0.3.1 → 0.4.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 +4 -4
- data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/ast/2.4/ast.rbs +73 -0
- data/.gem_rbs_collection/minitest/5.25/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/abstract_reporter.rbs +52 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/assertion.rbs +17 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/assertions.rbs +590 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/backtrace_filter.rbs +23 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/bench_spec.rbs +102 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/benchmark.rbs +259 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/composite_reporter.rbs +25 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/compress.rbs +13 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/error_on_warning.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/expectation.rbs +2 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/expectations.rbs +21 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/guard.rbs +64 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/mock.rbs +64 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/executor.rbs +46 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test/class_methods.rbs +5 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel.rbs +2 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/pride_io.rbs +62 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/pride_lol.rbs +19 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/progress_reporter.rbs +11 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/reportable.rbs +53 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/reporter.rbs +5 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/result.rbs +28 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/runnable.rbs +163 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/skip.rbs +6 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl/instance_methods.rbs +48 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl.rbs +129 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec.rbs +11 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/statistics_reporter.rbs +81 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/summary_reporter.rbs +18 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/test/lifecycle_hooks.rbs +92 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/test.rbs +69 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_error.rbs +12 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_warning.rbs +6 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unit/test_case.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unit.rbs +4 -0
- data/.gem_rbs_collection/minitest/5.25/minitest.rbs +115 -0
- data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
- data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parser/3.2/manifest.yaml +7 -0
- data/.gem_rbs_collection/parser/3.2/parser.rbs +193 -0
- data/.gem_rbs_collection/parser/3.2/polyfill.rbs +4 -0
- data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rainbow/3.0/global.rbs +7 -0
- data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +209 -0
- data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +5 -0
- data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
- data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
- data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +17 -0
- data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +129 -0
- data/.gem_rbs_collection/rubocop-ast/1.30/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop-ast/1.30/rubocop-ast.rbs +771 -0
- data/.gem_rbs_collection/simplecov/0.22/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/simplecov/0.22/simplecov.rbs +54 -0
- data/README.md +138 -247
- data/Steepfile +19 -0
- data/docs/advanced-features.md +625 -0
- data/docs/api-guide.md +509 -0
- data/docs/error-handling.md +684 -0
- data/examples/README.md +95 -42
- data/examples/context_demo.rb +112 -0
- data/examples/data_pipeline_demo.rb +231 -0
- data/examples/parallel_progress_demo.rb +72 -0
- data/examples/quick_start.rb +4 -4
- data/examples/reexecution_demo.rb +127 -0
- data/examples/{section_configuration.rb → section_demo.rb} +49 -60
- data/lib/taski/context.rb +52 -0
- data/lib/taski/execution/coordinator.rb +63 -0
- data/lib/taski/execution/parallel_progress_display.rb +201 -0
- data/lib/taski/execution/registry.rb +72 -0
- data/lib/taski/execution/task_wrapper.rb +255 -0
- data/lib/taski/section.rb +26 -254
- data/lib/taski/static_analysis/analyzer.rb +34 -0
- data/lib/taski/static_analysis/dependency_graph.rb +90 -0
- data/lib/taski/static_analysis/visitor.rb +114 -0
- data/lib/taski/task.rb +173 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +45 -39
- data/rbs_collection.lock.yaml +116 -0
- data/rbs_collection.yaml +19 -0
- data/sig/taski.rbs +269 -62
- metadata +97 -32
- data/examples/advanced_patterns.rb +0 -119
- data/examples/progress_demo.rb +0 -166
- data/examples/tree_demo.rb +0 -205
- data/lib/taski/dependency_analyzer.rb +0 -232
- data/lib/taski/exceptions.rb +0 -17
- data/lib/taski/logger.rb +0 -158
- data/lib/taski/logging/formatter_factory.rb +0 -34
- data/lib/taski/logging/formatter_interface.rb +0 -19
- data/lib/taski/logging/json_formatter.rb +0 -26
- data/lib/taski/logging/simple_formatter.rb +0 -16
- data/lib/taski/logging/structured_formatter.rb +0 -44
- data/lib/taski/progress/display_colors.rb +0 -17
- data/lib/taski/progress/display_manager.rb +0 -117
- data/lib/taski/progress/output_capture.rb +0 -105
- data/lib/taski/progress/spinner_animation.rb +0 -49
- data/lib/taski/progress/task_formatter.rb +0 -25
- data/lib/taski/progress/task_status.rb +0 -38
- data/lib/taski/progress/terminal_controller.rb +0 -35
- data/lib/taski/progress_display.rb +0 -57
- data/lib/taski/reference.rb +0 -40
- data/lib/taski/task/base.rb +0 -91
- data/lib/taski/task/define_api.rb +0 -156
- data/lib/taski/task/dependency_resolver.rb +0 -73
- data/lib/taski/task/exports_api.rb +0 -29
- data/lib/taski/task/instance_management.rb +0 -201
- data/lib/taski/tree_colors.rb +0 -91
- data/lib/taski/utils/dependency_resolver_helper.rb +0 -85
- data/lib/taski/utils/tree_display_helper.rb +0 -68
- data/lib/taski/utils.rb +0 -107
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Taski Re-execution Demo
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates cache control and re-execution:
|
|
7
|
+
# - Default caching behavior
|
|
8
|
+
# - Task.new for fresh instances
|
|
9
|
+
# - Task.reset! for clearing all caches
|
|
10
|
+
#
|
|
11
|
+
# Run: ruby examples/reexecution_demo.rb
|
|
12
|
+
|
|
13
|
+
require_relative "../lib/taski"
|
|
14
|
+
|
|
15
|
+
puts "Taski Re-execution Demo"
|
|
16
|
+
puts "=" * 40
|
|
17
|
+
|
|
18
|
+
# Task that generates random values (to demonstrate caching)
|
|
19
|
+
class RandomGenerator < Taski::Task
|
|
20
|
+
exports :value, :timestamp
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
@value = rand(1000)
|
|
24
|
+
@timestamp = Time.now.strftime("%H:%M:%S.%L")
|
|
25
|
+
puts " RandomGenerator.run called: value=#{@value}, time=#{@timestamp}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Task that depends on RandomGenerator
|
|
30
|
+
class Consumer < Taski::Task
|
|
31
|
+
exports :result
|
|
32
|
+
|
|
33
|
+
def run
|
|
34
|
+
random_value = RandomGenerator.value
|
|
35
|
+
@result = "Consumed value: #{random_value}"
|
|
36
|
+
puts " Consumer.run called: #{@result}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts "\n1. Default Caching Behavior"
|
|
41
|
+
puts "-" * 40
|
|
42
|
+
puts "First call to RandomGenerator.value:"
|
|
43
|
+
value1 = RandomGenerator.value
|
|
44
|
+
puts " => #{value1}"
|
|
45
|
+
|
|
46
|
+
puts "\nSecond call to RandomGenerator.value (cached, no run):"
|
|
47
|
+
value2 = RandomGenerator.value
|
|
48
|
+
puts " => #{value2}"
|
|
49
|
+
|
|
50
|
+
puts "\nValues are identical: #{value1 == value2}"
|
|
51
|
+
|
|
52
|
+
puts "\n" + "=" * 40
|
|
53
|
+
puts "\n2. Using Task.new for Fresh Instance"
|
|
54
|
+
puts "-" * 40
|
|
55
|
+
puts "Creating new instance with RandomGenerator.new:"
|
|
56
|
+
|
|
57
|
+
instance1 = RandomGenerator.new
|
|
58
|
+
instance1.run
|
|
59
|
+
puts " instance1.value = #{instance1.value}"
|
|
60
|
+
|
|
61
|
+
instance2 = RandomGenerator.new
|
|
62
|
+
instance2.run
|
|
63
|
+
puts " instance2.value = #{instance2.value}"
|
|
64
|
+
|
|
65
|
+
puts "\nNote: Each .new creates independent instance"
|
|
66
|
+
puts "Class-level cache unchanged: RandomGenerator.value = #{RandomGenerator.value}"
|
|
67
|
+
|
|
68
|
+
puts "\n" + "=" * 40
|
|
69
|
+
puts "\n3. Using reset! to Clear Cache"
|
|
70
|
+
puts "-" * 40
|
|
71
|
+
puts "Before reset!:"
|
|
72
|
+
puts " RandomGenerator.value = #{RandomGenerator.value}"
|
|
73
|
+
|
|
74
|
+
puts "\nCalling RandomGenerator.reset!..."
|
|
75
|
+
RandomGenerator.reset!
|
|
76
|
+
|
|
77
|
+
puts "\nAfter reset! (fresh execution):"
|
|
78
|
+
new_value = RandomGenerator.value
|
|
79
|
+
puts " RandomGenerator.value = #{new_value}"
|
|
80
|
+
|
|
81
|
+
puts "\n" + "=" * 40
|
|
82
|
+
puts "\n4. Dependency Chain with Re-execution"
|
|
83
|
+
puts "-" * 40
|
|
84
|
+
|
|
85
|
+
# Reset both tasks
|
|
86
|
+
RandomGenerator.reset!
|
|
87
|
+
Consumer.reset!
|
|
88
|
+
|
|
89
|
+
puts "First Consumer execution:"
|
|
90
|
+
result1 = Consumer.result
|
|
91
|
+
puts " => #{result1}"
|
|
92
|
+
|
|
93
|
+
puts "\nSecond Consumer execution (cached):"
|
|
94
|
+
result2 = Consumer.result
|
|
95
|
+
puts " => #{result2}"
|
|
96
|
+
|
|
97
|
+
puts "\nReset Consumer and re-execute:"
|
|
98
|
+
Consumer.reset!
|
|
99
|
+
result3 = Consumer.result
|
|
100
|
+
puts " => #{result3}"
|
|
101
|
+
puts " (Dependencies are re-resolved when task is reset)"
|
|
102
|
+
|
|
103
|
+
puts "\nReset both tasks:"
|
|
104
|
+
RandomGenerator.reset!
|
|
105
|
+
Consumer.reset!
|
|
106
|
+
result4 = Consumer.result
|
|
107
|
+
puts " => #{result4}"
|
|
108
|
+
puts " (New random value because both were reset)"
|
|
109
|
+
|
|
110
|
+
puts "\n" + "=" * 40
|
|
111
|
+
puts "\n5. Use Cases Summary"
|
|
112
|
+
puts "-" * 40
|
|
113
|
+
puts <<~SUMMARY
|
|
114
|
+
TaskClass.run / TaskClass.value
|
|
115
|
+
=> Normal execution with caching (recommended for dependency graphs)
|
|
116
|
+
|
|
117
|
+
TaskClass.new.run
|
|
118
|
+
=> Re-execute only this task (dependencies still use cache)
|
|
119
|
+
=> Useful for: testing, one-off executions
|
|
120
|
+
|
|
121
|
+
TaskClass.reset!
|
|
122
|
+
=> Clear this task's cache, next call will re-execute
|
|
123
|
+
=> Useful for: environment changes, refreshing data
|
|
124
|
+
SUMMARY
|
|
125
|
+
|
|
126
|
+
puts "\n" + "=" * 40
|
|
127
|
+
puts "Re-execution demonstration complete!"
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# Section
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# - interface declaration automatically adds exports to nested Task classes
|
|
11
|
-
# 2. Consistent API: impl must return Task classes - .build is called automatically
|
|
12
|
-
# 3. Dynamic Implementation Selection: Different implementations based on environment
|
|
13
|
-
# 4. Dependency Resolution: Sections are properly detected in dependency analysis
|
|
14
|
-
# 5. Tree Visualization: Sections appear in dependency trees
|
|
2
|
+
|
|
3
|
+
# Section API Basics Example
|
|
4
|
+
# This example demonstrates runtime implementation selection with the Section API
|
|
5
|
+
|
|
6
|
+
# Section API is perfect for:
|
|
7
|
+
# - Environment-specific implementations (dev/staging/prod)
|
|
8
|
+
# - Different service adapters (AWS/GCP/local)
|
|
9
|
+
# - Clean abstraction with guaranteed interfaces
|
|
15
10
|
|
|
16
11
|
require_relative "../lib/taski"
|
|
17
12
|
|
|
@@ -20,10 +15,10 @@ require_relative "../lib/taski"
|
|
|
20
15
|
# for development and production environments
|
|
21
16
|
class DatabaseSection < Taski::Section
|
|
22
17
|
# Define the interface that implementations must provide
|
|
23
|
-
|
|
18
|
+
interfaces :host, :port, :username, :password, :database_name, :pool_size
|
|
24
19
|
|
|
25
20
|
# Select implementation based on environment
|
|
26
|
-
# Note: Must return a Task class - .
|
|
21
|
+
# Note: Must return a Task class - .run is automatically called
|
|
27
22
|
# No 'self' needed - just define as instance method!
|
|
28
23
|
def impl
|
|
29
24
|
if ENV["RAILS_ENV"] == "production"
|
|
@@ -34,9 +29,9 @@ class DatabaseSection < Taski::Section
|
|
|
34
29
|
end
|
|
35
30
|
|
|
36
31
|
# Production implementation with secure settings
|
|
37
|
-
#
|
|
32
|
+
# No exports needed - automatically inherited from interfaces
|
|
38
33
|
class Production < Taski::Task
|
|
39
|
-
def
|
|
34
|
+
def run
|
|
40
35
|
@host = "prod-db.example.com"
|
|
41
36
|
@port = 5432
|
|
42
37
|
@username = "app_user"
|
|
@@ -47,9 +42,8 @@ class DatabaseSection < Taski::Section
|
|
|
47
42
|
end
|
|
48
43
|
|
|
49
44
|
# Development implementation with local settings
|
|
50
|
-
# Note: exports are automatically inherited from interface declaration
|
|
51
45
|
class Development < Taski::Task
|
|
52
|
-
def
|
|
46
|
+
def run
|
|
53
47
|
@host = "localhost"
|
|
54
48
|
@port = 5432
|
|
55
49
|
@username = "dev_user"
|
|
@@ -63,12 +57,12 @@ end
|
|
|
63
57
|
# Example 2: API Configuration Section
|
|
64
58
|
# This section provides API endpoints and credentials
|
|
65
59
|
class ApiSection < Taski::Section
|
|
66
|
-
|
|
60
|
+
interfaces :base_url, :api_key, :timeout, :retry_count
|
|
67
61
|
|
|
68
62
|
# No 'self' needed - just define as instance method!
|
|
69
63
|
def impl
|
|
70
64
|
# Select based on feature flag
|
|
71
|
-
# Note: Must return a Task class - .
|
|
65
|
+
# Note: Must return a Task class - .run is automatically called
|
|
72
66
|
if ENV["USE_STAGING_API"] == "true"
|
|
73
67
|
Staging
|
|
74
68
|
else
|
|
@@ -76,9 +70,8 @@ class ApiSection < Taski::Section
|
|
|
76
70
|
end
|
|
77
71
|
end
|
|
78
72
|
|
|
79
|
-
# Note: exports are automatically inherited from interface declaration - DRY principle!
|
|
80
73
|
class Production < Taski::Task
|
|
81
|
-
def
|
|
74
|
+
def run
|
|
82
75
|
@base_url = "https://api.example.com/v1"
|
|
83
76
|
@api_key = ENV["PROD_API_KEY"] || "prod-key-123"
|
|
84
77
|
@timeout = 30
|
|
@@ -86,9 +79,8 @@ class ApiSection < Taski::Section
|
|
|
86
79
|
end
|
|
87
80
|
end
|
|
88
81
|
|
|
89
|
-
# Note: exports are automatically inherited from interface declaration - DRY principle!
|
|
90
82
|
class Staging < Taski::Task
|
|
91
|
-
def
|
|
83
|
+
def run
|
|
92
84
|
@base_url = "https://staging-api.example.com/v1"
|
|
93
85
|
@api_key = ENV["STAGING_API_KEY"] || "staging-key-456"
|
|
94
86
|
@timeout = 60
|
|
@@ -101,7 +93,7 @@ end
|
|
|
101
93
|
class ApplicationSetup < Taski::Task
|
|
102
94
|
exports :config_summary
|
|
103
95
|
|
|
104
|
-
def
|
|
96
|
+
def run
|
|
105
97
|
puts "Setting up application with configuration:"
|
|
106
98
|
puts "Database: #{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
|
107
99
|
puts "API: #{ApiSection.base_url}"
|
|
@@ -128,7 +120,7 @@ end
|
|
|
128
120
|
class DatabaseConnection < Taski::Task
|
|
129
121
|
exports :connection
|
|
130
122
|
|
|
131
|
-
def
|
|
123
|
+
def run
|
|
132
124
|
puts "Connecting to database..."
|
|
133
125
|
# Use section configuration to create connection
|
|
134
126
|
connection_string = "postgresql://#{DatabaseSection.username}:#{DatabaseSection.password}@#{DatabaseSection.host}:#{DatabaseSection.port}/#{DatabaseSection.database_name}"
|
|
@@ -140,7 +132,7 @@ end
|
|
|
140
132
|
class ApiClient < Taski::Task
|
|
141
133
|
exports :client
|
|
142
134
|
|
|
143
|
-
def
|
|
135
|
+
def run
|
|
144
136
|
puts "Initializing API client..."
|
|
145
137
|
@client = "API Client: #{ApiSection.base_url} (timeout: #{ApiSection.timeout}s, retries: #{ApiSection.retry_count})"
|
|
146
138
|
puts @client
|
|
@@ -148,12 +140,12 @@ class ApiClient < Taski::Task
|
|
|
148
140
|
end
|
|
149
141
|
|
|
150
142
|
class Application < Taski::Task
|
|
151
|
-
def
|
|
143
|
+
def run
|
|
152
144
|
puts "\n=== Starting Application ==="
|
|
153
145
|
|
|
154
146
|
# Dependencies are automatically resolved
|
|
155
|
-
# DatabaseConnection and ApiClient will be
|
|
156
|
-
# which triggers
|
|
147
|
+
# DatabaseConnection and ApiClient will be executed first
|
|
148
|
+
# which triggers execution of their respective sections
|
|
157
149
|
|
|
158
150
|
puts "\nDatabase ready: #{DatabaseConnection.connection}"
|
|
159
151
|
puts "API ready: #{ApiClient.client}"
|
|
@@ -165,42 +157,39 @@ class Application < Taski::Task
|
|
|
165
157
|
end
|
|
166
158
|
end
|
|
167
159
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
puts "Taski Section Configuration Example"
|
|
171
|
-
puts "=" * 50
|
|
160
|
+
puts "Taski Section Configuration Example"
|
|
161
|
+
puts "=" * 50
|
|
172
162
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
puts "\n1. Development Environment (default)"
|
|
164
|
+
ENV["RAILS_ENV"] = "development"
|
|
165
|
+
ENV["USE_STAGING_API"] = "false"
|
|
176
166
|
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
# Reset all tasks to ensure fresh build
|
|
168
|
+
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
|
179
169
|
|
|
180
|
-
|
|
170
|
+
Application.run
|
|
181
171
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
172
|
+
puts "\n" + "=" * 50
|
|
173
|
+
puts "\n2. Production Environment with Staging API"
|
|
174
|
+
ENV["RAILS_ENV"] = "production"
|
|
175
|
+
ENV["USE_STAGING_API"] = "true"
|
|
186
176
|
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
# Reset all tasks to see different configuration
|
|
178
|
+
[DatabaseSection, ApiSection, ApplicationSetup, DatabaseConnection, ApiClient, Application].each(&:reset!)
|
|
189
179
|
|
|
190
|
-
|
|
180
|
+
Application.run
|
|
191
181
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
182
|
+
puts "\n" + "=" * 50
|
|
183
|
+
puts "\n3. Dependency Tree Visualization"
|
|
184
|
+
puts "\nApplication dependency tree:"
|
|
185
|
+
puts Application.tree
|
|
196
186
|
|
|
197
|
-
|
|
198
|
-
|
|
187
|
+
puts "\nDatabaseConnection dependency tree:"
|
|
188
|
+
puts DatabaseConnection.tree
|
|
199
189
|
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
puts "\nApiClient dependency tree:"
|
|
191
|
+
puts ApiClient.tree
|
|
202
192
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
end
|
|
193
|
+
puts "\n" + "=" * 50
|
|
194
|
+
puts "\nSection dependency resolution successfully demonstrated!"
|
|
195
|
+
puts "Notice how sections appear in the dependency trees and logs."
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Taski
|
|
6
|
+
# Runtime context accessible from any task (not included in dependency analysis).
|
|
7
|
+
class Context
|
|
8
|
+
@monitor = Monitor.new
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# @return [String] The working directory path
|
|
12
|
+
def working_directory
|
|
13
|
+
@monitor.synchronize do
|
|
14
|
+
@working_directory ||= Dir.pwd
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Time] The start time
|
|
19
|
+
def started_at
|
|
20
|
+
@monitor.synchronize do
|
|
21
|
+
@started_at ||= Time.now
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Class, nil] The root task class or nil if not set
|
|
26
|
+
def root_task
|
|
27
|
+
@monitor.synchronize do
|
|
28
|
+
@root_task
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Called internally when a task is first invoked. Only the first call has effect.
|
|
33
|
+
# @param task_class [Class] The task class to set as root
|
|
34
|
+
def set_root_task(task_class)
|
|
35
|
+
@monitor.synchronize do
|
|
36
|
+
return if @root_task
|
|
37
|
+
@root_task = task_class
|
|
38
|
+
@started_at ||= Time.now
|
|
39
|
+
@working_directory ||= Dir.pwd
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset!
|
|
44
|
+
@monitor.synchronize do
|
|
45
|
+
@working_directory = nil
|
|
46
|
+
@started_at = nil
|
|
47
|
+
@root_task = nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taski
|
|
4
|
+
module Execution
|
|
5
|
+
class Coordinator
|
|
6
|
+
def initialize(registry:, analyzer:)
|
|
7
|
+
@registry = registry
|
|
8
|
+
@analyzer = analyzer
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @param task_class [Class] The task class whose dependencies should be started
|
|
12
|
+
def start_dependencies(task_class)
|
|
13
|
+
dependencies = get_dependencies(task_class)
|
|
14
|
+
return if dependencies.empty?
|
|
15
|
+
|
|
16
|
+
dependencies.each do |dep_class|
|
|
17
|
+
start_dependency_execution(dep_class)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param task_class [Class] The task class whose dependencies should be cleaned
|
|
22
|
+
def start_clean_dependencies(task_class)
|
|
23
|
+
dependencies = get_dependencies(task_class)
|
|
24
|
+
return if dependencies.empty?
|
|
25
|
+
|
|
26
|
+
dependencies.each do |dep_class|
|
|
27
|
+
start_dependency_clean(dep_class)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def get_dependencies(task_class)
|
|
34
|
+
if task_class.respond_to?(:cached_dependencies)
|
|
35
|
+
task_class.cached_dependencies
|
|
36
|
+
else
|
|
37
|
+
@analyzer.analyze(task_class)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def start_thread_with(&block)
|
|
42
|
+
thread = Thread.new(&block)
|
|
43
|
+
@registry.register_thread(thread)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def start_dependency_execution(dep_class)
|
|
47
|
+
exported_methods = dep_class.exported_methods
|
|
48
|
+
|
|
49
|
+
exported_methods.each do |method|
|
|
50
|
+
start_thread_with do
|
|
51
|
+
dep_class.public_send(method)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def start_dependency_clean(dep_class)
|
|
57
|
+
start_thread_with do
|
|
58
|
+
dep_class.public_send(:clean)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Taski
|
|
6
|
+
module Execution
|
|
7
|
+
class ParallelProgressDisplay
|
|
8
|
+
SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
9
|
+
|
|
10
|
+
class TaskProgress
|
|
11
|
+
attr_accessor :state, :start_time, :end_time, :error, :duration
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@state = :pending
|
|
15
|
+
@start_time = nil
|
|
16
|
+
@end_time = nil
|
|
17
|
+
@error = nil
|
|
18
|
+
@duration = nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(output: $stdout)
|
|
23
|
+
@output = output
|
|
24
|
+
@tasks = {}
|
|
25
|
+
@monitor = Monitor.new
|
|
26
|
+
@spinner_index = 0
|
|
27
|
+
@renderer_thread = nil
|
|
28
|
+
@running = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param task_class [Class] The task class to register
|
|
32
|
+
def register_task(task_class)
|
|
33
|
+
@monitor.synchronize do
|
|
34
|
+
@tasks[task_class] = TaskProgress.new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param task_class [Class] The task class to check
|
|
39
|
+
# @return [Boolean] true if the task is registered
|
|
40
|
+
def task_registered?(task_class)
|
|
41
|
+
@monitor.synchronize do
|
|
42
|
+
@tasks.key?(task_class)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param task_class [Class] The task class to update
|
|
47
|
+
# @param state [Symbol] The new state (:pending, :running, :completed, :failed)
|
|
48
|
+
# @param duration [Float] Duration in milliseconds (for completed tasks)
|
|
49
|
+
# @param error [Exception] Error object (for failed tasks)
|
|
50
|
+
def update_task(task_class, state:, duration: nil, error: nil)
|
|
51
|
+
@monitor.synchronize do
|
|
52
|
+
progress = @tasks[task_class]
|
|
53
|
+
return unless progress
|
|
54
|
+
|
|
55
|
+
progress.state = state
|
|
56
|
+
progress.duration = duration if duration
|
|
57
|
+
progress.error = error if error
|
|
58
|
+
|
|
59
|
+
case state
|
|
60
|
+
when :running
|
|
61
|
+
progress.start_time = Time.now
|
|
62
|
+
when :completed, :failed
|
|
63
|
+
progress.end_time = Time.now
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param task_class [Class] The task class
|
|
69
|
+
# @return [Symbol] The task state
|
|
70
|
+
def task_state(task_class)
|
|
71
|
+
@monitor.synchronize do
|
|
72
|
+
@tasks[task_class]&.state
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def render
|
|
77
|
+
@monitor.synchronize do
|
|
78
|
+
@tasks.each do |task_class, progress|
|
|
79
|
+
line = format_task_line(task_class, progress)
|
|
80
|
+
@output.puts line
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def start
|
|
86
|
+
return if @running
|
|
87
|
+
|
|
88
|
+
@running = true
|
|
89
|
+
@renderer_thread = Thread.new do
|
|
90
|
+
loop do
|
|
91
|
+
break unless @running
|
|
92
|
+
render_live
|
|
93
|
+
sleep 0.1
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def stop
|
|
99
|
+
return unless @running
|
|
100
|
+
|
|
101
|
+
@running = false
|
|
102
|
+
@renderer_thread&.join
|
|
103
|
+
render_final
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# @return [Array<String>] Array of formatted task lines
|
|
109
|
+
def collect_task_lines
|
|
110
|
+
@tasks.map do |task_class, progress|
|
|
111
|
+
format_task_line(task_class, progress)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def render_live
|
|
116
|
+
return unless @output.tty?
|
|
117
|
+
|
|
118
|
+
@monitor.synchronize do
|
|
119
|
+
@spinner_index += 1
|
|
120
|
+
|
|
121
|
+
lines = collect_task_lines
|
|
122
|
+
|
|
123
|
+
lines.each_with_index do |line, index|
|
|
124
|
+
@output.print "\r\e[K#{line}"
|
|
125
|
+
@output.print "\n" unless index == lines.length - 1
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
@output.print "\e[#{lines.length - 1}A" if lines.length > 1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def render_final
|
|
133
|
+
@monitor.synchronize do
|
|
134
|
+
lines = collect_task_lines
|
|
135
|
+
|
|
136
|
+
if @output.tty? && lines.length > 0
|
|
137
|
+
lines.each_with_index do |_, index|
|
|
138
|
+
@output.print "\r\e[K"
|
|
139
|
+
@output.print "\e[1B" unless index == lines.length - 1
|
|
140
|
+
end
|
|
141
|
+
@output.print "\e[#{lines.length - 1}A" if lines.length > 1
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
lines.each do |line|
|
|
145
|
+
@output.puts line
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @param task_class [Class] The task class
|
|
151
|
+
# @param progress [TaskProgress] The task progress
|
|
152
|
+
# @return [String] Formatted line
|
|
153
|
+
def format_task_line(task_class, progress)
|
|
154
|
+
icon = task_icon(progress.state)
|
|
155
|
+
name = task_class.name || "AnonymousTask"
|
|
156
|
+
details = task_details(progress)
|
|
157
|
+
|
|
158
|
+
"#{icon} #{name}#{details}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @param state [Symbol] The task state
|
|
162
|
+
# @return [String] The icon character
|
|
163
|
+
def task_icon(state)
|
|
164
|
+
case state
|
|
165
|
+
when :completed
|
|
166
|
+
"✅"
|
|
167
|
+
when :failed
|
|
168
|
+
"❌"
|
|
169
|
+
when :running
|
|
170
|
+
spinner_char
|
|
171
|
+
when :pending
|
|
172
|
+
"⏳"
|
|
173
|
+
else
|
|
174
|
+
"❓"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @return [String] Current spinner frame
|
|
179
|
+
def spinner_char
|
|
180
|
+
SPINNER_FRAMES[@spinner_index % SPINNER_FRAMES.length]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @param progress [TaskProgress] The task progress
|
|
184
|
+
# @return [String] Details string
|
|
185
|
+
def task_details(progress)
|
|
186
|
+
case progress.state
|
|
187
|
+
when :completed
|
|
188
|
+
" (#{progress.duration}ms)"
|
|
189
|
+
when :failed
|
|
190
|
+
" (failed)"
|
|
191
|
+
when :running
|
|
192
|
+
" (running)"
|
|
193
|
+
when :pending
|
|
194
|
+
" (pending)"
|
|
195
|
+
else
|
|
196
|
+
""
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|