taski 0.4.0 → 0.4.1

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 +4 -4
  2. data/README.md +49 -110
  3. data/examples/README.md +8 -5
  4. data/examples/context_demo.rb +22 -16
  5. data/lib/taski/context.rb +36 -38
  6. data/lib/taski/static_analysis/analyzer.rb +17 -5
  7. data/lib/taski/static_analysis/visitor.rb +22 -6
  8. data/lib/taski/task.rb +39 -13
  9. data/lib/taski/version.rb +1 -1
  10. data/lib/taski.rb +23 -0
  11. metadata +1 -62
  12. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
  13. data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
  14. data/.gem_rbs_collection/minitest/5.25/.rbs_meta.yaml +0 -9
  15. data/.gem_rbs_collection/minitest/5.25/minitest/abstract_reporter.rbs +0 -52
  16. data/.gem_rbs_collection/minitest/5.25/minitest/assertion.rbs +0 -17
  17. data/.gem_rbs_collection/minitest/5.25/minitest/assertions.rbs +0 -590
  18. data/.gem_rbs_collection/minitest/5.25/minitest/backtrace_filter.rbs +0 -23
  19. data/.gem_rbs_collection/minitest/5.25/minitest/bench_spec.rbs +0 -102
  20. data/.gem_rbs_collection/minitest/5.25/minitest/benchmark.rbs +0 -259
  21. data/.gem_rbs_collection/minitest/5.25/minitest/composite_reporter.rbs +0 -25
  22. data/.gem_rbs_collection/minitest/5.25/minitest/compress.rbs +0 -13
  23. data/.gem_rbs_collection/minitest/5.25/minitest/error_on_warning.rbs +0 -3
  24. data/.gem_rbs_collection/minitest/5.25/minitest/expectation.rbs +0 -2
  25. data/.gem_rbs_collection/minitest/5.25/minitest/expectations.rbs +0 -21
  26. data/.gem_rbs_collection/minitest/5.25/minitest/guard.rbs +0 -64
  27. data/.gem_rbs_collection/minitest/5.25/minitest/mock.rbs +0 -64
  28. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/executor.rbs +0 -46
  29. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test/class_methods.rbs +0 -5
  30. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test.rbs +0 -3
  31. data/.gem_rbs_collection/minitest/5.25/minitest/parallel.rbs +0 -2
  32. data/.gem_rbs_collection/minitest/5.25/minitest/pride_io.rbs +0 -62
  33. data/.gem_rbs_collection/minitest/5.25/minitest/pride_lol.rbs +0 -19
  34. data/.gem_rbs_collection/minitest/5.25/minitest/progress_reporter.rbs +0 -11
  35. data/.gem_rbs_collection/minitest/5.25/minitest/reportable.rbs +0 -53
  36. data/.gem_rbs_collection/minitest/5.25/minitest/reporter.rbs +0 -5
  37. data/.gem_rbs_collection/minitest/5.25/minitest/result.rbs +0 -28
  38. data/.gem_rbs_collection/minitest/5.25/minitest/runnable.rbs +0 -163
  39. data/.gem_rbs_collection/minitest/5.25/minitest/skip.rbs +0 -6
  40. data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl/instance_methods.rbs +0 -48
  41. data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl.rbs +0 -129
  42. data/.gem_rbs_collection/minitest/5.25/minitest/spec.rbs +0 -11
  43. data/.gem_rbs_collection/minitest/5.25/minitest/statistics_reporter.rbs +0 -81
  44. data/.gem_rbs_collection/minitest/5.25/minitest/summary_reporter.rbs +0 -18
  45. data/.gem_rbs_collection/minitest/5.25/minitest/test/lifecycle_hooks.rbs +0 -92
  46. data/.gem_rbs_collection/minitest/5.25/minitest/test.rbs +0 -69
  47. data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_error.rbs +0 -12
  48. data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_warning.rbs +0 -6
  49. data/.gem_rbs_collection/minitest/5.25/minitest/unit/test_case.rbs +0 -3
  50. data/.gem_rbs_collection/minitest/5.25/minitest/unit.rbs +0 -4
  51. data/.gem_rbs_collection/minitest/5.25/minitest.rbs +0 -115
  52. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
  53. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
  54. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
  55. data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
  56. data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -193
  57. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
  58. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
  59. data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
  60. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
  61. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
  62. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
  63. data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
  64. data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
  65. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
  66. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
  67. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
  68. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -129
  69. data/.gem_rbs_collection/rubocop-ast/1.30/.rbs_meta.yaml +0 -9
  70. data/.gem_rbs_collection/rubocop-ast/1.30/rubocop-ast.rbs +0 -771
  71. data/.gem_rbs_collection/simplecov/0.22/.rbs_meta.yaml +0 -9
  72. data/.gem_rbs_collection/simplecov/0.22/simplecov.rbs +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17f3cd1627f0b49f60b2173fcfef71ce6bbc60b6280d1d3b35cf9edb5e89ea9c
