taski 0.3.0 → 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 +137 -248
- 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 -66
- 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 -250
- 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 -231
- 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 -115
- data/lib/taski/progress/output_capture.rb +0 -105
- data/lib/taski/progress/spinner_animation.rb +0 -46
- 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 -59
- data/lib/taski/reference.rb +0 -40
- data/lib/taski/task/base.rb +0 -90
- data/lib/taski/task/define_api.rb +0 -154
- data/lib/taski/task/dependency_resolver.rb +0 -73
- data/lib/taski/task/exports_api.rb +0 -31
- data/lib/taski/task/instance_management.rb +0 -203
- 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 -71
- data/lib/taski/utils.rb +0 -107
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "progress/terminal_controller"
|
|
4
|
-
require_relative "progress/spinner_animation"
|
|
5
|
-
require_relative "progress/output_capture"
|
|
6
|
-
require_relative "progress/display_manager"
|
|
7
|
-
|
|
8
|
-
module Taski
|
|
9
|
-
# Backward compatibility aliases
|
|
10
|
-
TerminalController = Progress::TerminalController
|
|
11
|
-
SpinnerAnimation = Progress::SpinnerAnimation
|
|
12
|
-
OutputCapture = Progress::OutputCapture
|
|
13
|
-
TaskStatus = Progress::TaskStatus
|
|
14
|
-
|
|
15
|
-
# Main progress display controller - refactored for better separation of concerns
|
|
16
|
-
class ProgressDisplay
|
|
17
|
-
def initialize(output: $stdout, enable: true, include_captured_output: nil)
|
|
18
|
-
@output = output
|
|
19
|
-
@terminal = Progress::TerminalController.new(output)
|
|
20
|
-
@spinner = Progress::SpinnerAnimation.new
|
|
21
|
-
@output_capture = Progress::OutputCapture.new(output)
|
|
22
|
-
|
|
23
|
-
# Default to including captured output in test environments (when output != $stdout)
|
|
24
|
-
# This ensures test output is visible in the test output stream
|
|
25
|
-
include_captured_output = include_captured_output.nil? ? (output != $stdout) : include_captured_output
|
|
26
|
-
@display_manager = Progress::DisplayManager.new(@terminal, @spinner, @output_capture, include_captured_output: include_captured_output)
|
|
27
|
-
|
|
28
|
-
@enabled = ENV["TASKI_PROGRESS_DISABLE"] != "1" && enable
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def start_task(task_name, dependencies: [])
|
|
32
|
-
return unless @enabled
|
|
33
|
-
|
|
34
|
-
@display_manager.start_task_display(task_name)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def complete_task(task_name, duration:)
|
|
38
|
-
return unless @enabled
|
|
39
|
-
|
|
40
|
-
@display_manager.complete_task_display(task_name, duration: duration)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def fail_task(task_name, error:, duration:)
|
|
44
|
-
return unless @enabled
|
|
45
|
-
|
|
46
|
-
@display_manager.fail_task_display(task_name, error: error, duration: duration)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def clear
|
|
50
|
-
return unless @enabled
|
|
51
|
-
|
|
52
|
-
@display_manager.clear_all_displays
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def enabled?
|
|
56
|
-
@enabled
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
data/lib/taski/reference.rb
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "exceptions"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
# Reference class for task references
|
|
7
|
-
#
|
|
8
|
-
# Used to create lazy references to task classes by name,
|
|
9
|
-
# which is useful for dependency tracking and metaprogramming.
|
|
10
|
-
class Reference
|
|
11
|
-
# @param klass [String] The name of the class to reference
|
|
12
|
-
def initialize(klass)
|
|
13
|
-
@klass = klass
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Dereference to get the actual class object
|
|
17
|
-
# @return [Class] The referenced class
|
|
18
|
-
# @raise [TaskAnalysisError] If the constant cannot be resolved
|
|
19
|
-
def deref
|
|
20
|
-
Object.const_get(@klass)
|
|
21
|
-
rescue NameError => e
|
|
22
|
-
raise TaskAnalysisError, "Cannot resolve constant '#{@klass}': #{e.message}"
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Compare reference with another object
|
|
26
|
-
# @param other [Object] Object to compare with
|
|
27
|
-
# @return [Boolean] True if the referenced class equals the other object
|
|
28
|
-
def ==(other)
|
|
29
|
-
Object.const_get(@klass) == other
|
|
30
|
-
rescue NameError
|
|
31
|
-
false
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# String representation of the reference
|
|
35
|
-
# @return [String] Reference representation
|
|
36
|
-
def inspect
|
|
37
|
-
"&#{@klass}"
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
data/lib/taski/task/base.rb
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../exceptions"
|
|
4
|
-
require_relative "../utils/tree_display_helper"
|
|
5
|
-
|
|
6
|
-
module Taski
|
|
7
|
-
# Base Task class that provides the foundation for task framework
|
|
8
|
-
# This module contains the core constants and basic structure
|
|
9
|
-
class Task
|
|
10
|
-
# Constants for thread-local keys and method tracking
|
|
11
|
-
THREAD_KEY_SUFFIX = "_building"
|
|
12
|
-
TASKI_ANALYZING_DEFINE_KEY = :taski_analyzing_define
|
|
13
|
-
ANALYZED_METHODS = [:build, :clean].freeze
|
|
14
|
-
|
|
15
|
-
class << self
|
|
16
|
-
# === Hook Methods ===
|
|
17
|
-
|
|
18
|
-
# Hook called when build/clean methods are defined
|
|
19
|
-
# This triggers static analysis of dependencies
|
|
20
|
-
def method_added(method_name)
|
|
21
|
-
super
|
|
22
|
-
return unless ANALYZED_METHODS.include?(method_name)
|
|
23
|
-
# Only call if the method is available (loaded by dependency_resolver)
|
|
24
|
-
analyze_dependencies_at_definition if respond_to?(:analyze_dependencies_at_definition, true)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Create a reference to a task class (can be used anywhere)
|
|
28
|
-
# @param klass [String] The class name to reference
|
|
29
|
-
# @return [Reference] A reference object
|
|
30
|
-
def ref(klass)
|
|
31
|
-
reference = Reference.new(klass)
|
|
32
|
-
# If we're in a define context, throw for dependency tracking
|
|
33
|
-
if Thread.current[TASKI_ANALYZING_DEFINE_KEY]
|
|
34
|
-
reference.tap { |ref| throw :unresolved, ref }
|
|
35
|
-
else
|
|
36
|
-
reference
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Get or create resolution state for define API
|
|
41
|
-
# @return [Hash] Resolution state hash
|
|
42
|
-
def __resolve__
|
|
43
|
-
@__resolve__ ||= {}
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Display dependency tree for this task
|
|
47
|
-
# @param prefix [String] Current indentation prefix
|
|
48
|
-
# @param visited [Set] Set of visited classes to prevent infinite loops
|
|
49
|
-
# @return [String] Formatted dependency tree
|
|
50
|
-
def tree(prefix = "", visited = Set.new, color: TreeColors.enabled?)
|
|
51
|
-
should_return_early, early_result, new_visited = handle_circular_dependency_check(visited, self, prefix)
|
|
52
|
-
return early_result if should_return_early
|
|
53
|
-
|
|
54
|
-
task_name = color ? TreeColors.task(name) : name
|
|
55
|
-
result = "#{prefix}#{task_name}\n"
|
|
56
|
-
|
|
57
|
-
dependencies = @dependencies || []
|
|
58
|
-
result += render_dependencies_tree(dependencies, prefix, new_visited, color)
|
|
59
|
-
|
|
60
|
-
result
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
include Utils::DependencyUtils
|
|
66
|
-
include Utils::TreeDisplayHelper
|
|
67
|
-
private :extract_class
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# === Instance Methods ===
|
|
71
|
-
|
|
72
|
-
# Build method that must be implemented by subclasses
|
|
73
|
-
# @raise [NotImplementedError] If not implemented by subclass
|
|
74
|
-
def build
|
|
75
|
-
raise NotImplementedError, "You must implement the build method in your task class"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Access build arguments passed to parametrized builds
|
|
79
|
-
# @return [Hash] Build arguments or empty hash if none provided
|
|
80
|
-
def build_args
|
|
81
|
-
@build_args || {}
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Clean method with default empty implementation
|
|
85
|
-
# Subclasses can override this method to implement cleanup logic
|
|
86
|
-
def clean
|
|
87
|
-
# Default implementation does nothing
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
class Task
|
|
5
|
-
class << self
|
|
6
|
-
# === Define API ===
|
|
7
|
-
# Define lazy-evaluated values with dynamic dependency resolution
|
|
8
|
-
|
|
9
|
-
# Define a lazy-evaluated value using a block
|
|
10
|
-
# Use this API when dependencies change based on runtime conditions,
|
|
11
|
-
# environment-specific configurations, feature flags, or complex conditional logic
|
|
12
|
-
# @param name [Symbol] Name of the value
|
|
13
|
-
# @param block [Proc] Block that computes the value and determines dependencies at runtime
|
|
14
|
-
# @param options [Hash] Additional options
|
|
15
|
-
def define(name, block, **options)
|
|
16
|
-
@dependencies ||= []
|
|
17
|
-
@definitions ||= {}
|
|
18
|
-
|
|
19
|
-
# Ensure ref method is defined first time define is called
|
|
20
|
-
create_ref_method_if_needed
|
|
21
|
-
|
|
22
|
-
# Create method that tracks dependencies on first call
|
|
23
|
-
create_tracking_method(name)
|
|
24
|
-
|
|
25
|
-
# Analyze dependencies by executing the block
|
|
26
|
-
dependencies = analyze_define_dependencies(block)
|
|
27
|
-
|
|
28
|
-
@dependencies += dependencies
|
|
29
|
-
@definitions[name] = {block:, options:, classes: dependencies}
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
# === Define API Implementation ===
|
|
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) do |klass_name|
|
|
41
|
-
# During dependency analysis, track as dependency but defer resolution
|
|
42
|
-
if Thread.current[TASKI_ANALYZING_DEFINE_KEY]
|
|
43
|
-
# Create Reference object for deferred resolution
|
|
44
|
-
reference = Taski::Reference.new(klass_name)
|
|
45
|
-
|
|
46
|
-
# Track as dependency by throwing unresolved
|
|
47
|
-
throw :unresolved, [reference, :deref]
|
|
48
|
-
else
|
|
49
|
-
# At runtime, resolve to actual class
|
|
50
|
-
Object.const_get(klass_name)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
mark_method_as_defined(:ref)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Create method that tracks dependencies for define API
|
|
57
|
-
# @param name [Symbol] Method name to create
|
|
58
|
-
def create_tracking_method(name)
|
|
59
|
-
# Only create tracking method during dependency analysis
|
|
60
|
-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
|
61
|
-
def self.#{name}
|
|
62
|
-
__resolve__[__callee__] ||= false
|
|
63
|
-
if __resolve__[__callee__]
|
|
64
|
-
# already resolved
|
|
65
|
-
else
|
|
66
|
-
__resolve__[__callee__] = true
|
|
67
|
-
throw :unresolved, [self, __callee__]
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
RUBY
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Analyze dependencies in define block
|
|
74
|
-
# @param block [Proc] Block to analyze
|
|
75
|
-
# @return [Array<Hash>] Array of dependency information
|
|
76
|
-
def analyze_define_dependencies(block)
|
|
77
|
-
classes = []
|
|
78
|
-
|
|
79
|
-
# Set flag to indicate we're analyzing define dependencies
|
|
80
|
-
Thread.current[TASKI_ANALYZING_DEFINE_KEY] = true
|
|
81
|
-
|
|
82
|
-
loop do
|
|
83
|
-
klass, task = catch(:unresolved) do
|
|
84
|
-
block.call
|
|
85
|
-
nil
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
break if klass.nil?
|
|
89
|
-
|
|
90
|
-
classes << {klass:, task:}
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Reset resolution state
|
|
94
|
-
classes.each do |task_class|
|
|
95
|
-
klass = task_class[:klass]
|
|
96
|
-
# Only reset Task classes, not Reference objects
|
|
97
|
-
if klass.respond_to?(:instance_variable_set) && !klass.is_a?(Taski::Reference)
|
|
98
|
-
klass.instance_variable_set(:@__resolve__, {})
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
classes
|
|
103
|
-
ensure
|
|
104
|
-
Thread.current[TASKI_ANALYZING_DEFINE_KEY] = false
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Create methods for values defined with define API
|
|
108
|
-
def create_defined_methods
|
|
109
|
-
@definitions ||= {}
|
|
110
|
-
@definitions.each do |name, definition|
|
|
111
|
-
create_defined_method(name, definition) unless method_defined_for_define?(name)
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Create a single defined method (both class and instance)
|
|
116
|
-
# @param name [Symbol] Method name
|
|
117
|
-
# @param definition [Hash] Method definition information
|
|
118
|
-
def create_defined_method(name, definition)
|
|
119
|
-
# Remove tracking method first to avoid redefinition warnings
|
|
120
|
-
singleton_class.undef_method(name) if singleton_class.method_defined?(name)
|
|
121
|
-
|
|
122
|
-
# Class method with lazy evaluation
|
|
123
|
-
define_singleton_method(name) do
|
|
124
|
-
@__defined_values ||= {}
|
|
125
|
-
@__defined_values[name] ||= definition[:block].call
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Instance method that delegates to class method
|
|
129
|
-
define_method(name) do
|
|
130
|
-
@__defined_values ||= {}
|
|
131
|
-
@__defined_values[name] ||= self.class.send(name)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Mark as defined for this resolution
|
|
135
|
-
mark_method_as_defined(name)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Mark method as defined for this resolution cycle
|
|
139
|
-
# @param method_name [Symbol] Method name to mark
|
|
140
|
-
def mark_method_as_defined(method_name)
|
|
141
|
-
@__defined_for_resolve ||= Set.new
|
|
142
|
-
@__defined_for_resolve << method_name
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Check if method was already defined for define API
|
|
146
|
-
# @param method_name [Symbol] Method name to check
|
|
147
|
-
# @return [Boolean] True if already defined
|
|
148
|
-
def method_defined_for_define?(method_name)
|
|
149
|
-
@__defined_for_resolve ||= Set.new
|
|
150
|
-
@__defined_for_resolve.include?(method_name)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../dependency_analyzer"
|
|
4
|
-
require_relative "../utils/dependency_resolver_helper"
|
|
5
|
-
|
|
6
|
-
module Taski
|
|
7
|
-
class Task
|
|
8
|
-
class << self
|
|
9
|
-
# === Dependency Resolution ===
|
|
10
|
-
|
|
11
|
-
# Resolve method for dependency graph (called by resolve_dependencies)
|
|
12
|
-
# @param queue [Array] Queue of tasks to process
|
|
13
|
-
# @param resolved [Array] Array of resolved tasks
|
|
14
|
-
# @return [self] Returns self for method chaining
|
|
15
|
-
def resolve(queue, resolved)
|
|
16
|
-
resolve_common(queue, resolved, custom_hook: -> { create_defined_methods })
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Resolve all dependencies in topological order with circular dependency detection
|
|
20
|
-
# @return [Array<Class>] Array of tasks in dependency order
|
|
21
|
-
def resolve_dependencies
|
|
22
|
-
resolve_dependencies_common
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
public
|
|
26
|
-
|
|
27
|
-
# === Static Analysis ===
|
|
28
|
-
|
|
29
|
-
# Analyze dependencies when methods are defined
|
|
30
|
-
def analyze_dependencies_at_definition
|
|
31
|
-
dependencies = gather_static_dependencies
|
|
32
|
-
add_unique_dependencies(dependencies)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Gather dependencies from build and clean methods
|
|
36
|
-
# @return [Array<Class>] Array of dependency classes
|
|
37
|
-
def gather_static_dependencies
|
|
38
|
-
build_deps = DependencyAnalyzer.analyze_method(self, :build)
|
|
39
|
-
clean_deps = DependencyAnalyzer.analyze_method(self, :clean)
|
|
40
|
-
(build_deps + clean_deps).uniq
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Add dependencies that don't already exist
|
|
44
|
-
# @param dep_classes [Array<Class>] Array of dependency classes
|
|
45
|
-
def add_unique_dependencies(dep_classes)
|
|
46
|
-
dep_classes.each do |dep_class|
|
|
47
|
-
next if dep_class == self || dependency_exists?(dep_class)
|
|
48
|
-
add_dependency(dep_class)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Add a single dependency
|
|
53
|
-
# @param dep_class [Class] Dependency class to add
|
|
54
|
-
def add_dependency(dep_class)
|
|
55
|
-
@dependencies ||= []
|
|
56
|
-
@dependencies << {klass: dep_class}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Check if dependency already exists
|
|
60
|
-
# @param dep_class [Class] Dependency class to check
|
|
61
|
-
# @return [Boolean] True if dependency exists
|
|
62
|
-
def dependency_exists?(dep_class)
|
|
63
|
-
(@dependencies || []).any? { |d| d[:klass] == dep_class }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
include Utils::DependencyUtils
|
|
69
|
-
include Utils::DependencyResolverHelper
|
|
70
|
-
private :extract_class
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
class Task
|
|
5
|
-
class << self
|
|
6
|
-
# === Exports API ===
|
|
7
|
-
# Export instance variables as class methods for static dependencies
|
|
8
|
-
|
|
9
|
-
# Export instance variables as both class and instance methods
|
|
10
|
-
# @param names [Array<Symbol>] Names of instance variables to export
|
|
11
|
-
def exports(*names)
|
|
12
|
-
@exports ||= []
|
|
13
|
-
@exports += names
|
|
14
|
-
|
|
15
|
-
names.each do |name|
|
|
16
|
-
next if respond_to?(name)
|
|
17
|
-
|
|
18
|
-
# Define class method to access exported value
|
|
19
|
-
define_singleton_method(name) do
|
|
20
|
-
ensure_instance_built.send(name)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Define instance method getter
|
|
24
|
-
define_method(name) do
|
|
25
|
-
instance_variable_get("@#{name}")
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "monitor"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
class Task
|
|
7
|
-
class << self
|
|
8
|
-
# === Lifecycle Management ===
|
|
9
|
-
|
|
10
|
-
# Build this task and all its dependencies
|
|
11
|
-
# @param args [Hash] Optional arguments for parametrized builds
|
|
12
|
-
# @return [Task] Returns task instance (singleton or temporary)
|
|
13
|
-
def build(**args)
|
|
14
|
-
if args.empty?
|
|
15
|
-
# Traditional build: singleton instance with caching
|
|
16
|
-
resolve_dependencies.reverse_each do |task_class|
|
|
17
|
-
task_class.ensure_instance_built
|
|
18
|
-
end
|
|
19
|
-
# Return the singleton instance for consistency
|
|
20
|
-
instance_variable_get(:@__task_instance)
|
|
21
|
-
else
|
|
22
|
-
# Parametrized build: temporary instance without caching
|
|
23
|
-
build_with_args(args)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Clean this task and all its dependencies in reverse order
|
|
28
|
-
def clean
|
|
29
|
-
resolve_dependencies.each do |task_class|
|
|
30
|
-
# Get existing instance or create new one for cleaning
|
|
31
|
-
instance = task_class.instance_variable_get(:@__task_instance) || task_class.new
|
|
32
|
-
instance.clean
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Reset task instance and cached data to prevent memory leaks
|
|
37
|
-
# @return [self] Returns self for method chaining
|
|
38
|
-
def reset!
|
|
39
|
-
build_monitor.synchronize do
|
|
40
|
-
@__task_instance = nil
|
|
41
|
-
@__defined_values = nil
|
|
42
|
-
@__defined_for_resolve = nil
|
|
43
|
-
clear_thread_local_state
|
|
44
|
-
end
|
|
45
|
-
self
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Refresh task state (currently just resets)
|
|
49
|
-
# @return [self] Returns self for method chaining
|
|
50
|
-
def refresh
|
|
51
|
-
reset!
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# === Parametrized Build Support ===
|
|
55
|
-
|
|
56
|
-
# Build temporary instance with arguments
|
|
57
|
-
# @param args [Hash] Build arguments
|
|
58
|
-
# @return [Task] Temporary task instance
|
|
59
|
-
def build_with_args(args)
|
|
60
|
-
# Resolve dependencies first (same as normal build)
|
|
61
|
-
resolve_dependencies.reverse_each do |task_class|
|
|
62
|
-
task_class.ensure_instance_built
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Create temporary instance with arguments
|
|
66
|
-
temp_instance = new
|
|
67
|
-
temp_instance.instance_variable_set(:@build_args, args)
|
|
68
|
-
|
|
69
|
-
# Build with logging using common utility
|
|
70
|
-
Utils::TaskBuildHelpers.with_build_logging(name.to_s,
|
|
71
|
-
dependencies: @dependencies || [],
|
|
72
|
-
args: args) do
|
|
73
|
-
temp_instance.build
|
|
74
|
-
temp_instance
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private :build_with_args
|
|
79
|
-
|
|
80
|
-
# === Instance Management ===
|
|
81
|
-
|
|
82
|
-
# Ensure task instance is built (public because called from build)
|
|
83
|
-
# @return [Task] The built task instance
|
|
84
|
-
def ensure_instance_built
|
|
85
|
-
# Use double-checked locking pattern for thread safety
|
|
86
|
-
return @__task_instance if @__task_instance
|
|
87
|
-
|
|
88
|
-
build_monitor.synchronize do
|
|
89
|
-
# Check again after acquiring lock
|
|
90
|
-
return @__task_instance if @__task_instance
|
|
91
|
-
|
|
92
|
-
check_circular_dependency
|
|
93
|
-
create_and_build_instance
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
@__task_instance
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
# === Instance Management Helper Methods ===
|
|
102
|
-
|
|
103
|
-
# Check for circular dependencies and raise error if detected
|
|
104
|
-
# @raise [CircularDependencyError] If circular dependency is detected
|
|
105
|
-
def check_circular_dependency
|
|
106
|
-
thread_key = build_thread_key
|
|
107
|
-
if Thread.current[thread_key]
|
|
108
|
-
# Build dependency path for better error message
|
|
109
|
-
cycle_path = build_current_dependency_path
|
|
110
|
-
raise CircularDependencyError, build_runtime_circular_dependency_message(cycle_path)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Create and build instance with proper thread-local state management
|
|
115
|
-
# @return [void] Sets @__task_instance
|
|
116
|
-
def create_and_build_instance
|
|
117
|
-
thread_key = build_thread_key
|
|
118
|
-
Thread.current[thread_key] = true
|
|
119
|
-
begin
|
|
120
|
-
build_dependencies
|
|
121
|
-
@__task_instance = build_instance
|
|
122
|
-
ensure
|
|
123
|
-
Thread.current[thread_key] = false
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# === Core Helper Methods ===
|
|
128
|
-
|
|
129
|
-
# Get or create build monitor for thread safety
|
|
130
|
-
# @return [Monitor] Thread-safe monitor object
|
|
131
|
-
def build_monitor
|
|
132
|
-
@__build_monitor ||= Monitor.new
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Generate thread key for recursion detection
|
|
136
|
-
# @return [String] Thread key for this task
|
|
137
|
-
def build_thread_key
|
|
138
|
-
"#{name}#{THREAD_KEY_SUFFIX}"
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Build and configure task instance
|
|
142
|
-
# @return [Task] Built task instance
|
|
143
|
-
def build_instance
|
|
144
|
-
instance = new
|
|
145
|
-
Utils::TaskBuildHelpers.with_build_logging(name.to_s,
|
|
146
|
-
dependencies: @dependencies || []) do
|
|
147
|
-
instance.build
|
|
148
|
-
instance
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Clear thread-local state for this task
|
|
153
|
-
def clear_thread_local_state
|
|
154
|
-
Thread.current.keys.each do |key|
|
|
155
|
-
Thread.current[key] = nil if key.to_s.include?(build_thread_key)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# === Dependency Management ===
|
|
160
|
-
|
|
161
|
-
# Build all dependencies of this task
|
|
162
|
-
def build_dependencies
|
|
163
|
-
resolve_dependencies
|
|
164
|
-
|
|
165
|
-
(@dependencies || []).each do |dep|
|
|
166
|
-
dep_class = extract_class(dep)
|
|
167
|
-
next if dep_class == self
|
|
168
|
-
|
|
169
|
-
dep_class.ensure_instance_built if dep_class.respond_to?(:ensure_instance_built)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
private
|
|
174
|
-
|
|
175
|
-
# Build current dependency path from thread-local storage
|
|
176
|
-
# @return [Array<Class>] Array of classes in the current build path
|
|
177
|
-
def build_current_dependency_path
|
|
178
|
-
path = []
|
|
179
|
-
Thread.current.keys.each do |key|
|
|
180
|
-
if key.to_s.end_with?(THREAD_KEY_SUFFIX) && Thread.current[key]
|
|
181
|
-
class_name = key.to_s.sub(THREAD_KEY_SUFFIX, "")
|
|
182
|
-
begin
|
|
183
|
-
path << Object.const_get(class_name)
|
|
184
|
-
rescue NameError
|
|
185
|
-
# Skip if class not found
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
path << self
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Build runtime circular dependency error message
|
|
193
|
-
# @param cycle_path [Array<Class>] The circular dependency path
|
|
194
|
-
# @return [String] Formatted error message
|
|
195
|
-
def build_runtime_circular_dependency_message(cycle_path)
|
|
196
|
-
Utils::CircularDependencyHelpers.build_error_message(cycle_path, "runtime")
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
include Utils::DependencyUtils
|
|
200
|
-
private :extract_class
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|