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.
@@ -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
- # Only call if the method is available (loaded by dependency_resolver)
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
- # If we're in a define context, throw for dependency tracking
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
- return "#{prefix}#{name} (circular)\n" if visited.include?(self)
51
-
52
- visited = visited.dup
53
- visited << self
54
-
55
- result = "#{prefix}#{name}\n"
56
-
57
- dependencies = (@dependencies || []).uniq { |dep| extract_class(dep) }
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
- # Ensure ref method is defined first time define is called
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
- # Only reset Task classes, not Reference objects
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
- @dependencies ||= []
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
- queue = [self]
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 (currently just resets)
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
- # Use double-checked locking pattern for thread safety
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
- # Prevent infinite recursion using thread-local storage
93
- thread_key = build_thread_key
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Taski
4
- VERSION = "0.2.3"
4
+ VERSION = "0.3.1"
5
5
  end
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 two complementary APIs:
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
- # Use Define API when:
30
- # - Dependencies change based on runtime conditions
31
- # - Environment-specific configurations
32
- # - Feature flags determine which classes to use
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 dependency resolution for conditional logic
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.2.3
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