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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +9 -0
  3. data/.gem_rbs_collection/ast/2.4/ast.rbs +73 -0
  4. data/.gem_rbs_collection/minitest/5.25/.rbs_meta.yaml +9 -0
  5. data/.gem_rbs_collection/minitest/5.25/minitest/abstract_reporter.rbs +52 -0
  6. data/.gem_rbs_collection/minitest/5.25/minitest/assertion.rbs +17 -0
  7. data/.gem_rbs_collection/minitest/5.25/minitest/assertions.rbs +590 -0
  8. data/.gem_rbs_collection/minitest/5.25/minitest/backtrace_filter.rbs +23 -0
  9. data/.gem_rbs_collection/minitest/5.25/minitest/bench_spec.rbs +102 -0
  10. data/.gem_rbs_collection/minitest/5.25/minitest/benchmark.rbs +259 -0
  11. data/.gem_rbs_collection/minitest/5.25/minitest/composite_reporter.rbs +25 -0
  12. data/.gem_rbs_collection/minitest/5.25/minitest/compress.rbs +13 -0
  13. data/.gem_rbs_collection/minitest/5.25/minitest/error_on_warning.rbs +3 -0
  14. data/.gem_rbs_collection/minitest/5.25/minitest/expectation.rbs +2 -0
  15. data/.gem_rbs_collection/minitest/5.25/minitest/expectations.rbs +21 -0
  16. data/.gem_rbs_collection/minitest/5.25/minitest/guard.rbs +64 -0
  17. data/.gem_rbs_collection/minitest/5.25/minitest/mock.rbs +64 -0
  18. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/executor.rbs +46 -0
  19. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test/class_methods.rbs +5 -0
  20. data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test.rbs +3 -0
  21. data/.gem_rbs_collection/minitest/5.25/minitest/parallel.rbs +2 -0
  22. data/.gem_rbs_collection/minitest/5.25/minitest/pride_io.rbs +62 -0
  23. data/.gem_rbs_collection/minitest/5.25/minitest/pride_lol.rbs +19 -0
  24. data/.gem_rbs_collection/minitest/5.25/minitest/progress_reporter.rbs +11 -0
  25. data/.gem_rbs_collection/minitest/5.25/minitest/reportable.rbs +53 -0
  26. data/.gem_rbs_collection/minitest/5.25/minitest/reporter.rbs +5 -0
  27. data/.gem_rbs_collection/minitest/5.25/minitest/result.rbs +28 -0
  28. data/.gem_rbs_collection/minitest/5.25/minitest/runnable.rbs +163 -0
  29. data/.gem_rbs_collection/minitest/5.25/minitest/skip.rbs +6 -0
  30. data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl/instance_methods.rbs +48 -0
  31. data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl.rbs +129 -0
  32. data/.gem_rbs_collection/minitest/5.25/minitest/spec.rbs +11 -0
  33. data/.gem_rbs_collection/minitest/5.25/minitest/statistics_reporter.rbs +81 -0
  34. data/.gem_rbs_collection/minitest/5.25/minitest/summary_reporter.rbs +18 -0
  35. data/.gem_rbs_collection/minitest/5.25/minitest/test/lifecycle_hooks.rbs +92 -0
  36. data/.gem_rbs_collection/minitest/5.25/minitest/test.rbs +69 -0
  37. data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_error.rbs +12 -0
  38. data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_warning.rbs +6 -0
  39. data/.gem_rbs_collection/minitest/5.25/minitest/unit/test_case.rbs +3 -0
  40. data/.gem_rbs_collection/minitest/5.25/minitest/unit.rbs +4 -0
  41. data/.gem_rbs_collection/minitest/5.25/minitest.rbs +115 -0
  42. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
  43. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
  44. data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +9 -0
  45. data/.gem_rbs_collection/parser/3.2/manifest.yaml +7 -0
  46. data/.gem_rbs_collection/parser/3.2/parser.rbs +193 -0
  47. data/.gem_rbs_collection/parser/3.2/polyfill.rbs +4 -0
  48. data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +9 -0
  49. data/.gem_rbs_collection/rainbow/3.0/global.rbs +7 -0
  50. data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +209 -0
  51. data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +5 -0
  52. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
  53. data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
  54. data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
  55. data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +9 -0
  56. data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +17 -0
  57. data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +9 -0
  58. data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +129 -0
  59. data/.gem_rbs_collection/rubocop-ast/1.30/.rbs_meta.yaml +9 -0
  60. data/.gem_rbs_collection/rubocop-ast/1.30/rubocop-ast.rbs +771 -0
  61. data/.gem_rbs_collection/simplecov/0.22/.rbs_meta.yaml +9 -0
  62. data/.gem_rbs_collection/simplecov/0.22/simplecov.rbs +54 -0
  63. data/README.md +137 -248
  64. data/Steepfile +19 -0
  65. data/docs/advanced-features.md +625 -0
  66. data/docs/api-guide.md +509 -0
  67. data/docs/error-handling.md +684 -0
  68. data/examples/README.md +95 -42
  69. data/examples/context_demo.rb +112 -0
  70. data/examples/data_pipeline_demo.rb +231 -0
  71. data/examples/parallel_progress_demo.rb +72 -0
  72. data/examples/quick_start.rb +4 -4
  73. data/examples/reexecution_demo.rb +127 -0
  74. data/examples/{section_configuration.rb → section_demo.rb} +49 -66
  75. data/lib/taski/context.rb +52 -0
  76. data/lib/taski/execution/coordinator.rb +63 -0
  77. data/lib/taski/execution/parallel_progress_display.rb +201 -0
  78. data/lib/taski/execution/registry.rb +72 -0
  79. data/lib/taski/execution/task_wrapper.rb +255 -0
  80. data/lib/taski/section.rb +26 -250
  81. data/lib/taski/static_analysis/analyzer.rb +34 -0
  82. data/lib/taski/static_analysis/dependency_graph.rb +90 -0
  83. data/lib/taski/static_analysis/visitor.rb +114 -0
  84. data/lib/taski/task.rb +173 -0
  85. data/lib/taski/version.rb +1 -1
  86. data/lib/taski.rb +45 -39
  87. data/rbs_collection.lock.yaml +116 -0
  88. data/rbs_collection.yaml +19 -0
  89. data/sig/taski.rbs +269 -62
  90. metadata +97 -32
  91. data/examples/advanced_patterns.rb +0 -119
  92. data/examples/progress_demo.rb +0 -166
  93. data/examples/tree_demo.rb +0 -205
  94. data/lib/taski/dependency_analyzer.rb +0 -231
  95. data/lib/taski/exceptions.rb +0 -17
  96. data/lib/taski/logger.rb +0 -158
  97. data/lib/taski/logging/formatter_factory.rb +0 -34
  98. data/lib/taski/logging/formatter_interface.rb +0 -19
  99. data/lib/taski/logging/json_formatter.rb +0 -26
  100. data/lib/taski/logging/simple_formatter.rb +0 -16
  101. data/lib/taski/logging/structured_formatter.rb +0 -44
  102. data/lib/taski/progress/display_colors.rb +0 -17
  103. data/lib/taski/progress/display_manager.rb +0 -115
  104. data/lib/taski/progress/output_capture.rb +0 -105
  105. data/lib/taski/progress/spinner_animation.rb +0 -46
  106. data/lib/taski/progress/task_formatter.rb +0 -25
  107. data/lib/taski/progress/task_status.rb +0 -38
  108. data/lib/taski/progress/terminal_controller.rb +0 -35
  109. data/lib/taski/progress_display.rb +0 -59
  110. data/lib/taski/reference.rb +0 -40
  111. data/lib/taski/task/base.rb +0 -90
  112. data/lib/taski/task/define_api.rb +0 -154
  113. data/lib/taski/task/dependency_resolver.rb +0 -73
  114. data/lib/taski/task/exports_api.rb +0 -31
  115. data/lib/taski/task/instance_management.rb +0 -203
  116. data/lib/taski/tree_colors.rb +0 -91
  117. data/lib/taski/utils/dependency_resolver_helper.rb +0 -85
  118. data/lib/taski/utils/tree_display_helper.rb +0 -71
  119. 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
@@ -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
@@ -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