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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -271
  3. data/Steepfile +19 -0
  4. data/docs/advanced-features.md +625 -0
  5. data/docs/api-guide.md +509 -0
  6. data/docs/error-handling.md +684 -0
  7. data/examples/README.md +98 -42
  8. data/examples/context_demo.rb +118 -0
  9. data/examples/data_pipeline_demo.rb +231 -0
  10. data/examples/parallel_progress_demo.rb +72 -0
  11. data/examples/quick_start.rb +4 -4
  12. data/examples/reexecution_demo.rb +127 -0
  13. data/examples/{section_configuration.rb → section_demo.rb} +49 -60
  14. data/lib/taski/context.rb +50 -0
  15. data/lib/taski/execution/coordinator.rb +63 -0
  16. data/lib/taski/execution/parallel_progress_display.rb +201 -0
  17. data/lib/taski/execution/registry.rb +72 -0
  18. data/lib/taski/execution/task_wrapper.rb +255 -0
  19. data/lib/taski/section.rb +26 -254
  20. data/lib/taski/static_analysis/analyzer.rb +46 -0
  21. data/lib/taski/static_analysis/dependency_graph.rb +90 -0
  22. data/lib/taski/static_analysis/visitor.rb +130 -0
  23. data/lib/taski/task.rb +199 -0
  24. data/lib/taski/version.rb +1 -1
  25. data/lib/taski.rb +68 -39
  26. data/rbs_collection.lock.yaml +116 -0
  27. data/rbs_collection.yaml +19 -0
  28. data/sig/taski.rbs +269 -62
  29. metadata +36 -32
  30. data/examples/advanced_patterns.rb +0 -119
  31. data/examples/progress_demo.rb +0 -166
  32. data/examples/tree_demo.rb +0 -205
  33. data/lib/taski/dependency_analyzer.rb +0 -232
  34. data/lib/taski/exceptions.rb +0 -17
  35. data/lib/taski/logger.rb +0 -158
  36. data/lib/taski/logging/formatter_factory.rb +0 -34
  37. data/lib/taski/logging/formatter_interface.rb +0 -19
  38. data/lib/taski/logging/json_formatter.rb +0 -26
  39. data/lib/taski/logging/simple_formatter.rb +0 -16
  40. data/lib/taski/logging/structured_formatter.rb +0 -44
  41. data/lib/taski/progress/display_colors.rb +0 -17
  42. data/lib/taski/progress/display_manager.rb +0 -117
  43. data/lib/taski/progress/output_capture.rb +0 -105
  44. data/lib/taski/progress/spinner_animation.rb +0 -49
  45. data/lib/taski/progress/task_formatter.rb +0 -25
  46. data/lib/taski/progress/task_status.rb +0 -38
  47. data/lib/taski/progress/terminal_controller.rb +0 -35
  48. data/lib/taski/progress_display.rb +0 -57
  49. data/lib/taski/reference.rb +0 -40
  50. data/lib/taski/task/base.rb +0 -91
  51. data/lib/taski/task/define_api.rb +0 -156
  52. data/lib/taski/task/dependency_resolver.rb +0 -73
  53. data/lib/taski/task/exports_api.rb +0 -29
  54. data/lib/taski/task/instance_management.rb +0 -201
  55. data/lib/taski/tree_colors.rb +0 -91
  56. data/lib/taski/utils/dependency_resolver_helper.rb +0 -85
  57. data/lib/taski/utils/tree_display_helper.rb +0 -68
  58. data/lib/taski/utils.rb +0 -107
@@ -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