taski 0.2.0 → 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 +93 -201
- data/Rakefile +7 -1
- data/examples/complex_example.rb +16 -18
- data/examples/readme_example.rb +2 -2
- data/lib/taski/dependency_analyzer.rb +18 -8
- data/lib/taski/exceptions.rb +4 -4
- data/lib/taski/logger.rb +202 -0
- data/lib/taski/reference.rb +3 -3
- data/lib/taski/task/base.rb +3 -3
- data/lib/taski/task/define_api.rb +19 -6
- data/lib/taski/task/dependency_resolver.rb +44 -11
- data/lib/taski/task/exports_api.rb +1 -1
- data/lib/taski/task/instance_management.rb +51 -10
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +5 -5
- data/sig/taski.rbs +39 -2
- metadata +5 -4
- data/Steepfile +0 -20
data/lib/taski/logger.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Taski
|
4
|
+
# Enhanced logging functionality for Taski framework
|
5
|
+
# Provides structured logging with multiple levels and context information
|
6
|
+
class Logger
|
7
|
+
# Log levels in order of severity
|
8
|
+
LEVELS = {debug: 0, info: 1, warn: 2, error: 3}.freeze
|
9
|
+
|
10
|
+
# @param level [Symbol] Minimum log level to output (:debug, :info, :warn, :error)
|
11
|
+
# @param output [IO] Output destination (default: $stdout)
|
12
|
+
# @param format [Symbol] Log format (:simple, :structured, :json)
|
13
|
+
def initialize(level: :info, output: $stdout, format: :structured)
|
14
|
+
@level = level
|
15
|
+
@output = output
|
16
|
+
@format = format
|
17
|
+
@start_time = Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
# Log debug message with optional context
|
21
|
+
# @param message [String] Log message
|
22
|
+
# @param context [Hash] Additional context information
|
23
|
+
def debug(message, **context)
|
24
|
+
log(:debug, message, context)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Log info message with optional context
|
28
|
+
# @param message [String] Log message
|
29
|
+
# @param context [Hash] Additional context information
|
30
|
+
def info(message, **context)
|
31
|
+
log(:info, message, context)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Log warning message with optional context
|
35
|
+
# @param message [String] Log message
|
36
|
+
# @param context [Hash] Additional context information
|
37
|
+
def warn(message, **context)
|
38
|
+
log(:warn, message, context)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Log error message with optional context
|
42
|
+
# @param message [String] Log message
|
43
|
+
# @param context [Hash] Additional context information
|
44
|
+
def error(message, **context)
|
45
|
+
log(:error, message, context)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Log task build start event
|
49
|
+
# @param task_name [String] Name of the task being built
|
50
|
+
# @param dependencies [Array] List of task dependencies
|
51
|
+
def task_build_start(task_name, dependencies: [])
|
52
|
+
info("Task build started",
|
53
|
+
task: task_name,
|
54
|
+
dependencies: dependencies.size,
|
55
|
+
dependency_names: dependencies.map { |dep| dep.is_a?(Hash) ? dep[:klass].inspect : dep.inspect })
|
56
|
+
end
|
57
|
+
|
58
|
+
# Log task build completion event
|
59
|
+
# @param task_name [String] Name of the task that was built
|
60
|
+
# @param duration [Float] Build duration in seconds
|
61
|
+
def task_build_complete(task_name, duration: nil)
|
62
|
+
context = {task: task_name}
|
63
|
+
context[:duration_ms] = (duration * 1000).round(2) if duration
|
64
|
+
info("Task build completed", **context)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Log task build failure event
|
68
|
+
# @param task_name [String] Name of the task that failed
|
69
|
+
# @param error [Exception] The error that occurred
|
70
|
+
# @param duration [Float] Duration before failure in seconds
|
71
|
+
def task_build_failed(task_name, error:, duration: nil)
|
72
|
+
context = {
|
73
|
+
task: task_name,
|
74
|
+
error_class: error.class.name,
|
75
|
+
error_message: error.message
|
76
|
+
}
|
77
|
+
context[:duration_ms] = (duration * 1000).round(2) if duration
|
78
|
+
context[:backtrace] = error.backtrace&.first(3) if error.backtrace
|
79
|
+
error("Task build failed", **context)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Log dependency resolution event
|
83
|
+
# @param task_name [String] Name of the task resolving dependencies
|
84
|
+
# @param resolved_count [Integer] Number of dependencies resolved
|
85
|
+
def dependency_resolved(task_name, resolved_count:)
|
86
|
+
debug("Dependencies resolved",
|
87
|
+
task: task_name,
|
88
|
+
resolved_dependencies: resolved_count)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Log circular dependency detection
|
92
|
+
# @param cycle_path [Array] The circular dependency path
|
93
|
+
def circular_dependency_detected(cycle_path)
|
94
|
+
error("Circular dependency detected",
|
95
|
+
cycle: cycle_path.map { |klass| klass.name || klass.inspect },
|
96
|
+
cycle_length: cycle_path.size)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Core logging method
|
102
|
+
# @param level [Symbol] Log level
|
103
|
+
# @param message [String] Log message
|
104
|
+
# @param context [Hash] Additional context
|
105
|
+
def log(level, message, context)
|
106
|
+
return unless should_log?(level)
|
107
|
+
|
108
|
+
case @format
|
109
|
+
when :simple
|
110
|
+
log_simple(level, message, context)
|
111
|
+
when :structured
|
112
|
+
log_structured(level, message, context)
|
113
|
+
when :json
|
114
|
+
log_json(level, message, context)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Check if message should be logged based on current level
|
119
|
+
# @param level [Symbol] Message level to check
|
120
|
+
# @return [Boolean] True if message should be logged
|
121
|
+
def should_log?(level)
|
122
|
+
LEVELS[@level] <= LEVELS[level]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Simple log format: [LEVEL] message
|
126
|
+
def log_simple(level, message, context)
|
127
|
+
@output.puts "[#{level.upcase}] #{message}"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Structured log format with timestamp and context
|
131
|
+
def log_structured(level, message, context)
|
132
|
+
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
133
|
+
elapsed = ((Time.now - @start_time) * 1000).round(1)
|
134
|
+
|
135
|
+
line = "[#{timestamp}] [#{elapsed}ms] #{level.to_s.upcase.ljust(5)} Taski: #{message}"
|
136
|
+
|
137
|
+
unless context.empty?
|
138
|
+
context_parts = context.map do |key, value|
|
139
|
+
"#{key}=#{format_value(value)}"
|
140
|
+
end
|
141
|
+
line += " (#{context_parts.join(", ")})"
|
142
|
+
end
|
143
|
+
|
144
|
+
@output.puts line
|
145
|
+
end
|
146
|
+
|
147
|
+
# JSON log format for structured logging systems
|
148
|
+
def log_json(level, message, context)
|
149
|
+
require "json"
|
150
|
+
|
151
|
+
log_entry = {
|
152
|
+
timestamp: Time.now.iso8601(3),
|
153
|
+
level: level.to_s,
|
154
|
+
logger: "taski",
|
155
|
+
message: message,
|
156
|
+
elapsed_ms: ((Time.now - @start_time) * 1000).round(1)
|
157
|
+
}.merge(context)
|
158
|
+
|
159
|
+
@output.puts JSON.generate(log_entry)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Format values for structured logging
|
163
|
+
def format_value(value)
|
164
|
+
case value
|
165
|
+
when String
|
166
|
+
(value.length > 50) ? "#{value[0..47]}..." : value
|
167
|
+
when Array
|
168
|
+
(value.size > 5) ? "[#{value[0..4].join(", ")}, ...]" : value.inspect
|
169
|
+
when Hash
|
170
|
+
(value.size > 3) ? "{#{value.keys[0..2].join(", ")}, ...}" : value.inspect
|
171
|
+
else
|
172
|
+
value.inspect
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class << self
|
178
|
+
# Get the current logger instance
|
179
|
+
# @return [Logger] Current logger instance
|
180
|
+
def logger
|
181
|
+
@logger ||= Logger.new
|
182
|
+
end
|
183
|
+
|
184
|
+
# Configure the logger with new settings
|
185
|
+
# @param level [Symbol] Log level (:debug, :info, :warn, :error)
|
186
|
+
# @param output [IO] Output destination
|
187
|
+
# @param format [Symbol] Log format (:simple, :structured, :json)
|
188
|
+
def configure_logger(level: :info, output: $stdout, format: :structured)
|
189
|
+
@logger = Logger.new(level: level, output: output, format: format)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Set logger to quiet mode (only errors)
|
193
|
+
def quiet!
|
194
|
+
@logger = Logger.new(level: :error, output: @logger&.instance_variable_get(:@output) || $stdout)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Set logger to verbose mode (all messages)
|
198
|
+
def verbose!
|
199
|
+
@logger = Logger.new(level: :debug, output: @logger&.instance_variable_get(:@output) || $stdout)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
data/lib/taski/reference.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "exceptions"
|
4
4
|
|
5
5
|
module Taski
|
6
6
|
# Reference class for task references
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# Used to create lazy references to task classes by name,
|
9
9
|
# which is useful for dependency tracking and metaprogramming.
|
10
10
|
class Reference
|
@@ -37,4 +37,4 @@ module Taski
|
|
37
37
|
"&#{@klass}"
|
38
38
|
end
|
39
39
|
end
|
40
|
-
end
|
40
|
+
end
|
data/lib/taski/task/base.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "../exceptions"
|
4
4
|
|
5
5
|
module Taski
|
6
6
|
# Base Task class that provides the foundation for task framework
|
7
7
|
# This module contains the core constants and basic structure
|
8
8
|
class Task
|
9
9
|
# Constants for thread-local keys and method tracking
|
10
|
-
THREAD_KEY_SUFFIX =
|
10
|
+
THREAD_KEY_SUFFIX = "_building"
|
11
11
|
TASKI_ANALYZING_DEFINE_KEY = :taski_analyzing_define
|
12
12
|
ANALYZED_METHODS = [:build, :clean].freeze
|
13
13
|
|
@@ -57,4 +57,4 @@ module Taski
|
|
57
57
|
# Default implementation does nothing
|
58
58
|
end
|
59
59
|
end
|
60
|
-
end
|
60
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'set'
|
4
|
-
|
5
3
|
module Taski
|
6
4
|
class Task
|
7
5
|
class << self
|
@@ -18,6 +16,9 @@ module Taski
|
|
18
16
|
@dependencies ||= []
|
19
17
|
@definitions ||= {}
|
20
18
|
|
19
|
+
# Ensure ref method is defined first time define is called
|
20
|
+
create_ref_method_if_needed
|
21
|
+
|
21
22
|
# Create method that tracks dependencies on first call
|
22
23
|
create_tracking_method(name)
|
23
24
|
|
@@ -25,16 +26,25 @@ module Taski
|
|
25
26
|
dependencies = analyze_define_dependencies(block)
|
26
27
|
|
27
28
|
@dependencies += dependencies
|
28
|
-
@definitions[name] = {
|
29
|
+
@definitions[name] = {block:, options:, classes: dependencies}
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
32
33
|
|
33
34
|
# === Define API Implementation ===
|
34
35
|
|
36
|
+
# Create ref method if needed to avoid redefinition warnings
|
37
|
+
def create_ref_method_if_needed
|
38
|
+
return if method_defined_for_define?(:ref)
|
39
|
+
|
40
|
+
define_singleton_method(:ref) { |klass| Object.const_get(klass) }
|
41
|
+
mark_method_as_defined(:ref)
|
42
|
+
end
|
43
|
+
|
35
44
|
# Create method that tracks dependencies for define API
|
36
45
|
# @param name [Symbol] Method name to create
|
37
46
|
def create_tracking_method(name)
|
47
|
+
# Only create tracking method during dependency analysis
|
38
48
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
39
49
|
def self.#{name}
|
40
50
|
__resolve__[__callee__] ||= false
|
@@ -65,7 +75,7 @@ module Taski
|
|
65
75
|
|
66
76
|
break if klass.nil?
|
67
77
|
|
68
|
-
classes << {
|
78
|
+
classes << {klass:, task:}
|
69
79
|
end
|
70
80
|
|
71
81
|
# Reset resolution state
|
@@ -90,6 +100,9 @@ module Taski
|
|
90
100
|
# @param name [Symbol] Method name
|
91
101
|
# @param definition [Hash] Method definition information
|
92
102
|
def create_defined_method(name, definition)
|
103
|
+
# Remove tracking method first to avoid redefinition warnings
|
104
|
+
singleton_class.undef_method(name) if singleton_class.method_defined?(name)
|
105
|
+
|
93
106
|
# Class method with lazy evaluation
|
94
107
|
define_singleton_method(name) do
|
95
108
|
@__defined_values ||= {}
|
@@ -101,7 +114,7 @@ module Taski
|
|
101
114
|
@__defined_values ||= {}
|
102
115
|
@__defined_values[name] ||= self.class.send(name)
|
103
116
|
end
|
104
|
-
|
117
|
+
|
105
118
|
# Mark as defined for this resolution
|
106
119
|
mark_method_as_defined(name)
|
107
120
|
end
|
@@ -122,4 +135,4 @@ module Taski
|
|
122
135
|
end
|
123
136
|
end
|
124
137
|
end
|
125
|
-
end
|
138
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative '../dependency_analyzer'
|
3
|
+
require_relative "../dependency_analyzer"
|
5
4
|
|
6
5
|
module Taski
|
7
6
|
class Task
|
@@ -23,12 +22,6 @@ module Taski
|
|
23
22
|
queue << task_class
|
24
23
|
end
|
25
24
|
|
26
|
-
# Override ref method after resolution for define API compatibility
|
27
|
-
# Note: This may cause "method redefined" warnings, which is expected behavior
|
28
|
-
if (@definitions && !@definitions.empty?) && !method_defined_for_define?(:ref)
|
29
|
-
define_singleton_method(:ref) { |klass| Object.const_get(klass) }
|
30
|
-
end
|
31
|
-
|
32
25
|
# Create getter methods for defined values
|
33
26
|
create_defined_methods
|
34
27
|
|
@@ -42,6 +35,7 @@ module Taski
|
|
42
35
|
resolved = []
|
43
36
|
visited = Set.new
|
44
37
|
resolving = Set.new # Track currently resolving tasks
|
38
|
+
path_map = {self => []} # Track paths to each task
|
45
39
|
|
46
40
|
while queue.any?
|
47
41
|
task_class = queue.shift
|
@@ -49,12 +43,26 @@ module Taski
|
|
49
43
|
|
50
44
|
# Check for circular dependency
|
51
45
|
if resolving.include?(task_class)
|
52
|
-
|
46
|
+
# Build error message with path information
|
47
|
+
cycle_path = build_cycle_path(task_class, path_map)
|
48
|
+
raise CircularDependencyError, build_circular_dependency_message(cycle_path)
|
53
49
|
end
|
54
50
|
|
55
51
|
resolving << task_class
|
56
52
|
visited << task_class
|
53
|
+
|
54
|
+
# Store current path for dependencies
|
55
|
+
current_path = path_map[task_class] || []
|
56
|
+
|
57
|
+
# Let task resolve its dependencies
|
57
58
|
task_class.resolve(queue, resolved)
|
59
|
+
|
60
|
+
# Track paths for each dependency
|
61
|
+
task_class.instance_variable_get(:@dependencies)&.each do |dep|
|
62
|
+
dep_class = extract_class(dep)
|
63
|
+
path_map[dep_class] = current_path + [task_class] unless path_map.key?(dep_class)
|
64
|
+
end
|
65
|
+
|
58
66
|
resolving.delete(task_class)
|
59
67
|
resolved << task_class unless resolved.include?(task_class)
|
60
68
|
end
|
@@ -62,6 +70,31 @@ module Taski
|
|
62
70
|
resolved
|
63
71
|
end
|
64
72
|
|
73
|
+
private
|
74
|
+
|
75
|
+
# Build the cycle path from path tracking information
|
76
|
+
def build_cycle_path(task_class, path_map)
|
77
|
+
path = path_map[task_class] || []
|
78
|
+
path + [task_class]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Build detailed error message for circular dependencies
|
82
|
+
def build_circular_dependency_message(cycle_path)
|
83
|
+
path_names = cycle_path.map { |klass| klass.name || klass.to_s }
|
84
|
+
|
85
|
+
message = "Circular dependency detected!\n"
|
86
|
+
message += "Cycle: #{path_names.join(" → ")}\n\n"
|
87
|
+
message += "Detailed dependency chain:\n"
|
88
|
+
|
89
|
+
cycle_path.each_cons(2).with_index do |(from, to), index|
|
90
|
+
message += " #{index + 1}. #{from.name} depends on → #{to.name}\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
message
|
94
|
+
end
|
95
|
+
|
96
|
+
public
|
97
|
+
|
65
98
|
# === Static Analysis ===
|
66
99
|
|
67
100
|
# Analyze dependencies when methods are defined
|
@@ -91,7 +124,7 @@ module Taski
|
|
91
124
|
# @param dep_class [Class] Dependency class to add
|
92
125
|
def add_dependency(dep_class)
|
93
126
|
@dependencies ||= []
|
94
|
-
@dependencies << {
|
127
|
+
@dependencies << {klass: dep_class}
|
95
128
|
end
|
96
129
|
|
97
130
|
# Check if dependency already exists
|
@@ -102,4 +135,4 @@ module Taski
|
|
102
135
|
end
|
103
136
|
end
|
104
137
|
end
|
105
|
-
end
|
138
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "monitor"
|
4
4
|
|
5
5
|
module Taski
|
6
6
|
class Task
|
@@ -9,7 +9,7 @@ module Taski
|
|
9
9
|
|
10
10
|
# Build this task and all its dependencies
|
11
11
|
def build
|
12
|
-
resolve_dependencies.
|
12
|
+
resolve_dependencies.reverse_each do |task_class|
|
13
13
|
task_class.ensure_instance_built
|
14
14
|
end
|
15
15
|
end
|
@@ -56,7 +56,9 @@ module Taski
|
|
56
56
|
# Prevent infinite recursion using thread-local storage
|
57
57
|
thread_key = build_thread_key
|
58
58
|
if Thread.current[thread_key]
|
59
|
-
|
59
|
+
# Build dependency path for better error message
|
60
|
+
cycle_path = build_current_dependency_path
|
61
|
+
raise CircularDependencyError, build_runtime_circular_dependency_message(cycle_path)
|
60
62
|
end
|
61
63
|
|
62
64
|
Thread.current[thread_key] = true
|
@@ -84,21 +86,25 @@ module Taski
|
|
84
86
|
# Generate thread key for recursion detection
|
85
87
|
# @return [String] Thread key for this task
|
86
88
|
def build_thread_key
|
87
|
-
"#{
|
89
|
+
"#{name}#{THREAD_KEY_SUFFIX}"
|
88
90
|
end
|
89
91
|
|
90
92
|
# Build and configure task instance
|
91
93
|
# @return [Task] Built task instance
|
92
94
|
def build_instance
|
93
|
-
instance =
|
95
|
+
instance = new
|
96
|
+
build_start_time = Time.now
|
94
97
|
begin
|
98
|
+
Taski.logger.task_build_start(name.to_s, dependencies: @dependencies || [])
|
95
99
|
instance.build
|
100
|
+
duration = Time.now - build_start_time
|
101
|
+
Taski.logger.task_build_complete(name.to_s, duration: duration)
|
96
102
|
instance
|
97
103
|
rescue => e
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
raise TaskBuildError, "Failed to build task #{
|
104
|
+
duration = Time.now - build_start_time
|
105
|
+
# Log the error with full context
|
106
|
+
Taski.logger.task_build_failed(name.to_s, error: e, duration: duration)
|
107
|
+
raise TaskBuildError, "Failed to build task #{name}: #{e.message}"
|
102
108
|
end
|
103
109
|
end
|
104
110
|
|
@@ -123,6 +129,41 @@ module Taski
|
|
123
129
|
end
|
124
130
|
end
|
125
131
|
|
132
|
+
# Build current dependency path from thread-local storage
|
133
|
+
# @return [Array<Class>] Array of classes in the current build path
|
134
|
+
def build_current_dependency_path
|
135
|
+
path = []
|
136
|
+
Thread.current.keys.each do |key|
|
137
|
+
if key.to_s.end_with?(THREAD_KEY_SUFFIX) && Thread.current[key]
|
138
|
+
class_name = key.to_s.sub(THREAD_KEY_SUFFIX, "")
|
139
|
+
begin
|
140
|
+
path << Object.const_get(class_name)
|
141
|
+
rescue NameError
|
142
|
+
# Skip if class not found
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
path << self
|
147
|
+
end
|
148
|
+
|
149
|
+
# Build runtime circular dependency error message
|
150
|
+
# @param cycle_path [Array<Class>] The circular dependency path
|
151
|
+
# @return [String] Formatted error message
|
152
|
+
def build_runtime_circular_dependency_message(cycle_path)
|
153
|
+
path_names = cycle_path.map { |klass| klass.name || klass.to_s }
|
154
|
+
|
155
|
+
message = "Circular dependency detected!\n"
|
156
|
+
message += "Cycle: #{path_names.join(" → ")}\n\n"
|
157
|
+
message += "The dependency chain is:\n"
|
158
|
+
|
159
|
+
cycle_path.each_cons(2).with_index do |(from, to), index|
|
160
|
+
message += " #{index + 1}. #{from.name} is trying to build → #{to.name}\n"
|
161
|
+
end
|
162
|
+
|
163
|
+
message += "\nThis creates an infinite loop that cannot be resolved."
|
164
|
+
message
|
165
|
+
end
|
166
|
+
|
126
167
|
# Extract class from dependency hash
|
127
168
|
# @param dep [Hash] Dependency information
|
128
169
|
# @return [Class] The dependency class
|
@@ -132,4 +173,4 @@ module Taski
|
|
132
173
|
end
|
133
174
|
end
|
134
175
|
end
|
135
|
-
end
|
176
|
+
end
|
data/lib/taski/version.rb
CHANGED
data/lib/taski.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'monitor'
|
3
|
+
require "monitor"
|
5
4
|
|
6
5
|
# Load core components
|
7
6
|
require_relative "taski/version"
|
8
7
|
require_relative "taski/exceptions"
|
8
|
+
require_relative "taski/logger"
|
9
9
|
require_relative "taski/reference"
|
10
10
|
require_relative "taski/dependency_analyzer"
|
11
11
|
|
@@ -18,7 +18,7 @@ require_relative "taski/task/dependency_resolver"
|
|
18
18
|
|
19
19
|
module Taski
|
20
20
|
# Main module for the Taski task framework
|
21
|
-
#
|
21
|
+
#
|
22
22
|
# Taski provides a framework for defining and managing task dependencies
|
23
23
|
# with two complementary APIs:
|
24
24
|
# 1. Exports API - Export instance variables as class methods (static dependencies)
|
@@ -26,7 +26,7 @@ module Taski
|
|
26
26
|
#
|
27
27
|
# Use Define API when:
|
28
28
|
# - Dependencies change based on runtime conditions
|
29
|
-
# - Environment-specific configurations
|
29
|
+
# - Environment-specific configurations
|
30
30
|
# - Feature flags determine which classes to use
|
31
31
|
# - Complex conditional logic determines dependencies
|
32
32
|
#
|
@@ -37,4 +37,4 @@ module Taski
|
|
37
37
|
# - Thread-safe task building
|
38
38
|
# - Circular dependency detection
|
39
39
|
# - Memory leak prevention
|
40
|
-
end
|
40
|
+
end
|
data/sig/taski.rbs
CHANGED
@@ -33,15 +33,19 @@ module Taski
|
|
33
33
|
|
34
34
|
# Public API methods
|
35
35
|
def self.exports: (*Symbol names) -> void
|
36
|
-
def self.define: (Symbol name
|
36
|
+
def self.define: (Symbol name) { () -> untyped } -> void
|
37
37
|
def self.build: () -> void
|
38
38
|
def self.clean: () -> void
|
39
39
|
def self.reset!: () -> self
|
40
40
|
def self.refresh: () -> self
|
41
41
|
def self.resolve: (Array[untyped] queue, Array[untyped] resolved) -> self
|
42
|
-
def self.ref: (String klass) -> Reference
|
42
|
+
def self.ref: (String | Class klass) -> (Reference | Class)
|
43
43
|
def self.ensure_instance_built: () -> Task
|
44
44
|
|
45
|
+
# Define API methods
|
46
|
+
def self.__resolve__: () -> Hash[Symbol, untyped]
|
47
|
+
def self.resolve_dependencies: () -> void
|
48
|
+
|
45
49
|
# Instance methods
|
46
50
|
def build: () -> void
|
47
51
|
def clean: () -> void
|
@@ -53,10 +57,43 @@ module Taski
|
|
53
57
|
# Allow dynamic class method definitions
|
54
58
|
def self.method_missing: (Symbol name, *untyped args) ?{ (*untyped) -> untyped } -> untyped
|
55
59
|
def self.respond_to_missing?: (Symbol name, bool include_private) -> bool
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Private class methods for dependency resolution
|
64
|
+
def self.resolve_queue: (Array[Class] queue, Array[Class] resolved) -> Array[Class]
|
65
|
+
def self.detect_circular_dependencies: (Array[Class] queue, Array[Class] resolved) -> void
|
66
|
+
def self.build_instance: (Class task_class) -> void
|
67
|
+
|
68
|
+
# Private class methods for Define API
|
69
|
+
def self.create_defined_method: (Symbol name) { () -> untyped } -> void
|
70
|
+
def self.create_ref_method_if_needed: () -> void
|
71
|
+
def self.method_defined_for_define?: (Symbol method_name) -> bool
|
72
|
+
def self.mark_method_as_defined: (Symbol method_name) -> void
|
56
73
|
end
|
57
74
|
|
58
75
|
# Dependency analyzer module
|
59
76
|
module DependencyAnalyzer
|
60
77
|
def self.analyze_method: (Class klass, Symbol method_name) -> Array[Class]
|
78
|
+
|
79
|
+
# Task dependency visitor for AST analysis
|
80
|
+
class TaskDependencyVisitor < Prism::Visitor
|
81
|
+
@dependencies: Array[Class]
|
82
|
+
|
83
|
+
def initialize: () -> void
|
84
|
+
def dependencies: () -> Array[Class]
|
85
|
+
def visit_call_node: (Prism::CallNode node) -> void
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def extract_class_from_constant: (String constant_name) -> Class?
|
90
|
+
def safe_constantize: (String name) -> Class?
|
91
|
+
def extract_class_from_ref_call: (Prism::CallNode node) -> Class?
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def self.parse_method_code: (Class klass, Symbol method_name) -> Prism::ParseResult?
|
97
|
+
def self.extract_dependencies_from_ast: (Prism::Node ast) -> Array[Class]
|
61
98
|
end
|
62
99
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taski
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ahogappa
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-06-
|
10
|
+
date: 2025-06-21 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: prism
|
@@ -35,15 +35,16 @@ executables: []
|
|
35
35
|
extensions: []
|
36
36
|
extra_rdoc_files: []
|
37
37
|
files:
|
38
|
+
- ".standard.yml"
|
38
39
|
- LICENSE
|
39
40
|
- README.md
|
40
41
|
- Rakefile
|
41
|
-
- Steepfile
|
42
42
|
- examples/complex_example.rb
|
43
43
|
- examples/readme_example.rb
|
44
44
|
- lib/taski.rb
|
45
45
|
- lib/taski/dependency_analyzer.rb
|
46
46
|
- lib/taski/exceptions.rb
|
47
|
+
- lib/taski/logger.rb
|
47
48
|
- lib/taski/reference.rb
|
48
49
|
- lib/taski/task/base.rb
|
49
50
|
- lib/taski/task/define_api.rb
|
@@ -65,7 +66,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
66
|
requirements:
|
66
67
|
- - ">="
|
67
68
|
- !ruby/object:Gem::Version
|
68
|
-
version: 3.
|
69
|
+
version: 3.2.0
|
69
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
71
|
requirements:
|
71
72
|
- - ">="
|