4
- data.tar.gz: 25e99fdf4d4fbdc48be47a65764efd0884a71355db16c7d674b796f178bdf900
3
+ metadata.gz: 46cb1222487f928c10241827374311ed65541773bd1cd6d40d8fd2e6ea8af64e
4
+ data.tar.gz: c16aad068cef227130a8051fd71c8847845212dc3f990ae706b2d1b10104a9d6
5
5
  SHA512:
6
- metadata.gz: 19697472cbdff99b1a4a5315be18d274637c1806b225550d3a4acb541771a618a6fd1e14156599335174e12f3dd27d7f714bd0e7c87e0c76d525ca0a3375e361
7
- data.tar.gz: 20bff2f125897ca23dab9a1d78831c14e45fca5f40f2fed29bb6c1114a7d040569b2e46994614049963dcca0cb3831d8cee91ebc3dc7892a7def59e1a965198a
6
+ metadata.gz: a37a616b526c7c67811286e2c50dd5319c9ce98779f4edf495f6337c214c9f0915b46694f4b8edfd5a8a6b7f66ce9fb9d946ffd36cf0be2f2a4991452875a765
7
+ data.tar.gz: ff577241138a18dcf8018c4c0c68ea8137301e33bfa6b31d58e564862eeb8f520a9120c755aa0c6acf61b5c1f18b71bb728dd785627ee278c3c6eee08d31dc30
data/README.md CHANGED
@@ -4,15 +4,11 @@
4
4
  [![Codecov](https://codecov.io/gh/ahogappa/taski/branch/master/graph/badge.svg)](https://codecov.io/gh/ahogappa/taski)
5
5
  [![Gem Version](https://badge.fury.io/rb/taski.svg)](https://badge.fury.io/rb/taski)
6
6
 
7
- > **🚧 Development Status:** Taski is currently under active development. Not yet recommended for production use.
8
-
9
- **Taski** is a Ruby framework for building task dependency graphs with automatic resolution and **parallel execution**.
7
+ **Taski** is a Ruby framework for building task dependency graphs with automatic resolution and parallel execution.
10
8
 
11
9
  > **Name Origin**: "Taski" comes from the Japanese word "襷" (tasuki), a sash used in relay races. Just like how runners pass the sash to the next teammate, tasks in Taski pass dependencies to one another in a continuous chain.
12
10
 
13
- ## Why Taski?
14
-
15
- Build complex workflows by defining tasks and their dependencies - Taski automatically resolves execution order and executes them in parallel.
11
+ ## Features
16
12
 
17
13
  - **Automatic Dependency Resolution**: Dependencies detected via static analysis
18
14
  - **Parallel Execution**: Independent tasks run concurrently for maximum performance
@@ -20,18 +16,16 @@ Build complex workflows by defining tasks and their dependencies - Taski automat
20
16
  - **Real-time Progress**: Visual feedback with parallel task progress display
21
17
  - **Thread-Safe**: Built on Monitor-based synchronization for reliable concurrent execution
22
18
 
23
- ## 🚀 Quick Start
19
+ ## Quick Start
24
20
 
25
21
  ```ruby
26
22
  require 'taski'
27
23
 
28
- # Define tasks with dependencies
29
24
  class DatabaseSetup < Taski::Task
30
25
  exports :connection_string
31
26
 
32
27
  def run
33
28
  @connection_string = "postgresql://localhost/myapp"
34
- puts "Database configured"
35
29
  end
36
30
  end
37
31
 
@@ -40,31 +34,32 @@ class CacheSetup < Taski::Task
40
34
 
41
35
  def run
42
36
  @cache_url = "redis://localhost:6379"
43
- puts "Cache configured"
44
37
  end
45
38
  end
46
39
 
47
40
  class APIServer < Taski::Task
48
41
  def run
49
42
  # Dependencies execute automatically and in parallel
50
- puts "Starting API with #{DatabaseSetup.connection_string}"
51
- puts "Using cache at #{CacheSetup.cache_url}"
43
+ puts "DB: #{DatabaseSetup.connection_string}"
44
+ puts "Cache: #{CacheSetup.cache_url}"
52
45
  end
53
46
  end
54
47
 
55
- # Run any task - dependencies resolve and execute in parallel
56
48
  APIServer.run
57
- # => Database configured
58
- # => Cache configured
59
- # => Starting API with postgresql://localhost/myapp
60
- # => Using cache at redis://localhost:6379
49
+ ```
50
+
51
+ ## Installation
52
+
53
+ ```ruby
54
+ gem 'taski'
61
55
  ```
62
56
 
63
57
  ## Core Concepts
64
58
 
65
- Taski provides two complementary APIs:
59
+ ### Exports - Value Sharing Between Tasks
60
+
61
+ Share computed values between tasks:
66
62
 
67
- ### 1. Exports API - Value Sharing Between Tasks
68
63
  ```ruby
69
64
  class Config < Taski::Task
70
65
  exports :app_name, :port
@@ -77,26 +72,27 @@ end
77
72
 
78
73
  class Server < Taski::Task
79
74
  def run
80
- # Automatically depends on Config task
81
75
  puts "Starting #{Config.app_name} on port #{Config.port}"
82
76
  end
83
77
  end
84
78
  ```
85
79
 
86
- ### 2. Section API - Runtime Implementation Selection
80
+ ### Section - Runtime Implementation Selection
81
+
82
+ Switch implementations based on environment:
83
+
87
84
  ```ruby
88
85
  class DatabaseSection < Taski::Section
89
86
  interfaces :host, :port
90
87
 
91
- # Nested classes automatically inherit interfaces - no exports needed
92
- class ProductionDB < Taski::Task
88
+ class Production < Taski::Task
93
89
  def run
94
90
  @host = "prod.example.com"
95
91
  @port = 5432
96
92
  end
97
93
  end
98
94
 
99
- class LocalDB < Taski::Task
95
+ class Development < Taski::Task
100
96
  def run
101
97
  @host = "localhost"
102
98
  @port = 5432
@@ -104,97 +100,67 @@ class DatabaseSection < Taski::Section
104
100
  end
105
101
 
106
102
  def impl
107
- # Select implementation at runtime
108
- ENV['RAILS_ENV'] == 'production' ? ProductionDB : LocalDB
103
+ ENV['RAILS_ENV'] == 'production' ? Production : Development
109
104
  end
110
105
  end
111
106
 
112
107
  class App < Taski::Task
113
108
  def run
114
- # Access through Section - implementation selected at runtime
115
109
  puts "Connecting to #{DatabaseSection.host}:#{DatabaseSection.port}"
116
110
  end
117
111
  end
118
-
119
- App.run
120
- # In development (RAILS_ENV != 'production'):
121
- # => Connecting to localhost:5432
122
-
123
- # In production (RAILS_ENV == 'production'):
124
- # => Connecting to prod.example.com:5432
125
112
  ```
126
113
 
127
- **When to use each:**
128
- - **Exports**: Share computed values or side effects between tasks
129
- - **Section**: Switch implementations based on environment or conditions
114
+ > **Note**: Nested implementation classes automatically inherit Section's `interfaces` as `exports`.
130
115
 
131
- > **Note**: When implementation classes are nested inside a Section, they automatically inherit the Section's `interfaces` as `exports`. External implementations must declare `exports` explicitly.
116
+ ## Advanced Usage
132
117
 
133
- ### 3. Context - Runtime Information Access
118
+ ### Context - Runtime Information and Options
134
119
 
135
- Access execution context information from any task:
120
+ Pass custom options and access execution context from any task:
136
121
 
137
122
  ```ruby
138
123
  class DeployTask < Taski::Task
139
124
  def run
140
- puts "Working directory: #{Taski::Context.working_directory}"
141
- puts "Started at: #{Taski::Context.started_at}"
142
- puts "Root task: #{Taski::Context.root_task}"
125
+ # User-defined options
126
+ env = Taski.context[:env]
127
+ debug = Taski.context.fetch(:debug, false)
128
+
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}"
133
+ puts "Deploying to: #{env}"
143
134
  end
144
135
  end
145
- ```
146
-
147
- **Available context:**
148
- - `working_directory`: Directory where execution started
149
- - `started_at`: Time when execution began
150
- - `root_task`: The first task class that was called
151
136
 
152
- > **Note**: Context is not included in dependency analysis - it provides runtime information only.
153
-
154
- > **Important**: Tasks must be defined in source files, not dynamically with `Class.new`. Static analysis requires actual source files to detect dependencies.
137
+ # Pass options when running
138
+ DeployTask.run(context: { env: "production", debug: true })
139
+ ```
155
140
 
156
- ### 4. Re-execution with `new`
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
157
148
 
158
- By default, task results are cached. Use `new` to create a fresh instance for re-execution:
149
+ ### Re-execution
159
150
 
160
151
  ```ruby
161
- class RandomTask < Taski::Task
162
- exports :value
163
-
164
- def run
165
- @value = rand(1000)
166
- end
167
- end
168
-
169
- # Cached execution (same result)
152
+ # Cached execution (default)
170
153
  RandomTask.value # => 42
171
154
  RandomTask.value # => 42 (cached)
172
155
 
173
- # Fresh execution with new
156
+ # Fresh execution
174
157
  RandomTask.new.run # => 123 (new instance)
175
- RandomTask.new.run # => 456 (another new instance)
176
158
 
177
159
  # Reset all caches
178
160
  RandomTask.reset!
179
- RandomTask.value # => 789 (fresh after reset)
180
161
  ```
181
162
 
182
- **When to use:**
183
- - `TaskClass.run` / `TaskClass.value`: Normal execution with caching (recommended for dependency graphs)
184
- - `TaskClass.new.run`: Re-execute only this task (dependencies still use cache)
185
- - `TaskClass.reset!`: Clear all caches and re-execute everything
186
-
187
- ## Key Features
188
-
189
- - **Parallel Execution**: Independent tasks run concurrently using threads
190
- - **Static Analysis**: Dependencies detected automatically via Prism AST parsing
191
- - **Thread-Safe**: Monitor-based synchronization ensures safe concurrent access
192
- - **Progress Display**: Real-time visual feedback with spinner animations and timing
193
- - **Tree Visualization**: See your dependency graph structure
194
- - **Graceful Abort**: Stop execution cleanly without starting new tasks (Ctrl+C)
195
- - **Runtime Context**: Access execution information from any task
196
-
197
- ### Parallel Progress Display
163
+ ### Progress Display
198
164
 
199
165
  Enable real-time progress visualization:
200
166
 
@@ -202,14 +168,11 @@ Enable real-time progress visualization:
202
168
  TASKI_FORCE_PROGRESS=1 ruby your_script.rb
203
169
  ```
204
170
 
205
- Output example:
206
171
  ```
207
172
  ⠋ DatabaseSetup (running)
208
173
  ⠙ CacheSetup (running)
209
174
  ✅ DatabaseSetup (123.4ms)
210
175
  ✅ CacheSetup (98.2ms)
211
- ⠸ WebServer (running)
212
- ✅ WebServer (45.1ms)
213
176
  ```
214
177
 
215
178
  ### Tree Visualization
@@ -222,33 +185,13 @@ puts WebServer.tree
222
185
  # => └── Cache
223
186
  ```
224
187
 
225
- ## 📦 Installation
226
-
227
- ```ruby
228
- gem 'taski'
229
- ```
230
-
231
- ```bash
232
- bundle install
233
- ```
234
-
235
- ## 🧪 Testing
188
+ ## Development
236
189
 
237
190
  ```bash
238
191
  rake test # Run all tests
239
192
  rake standard # Check code style
240
193
  ```
241
194
 
242
- ## 📚 Learn More
243
-
244
- - **[Examples](examples/)**: Practical examples from basic to advanced patterns
245
- - **[Tests](test/)**: Comprehensive test suite showing real-world usage
246
- - **[Source Code](lib/taski/)**: Clean, well-documented implementation
247
- - `lib/taski/task.rb` - Core Task implementation with exports API
248
- - `lib/taski/section.rb` - Section API for runtime selection
249
- - `lib/taski/execution/` - Parallel execution engine
250
- - `lib/taski/static_analysis/` - Prism-based dependency analyzer
251
-
252
195
  ## Support
253
196
 
254
197
  Bug reports and pull requests welcome at https://github.com/ahogappa/taski.
@@ -256,7 +199,3 @@ Bug reports and pull requests welcome at https://github.com/ahogappa/taski.
256
199
  ## License
257
200
 
258
201
  MIT License
259
-
260
- ---
261
-
262
- **Taski** - Parallel task execution with automatic dependency resolution. 🚀
data/examples/README.md CHANGED
@@ -34,18 +34,21 @@ ruby examples/section_demo.rb
34
34
 
35
35
  ---
36
36
 
37
- ### 3. context_demo.rb - Runtime Context
37
+ ### 3. context_demo.rb - Runtime Context and Options
38
38
 
39
- Access execution context information from any task.
39
+ Access execution context and pass custom options to tasks.
40
40
 
41
41
  ```bash
42
42
  ruby examples/context_demo.rb
43
43
  ```
44
44
 
45
45
  **Covers:**
46
- - `Taski::Context.working_directory`
47
- - `Taski::Context.started_at`
48
- - `Taski::Context.root_task`
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`
49
52
 
50
53
  ---
51
54
 
@@ -7,6 +7,7 @@
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
11
  #
11
12
  # Run: ruby examples/context_demo.rb
12
13
 
@@ -21,13 +22,15 @@ class SetupTask < Taski::Task
21
22
 
22
23
  def run
23
24
  puts "Setup running..."
24
- puts " Working directory: #{Taski::Context.working_directory}"
25
- puts " Started at: #{Taski::Context.started_at}"
26
- puts " Root task: #{Taski::Context.root_task}"
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]}"
27
29
 
28
30
  @setup_info = {
29
- directory: Taski::Context.working_directory,
30
- timestamp: Taski::Context.started_at
31
+ directory: Taski.context.working_directory,
32
+ timestamp: Taski.context.started_at,
33
+ env: Taski.context[:env]
31
34
  }
32
35
  end
33
36
  end
@@ -38,11 +41,12 @@ class FileProcessor < Taski::Task
38
41
 
39
42
  def run
40
43
  # Use context to determine output location
41
- base_dir = Taski::Context.working_directory
42
- @output_path = File.join(base_dir, "tmp", "output.txt")
44
+ base_dir = Taski.context.working_directory
45
+ env = Taski.context.fetch(:env, "development")
46
+ @output_path = File.join(base_dir, "tmp", env, "output.txt")
43
47
 
44
48
  puts "FileProcessor: Would write to #{@output_path}"
45
- puts " (relative to working directory)"
49
+ puts " (relative to working directory, env: #{env})"
46
50
  end
47
51
  end
48
52
 
@@ -51,11 +55,12 @@ class TimingTask < Taski::Task
51
55
  exports :duration_info
52
56
 
53
57
  def run
54
- start_time = Taski::Context.started_at
58
+ start_time = Taski.context.started_at
55
59
  current_time = Time.now
56
60
  elapsed = current_time - start_time
57
61
 
58
62
  puts "TimingTask: #{elapsed.round(3)}s since execution started"
63
+ puts " Debug mode: #{Taski.context.fetch(:debug, false)}"
59
64
 
60
65
  @duration_info = {
61
66
  started: start_time,
@@ -71,7 +76,8 @@ class MainTask < Taski::Task
71
76
 
72
77
  def run
73
78
  puts "\nMainTask executing..."
74
- puts " Root task is: #{Taski::Context.root_task}"
79
+ puts " Root task is: #{Taski.context.root_task}"
80
+ puts " Environment: #{Taski.context[:env]}"
75
81
 
76
82
  # Access dependencies
77
83
  setup = SetupTask.setup_info
@@ -82,7 +88,7 @@ class MainTask < Taski::Task
82
88
  setup: setup,
83
89
  output_path: output,
84
90
  timing: timing,
85
- root_task: Taski::Context.root_task.to_s
91
+ root_task: Taski.context.root_task.to_s
86
92
  }
87
93
 
88
94
  puts "\nExecution Summary:"
@@ -92,15 +98,15 @@ class MainTask < Taski::Task
92
98
  end
93
99
  end
94
100
 
95
- puts "\n1. Running MainTask (context will show MainTask as root)"
101
+ puts "\n1. Running MainTask with context options"
96
102
  puts "-" * 40
97
- MainTask.run
103
+ MainTask.run(context: {env: "production", debug: true})
98
104
 
99
105
  puts "\n" + "=" * 40
100
- puts "\n2. Running SetupTask directly (context will show SetupTask as root)"
106
+ puts "\n2. Running SetupTask directly with different context"
101
107
  puts "-" * 40
102
108
  SetupTask.reset!
103
- SetupTask.run
109
+ SetupTask.run(context: {env: "staging"})
104
110
 
105
111
  puts "\n" + "=" * 40
106
112
  puts "\n3. Dependency Tree"
@@ -109,4 +115,4 @@ puts MainTask.tree
109
115
 
110
116
  puts "\n" + "=" * 40
111
117
  puts "Context API demonstration complete!"
112
- puts "Note: Context provides runtime information without affecting dependency analysis."
118
+ puts "Note: Context provides runtime information and user options without affecting dependency analysis."
data/lib/taski/context.rb CHANGED
@@ -3,50 +3,48 @@
3
3
  require "monitor"
4
4
 
5
5
  module Taski
6
- # Runtime context accessible from any task (not included in dependency analysis).
6
+ # Runtime context accessible from any task.
7
+ # Holds user-defined options and execution metadata.
8
+ # Context is immutable after creation - options cannot be modified during task execution.
7
9
  class Context
8
- @monitor = Monitor.new
10
+ attr_reader :started_at, :working_directory, :root_task
9
11
 
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
12
+ # @param options [Hash] User-defined options (immutable after creation)
13
+ # @param root_task [Class] The root task class that initiated execution
14
+ def initialize(options:, root_task:)
15
+ @options = options.dup.freeze
16
+ @root_task = root_task
17
+ @started_at = Time.now
18
+ @working_directory = Dir.pwd
19
+ end
24
20
 
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
21
+ # Get a user-defined option value
22
+ # @param key [Symbol, String] The option key
23
+ # @return [Object, nil] The option value or nil if not set
24
+ def [](key)
25
+ @options[key]
26
+ end
31
27
 
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
28
+ # Get a user-defined option value with a default
29
+ # @param key [Symbol, String] The option key
30
+ # @param default [Object] Default value if key is not present
31
+ # @yield Block to compute default value if key is not present
32
+ # @return [Object] The option value or default
33
+ def fetch(key, default = nil, &block)
34
+ if @options.key?(key)
35
+ @options[key]
36
+ elsif block
37
+ block.call
38
+ else
39
+ default
41
40
  end
41
+ end
42
42
 
43
- def reset!
44
- @monitor.synchronize do
45
- @working_directory = nil
46
- @started_at = nil
47
- @root_task = nil
48
- end
49
- end
43
+ # Check if a user-defined option key exists
44
+ # @param key [Symbol, String] The option key
45
+ # @return [Boolean] true if the key exists
46
+ def key?(key)
47
+ @options.key?(key)
50
48
  end
51
49
  end
52
50
  end
@@ -9,26 +9,38 @@ module Taski
9
9
  # @param task_class [Class] The task class to analyze
10
10
  # @return [Set<Class>] Set of task classes that are dependencies
11
11
  def self.analyze(task_class)
12
- source_location = extract_run_method_location(task_class)
12
+ target_method = target_method_for(task_class)
13
+ source_location = extract_method_location(task_class, target_method)
13
14
  return Set.new unless source_location
14
15
 
15
16
  file_path, _line_number = source_location
16
17
  parse_result = Prism.parse_file(file_path)
17
18
 
18
- visitor = Visitor.new(task_class)
19
+ visitor = Visitor.new(task_class, target_method)
19
20
  visitor.visit(parse_result.value)
20
21
  visitor.dependencies
21
22
  end
22
23
 
23
24
  # @param task_class [Class] The task class
25
+ # @return [Symbol] The method name to analyze (:run for Task, :impl for Section)
26
+ def self.target_method_for(task_class)
27
+ if defined?(Taski::Section) && task_class < Taski::Section
28
+ :impl
29
+ else
30
+ :run
31
+ end
32
+ end
33
+
34
+ # @param task_class [Class] The task class
35
+ # @param method_name [Symbol] The method name to extract location for
24
36
  # @return [Array<String, Integer>, nil] File path and line number, or nil
25
- def self.extract_run_method_location(task_class)
26
- task_class.instance_method(:run).source_location
37
+ def self.extract_method_location(task_class, method_name)
38
+ task_class.instance_method(method_name).source_location
27
39
  rescue NameError
28
40
  nil
29
41
  end
30
42
 
31
- private_class_method :extract_run_method_location
43
+ private_class_method :target_method_for, :extract_method_location
32
44
  end
33
45
  end
34
46
  end
@@ -7,11 +7,12 @@ module Taski
7
7
  class Visitor < Prism::Visitor
8
8
  attr_reader :dependencies
9
9
 
10
- def initialize(target_task_class)
10
+ def initialize(target_task_class, target_method = :run)
11
11
  super()
12
12
  @target_task_class = target_task_class
13
+ @target_method = target_method
13
14
  @dependencies = Set.new
14
- @in_target_run_method = false
15
+ @in_target_method = false
15
16
  @current_namespace_path = []
16
17
  end
17
18
 
@@ -24,17 +25,27 @@ module Taski
24
25
  end
25
26
 
26
27
  def visit_def_node(node)
27
- if node.name == :run && in_target_class?
28
- @in_target_run_method = true
28
+ if node.name == @target_method && in_target_class?
29
+ @in_target_method = true
29
30
  super
30
- @in_target_run_method = false
31
+ @in_target_method = false
31
32
  else
32
33
  super
33
34
  end
34
35
  end
35
36
 
36
37
  def visit_call_node(node)
37
- detect_task_dependency(node) if @in_target_run_method
38
+ detect_task_dependency(node) if @in_target_method
39
+ super
40
+ end
41
+
42
+ def visit_constant_read_node(node)
43
+ detect_return_constant(node) if @in_target_method && @target_method == :impl
44
+ super
45
+ end
46
+
47
+ def visit_constant_path_node(node)
48
+ detect_return_constant(node) if @in_target_method && @target_method == :impl
38
49
  super
39
50
  end
40
51
 
@@ -62,6 +73,11 @@ module Taski
62
73
  resolve_and_add_dependency(constant_name) if constant_name
63
74
  end
64
75
 
76
+ def detect_return_constant(node)
77
+ constant_name = node.slice
78
+ resolve_and_add_dependency(constant_name)
79
+ end
80
+
65
81
  def extract_receiver_constant(receiver)
66
82
  case receiver
67
83
  when Prism::ConstantReadNode, Prism::ConstantPathNode