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.
- checksums.yaml +4 -4
- data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/ast/2.4/ast.rbs +73 -0
- data/.gem_rbs_collection/minitest/5.25/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/abstract_reporter.rbs +52 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/assertion.rbs +17 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/assertions.rbs +590 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/backtrace_filter.rbs +23 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/bench_spec.rbs +102 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/benchmark.rbs +259 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/composite_reporter.rbs +25 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/compress.rbs +13 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/error_on_warning.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/expectation.rbs +2 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/expectations.rbs +21 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/guard.rbs +64 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/mock.rbs +64 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/executor.rbs +46 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test/class_methods.rbs +5 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel/test.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/parallel.rbs +2 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/pride_io.rbs +62 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/pride_lol.rbs +19 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/progress_reporter.rbs +11 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/reportable.rbs +53 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/reporter.rbs +5 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/result.rbs +28 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/runnable.rbs +163 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/skip.rbs +6 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl/instance_methods.rbs +48 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec/dsl.rbs +129 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/spec.rbs +11 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/statistics_reporter.rbs +81 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/summary_reporter.rbs +18 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/test/lifecycle_hooks.rbs +92 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/test.rbs +69 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_error.rbs +12 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unexpected_warning.rbs +6 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unit/test_case.rbs +3 -0
- data/.gem_rbs_collection/minitest/5.25/minitest/unit.rbs +4 -0
- data/.gem_rbs_collection/minitest/5.25/minitest.rbs +115 -0
- data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
- data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parser/3.2/manifest.yaml +7 -0
- data/.gem_rbs_collection/parser/3.2/parser.rbs +193 -0
- data/.gem_rbs_collection/parser/3.2/polyfill.rbs +4 -0
- data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rainbow/3.0/global.rbs +7 -0
- data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +209 -0
- data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +5 -0
- data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
- data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
- data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +17 -0
- data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +129 -0
- data/.gem_rbs_collection/rubocop-ast/1.30/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop-ast/1.30/rubocop-ast.rbs +771 -0
- data/.gem_rbs_collection/simplecov/0.22/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/simplecov/0.22/simplecov.rbs +54 -0
- data/README.md +137 -248
- 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 +95 -42
- data/examples/context_demo.rb +112 -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 -66
- data/lib/taski/context.rb +52 -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 -250
- data/lib/taski/static_analysis/analyzer.rb +34 -0
- data/lib/taski/static_analysis/dependency_graph.rb +90 -0
- data/lib/taski/static_analysis/visitor.rb +114 -0
- data/lib/taski/task.rb +173 -0
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +45 -39
- data/rbs_collection.lock.yaml +116 -0
- data/rbs_collection.yaml +19 -0
- data/sig/taski.rbs +269 -62
- metadata +97 -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 -231
- 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 -115
- data/lib/taski/progress/output_capture.rb +0 -105
- data/lib/taski/progress/spinner_animation.rb +0 -46
- 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 -59
- data/lib/taski/reference.rb +0 -40
- data/lib/taski/task/base.rb +0 -90
- data/lib/taski/task/define_api.rb +0 -154
- data/lib/taski/task/dependency_resolver.rb +0 -73
- data/lib/taski/task/exports_api.rb +0 -31
- data/lib/taski/task/instance_management.rb +0 -203
- 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 -71
- data/lib/taski/utils.rb +0 -107
data/lib/taski/logger.rb
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "logging/formatter_factory"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
# Enhanced logging functionality for Taski framework
|
|
7
|
-
# Provides structured logging with multiple levels and context information
|
|
8
|
-
class Logger
|
|
9
|
-
# Log levels in order of severity
|
|
10
|
-
LEVELS = {debug: 0, info: 1, warn: 2, error: 3}.freeze
|
|
11
|
-
|
|
12
|
-
# @param level [Symbol] Minimum log level to output (:debug, :info, :warn, :error)
|
|
13
|
-
# @param output [IO] Output destination (default: $stdout)
|
|
14
|
-
# @param format [Symbol] Log format (:simple, :structured, :json)
|
|
15
|
-
def initialize(level: :info, output: $stdout, format: :structured)
|
|
16
|
-
@level = level
|
|
17
|
-
@output = output
|
|
18
|
-
@formatter = Logging::FormatterFactory.create(format)
|
|
19
|
-
@start_time = Time.now
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Log debug message with optional context
|
|
23
|
-
# @param message [String] Log message
|
|
24
|
-
# @param context [Hash] Additional context information
|
|
25
|
-
def debug(message, **context)
|
|
26
|
-
log(:debug, message, context)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Log info message with optional context
|
|
30
|
-
# @param message [String] Log message
|
|
31
|
-
# @param context [Hash] Additional context information
|
|
32
|
-
def info(message, **context)
|
|
33
|
-
log(:info, message, context)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Log warning message with optional context
|
|
37
|
-
# @param message [String] Log message
|
|
38
|
-
# @param context [Hash] Additional context information
|
|
39
|
-
def warn(message, **context)
|
|
40
|
-
log(:warn, message, context)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Log error message with optional context
|
|
44
|
-
# @param message [String] Log message
|
|
45
|
-
# @param context [Hash] Additional context information
|
|
46
|
-
def error(message, **context)
|
|
47
|
-
log(:error, message, context)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Log task build start event
|
|
51
|
-
# @param task_name [String] Name of the task being built
|
|
52
|
-
# @param dependencies [Array] List of task dependencies
|
|
53
|
-
# @param args [Hash] Build arguments for parametrized builds
|
|
54
|
-
def task_build_start(task_name, dependencies: [], args: nil)
|
|
55
|
-
context = {
|
|
56
|
-
task: task_name,
|
|
57
|
-
dependencies: dependencies.size,
|
|
58
|
-
dependency_names: dependencies.map { |dep| dep.is_a?(Hash) ? dep[:klass].inspect : dep.inspect }
|
|
59
|
-
}
|
|
60
|
-
context[:args] = args if args && !args.empty?
|
|
61
|
-
|
|
62
|
-
info("Task build started", **context)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Log task build completion event
|
|
66
|
-
# @param task_name [String] Name of the task that was built
|
|
67
|
-
# @param duration [Float] Build duration in seconds
|
|
68
|
-
def task_build_complete(task_name, duration: nil)
|
|
69
|
-
context = {task: task_name}
|
|
70
|
-
context[:duration_ms] = (duration * 1000).round(2) if duration
|
|
71
|
-
info("Task build completed", **context)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Log task build failure event
|
|
75
|
-
# @param task_name [String] Name of the task that failed
|
|
76
|
-
# @param error [Exception] The error that occurred
|
|
77
|
-
# @param duration [Float] Duration before failure in seconds
|
|
78
|
-
def task_build_failed(task_name, error:, duration: nil)
|
|
79
|
-
context = {
|
|
80
|
-
task: task_name,
|
|
81
|
-
error_class: error.class.name,
|
|
82
|
-
error_message: error.message
|
|
83
|
-
}
|
|
84
|
-
context[:duration_ms] = (duration * 1000).round(2) if duration
|
|
85
|
-
context[:backtrace] = error.backtrace&.first(3) if error.backtrace
|
|
86
|
-
error("Task build failed", **context)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Log dependency resolution event
|
|
90
|
-
# @param task_name [String] Name of the task resolving dependencies
|
|
91
|
-
# @param resolved_count [Integer] Number of dependencies resolved
|
|
92
|
-
def dependency_resolved(task_name, resolved_count:)
|
|
93
|
-
debug("Dependencies resolved",
|
|
94
|
-
task: task_name,
|
|
95
|
-
resolved_dependencies: resolved_count)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Log circular dependency detection
|
|
99
|
-
# @param cycle_path [Array] The circular dependency path
|
|
100
|
-
def circular_dependency_detected(cycle_path)
|
|
101
|
-
error("Circular dependency detected",
|
|
102
|
-
cycle: cycle_path.map { |klass| klass.name || klass.inspect },
|
|
103
|
-
cycle_length: cycle_path.size)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
# Core logging method using Strategy Pattern
|
|
109
|
-
# @param level [Symbol] Log level
|
|
110
|
-
# @param message [String] Log message
|
|
111
|
-
# @param context [Hash] Additional context
|
|
112
|
-
def log(level, message, context)
|
|
113
|
-
return unless should_log?(level)
|
|
114
|
-
|
|
115
|
-
formatted_line = @formatter.format(level, message, context, @start_time)
|
|
116
|
-
@output.puts formatted_line
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Check if message should be logged based on current level
|
|
120
|
-
# @param level [Symbol] Message level to check
|
|
121
|
-
# @return [Boolean] True if message should be logged
|
|
122
|
-
def should_log?(level)
|
|
123
|
-
LEVELS[@level] <= LEVELS[level]
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
class << self
|
|
128
|
-
# Get the current logger instance
|
|
129
|
-
# @return [Logger] Current logger instance
|
|
130
|
-
def logger
|
|
131
|
-
@logger ||= Logger.new
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Get the current progress display instance (always enabled)
|
|
135
|
-
# @return [ProgressDisplay] Current progress display instance
|
|
136
|
-
def progress_display
|
|
137
|
-
@progress_display ||= ProgressDisplay.new
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Configure the logger with new settings
|
|
141
|
-
# @param level [Symbol] Log level (:debug, :info, :warn, :error)
|
|
142
|
-
# @param output [IO] Output destination
|
|
143
|
-
# @param format [Symbol] Log format (:simple, :structured, :json)
|
|
144
|
-
def configure_logger(level: :info, output: $stdout, format: :structured)
|
|
145
|
-
@logger = Logger.new(level: level, output: output, format: format)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Set logger to quiet mode (only errors)
|
|
149
|
-
def quiet!
|
|
150
|
-
@logger = Logger.new(level: :error, output: @logger&.instance_variable_get(:@output) || $stdout)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# Set logger to verbose mode (all messages)
|
|
154
|
-
def verbose!
|
|
155
|
-
@logger = Logger.new(level: :debug, output: @logger&.instance_variable_get(:@output) || $stdout)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "simple_formatter"
|
|
4
|
-
require_relative "structured_formatter"
|
|
5
|
-
require_relative "json_formatter"
|
|
6
|
-
|
|
7
|
-
module Taski
|
|
8
|
-
module Logging
|
|
9
|
-
# Factory for creating log formatters
|
|
10
|
-
class FormatterFactory
|
|
11
|
-
# Create a formatter instance based on format symbol
|
|
12
|
-
# @param format [Symbol] Format type (:simple, :structured, :json)
|
|
13
|
-
# @return [FormatterInterface] Formatter instance
|
|
14
|
-
def self.create(format)
|
|
15
|
-
case format
|
|
16
|
-
when :simple
|
|
17
|
-
SimpleFormatter.new
|
|
18
|
-
when :structured
|
|
19
|
-
StructuredFormatter.new
|
|
20
|
-
when :json
|
|
21
|
-
JsonFormatter.new
|
|
22
|
-
else
|
|
23
|
-
raise ArgumentError, "Unknown format: #{format}. Valid formats: :simple, :structured, :json"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Get list of available formats
|
|
28
|
-
# @return [Array<Symbol>] Available format symbols
|
|
29
|
-
def self.available_formats
|
|
30
|
-
[:simple, :structured, :json]
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Logging
|
|
5
|
-
# Interface for log formatters
|
|
6
|
-
# All formatters must implement the format method
|
|
7
|
-
module FormatterInterface
|
|
8
|
-
# Format a log entry
|
|
9
|
-
# @param level [Symbol] Log level (:debug, :info, :warn, :error)
|
|
10
|
-
# @param message [String] Log message
|
|
11
|
-
# @param context [Hash] Additional context information
|
|
12
|
-
# @param start_time [Time] Logger start time for elapsed calculation
|
|
13
|
-
# @return [String] Formatted log line
|
|
14
|
-
def format(level, message, context, start_time)
|
|
15
|
-
raise NotImplementedError, "Subclass must implement format method"
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "formatter_interface"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
module Logging
|
|
7
|
-
# JSON log formatter for structured logging systems
|
|
8
|
-
class JsonFormatter
|
|
9
|
-
include FormatterInterface
|
|
10
|
-
|
|
11
|
-
def format(level, message, context, start_time)
|
|
12
|
-
require "json"
|
|
13
|
-
|
|
14
|
-
log_entry = {
|
|
15
|
-
timestamp: Time.now.iso8601(3),
|
|
16
|
-
level: level.to_s,
|
|
17
|
-
logger: "taski",
|
|
18
|
-
message: message,
|
|
19
|
-
elapsed_ms: ((Time.now - start_time) * 1000).round(1)
|
|
20
|
-
}.merge(context)
|
|
21
|
-
|
|
22
|
-
JSON.generate(log_entry)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "formatter_interface"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
module Logging
|
|
7
|
-
# Simple log formatter: [LEVEL] message
|
|
8
|
-
class SimpleFormatter
|
|
9
|
-
include FormatterInterface
|
|
10
|
-
|
|
11
|
-
def format(level, message, context, start_time)
|
|
12
|
-
"[#{level.upcase}] #{message}"
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "formatter_interface"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
module Logging
|
|
7
|
-
# Structured log formatter with timestamp and context
|
|
8
|
-
class StructuredFormatter
|
|
9
|
-
include FormatterInterface
|
|
10
|
-
|
|
11
|
-
def format(level, message, context, start_time)
|
|
12
|
-
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
13
|
-
elapsed = ((Time.now - start_time) * 1000).round(1)
|
|
14
|
-
|
|
15
|
-
line = "[#{timestamp}] [#{elapsed}ms] #{level.to_s.upcase.ljust(5)} Taski: #{message}"
|
|
16
|
-
|
|
17
|
-
unless context.empty?
|
|
18
|
-
context_parts = context.map do |key, value|
|
|
19
|
-
"#{key}=#{format_value(value)}"
|
|
20
|
-
end
|
|
21
|
-
line += " (#{context_parts.join(", ")})"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
line
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
# Format values for structured logging
|
|
30
|
-
def format_value(value)
|
|
31
|
-
case value
|
|
32
|
-
when String
|
|
33
|
-
(value.length > 50) ? "#{value[0..47]}..." : value
|
|
34
|
-
when Array
|
|
35
|
-
(value.size > 5) ? "[#{value[0..4].join(", ")}, ...]" : value.inspect
|
|
36
|
-
when Hash
|
|
37
|
-
(value.size > 3) ? "{#{value.keys[0..2].join(", ")}, ...}" : value.inspect
|
|
38
|
-
else
|
|
39
|
-
value.inspect
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Progress
|
|
5
|
-
# Color constants for progress display
|
|
6
|
-
module DisplayColors
|
|
7
|
-
COLORS = {
|
|
8
|
-
reset: "\033[0m",
|
|
9
|
-
bold: "\033[1m",
|
|
10
|
-
dim: "\033[2m",
|
|
11
|
-
cyan: "\033[36m",
|
|
12
|
-
green: "\033[32m",
|
|
13
|
-
red: "\033[31m"
|
|
14
|
-
}.freeze
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "task_status"
|
|
4
|
-
require_relative "task_formatter"
|
|
5
|
-
|
|
6
|
-
module Taski
|
|
7
|
-
module Progress
|
|
8
|
-
# Manages the display state and coordinates display updates
|
|
9
|
-
class DisplayManager
|
|
10
|
-
def initialize(terminal, spinner, output_capture, include_captured_output: false)
|
|
11
|
-
@terminal = terminal
|
|
12
|
-
@spinner = spinner
|
|
13
|
-
@output_capture = output_capture
|
|
14
|
-
@include_captured_output = include_captured_output
|
|
15
|
-
@formatter = TaskFormatter.new
|
|
16
|
-
@completed_tasks = []
|
|
17
|
-
@current_display_lines = 0
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def start_task_display(task_name)
|
|
21
|
-
clear_current_display
|
|
22
|
-
@output_capture.start
|
|
23
|
-
start_spinner_display(task_name)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def complete_task_display(task_name, duration:)
|
|
27
|
-
status = TaskStatus.new(name: task_name, duration: duration)
|
|
28
|
-
finish_task_display(status)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def fail_task_display(task_name, error:, duration:)
|
|
32
|
-
status = TaskStatus.new(name: task_name, duration: duration, error: error)
|
|
33
|
-
finish_task_display(status)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def clear_all_displays
|
|
37
|
-
@spinner.stop
|
|
38
|
-
@output_capture.stop
|
|
39
|
-
clear_current_display
|
|
40
|
-
|
|
41
|
-
# Display final summary of all completed tasks
|
|
42
|
-
if @completed_tasks.any?
|
|
43
|
-
@completed_tasks.each do |status|
|
|
44
|
-
@terminal.puts @formatter.format_completed_task(status)
|
|
45
|
-
end
|
|
46
|
-
@terminal.flush
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
@completed_tasks.clear
|
|
50
|
-
@current_display_lines = 0
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
def start_spinner_display(task_name)
|
|
56
|
-
@spinner.start(@terminal, task_name) do |spinner_char, name|
|
|
57
|
-
display_current_state(spinner_char, name)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def display_current_state(spinner_char, task_name)
|
|
62
|
-
clear_current_display
|
|
63
|
-
|
|
64
|
-
lines_count = 0
|
|
65
|
-
|
|
66
|
-
# Only display current task with spinner (no past completed tasks during execution)
|
|
67
|
-
@terminal.puts @formatter.format_current_task(spinner_char, task_name)
|
|
68
|
-
lines_count += 1
|
|
69
|
-
|
|
70
|
-
# Display output lines
|
|
71
|
-
@output_capture.last_lines.each do |line|
|
|
72
|
-
@terminal.puts @formatter.format_output_line(line)
|
|
73
|
-
lines_count += 1
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
@current_display_lines = lines_count
|
|
77
|
-
@terminal.flush
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def finish_task_display(status)
|
|
81
|
-
@spinner.stop
|
|
82
|
-
|
|
83
|
-
# Capture output before stopping
|
|
84
|
-
captured_output = @output_capture.last_lines
|
|
85
|
-
@output_capture.stop
|
|
86
|
-
clear_current_display
|
|
87
|
-
|
|
88
|
-
# Include captured output if requested (typically for test environments)
|
|
89
|
-
if @include_captured_output && captured_output.any?
|
|
90
|
-
captured_output.each do |line|
|
|
91
|
-
@terminal.puts line.chomp
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
@completed_tasks << status
|
|
96
|
-
display_final_state
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def display_final_state
|
|
100
|
-
# Only display the newly completed task (last one)
|
|
101
|
-
if @completed_tasks.any?
|
|
102
|
-
latest_task = @completed_tasks.last
|
|
103
|
-
@terminal.puts @formatter.format_completed_task(latest_task)
|
|
104
|
-
end
|
|
105
|
-
@terminal.flush
|
|
106
|
-
@current_display_lines = 1 # Only one line for the latest task
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def clear_current_display
|
|
110
|
-
@terminal.clear_lines(@current_display_lines)
|
|
111
|
-
@current_display_lines = 0
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Progress
|
|
5
|
-
# Captures stdout and maintains last N lines like tail -f
|
|
6
|
-
class OutputCapture
|
|
7
|
-
MAX_LINES = 10
|
|
8
|
-
DISPLAY_LINES = 5
|
|
9
|
-
|
|
10
|
-
def initialize(main_output)
|
|
11
|
-
@main_output = main_output
|
|
12
|
-
@buffer = []
|
|
13
|
-
@capturing = false
|
|
14
|
-
@original_stdout = nil
|
|
15
|
-
@pipe_reader = nil
|
|
16
|
-
@pipe_writer = nil
|
|
17
|
-
@capture_thread = nil
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def start
|
|
21
|
-
return if @capturing
|
|
22
|
-
|
|
23
|
-
@buffer.clear
|
|
24
|
-
setup_stdout_redirection
|
|
25
|
-
@capturing = true
|
|
26
|
-
|
|
27
|
-
start_capture_thread
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def stop
|
|
31
|
-
return unless @capturing
|
|
32
|
-
|
|
33
|
-
@capturing = false
|
|
34
|
-
|
|
35
|
-
# Restore stdout
|
|
36
|
-
restore_stdout
|
|
37
|
-
|
|
38
|
-
# Clean up pipes and thread
|
|
39
|
-
cleanup_capture_thread
|
|
40
|
-
cleanup_pipes
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def last_lines
|
|
44
|
-
@buffer.last(DISPLAY_LINES)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def capturing?
|
|
48
|
-
@capturing
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
def setup_stdout_redirection
|
|
54
|
-
@original_stdout = $stdout
|
|
55
|
-
@pipe_reader, @pipe_writer = IO.pipe
|
|
56
|
-
$stdout = @pipe_writer
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def restore_stdout
|
|
60
|
-
return unless @original_stdout
|
|
61
|
-
|
|
62
|
-
$stdout = @original_stdout
|
|
63
|
-
@original_stdout = nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def start_capture_thread
|
|
67
|
-
@capture_thread = Thread.new do
|
|
68
|
-
while (line = @pipe_reader.gets)
|
|
69
|
-
line = line.chomp
|
|
70
|
-
next if line.empty?
|
|
71
|
-
next if skip_line?(line)
|
|
72
|
-
|
|
73
|
-
add_line_to_buffer(line)
|
|
74
|
-
end
|
|
75
|
-
rescue IOError
|
|
76
|
-
# Pipe closed, normal termination
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def skip_line?(line)
|
|
81
|
-
# Skip logger lines (they appear separately)
|
|
82
|
-
line.match?(/^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]/)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def add_line_to_buffer(line)
|
|
86
|
-
@buffer << line
|
|
87
|
-
@buffer.shift while @buffer.length > MAX_LINES
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def cleanup_capture_thread
|
|
91
|
-
@capture_thread&.join(0.1)
|
|
92
|
-
@capture_thread = nil
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def cleanup_pipes
|
|
96
|
-
[@pipe_writer, @pipe_reader].each do |pipe|
|
|
97
|
-
pipe&.close
|
|
98
|
-
rescue IOError
|
|
99
|
-
# Already closed, ignore
|
|
100
|
-
end
|
|
101
|
-
@pipe_writer = @pipe_reader = nil
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Progress
|
|
5
|
-
# Spinner animation with dots-style characters
|
|
6
|
-
class SpinnerAnimation
|
|
7
|
-
SPINNER_CHARS = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"].freeze
|
|
8
|
-
FRAME_DELAY = 0.1
|
|
9
|
-
|
|
10
|
-
def initialize
|
|
11
|
-
@frame = 0
|
|
12
|
-
@running = false
|
|
13
|
-
@thread = nil
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def start(terminal, task_name, &display_callback)
|
|
17
|
-
return if @running
|
|
18
|
-
|
|
19
|
-
@running = true
|
|
20
|
-
@frame = 0
|
|
21
|
-
|
|
22
|
-
@thread = Thread.new do
|
|
23
|
-
while @running
|
|
24
|
-
current_char = SPINNER_CHARS[@frame % SPINNER_CHARS.length]
|
|
25
|
-
display_callback&.call(current_char, task_name)
|
|
26
|
-
|
|
27
|
-
@frame += 1
|
|
28
|
-
sleep FRAME_DELAY
|
|
29
|
-
end
|
|
30
|
-
rescue
|
|
31
|
-
# Silently handle thread errors
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def stop
|
|
36
|
-
@running = false
|
|
37
|
-
@thread&.join(0.2)
|
|
38
|
-
@thread = nil
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def running?
|
|
42
|
-
@running
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "display_colors"
|
|
4
|
-
|
|
5
|
-
module Taski
|
|
6
|
-
module Progress
|
|
7
|
-
# Handles formatting of task display messages
|
|
8
|
-
class TaskFormatter
|
|
9
|
-
include DisplayColors
|
|
10
|
-
|
|
11
|
-
def format_completed_task(status)
|
|
12
|
-
color = status.success? ? COLORS[:green] : COLORS[:red]
|
|
13
|
-
"#{color}#{COLORS[:bold]}#{status.icon} #{status.name}#{COLORS[:reset]} #{COLORS[:dim]}#{status.format_duration}#{COLORS[:reset]}"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def format_current_task(spinner_char, task_name)
|
|
17
|
-
"#{COLORS[:cyan]}#{spinner_char}#{COLORS[:reset]} #{COLORS[:bold]}#{task_name}#{COLORS[:reset]}"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def format_output_line(line)
|
|
21
|
-
" #{COLORS[:dim]}#{line}#{COLORS[:reset]}"
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Progress
|
|
5
|
-
# Represents task execution status
|
|
6
|
-
class TaskStatus
|
|
7
|
-
attr_reader :name, :duration, :error
|
|
8
|
-
|
|
9
|
-
def initialize(name:, duration: nil, error: nil)
|
|
10
|
-
@name = name
|
|
11
|
-
@duration = duration
|
|
12
|
-
@error = error
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def success?
|
|
16
|
-
@error.nil?
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def failure?
|
|
20
|
-
!success?
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def duration_ms
|
|
24
|
-
return nil unless @duration
|
|
25
|
-
(@duration * 1000).round(1)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def icon
|
|
29
|
-
success? ? "✅" : "❌"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def format_duration
|
|
33
|
-
return "" unless duration_ms
|
|
34
|
-
"(#{duration_ms}ms)"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Taski
|
|
4
|
-
module Progress
|
|
5
|
-
# Terminal control operations with ANSI escape sequences
|
|
6
|
-
class TerminalController
|
|
7
|
-
# ANSI escape sequences
|
|
8
|
-
MOVE_UP = "\033[A"
|
|
9
|
-
CLEAR_LINE = "\033[K"
|
|
10
|
-
MOVE_UP_AND_CLEAR = "#{MOVE_UP}#{CLEAR_LINE}"
|
|
11
|
-
|
|
12
|
-
def initialize(output)
|
|
13
|
-
@output = output
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def clear_lines(count)
|
|
17
|
-
return if count == 0
|
|
18
|
-
|
|
19
|
-
count.times { @output.print MOVE_UP_AND_CLEAR }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def puts(text)
|
|
23
|
-
@output.puts text
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def print(text)
|
|
27
|
-
@output.print text
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def flush
|
|
31
|
-
@output.flush
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|