taski 0.2.3 → 0.3.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/README.md +50 -4
- data/examples/README.md +13 -1
- data/examples/section_configuration.rb +206 -0
- data/examples/tree_demo.rb +125 -0
- data/lib/taski/dependency_analyzer.rb +68 -40
- data/lib/taski/exceptions.rb +3 -0
- data/lib/taski/logger.rb +7 -62
- data/lib/taski/logging/formatter_factory.rb +34 -0
- data/lib/taski/logging/formatter_interface.rb +19 -0
- data/lib/taski/logging/json_formatter.rb +26 -0
- data/lib/taski/logging/simple_formatter.rb +16 -0
- data/lib/taski/logging/structured_formatter.rb +44 -0
- data/lib/taski/progress/display_colors.rb +17 -0
- data/lib/taski/progress/display_manager.rb +117 -0
- data/lib/taski/progress/output_capture.rb +105 -0
- data/lib/taski/progress/spinner_animation.rb +49 -0
- data/lib/taski/progress/task_formatter.rb +25 -0
- data/lib/taski/progress/task_status.rb +38 -0
- data/lib/taski/progress/terminal_controller.rb +35 -0
- data/lib/taski/progress_display.rb +21 -320
- data/lib/taski/section.rb +272 -0
- data/lib/taski/task/base.rb +15 -35
- data/lib/taski/task/define_api.rb +5 -3
- data/lib/taski/task/dependency_resolver.rb +4 -64
- data/lib/taski/task/exports_api.rb +0 -2
- data/lib/taski/task/instance_management.rb +31 -20
- data/lib/taski/tree_colors.rb +91 -0
- data/lib/taski/utils/dependency_resolver_helper.rb +85 -0
- data/lib/taski/utils/tree_display_helper.rb +68 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +11 -7
- metadata +18 -1
data/lib/taski/task/base.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../exceptions"
|
4
|
+
require_relative "../utils/tree_display_helper"
|
4
5
|
|
5
6
|
module Taski
|
6
7
|
# Base Task class that provides the foundation for task framework
|
@@ -19,7 +20,7 @@ module Taski
|
|
19
20
|
def method_added(method_name)
|
20
21
|
super
|
21
22
|
return unless ANALYZED_METHODS.include?(method_name)
|
22
|
-
#
|
23
|
+
# Avoid calling before dependency_resolver module is loaded
|
23
24
|
analyze_dependencies_at_definition if respond_to?(:analyze_dependencies_at_definition, true)
|
24
25
|
end
|
25
26
|
|
@@ -28,7 +29,8 @@ module Taski
|
|
28
29
|
# @return [Reference] A reference object
|
29
30
|
def ref(klass)
|
30
31
|
reference = Reference.new(klass)
|
31
|
-
#
|
32
|
+
# Use throw/catch mechanism for dependency collection during define API analysis
|
33
|
+
# This allows catching unresolved references without unwinding the entire call stack
|
32
34
|
if Thread.current[TASKI_ANALYZING_DEFINE_KEY]
|
33
35
|
reference.tap { |ref| throw :unresolved, ref }
|
34
36
|
else
|
@@ -46,38 +48,15 @@ module Taski
|
|
46
48
|
# @param prefix [String] Current indentation prefix
|
47
49
|
# @param visited [Set] Set of visited classes to prevent infinite loops
|
48
50
|
# @return [String] Formatted dependency tree
|
49
|
-
def tree(prefix = "", visited = Set.new)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
dependencies.each_with_index do |dep, index|
|
59
|
-
dep_class = extract_class(dep)
|
60
|
-
is_last = index == dependencies.length - 1
|
61
|
-
|
62
|
-
connector = is_last ? "└── " : "├── "
|
63
|
-
child_prefix = prefix + (is_last ? " " : "│ ")
|
64
|
-
|
65
|
-
# For the dependency itself, we want to use the connector
|
66
|
-
# For its children, we want to use the child_prefix
|
67
|
-
dep_tree = dep_class.tree(child_prefix, visited)
|
68
|
-
# Replace the first line (which has child_prefix) with the proper connector
|
69
|
-
dep_lines = dep_tree.lines
|
70
|
-
if dep_lines.any?
|
71
|
-
# Replace the first line prefix with connector
|
72
|
-
first_line = dep_lines[0]
|
73
|
-
fixed_first_line = first_line.sub(/^#{Regexp.escape(child_prefix)}/, prefix + connector)
|
74
|
-
result += fixed_first_line
|
75
|
-
# Add the rest of the lines as-is
|
76
|
-
result += dep_lines[1..].join if dep_lines.length > 1
|
77
|
-
else
|
78
|
-
result += "#{prefix}#{connector}#{dep_class.name}\n"
|
79
|
-
end
|
80
|
-
end
|
51
|
+
def tree(prefix = "", visited = Set.new, color: TreeColors.enabled?)
|
52
|
+
should_return_early, early_result, new_visited = handle_circular_dependency_check(visited, self, prefix)
|
53
|
+
return early_result if should_return_early
|
54
|
+
|
55
|
+
task_name = color ? TreeColors.task(name) : name
|
56
|
+
result = "#{prefix}#{task_name}\n"
|
57
|
+
|
58
|
+
dependencies = @dependencies || []
|
59
|
+
result += render_dependencies_tree(dependencies, prefix, new_visited, color)
|
81
60
|
|
82
61
|
result
|
83
62
|
end
|
@@ -85,6 +64,7 @@ module Taski
|
|
85
64
|
private
|
86
65
|
|
87
66
|
include Utils::DependencyUtils
|
67
|
+
include Utils::TreeDisplayHelper
|
88
68
|
private :extract_class
|
89
69
|
end
|
90
70
|
|
@@ -105,7 +85,7 @@ module Taski
|
|
105
85
|
# Clean method with default empty implementation
|
106
86
|
# Subclasses can override this method to implement cleanup logic
|
107
87
|
def clean
|
108
|
-
# Default implementation does nothing
|
88
|
+
# Default implementation does nothing - allows optional cleanup in subclasses
|
109
89
|
end
|
110
90
|
end
|
111
91
|
end
|
@@ -16,7 +16,8 @@ module Taski
|
|
16
16
|
@dependencies ||= []
|
17
17
|
@definitions ||= {}
|
18
18
|
|
19
|
-
#
|
19
|
+
# Enable forward declarations by creating ref method on first define usage
|
20
|
+
# This allows tasks to reference other tasks before they're defined
|
20
21
|
create_ref_method_if_needed
|
21
22
|
|
22
23
|
# Create method that tracks dependencies on first call
|
@@ -61,7 +62,7 @@ module Taski
|
|
61
62
|
def self.#{name}
|
62
63
|
__resolve__[__callee__] ||= false
|
63
64
|
if __resolve__[__callee__]
|
64
|
-
# already resolved
|
65
|
+
# already resolved - prevents infinite recursion
|
65
66
|
else
|
66
67
|
__resolve__[__callee__] = true
|
67
68
|
throw :unresolved, [self, __callee__]
|
@@ -93,7 +94,8 @@ module Taski
|
|
93
94
|
# Reset resolution state
|
94
95
|
classes.each do |task_class|
|
95
96
|
klass = task_class[:klass]
|
96
|
-
#
|
97
|
+
# Reference objects are stateless but Task classes store analysis state
|
98
|
+
# Selective reset prevents errors while ensuring clean state for next analysis
|
97
99
|
if klass.respond_to?(:instance_variable_set) && !klass.is_a?(Taski::Reference)
|
98
100
|
klass.instance_variable_set(:@__resolve__, {})
|
99
101
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../dependency_analyzer"
|
4
|
+
require_relative "../utils/dependency_resolver_helper"
|
4
5
|
|
5
6
|
module Taski
|
6
7
|
class Task
|
@@ -12,75 +13,13 @@ module Taski
|
|
12
13
|
# @param resolved [Array] Array of resolved tasks
|
13
14
|
# @return [self] Returns self for method chaining
|
14
15
|
def resolve(queue, resolved)
|
15
|
-
|
16
|
-
|
17
|
-
@dependencies.each do |task|
|
18
|
-
task_class = extract_class(task)
|
19
|
-
|
20
|
-
# Reorder in resolved list for correct priority
|
21
|
-
resolved.delete(task_class) if resolved.include?(task_class)
|
22
|
-
queue << task_class
|
23
|
-
end
|
24
|
-
|
25
|
-
# Create getter methods for defined values
|
26
|
-
create_defined_methods
|
27
|
-
|
28
|
-
self
|
16
|
+
resolve_common(queue, resolved, custom_hook: -> { create_defined_methods })
|
29
17
|
end
|
30
18
|
|
31
19
|
# Resolve all dependencies in topological order with circular dependency detection
|
32
20
|
# @return [Array<Class>] Array of tasks in dependency order
|
33
21
|
def resolve_dependencies
|
34
|
-
|
35
|
-
resolved = []
|
36
|
-
visited = Set.new
|
37
|
-
resolving = Set.new # Track currently resolving tasks
|
38
|
-
path_map = {self => []} # Track paths to each task
|
39
|
-
|
40
|
-
while queue.any?
|
41
|
-
task_class = queue.shift
|
42
|
-
next if visited.include?(task_class)
|
43
|
-
|
44
|
-
# Check for circular dependency
|
45
|
-
if resolving.include?(task_class)
|
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)
|
49
|
-
end
|
50
|
-
|
51
|
-
resolving << task_class
|
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
|
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
|
-
|
66
|
-
resolving.delete(task_class)
|
67
|
-
resolved << task_class unless resolved.include?(task_class)
|
68
|
-
end
|
69
|
-
|
70
|
-
resolved
|
71
|
-
end
|
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
|
-
Utils::CircularDependencyHelpers.build_error_message(cycle_path, "dependency")
|
22
|
+
resolve_dependencies_common
|
84
23
|
end
|
85
24
|
|
86
25
|
public
|
@@ -127,6 +66,7 @@ module Taski
|
|
127
66
|
private
|
128
67
|
|
129
68
|
include Utils::DependencyUtils
|
69
|
+
include Utils::DependencyResolverHelper
|
130
70
|
private :extract_class
|
131
71
|
end
|
132
72
|
end
|
@@ -15,12 +15,10 @@ module Taski
|
|
15
15
|
names.each do |name|
|
16
16
|
next if respond_to?(name)
|
17
17
|
|
18
|
-
# Define class method to access exported value
|
19
18
|
define_singleton_method(name) do
|
20
19
|
ensure_instance_built.send(name)
|
21
20
|
end
|
22
21
|
|
23
|
-
# Define instance method getter
|
24
22
|
define_method(name) do
|
25
23
|
instance_variable_get("@#{name}")
|
26
24
|
end
|
@@ -12,14 +12,12 @@ module Taski
|
|
12
12
|
# @return [Task] Returns task instance (singleton or temporary)
|
13
13
|
def build(**args)
|
14
14
|
if args.empty?
|
15
|
-
# Traditional build: singleton instance with caching
|
16
15
|
resolve_dependencies.reverse_each do |task_class|
|
17
16
|
task_class.ensure_instance_built
|
18
17
|
end
|
19
18
|
# Return the singleton instance for consistency
|
20
19
|
instance_variable_get(:@__task_instance)
|
21
20
|
else
|
22
|
-
# Parametrized build: temporary instance without caching
|
23
21
|
build_with_args(args)
|
24
22
|
end
|
25
23
|
end
|
@@ -45,7 +43,7 @@ module Taski
|
|
45
43
|
self
|
46
44
|
end
|
47
45
|
|
48
|
-
# Refresh task state
|
46
|
+
# Refresh task state
|
49
47
|
# @return [self] Returns self for method chaining
|
50
48
|
def refresh
|
51
49
|
reset!
|
@@ -82,28 +80,15 @@ module Taski
|
|
82
80
|
# Ensure task instance is built (public because called from build)
|
83
81
|
# @return [Task] The built task instance
|
84
82
|
def ensure_instance_built
|
85
|
-
#
|
83
|
+
# Double-checked locking prevents lock contention in multi-threaded builds
|
84
|
+
# First check avoids expensive synchronization when instance already exists
|
86
85
|
return @__task_instance if @__task_instance
|
87
86
|
|
88
87
|
build_monitor.synchronize do
|
89
|
-
# Check again after acquiring lock
|
90
88
|
return @__task_instance if @__task_instance
|
91
89
|
|
92
|
-
|
93
|
-
|
94
|
-
if Thread.current[thread_key]
|
95
|
-
# Build dependency path for better error message
|
96
|
-
cycle_path = build_current_dependency_path
|
97
|
-
raise CircularDependencyError, build_runtime_circular_dependency_message(cycle_path)
|
98
|
-
end
|
99
|
-
|
100
|
-
Thread.current[thread_key] = true
|
101
|
-
begin
|
102
|
-
build_dependencies
|
103
|
-
@__task_instance = build_instance
|
104
|
-
ensure
|
105
|
-
Thread.current[thread_key] = false
|
106
|
-
end
|
90
|
+
check_circular_dependency
|
91
|
+
create_and_build_instance
|
107
92
|
end
|
108
93
|
|
109
94
|
@__task_instance
|
@@ -111,6 +96,32 @@ module Taski
|
|
111
96
|
|
112
97
|
private
|
113
98
|
|
99
|
+
# === Instance Management Helper Methods ===
|
100
|
+
|
101
|
+
# Check for circular dependencies and raise error if detected
|
102
|
+
# @raise [CircularDependencyError] If circular dependency is detected
|
103
|
+
def check_circular_dependency
|
104
|
+
thread_key = build_thread_key
|
105
|
+
if Thread.current[thread_key]
|
106
|
+
# Build dependency path for better error message
|
107
|
+
cycle_path = build_current_dependency_path
|
108
|
+
raise CircularDependencyError, build_runtime_circular_dependency_message(cycle_path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create and build instance with proper thread-local state management
|
113
|
+
# @return [void] Sets @__task_instance
|
114
|
+
def create_and_build_instance
|
115
|
+
thread_key = build_thread_key
|
116
|
+
Thread.current[thread_key] = true
|
117
|
+
begin
|
118
|
+
build_dependencies
|
119
|
+
@__task_instance = build_instance
|
120
|
+
ensure
|
121
|
+
Thread.current[thread_key] = false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
114
125
|
# === Core Helper Methods ===
|
115
126
|
|
116
127
|
# Get or create build monitor for thread safety
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Taski
|
4
|
+
# Color utilities for tree display
|
5
|
+
# Provides ANSI color codes for enhanced tree visualization
|
6
|
+
class TreeColors
|
7
|
+
# ANSI color codes
|
8
|
+
COLORS = {
|
9
|
+
red: "\e[31m",
|
10
|
+
green: "\e[32m",
|
11
|
+
yellow: "\e[33m",
|
12
|
+
blue: "\e[34m",
|
13
|
+
magenta: "\e[35m",
|
14
|
+
cyan: "\e[36m",
|
15
|
+
gray: "\e[90m",
|
16
|
+
reset: "\e[0m",
|
17
|
+
bold: "\e[1m"
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
class << self
|
21
|
+
# Check if colors should be enabled
|
22
|
+
# @return [Boolean] true if colors should be used
|
23
|
+
def enabled?
|
24
|
+
return @enabled unless @enabled.nil?
|
25
|
+
@enabled = tty? && !no_color?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Enable or disable colors
|
29
|
+
# @param value [Boolean] whether to enable colors
|
30
|
+
attr_writer :enabled
|
31
|
+
|
32
|
+
# Colorize text for Section names (blue)
|
33
|
+
# @param text [String] text to colorize
|
34
|
+
# @return [String] colorized text
|
35
|
+
def section(text)
|
36
|
+
colorize(text, :blue, bold: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Colorize text for Task names (green)
|
40
|
+
# @param text [String] text to colorize
|
41
|
+
# @return [String] colorized text
|
42
|
+
def task(text)
|
43
|
+
colorize(text, :green)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Colorize text for implementation candidates (yellow)
|
47
|
+
# @param text [String] text to colorize
|
48
|
+
# @return [String] colorized text
|
49
|
+
def implementations(text)
|
50
|
+
colorize(text, :yellow)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Colorize tree connectors (gray)
|
54
|
+
# @param text [String] text to colorize
|
55
|
+
# @return [String] colorized text
|
56
|
+
def connector(text)
|
57
|
+
colorize(text, :gray)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Apply color to text
|
63
|
+
# @param text [String] text to colorize
|
64
|
+
# @param color [Symbol] color name
|
65
|
+
# @param bold [Boolean] whether to make text bold
|
66
|
+
# @return [String] colorized text
|
67
|
+
def colorize(text, color, bold: false)
|
68
|
+
return text unless enabled?
|
69
|
+
|
70
|
+
result = ""
|
71
|
+
result += COLORS[:bold] if bold
|
72
|
+
result += COLORS[color]
|
73
|
+
result += text
|
74
|
+
result += COLORS[:reset]
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check if output is a TTY
|
79
|
+
# @return [Boolean] true if stdout is a TTY
|
80
|
+
def tty?
|
81
|
+
$stdout.tty?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if NO_COLOR environment variable is set
|
85
|
+
# @return [Boolean] true if colors should be disabled
|
86
|
+
def no_color?
|
87
|
+
ENV.key?("NO_COLOR")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Taski
|
4
|
+
module Utils
|
5
|
+
# Helper module for dependency resolution functionality
|
6
|
+
# Provides common logic for resolving dependencies and detecting circular dependencies
|
7
|
+
module DependencyResolverHelper
|
8
|
+
private
|
9
|
+
|
10
|
+
# Resolve all dependencies in topological order with circular dependency detection
|
11
|
+
# @return [Array<Class>] Array of tasks in dependency order
|
12
|
+
def resolve_dependencies_common
|
13
|
+
queue = [self]
|
14
|
+
resolved = []
|
15
|
+
visited = Set.new
|
16
|
+
resolving = Set.new
|
17
|
+
path_map = {self => []}
|
18
|
+
|
19
|
+
while queue.any?
|
20
|
+
task_class = queue.shift
|
21
|
+
next if visited.include?(task_class)
|
22
|
+
|
23
|
+
if resolving.include?(task_class)
|
24
|
+
cycle_path = build_cycle_path(task_class, path_map)
|
25
|
+
raise CircularDependencyError, build_circular_dependency_message(cycle_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
resolving << task_class
|
29
|
+
visited << task_class
|
30
|
+
|
31
|
+
current_path = path_map[task_class] || []
|
32
|
+
task_class.resolve(queue, resolved)
|
33
|
+
|
34
|
+
task_class.instance_variable_get(:@dependencies)&.each do |dep|
|
35
|
+
dep_class = extract_class(dep)
|
36
|
+
path_map[dep_class] = current_path + [task_class] unless path_map.key?(dep_class)
|
37
|
+
end
|
38
|
+
|
39
|
+
resolving.delete(task_class)
|
40
|
+
resolved << task_class unless resolved.include?(task_class)
|
41
|
+
end
|
42
|
+
|
43
|
+
resolved
|
44
|
+
end
|
45
|
+
|
46
|
+
# Resolve method for dependency graph (called by resolve_dependencies)
|
47
|
+
# @param queue [Array] Queue of tasks to process
|
48
|
+
# @param resolved [Array] Array of resolved tasks
|
49
|
+
# @param options [Hash] Optional parameters for customization
|
50
|
+
# @return [self] Returns self for method chaining
|
51
|
+
def resolve_common(queue, resolved, options = {})
|
52
|
+
@dependencies ||= []
|
53
|
+
|
54
|
+
@dependencies.each do |task|
|
55
|
+
task_class = extract_class(task)
|
56
|
+
|
57
|
+
# Reorder in resolved list for correct priority
|
58
|
+
resolved.delete(task_class) if resolved.include?(task_class)
|
59
|
+
queue << task_class
|
60
|
+
end
|
61
|
+
|
62
|
+
# Call custom hook if provided
|
63
|
+
options[:custom_hook]&.call
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Build the cycle path from path tracking information
|
69
|
+
# @param task_class [Class] Current task class
|
70
|
+
# @param path_map [Hash] Map of paths to each task
|
71
|
+
# @return [Array] Cycle path array
|
72
|
+
def build_cycle_path(task_class, path_map)
|
73
|
+
path = path_map[task_class] || []
|
74
|
+
path + [task_class]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Build detailed error message for circular dependencies
|
78
|
+
# @param cycle_path [Array] Array representing the circular dependency path
|
79
|
+
# @return [String] Formatted error message
|
80
|
+
def build_circular_dependency_message(cycle_path)
|
81
|
+
Utils::CircularDependencyHelpers.build_error_message(cycle_path, "dependency")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Taski
|
4
|
+
module Utils
|
5
|
+
# Helper module for tree display functionality
|
6
|
+
# Provides common logic for displaying dependency trees
|
7
|
+
module TreeDisplayHelper
|
8
|
+
private
|
9
|
+
|
10
|
+
# Render dependencies as tree structure
|
11
|
+
# @param dependencies [Array] Array of dependency objects
|
12
|
+
# @param prefix [String] Current indentation prefix
|
13
|
+
# @param visited [Set] Set of visited classes
|
14
|
+
# @param color [Boolean] Whether to use color output
|
15
|
+
# @return [String] Formatted dependency tree string
|
16
|
+
def render_dependencies_tree(dependencies, prefix, visited, color)
|
17
|
+
result = ""
|
18
|
+
|
19
|
+
dependencies = dependencies.uniq { |dep| extract_class(dep) }
|
20
|
+
dependencies.each_with_index do |dep, index|
|
21
|
+
dep_class = extract_class(dep)
|
22
|
+
is_last = index == dependencies.length - 1
|
23
|
+
|
24
|
+
connector_text = is_last ? "└── " : "├── "
|
25
|
+
connector = color ? TreeColors.connector(connector_text) : connector_text
|
26
|
+
child_prefix_text = is_last ? " " : "│ "
|
27
|
+
child_prefix = prefix + (color ? TreeColors.connector(child_prefix_text) : child_prefix_text)
|
28
|
+
|
29
|
+
dep_tree = if dep_class.respond_to?(:tree)
|
30
|
+
dep_class.tree(child_prefix, visited, color: color)
|
31
|
+
else
|
32
|
+
"#{child_prefix}#{dep_class.name}\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
dep_lines = dep_tree.lines
|
36
|
+
if dep_lines.any?
|
37
|
+
# Replace the first line prefix with connector
|
38
|
+
first_line = dep_lines[0]
|
39
|
+
fixed_first_line = first_line.sub(/^#{Regexp.escape(child_prefix)}/, prefix + connector)
|
40
|
+
result += fixed_first_line
|
41
|
+
# Add the rest of the lines as-is
|
42
|
+
result += dep_lines[1..].join if dep_lines.length > 1
|
43
|
+
else
|
44
|
+
dep_name = color ? TreeColors.task(dep_class.name) : dep_class.name
|
45
|
+
result += "#{prefix}#{connector}#{dep_name}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check for circular dependencies and handle visited set
|
53
|
+
# @param visited [Set] Set of visited classes
|
54
|
+
# @param current_class [Class] Current class being processed
|
55
|
+
# @param prefix [String] Current indentation prefix
|
56
|
+
# @return [Array] Returns [should_return_early, result_string, new_visited_set]
|
57
|
+
def handle_circular_dependency_check(visited, current_class, prefix)
|
58
|
+
if visited.include?(current_class)
|
59
|
+
return [true, "#{prefix}#{current_class.name} (circular)\n", visited]
|
60
|
+
end
|
61
|
+
|
62
|
+
new_visited = visited.dup
|
63
|
+
new_visited << current_class
|
64
|
+
[false, nil, new_visited]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/taski/version.rb
CHANGED
data/lib/taski.rb
CHANGED
@@ -10,6 +10,7 @@ require_relative "taski/progress_display"
|
|
10
10
|
require_relative "taski/reference"
|
11
11
|
require_relative "taski/dependency_analyzer"
|
12
12
|
require_relative "taski/utils"
|
13
|
+
require_relative "taski/tree_colors"
|
13
14
|
|
14
15
|
# Load Task class components
|
15
16
|
require_relative "taski/task/base"
|
@@ -18,24 +19,27 @@ require_relative "taski/task/define_api"
|
|
18
19
|
require_relative "taski/task/instance_management"
|
19
20
|
require_relative "taski/task/dependency_resolver"
|
20
21
|
|
22
|
+
# Load Section class
|
23
|
+
require_relative "taski/section"
|
24
|
+
|
21
25
|
module Taski
|
22
26
|
# Main module for the Taski task framework
|
23
27
|
#
|
24
28
|
# Taski provides a framework for defining and managing task dependencies
|
25
|
-
# with
|
29
|
+
# with three complementary APIs:
|
26
30
|
# 1. Exports API - Export instance variables as class methods (static dependencies)
|
27
31
|
# 2. Define API - Define lazy-evaluated values with dynamic dependency resolution
|
32
|
+
# 3. Section API - Abstraction layers with runtime implementation selection
|
28
33
|
#
|
29
|
-
#
|
30
|
-
# -
|
31
|
-
# -
|
32
|
-
# -
|
33
|
-
# - Complex conditional logic determines dependencies
|
34
|
+
# API Selection Guide:
|
35
|
+
# - Use Exports API for simple static dependencies
|
36
|
+
# - Use Define API for conditional dependencies analyzed at class definition time
|
37
|
+
# - Use Section API for environment-specific implementations with static analysis
|
34
38
|
#
|
35
39
|
# Features:
|
36
40
|
# - Automatic dependency resolution (static and dynamic)
|
37
41
|
# - Static analysis of method dependencies
|
38
|
-
# - Runtime
|
42
|
+
# - Runtime implementation selection with Section API
|
39
43
|
# - Thread-safe task building
|
40
44
|
# - Circular dependency detection
|
41
45
|
# - Memory leak prevention
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taski
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ahogappa
|
@@ -43,19 +43,36 @@ files:
|
|
43
43
|
- examples/advanced_patterns.rb
|
44
44
|
- examples/progress_demo.rb
|
45
45
|
- examples/quick_start.rb
|
46
|
+
- examples/section_configuration.rb
|
46
47
|
- examples/tree_demo.rb
|
47
48
|
- lib/taski.rb
|
48
49
|
- lib/taski/dependency_analyzer.rb
|
49
50
|
- lib/taski/exceptions.rb
|
50
51
|
- lib/taski/logger.rb
|
52
|
+
- lib/taski/logging/formatter_factory.rb
|
53
|
+
- lib/taski/logging/formatter_interface.rb
|
54
|
+
- lib/taski/logging/json_formatter.rb
|
55
|
+
- lib/taski/logging/simple_formatter.rb
|
56
|
+
- lib/taski/logging/structured_formatter.rb
|
57
|
+
- lib/taski/progress/display_colors.rb
|
58
|
+
- lib/taski/progress/display_manager.rb
|
59
|
+
- lib/taski/progress/output_capture.rb
|
60
|
+
- lib/taski/progress/spinner_animation.rb
|
61
|
+
- lib/taski/progress/task_formatter.rb
|
62
|
+
- lib/taski/progress/task_status.rb
|
63
|
+
- lib/taski/progress/terminal_controller.rb
|
51
64
|
- lib/taski/progress_display.rb
|
52
65
|
- lib/taski/reference.rb
|
66
|
+
- lib/taski/section.rb
|
53
67
|
- lib/taski/task/base.rb
|
54
68
|
- lib/taski/task/define_api.rb
|
55
69
|
- lib/taski/task/dependency_resolver.rb
|
56
70
|
- lib/taski/task/exports_api.rb
|
57
71
|
- lib/taski/task/instance_management.rb
|
72
|
+
- lib/taski/tree_colors.rb
|
58
73
|
- lib/taski/utils.rb
|
74
|
+
- lib/taski/utils/dependency_resolver_helper.rb
|
75
|
+
- lib/taski/utils/tree_display_helper.rb
|
59
76
|
- lib/taski/version.rb
|
60
77
|
- sig/taski.rbs
|
61
78
|
homepage: https://github.com/ahogappa/taski
|