taski 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/.standard.yml +9 -0
- data/README.md +189 -32
- data/Rakefile +7 -1
- data/examples/complex_example.rb +107 -0
- data/examples/readme_example.rb +30 -0
- data/lib/taski/dependency_analyzer.rb +172 -0
- data/lib/taski/exceptions.rb +14 -0
- data/lib/taski/logger.rb +202 -0
- data/lib/taski/reference.rb +40 -0
- data/lib/taski/task/base.rb +60 -0
- data/lib/taski/task/define_api.rb +138 -0
- data/lib/taski/task/dependency_resolver.rb +138 -0
- data/lib/taski/task/exports_api.rb +31 -0
- data/lib/taski/task/instance_management.rb +176 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +34 -137
- data/sig/taski.rbs +97 -2
- metadata +30 -5
- data/lib/taski/utils.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e44ea93464ceaf8994d761cec75cf4d32df7cdb8f90d25b4cfcae5be7f8a586
|
4
|
+
data.tar.gz: 8a1b5fae85bbffb28756f2e13b3f98c2cb8023c76346b5da722ac55e87ef3cc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '037285b1bc19edd6d674f6748674fa8443c0cb40d5199de86bdfa3696c1597308d69ff5236a7114c43cf666a27e62f5c4111c5a36301736c4fdac98cdd65b4c4'
|
7
|
+
data.tar.gz: afa967d630b56f9de1f4974879533572d06c42d574dce6bea69da9635ce69e06251af555168ff0b3475e95d5a1d6d07e8951e6052f0f6e04291d07b15d23239b
|
data/.standard.yml
ADDED
data/README.md
CHANGED
@@ -1,68 +1,225 @@
|
|
1
1
|
# Taski
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
[](https://github.com/ahogappa/taski/actions/workflows/ci.yml)
|
4
|
+
[](https://codecov.io/gh/ahogappa/taski)
|
5
|
+
[](https://badge.fury.io/rb/taski)
|
5
6
|
|
6
|
-
|
7
|
+
> **🚧 Development Status:** Taski is currently under active development. Not yet recommended for production use.
|
7
8
|
|
8
|
-
|
9
|
+
**Taski** is a Ruby framework for building task dependency graphs with automatic resolution and execution. It provides two APIs: static dependencies through **Exports** and dynamic dependencies through **Define**.
|
9
10
|
|
10
|
-
>
|
11
|
+
> **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.
|
11
12
|
|
12
|
-
|
13
|
+
## 🚀 Quick Start
|
13
14
|
|
14
|
-
|
15
|
+
```ruby
|
16
|
+
require 'taski'
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
- Reverse execution for cleanup
|
20
|
-
- Built entirely in Ruby
|
18
|
+
# Static dependency using Exports API
|
19
|
+
class DatabaseSetup < Taski::Task
|
20
|
+
exports :connection_string
|
21
21
|
|
22
|
-
|
22
|
+
def build
|
23
|
+
@connection_string = "postgresql://localhost/myapp"
|
24
|
+
puts "Database configured"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class APIServer < Taski::Task
|
29
|
+
def build
|
30
|
+
puts "Starting API with #{DatabaseSetup.connection_string}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
APIServer.build
|
35
|
+
# => Database configured
|
36
|
+
# => Starting API with postgresql://localhost/myapp
|
37
|
+
```
|
38
|
+
|
39
|
+
## 📚 API Guide
|
40
|
+
|
41
|
+
### Exports API - Static Dependencies
|
42
|
+
|
43
|
+
For simple, predictable dependencies:
|
23
44
|
|
24
45
|
```ruby
|
25
|
-
class
|
26
|
-
|
46
|
+
class ConfigLoader < Taski::Task
|
47
|
+
exports :app_name, :version
|
27
48
|
|
28
49
|
def build
|
29
|
-
|
50
|
+
@app_name = "MyApp"
|
51
|
+
@version = "1.0.0"
|
52
|
+
puts "Config loaded: #{@app_name} v#{@version}"
|
30
53
|
end
|
31
54
|
end
|
32
55
|
|
33
|
-
class
|
34
|
-
|
56
|
+
class Deployment < Taski::Task
|
57
|
+
def build
|
58
|
+
@deploy_url = "https://#{ConfigLoader.app_name}.example.com"
|
59
|
+
puts "Deploying to #{@deploy_url}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Deployment.build
|
64
|
+
# => Config loaded: MyApp v1.0.0
|
65
|
+
# => Deploying to https://MyApp.example.com
|
66
|
+
```
|
67
|
+
|
68
|
+
### Define API - Dynamic Dependencies
|
69
|
+
|
70
|
+
For dependencies that change based on runtime conditions:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class EnvironmentConfig < Taski::Task
|
74
|
+
define :database_service, -> {
|
75
|
+
case ENV['RAILS_ENV']
|
76
|
+
when 'production'
|
77
|
+
"production-db.example.com"
|
78
|
+
else
|
79
|
+
"localhost:5432"
|
80
|
+
end
|
81
|
+
}
|
35
82
|
|
36
83
|
def build
|
37
|
-
puts
|
84
|
+
puts "Using database: #{database_service}"
|
85
|
+
puts "Environment: #{ENV['RAILS_ENV'] || 'development'}"
|
38
86
|
end
|
39
87
|
end
|
40
88
|
|
41
|
-
|
42
|
-
# =>
|
43
|
-
# =>
|
89
|
+
EnvironmentConfig.build
|
90
|
+
# => Using database: localhost:5432
|
91
|
+
# => Environment: development
|
92
|
+
|
93
|
+
ENV['RAILS_ENV'] = 'production'
|
94
|
+
EnvironmentConfig.reset!
|
95
|
+
EnvironmentConfig.build
|
96
|
+
# => Using database: production-db.example.com
|
97
|
+
# => Environment: production
|
44
98
|
```
|
45
99
|
|
46
|
-
|
100
|
+
### When to Use Each API
|
47
101
|
|
48
|
-
|
102
|
+
- **Define API**: Best for dynamic runtime dependencies. Cannot contain side effects in definition blocks.
|
103
|
+
- **Exports API**: Ideal for static dependencies. Supports side effects in build methods.
|
49
104
|
|
50
|
-
|
51
|
-
|
105
|
+
| Use Case | API | Example |
|
106
|
+
|----------|-----|---------|
|
107
|
+
| Configuration values | Exports | File paths, settings |
|
108
|
+
| Environment-specific logic | Define | Different services per env |
|
109
|
+
| Side effects | Exports | Database connections, I/O |
|
110
|
+
| Conditional processing | Define | Algorithm selection |
|
111
|
+
|
112
|
+
## ✨ Key Features
|
113
|
+
|
114
|
+
- **Automatic Dependency Resolution**: Dependencies detected through static analysis
|
115
|
+
- **Thread-Safe**: Safe for concurrent access
|
116
|
+
- **Circular Dependency Detection**: Clear error messages with detailed paths
|
117
|
+
- **Granular Execution**: Build individual tasks or complete graphs
|
118
|
+
- **Memory Management**: Built-in reset mechanisms
|
119
|
+
|
120
|
+
### Granular Task Execution
|
121
|
+
|
122
|
+
Execute any task individually - Taski builds only required dependencies:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
# Build specific components
|
126
|
+
ConfigLoader.build # Builds only ConfigLoader
|
127
|
+
# => Config loaded: MyApp v1.0.0
|
128
|
+
|
129
|
+
EnvironmentConfig.build # Builds EnvironmentConfig and its dependencies
|
130
|
+
# => Using database: localhost:5432
|
131
|
+
# => Environment: development
|
132
|
+
|
133
|
+
# Access values (triggers build if needed)
|
134
|
+
puts ConfigLoader.version # Builds ConfigLoader if not built
|
135
|
+
# => 1.0.0
|
136
|
+
```
|
137
|
+
|
138
|
+
### Lifecycle Management
|
139
|
+
|
140
|
+
Tasks can define both build and clean methods. Clean operations run in reverse dependency order:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
class DatabaseSetup < Taski::Task
|
144
|
+
exports :connection
|
145
|
+
|
146
|
+
def build
|
147
|
+
@connection = "db-connection"
|
148
|
+
puts "Database connected"
|
149
|
+
end
|
150
|
+
|
151
|
+
def clean
|
152
|
+
puts "Database disconnected"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class WebServer < Taski::Task
|
157
|
+
def build
|
158
|
+
puts "Web server started with #{DatabaseSetup.connection}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def clean
|
162
|
+
puts "Web server stopped"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
WebServer.build
|
167
|
+
# => Database connected
|
168
|
+
# => Web server started with db-connection
|
169
|
+
|
170
|
+
WebServer.clean
|
171
|
+
# => Web server stopped
|
172
|
+
# => Database disconnected
|
173
|
+
```
|
174
|
+
|
175
|
+
### Error Handling
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
begin
|
179
|
+
TaskWithCircularDep.build
|
180
|
+
rescue Taski::CircularDependencyError => e
|
181
|
+
puts "Circular dependency: #{e.message}"
|
182
|
+
end
|
183
|
+
# => Circular dependency: Circular dependency detected!
|
184
|
+
# => Cycle: TaskA → TaskB → TaskA
|
185
|
+
# =>
|
186
|
+
# => The dependency chain is:
|
187
|
+
# => 1. TaskA is trying to build → TaskB
|
188
|
+
# => 2. TaskB is trying to build → TaskA
|
52
189
|
```
|
53
190
|
|
54
|
-
|
191
|
+
## 📦 Installation
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
gem 'taski'
|
195
|
+
```
|
55
196
|
|
56
197
|
```bash
|
57
|
-
|
198
|
+
bundle install
|
58
199
|
```
|
59
200
|
|
60
|
-
##
|
201
|
+
## 🧪 Testing
|
202
|
+
|
203
|
+
```bash
|
204
|
+
bundle exec rake test
|
205
|
+
```
|
61
206
|
|
62
|
-
|
207
|
+
## 🏛️ Architecture
|
63
208
|
|
64
|
-
|
209
|
+
- **Task Base**: Core framework
|
210
|
+
- **Exports API**: Static dependency resolution
|
211
|
+
- **Define API**: Dynamic dependency resolution
|
212
|
+
- **Instance Management**: Thread-safe lifecycle
|
213
|
+
- **Dependency Resolver**: Topological sorting
|
65
214
|
|
66
215
|
## Contributing
|
67
216
|
|
68
|
-
Bug reports and pull requests
|
217
|
+
Bug reports and pull requests welcome at https://github.com/ahogappa/taski.
|
218
|
+
|
219
|
+
## License
|
220
|
+
|
221
|
+
MIT License
|
222
|
+
|
223
|
+
---
|
224
|
+
|
225
|
+
**Taski** - Build dependency graphs with elegant Ruby code. 🚀
|
data/Rakefile
CHANGED
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Complex example from README showing both APIs
|
3
|
+
|
4
|
+
require_relative "../lib/taski"
|
5
|
+
|
6
|
+
# Mock classes for the example
|
7
|
+
class ProductionDB < Taski::Task
|
8
|
+
exports :connection_string
|
9
|
+
def build
|
10
|
+
@connection_string = "postgres://prod-server/app"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestDB < Taski::Task
|
15
|
+
exports :connection_string
|
16
|
+
def build
|
17
|
+
@connection_string = "postgres://test-server/app_test"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module FeatureFlag
|
22
|
+
def self.enabled?(flag)
|
23
|
+
ENV["FEATURE_#{flag.to_s.upcase}"] == "true"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class RedisService < Taski::Task
|
28
|
+
exports :configuration
|
29
|
+
def build
|
30
|
+
@configuration = "redis://localhost:6379"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Environment configuration using Define API
|
35
|
+
class Environment < Taski::Task
|
36
|
+
define :database_url, -> {
|
37
|
+
case ENV["RAILS_ENV"]
|
38
|
+
when "production"
|
39
|
+
ProductionDB.connection_string
|
40
|
+
when "test"
|
41
|
+
TestDB.connection_string
|
42
|
+
else
|
43
|
+
"sqlite3://development.db"
|
44
|
+
end
|
45
|
+
}
|
46
|
+
|
47
|
+
define :redis_config, -> {
|
48
|
+
if FeatureFlag.enabled?(:redis_cache)
|
49
|
+
RedisService.configuration
|
50
|
+
end
|
51
|
+
}
|
52
|
+
|
53
|
+
def build
|
54
|
+
# Environment configuration is handled by define blocks
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Static configuration using Exports API
|
59
|
+
class AppConfig < Taski::Task
|
60
|
+
exports :app_name, :version, :port
|
61
|
+
|
62
|
+
def build
|
63
|
+
@app_name = "MyWebApp"
|
64
|
+
@version = "2.1.0"
|
65
|
+
@port = ENV.fetch("PORT", 3000).to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Application startup combining both APIs
|
70
|
+
class Application < Taski::Task
|
71
|
+
def build
|
72
|
+
puts "Starting #{AppConfig.app_name} v#{AppConfig.version}"
|
73
|
+
puts "Database: #{Environment.database_url}"
|
74
|
+
puts "Redis: #{Environment.redis_config || "disabled"}"
|
75
|
+
puts "Port: #{AppConfig.port}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def clean
|
79
|
+
puts "Shutting down #{AppConfig.app_name}..."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Test different environments
|
84
|
+
puts "=== Complex Example ==="
|
85
|
+
|
86
|
+
puts "\n1. Development Environment (default):"
|
87
|
+
ENV.delete("RAILS_ENV")
|
88
|
+
ENV.delete("FEATURE_REDIS_CACHE")
|
89
|
+
Application.build
|
90
|
+
Application.reset!
|
91
|
+
|
92
|
+
puts "\n2. Test Environment:"
|
93
|
+
ENV["RAILS_ENV"] = "test"
|
94
|
+
# Reset Environment to re-evaluate define blocks
|
95
|
+
Environment.reset!
|
96
|
+
Application.build
|
97
|
+
Application.reset!
|
98
|
+
|
99
|
+
puts "\n3. Production with Redis:"
|
100
|
+
ENV["RAILS_ENV"] = "production"
|
101
|
+
ENV["FEATURE_REDIS_CACHE"] = "true"
|
102
|
+
# Reset Environment to re-evaluate define blocks
|
103
|
+
Environment.reset!
|
104
|
+
Application.build
|
105
|
+
|
106
|
+
puts "\n4. Cleanup:"
|
107
|
+
Application.clean
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Quick Start example from README
|
3
|
+
|
4
|
+
require_relative "../lib/taski"
|
5
|
+
|
6
|
+
# Simple static dependency using Exports API
|
7
|
+
class DatabaseSetup < Taski::Task
|
8
|
+
exports :connection_string
|
9
|
+
|
10
|
+
def build
|
11
|
+
@connection_string = "postgresql://localhost/myapp"
|
12
|
+
puts "Database configured"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class APIServer < Taski::Task
|
17
|
+
exports :port
|
18
|
+
|
19
|
+
def build
|
20
|
+
# Automatic dependency: DatabaseSetup will be built first
|
21
|
+
puts "Starting API with #{DatabaseSetup.connection_string}"
|
22
|
+
@port = 3000
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute - dependencies are resolved automatically
|
27
|
+
puts "=== Quick Start Example ==="
|
28
|
+
APIServer.build
|
29
|
+
|
30
|
+
puts "\nResult: APIServer running on port #{APIServer.port}"
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prism"
|
4
|
+
|
5
|
+
module Taski
|
6
|
+
module DependencyAnalyzer
|
7
|
+
class << self
|
8
|
+
def analyze_method(klass, method_name)
|
9
|
+
return [] unless klass.instance_methods(false).include?(method_name)
|
10
|
+
|
11
|
+
method = klass.instance_method(method_name)
|
12
|
+
source_location = method.source_location
|
13
|
+
return [] unless source_location
|
14
|
+
|
15
|
+
file_path, line_number = source_location
|
16
|
+
return [] unless File.exist?(file_path)
|
17
|
+
|
18
|
+
begin
|
19
|
+
result = Prism.parse_file(file_path)
|
20
|
+
|
21
|
+
unless result.success?
|
22
|
+
Taski.logger.error("Parse errors in source file",
|
23
|
+
file: file_path,
|
24
|
+
errors: result.errors.map(&:message),
|
25
|
+
method: "#{klass}##{method_name}")
|
26
|
+
return []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Handle warnings if present
|
30
|
+
if result.warnings.any?
|
31
|
+
Taski.logger.warn("Parse warnings in source file",
|
32
|
+
file: file_path,
|
33
|
+
warnings: result.warnings.map(&:message),
|
34
|
+
method: "#{klass}##{method_name}")
|
35
|
+
end
|
36
|
+
|
37
|
+
dependencies = []
|
38
|
+
method_node = find_method_node(result.value, method_name, line_number)
|
39
|
+
|
40
|
+
if method_node
|
41
|
+
visitor = TaskDependencyVisitor.new
|
42
|
+
visitor.visit(method_node)
|
43
|
+
dependencies = visitor.dependencies
|
44
|
+
end
|
45
|
+
|
46
|
+
dependencies.uniq
|
47
|
+
rescue IOError, SystemCallError => e
|
48
|
+
Taski.logger.error("Failed to read source file",
|
49
|
+
file: file_path,
|
50
|
+
error: e.message,
|
51
|
+
method: "#{klass}##{method_name}")
|
52
|
+
[]
|
53
|
+
rescue => e
|
54
|
+
Taski.logger.error("Failed to analyze method dependencies",
|
55
|
+
class: klass.name,
|
56
|
+
method: method_name,
|
57
|
+
error: e.message,
|
58
|
+
error_class: e.class.name)
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def find_method_node(node, method_name, target_line)
|
66
|
+
return nil unless node
|
67
|
+
|
68
|
+
case node
|
69
|
+
when Prism::DefNode
|
70
|
+
if node.name == method_name && node.location.start_line <= target_line && node.location.end_line >= target_line
|
71
|
+
return node
|
72
|
+
end
|
73
|
+
when Prism::ClassNode, Prism::ModuleNode
|
74
|
+
if node.respond_to?(:body)
|
75
|
+
return find_method_node(node.body, method_name, target_line)
|
76
|
+
end
|
77
|
+
when Prism::StatementsNode
|
78
|
+
node.body.each do |child|
|
79
|
+
result = find_method_node(child, method_name, target_line)
|
80
|
+
return result if result
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Recursively search child nodes
|
85
|
+
if node.respond_to?(:child_nodes)
|
86
|
+
node.child_nodes.each do |child|
|
87
|
+
result = find_method_node(child, method_name, target_line)
|
88
|
+
return result if result
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
# Task dependency visitor using Prism's visitor pattern
|
96
|
+
class TaskDependencyVisitor < Prism::Visitor
|
97
|
+
attr_reader :dependencies
|
98
|
+
|
99
|
+
def initialize
|
100
|
+
@dependencies = []
|
101
|
+
@constant_cache = {}
|
102
|
+
end
|
103
|
+
|
104
|
+
def visit_constant_read_node(node)
|
105
|
+
check_task_constant(node.name.to_s)
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
def visit_constant_path_node(node)
|
110
|
+
const_path = extract_constant_path(node)
|
111
|
+
check_task_constant(const_path) if const_path
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_call_node(node)
|
116
|
+
# Check for method calls on constants (e.g., TaskA.result)
|
117
|
+
case node.receiver
|
118
|
+
when Prism::ConstantReadNode
|
119
|
+
check_task_constant(node.receiver.name.to_s)
|
120
|
+
when Prism::ConstantPathNode
|
121
|
+
const_path = extract_constant_path(node.receiver)
|
122
|
+
check_task_constant(const_path) if const_path
|
123
|
+
end
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def check_task_constant(const_name)
|
130
|
+
return unless const_name
|
131
|
+
|
132
|
+
# Use caching to avoid repeated constant resolution
|
133
|
+
cached_result = @constant_cache[const_name]
|
134
|
+
return cached_result if cached_result == false # Cached negative result
|
135
|
+
return @dependencies << cached_result if cached_result # Cached positive result
|
136
|
+
|
137
|
+
begin
|
138
|
+
if Object.const_defined?(const_name)
|
139
|
+
klass = Object.const_get(const_name)
|
140
|
+
if klass.is_a?(Class) && klass < Taski::Task
|
141
|
+
@constant_cache[const_name] = klass
|
142
|
+
@dependencies << klass
|
143
|
+
else
|
144
|
+
@constant_cache[const_name] = false
|
145
|
+
end
|
146
|
+
else
|
147
|
+
@constant_cache[const_name] = false
|
148
|
+
end
|
149
|
+
rescue NameError, ArgumentError
|
150
|
+
@constant_cache[const_name] = false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def extract_constant_path(node)
|
155
|
+
case node
|
156
|
+
when Prism::ConstantReadNode
|
157
|
+
node.name.to_s
|
158
|
+
when Prism::ConstantPathNode
|
159
|
+
parent_path = extract_constant_path(node.parent) if node.parent
|
160
|
+
child_name = node.name.to_s
|
161
|
+
|
162
|
+
if parent_path && child_name
|
163
|
+
"#{parent_path}::#{child_name}"
|
164
|
+
else
|
165
|
+
child_name
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Taski
|
4
|
+
# Custom exceptions for Taski framework
|
5
|
+
|
6
|
+
# Raised when circular dependencies are detected between tasks
|
7
|
+
class CircularDependencyError < StandardError; end
|
8
|
+
|
9
|
+
# Raised when task analysis fails (e.g., constant resolution errors)
|
10
|
+
class TaskAnalysisError < StandardError; end
|
11
|
+
|
12
|
+
# Raised when task building fails during execution
|
13
|
+
class TaskBuildError < StandardError; end
|
14
|
+
end
|