taski 0.3.1 → 0.4.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 +101 -271
- 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 +98 -42
- data/examples/context_demo.rb +118 -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 -60
- data/lib/taski/context.rb +50 -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 -254
- data/lib/taski/static_analysis/analyzer.rb +46 -0
- data/lib/taski/static_analysis/dependency_graph.rb +90 -0
- data/lib/taski/static_analysis/visitor.rb +130 -0
- data/lib/taski/task.rb +199 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +68 -39
- data/rbs_collection.lock.yaml +116 -0
- data/rbs_collection.yaml +19 -0
- data/sig/taski.rbs +269 -62
- metadata +36 -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 -232
- 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 -117
- data/lib/taski/progress/output_capture.rb +0 -105
- data/lib/taski/progress/spinner_animation.rb +0 -49
- 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 -57
- data/lib/taski/reference.rb +0 -40
- data/lib/taski/task/base.rb +0 -91
- data/lib/taski/task/define_api.rb +0 -156
- data/lib/taski/task/dependency_resolver.rb +0 -73
- data/lib/taski/task/exports_api.rb +0 -29
- data/lib/taski/task/instance_management.rb +0 -201
- 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 -68
- data/lib/taski/utils.rb +0 -107
data/lib/taski/tree_colors.rb
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
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
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
|
@@ -1,68 +0,0 @@
|
|
|
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/utils.rb
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
# Common utility functions for the Taski framework
|
|
5
|
-
module Utils
|
|
6
|
-
# Handle circular dependency error message generation
|
|
7
|
-
module CircularDependencyHelpers
|
|
8
|
-
# Build detailed error message for circular dependencies
|
|
9
|
-
# @param cycle_path [Array<Class>] The circular dependency path
|
|
10
|
-
# @param context [String] Context of the error (dependency, runtime)
|
|
11
|
-
# @return [String] Formatted error message
|
|
12
|
-
def self.build_error_message(cycle_path, context = "dependency")
|
|
13
|
-
path_names = cycle_path.map { |klass| klass.name || klass.to_s }
|
|
14
|
-
|
|
15
|
-
message = "Circular dependency detected!\n"
|
|
16
|
-
message += "Cycle: #{path_names.join(" → ")}\n\n"
|
|
17
|
-
message += "The #{context} chain is:\n"
|
|
18
|
-
|
|
19
|
-
cycle_path.each_cons(2).with_index do |(from, to), index|
|
|
20
|
-
action = (context == "dependency") ? "depends on" : "is trying to build"
|
|
21
|
-
message += " #{index + 1}. #{from.name} #{action} → #{to.name}\n"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
message += "\nThis creates an infinite loop that cannot be resolved." if context == "dependency"
|
|
25
|
-
message
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Common dependency utility functions
|
|
30
|
-
module DependencyUtils
|
|
31
|
-
# Extract class from dependency hash
|
|
32
|
-
# @param dep [Hash] Dependency information
|
|
33
|
-
# @return [Class] The dependency class
|
|
34
|
-
def extract_class(dep)
|
|
35
|
-
klass = dep[:klass]
|
|
36
|
-
klass.is_a?(Reference) ? klass.deref : klass
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Common task build utility functions
|
|
41
|
-
module TaskBuildHelpers
|
|
42
|
-
# Format arguments hash for display in error messages
|
|
43
|
-
# @param args [Hash] Arguments hash
|
|
44
|
-
# @return [String] Formatted arguments string
|
|
45
|
-
def self.format_args(args)
|
|
46
|
-
return "" if args.nil? || args.empty?
|
|
47
|
-
|
|
48
|
-
formatted_pairs = args.map do |key, value|
|
|
49
|
-
"#{key}: #{value.inspect}"
|
|
50
|
-
end
|
|
51
|
-
"{#{formatted_pairs.join(", ")}}"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Execute block with comprehensive build logging and progress display
|
|
55
|
-
# @param task_name [String] Name of the task being built
|
|
56
|
-
# @param dependencies [Array] List of dependencies
|
|
57
|
-
# @param args [Hash] Build arguments for parametrized builds
|
|
58
|
-
# @yield Block to execute with logging
|
|
59
|
-
# @return [Object] Result of the block execution
|
|
60
|
-
def self.with_build_logging(task_name, dependencies: [], args: nil)
|
|
61
|
-
build_start_time = Time.now
|
|
62
|
-
|
|
63
|
-
begin
|
|
64
|
-
# Traditional logging first (before any stdout redirection)
|
|
65
|
-
Taski.logger.task_build_start(task_name, dependencies: dependencies, args: args)
|
|
66
|
-
|
|
67
|
-
# Show progress display if enabled (this may redirect stdout)
|
|
68
|
-
Taski.progress_display&.start_task(task_name, dependencies: dependencies)
|
|
69
|
-
|
|
70
|
-
result = yield
|
|
71
|
-
duration = Time.now - build_start_time
|
|
72
|
-
|
|
73
|
-
# Complete progress display first (this restores stdout)
|
|
74
|
-
Taski.progress_display&.complete_task(task_name, duration: duration)
|
|
75
|
-
|
|
76
|
-
# Then do logging (on restored stdout)
|
|
77
|
-
begin
|
|
78
|
-
Taski.logger.task_build_complete(task_name, duration: duration)
|
|
79
|
-
rescue IOError
|
|
80
|
-
# If logger fails due to closed stream, write to STDERR instead
|
|
81
|
-
warn "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")}] INFO Taski: Task build completed (task=#{task_name}, duration_ms=#{(duration * 1000).round(2)})"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
result
|
|
85
|
-
rescue => e
|
|
86
|
-
duration = Time.now - build_start_time
|
|
87
|
-
|
|
88
|
-
# Complete progress display first (with error)
|
|
89
|
-
Taski.progress_display&.fail_task(task_name, error: e, duration: duration)
|
|
90
|
-
|
|
91
|
-
# Then do error logging (on restored stdout)
|
|
92
|
-
begin
|
|
93
|
-
Taski.logger.task_build_failed(task_name, error: e, duration: duration)
|
|
94
|
-
rescue IOError
|
|
95
|
-
# If logger fails due to closed stream, write to STDERR instead
|
|
96
|
-
warn "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")}] ERROR Taski: Task build failed (task=#{task_name}, error=#{e.message}, duration_ms=#{(duration * 1000).round(2)})"
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
error_message = "Failed to build task #{task_name}"
|
|
100
|
-
error_message += " with args #{format_args(args)}" if args && !args.empty?
|
|
101
|
-
error_message += ": #{e.message}"
|
|
102
|
-
raise TaskBuildError, error_message
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|