taski 0.2.0 → 0.2.2

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.
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Taski Progress Display Demo
5
+ #
6
+ # This comprehensive example demonstrates all progress display features:
7
+ # 1. Basic spinner animation with success/failure indicators
8
+ # 2. Output capture with 5-line tail display
9
+ # 3. Real-world build scenario with external command simulation
10
+ # 4. TTY detection for clean file output
11
+ #
12
+ # Run: ruby examples/progress_demo.rb
13
+ # Try: ruby examples/progress_demo.rb > build.log 2>&1 && cat build.log
14
+
15
+ require_relative "../lib/taski"
16
+
17
+ puts "🎯 Taski Progress Display Demo"
18
+ puts "=" * 50
19
+
20
+ # SECTION 1: Basic Spinner Animation
21
+ puts "\n📍 SECTION 1: Basic Spinner & Success/Failure Indicators"
22
+ puts "-" * 50
23
+
24
+ class ConfigTask < Taski::Task
25
+ exports :database_url, :cache_url
26
+
27
+ def build
28
+ sleep 0.8
29
+ @database_url = "postgres://localhost/myapp"
30
+ @cache_url = "redis://localhost:6379"
31
+ end
32
+ end
33
+
34
+ class DatabaseTask < Taski::Task
35
+ exports :connection
36
+
37
+ def build
38
+ sleep 1.2
39
+ @connection = "Connected to #{ConfigTask.database_url}"
40
+ end
41
+ end
42
+
43
+ class ApplicationTask < Taski::Task
44
+ exports :status
45
+
46
+ def build
47
+ sleep 1.0
48
+ db = DatabaseTask.connection
49
+ @status = "App ready! #{db}"
50
+ end
51
+ end
52
+
53
+ ApplicationTask.build
54
+ puts "🎉 Application Status: #{ApplicationTask.status}"
55
+
56
+ # SECTION 2: Output Capture Demo
57
+ puts "\n📍 SECTION 2: Output Capture with 5-Line Tail"
58
+ puts "-" * 50
59
+
60
+ class VerboseTask < Taski::Task
61
+ exports :result
62
+
63
+ def build
64
+ puts "Starting task initialization..."
65
+ sleep 0.3
66
+
67
+ puts "Loading configuration files..."
68
+ puts "Connecting to database..."
69
+ puts "Connection established: localhost:5432"
70
+ sleep 0.3
71
+
72
+ puts "Running initial checks..."
73
+ puts "Checking schema version..."
74
+ puts "Schema is up to date"
75
+ puts "Performing data validation..."
76
+ puts "Validating user records..."
77
+ puts "Validating product records..."
78
+ puts "All validations passed"
79
+ sleep 0.4
80
+
81
+ puts "Task completed successfully!"
82
+ @result = "All operations completed"
83
+ end
84
+ end
85
+
86
+ VerboseTask.build
87
+ puts "📊 Verbose Task Result: #{VerboseTask.result}"
88
+
89
+ # SECTION 3: Production Build Scenario
90
+ puts "\n📍 SECTION 3: Production Build Scenario"
91
+ puts "-" * 50
92
+
93
+ class CompileTask < Taski::Task
94
+ exports :result
95
+
96
+ def build
97
+ puts "Starting compilation process..."
98
+ sleep 0.8
99
+
100
+ puts "Checking source files..."
101
+ puts "Found: main.c, utils.c, config.h"
102
+ sleep 0.6
103
+
104
+ puts "Running gcc compilation..."
105
+ puts "gcc -Wall -O2 -c main.c"
106
+ puts "gcc -Wall -O2 -c utils.c"
107
+ puts "main.c: In function 'main':"
108
+ puts "main.c:42: warning: unused variable 'temp'"
109
+ puts "utils.c: In function 'parse_config':"
110
+ puts "utils.c:15: warning: implicit declaration of function 'strcpy'"
111
+ sleep 0.8
112
+
113
+ puts "Linking objects..."
114
+ puts "gcc -o myapp main.o utils.o"
115
+ puts "Compilation successful!"
116
+
117
+ @result = "myapp binary created"
118
+ end
119
+ end
120
+
121
+ class TestTask < Taski::Task
122
+ exports :test_result
123
+
124
+ def build
125
+ puts "Running test suite..."
126
+ sleep 0.2
127
+
128
+ (1..8).each do |i|
129
+ puts "Test #{i}/8: #{["PASS", "PASS", "FAIL", "PASS", "PASS", "PASS", "PASS", "PASS"][i - 1]}"
130
+ sleep 0.4
131
+ end
132
+
133
+ puts "Test summary: 7/8 passed, 1 failed"
134
+ @test_result = "Tests completed with 1 failure"
135
+ end
136
+ end
137
+
138
+ CompileTask.build
139
+ puts "📦 Compilation: #{CompileTask.result}"
140
+
141
+ TestTask.build
142
+ puts "🧪 Test Result: #{TestTask.test_result}"
143
+
144
+ # SECTION 4: Error Handling Demo
145
+ puts "\n📍 SECTION 4: Error Handling Demo"
146
+ puts "-" * 50
147
+
148
+ class FailingTask < Taski::Task
149
+ def build
150
+ puts "Attempting network connection..."
151
+ sleep 1.0 # Watch it spin before failing
152
+ puts "Connection timeout after 30 seconds"
153
+ puts "Retrying connection..."
154
+ sleep 0.5
155
+ raise StandardError, "Network connection failed!"
156
+ end
157
+ end
158
+
159
+ begin
160
+ FailingTask.build
161
+ rescue Taski::TaskBuildError => e
162
+ puts "🛡️ Error handled gracefully: #{e.message}"
163
+ end
164
+
165
+ puts "\n✨ Demo Complete!"
166
+ puts "Note: Rich spinner display only appears in terminals, not when output is redirected."
@@ -1,7 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
- # Quick Start example from README
2
+ # frozen_string_literal: true
3
3
 
4
- require_relative '../lib/taski'
4
+ # Taski Quick Start Guide
5
+ #
6
+ # This example demonstrates the fundamentals of Taski:
7
+ # - Task definition with the Exports API
8
+ # - Automatic dependency resolution
9
+ # - Simple task execution
10
+ #
11
+ # Run: ruby examples/quick_start.rb
12
+
13
+ require_relative "../lib/taski"
14
+
15
+ puts "🚀 Taski Quick Start"
16
+ puts "=" * 30
5
17
 
6
18
  # Simple static dependency using Exports API
7
19
  class DatabaseSetup < Taski::Task
@@ -24,7 +36,6 @@ class APIServer < Taski::Task
24
36
  end
25
37
 
26
38
  # Execute - dependencies are resolved automatically
27
- puts "=== Quick Start Example ==="
28
39
  APIServer.build
29
40
 
30
- puts "\nResult: APIServer running on port #{APIServer.port}"
41
+ puts "\n✅ Result: APIServer running on port #{APIServer.port}"
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Tree Display Demo
5
+ #
6
+ # This example demonstrates the tree display functionality that shows
7
+ # task dependency relationships in a visual tree format.
8
+ #
9
+ # Run: ruby examples/tree_demo.rb
10
+
11
+ require_relative "../lib/taski"
12
+
13
+ puts "🌲 Taski Tree Display Demo"
14
+ puts "=" * 40
15
+
16
+ # Create a dependency chain for demonstration
17
+ class Database < Taski::Task
18
+ exports :connection_string
19
+
20
+ def build
21
+ @connection_string = "postgres://localhost/myapp"
22
+ end
23
+ end
24
+
25
+ class Cache < Taski::Task
26
+ exports :redis_url
27
+
28
+ def build
29
+ @redis_url = "redis://localhost:6379"
30
+ end
31
+ end
32
+
33
+ class Config < Taski::Task
34
+ exports :settings
35
+
36
+ def build
37
+ @settings = {
38
+ database: Database.connection_string,
39
+ cache: Cache.redis_url,
40
+ port: 3000
41
+ }
42
+ end
43
+ end
44
+
45
+ class Logger < Taski::Task
46
+ exports :log_level
47
+
48
+ def build
49
+ @log_level = "info"
50
+ end
51
+ end
52
+
53
+ class WebServer < Taski::Task
54
+ exports :server_instance
55
+
56
+ def build
57
+ @server_instance = "WebServer configured with #{Config.settings[:database]} and #{Logger.log_level}"
58
+ end
59
+ end
60
+
61
+ class Application < Taski::Task
62
+ def build
63
+ puts "Starting application..."
64
+ puts "Web server: #{WebServer.server_instance}"
65
+ puts "Config: #{Config.settings}"
66
+ end
67
+ end
68
+
69
+ puts "\n📊 Application Dependency Tree:"
70
+ puts Application.tree
71
+
72
+ puts "\n🔍 Individual Component Trees:"
73
+ puts "\nWebServer dependencies:"
74
+ puts WebServer.tree
75
+
76
+ puts "\nConfig dependencies:"
77
+ puts Config.tree
78
+
79
+ puts "\n▶️ Building Application (to verify dependencies work):"
80
+ Application.build
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'prism'
3
+ require "prism"
4
4
 
5
5
  module Taski
6
6
  module DependencyAnalyzer
@@ -19,13 +19,19 @@ module Taski
19
19
  result = Prism.parse_file(file_path)
20
20
 
21
21
  unless result.success?
22
- warn "Taski: Parse errors in #{file_path}: #{result.errors.map(&:message).join(', ')}"
22
+ Taski.logger.error("Parse errors in source file",
23
+ file: file_path,
24
+ errors: result.errors.map(&:message),
25
+ method: "#{klass}##{method_name}")
23
26
  return []
24
27
  end
25
28
 
26
29
  # Handle warnings if present
27
30
  if result.warnings.any?
28
- warn "Taski: Parse warnings in #{file_path}: #{result.warnings.map(&:message).join(', ')}"
31
+ Taski.logger.warn("Parse warnings in source file",
32
+ file: file_path,
33
+ warnings: result.warnings.map(&:message),
34
+ method: "#{klass}##{method_name}")
29
35
  end
30
36
 
31
37
  dependencies = []
@@ -39,10 +45,17 @@ module Taski
39
45
 
40
46
  dependencies.uniq
41
47
  rescue IOError, SystemCallError => e
42
- warn "Taski: Failed to read file #{file_path}: #{e.message}"
48
+ Taski.logger.error("Failed to read source file",
49
+ file: file_path,
50
+ error: e.message,
51
+ method: "#{klass}##{method_name}")
43
52
  []
44
53
  rescue => e
45
- warn "Taski: Failed to analyze method #{klass}##{method_name}: #{e.message}"
54
+ Taski.logger.error("Failed to analyze method dependencies",
55
+ class: klass.name,
56
+ method: method_name,
57
+ error: e.message,
58
+ error_class: e.class.name)
46
59
  []
47
60
  end
48
61
  end
@@ -151,12 +164,9 @@ module Taski
151
164
  else
152
165
  child_name
153
166
  end
154
- else
155
- nil
156
167
  end
157
168
  end
158
169
  end
159
-
160
170
  end
161
171
  end
162
172
  end
@@ -2,13 +2,13 @@
2
2
 
3
3
  module Taski
4
4
  # Custom exceptions for Taski framework
5
-
5
+
6
6
  # Raised when circular dependencies are detected between tasks
7
7
  class CircularDependencyError < StandardError; end
8
-
8
+
9
9
  # Raised when task analysis fails (e.g., constant resolution errors)
10
10
  class TaskAnalysisError < StandardError; end
11
-
11
+
12
12
  # Raised when task building fails during execution
13
13
  class TaskBuildError < StandardError; end
14
- end
14
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taski
4
+ # Enhanced logging functionality for Taski framework
5
+ # Provides structured logging with multiple levels and context information
6
+ class Logger
7
+ # Log levels in order of severity
8
+ LEVELS = {debug: 0, info: 1, warn: 2, error: 3}.freeze
9
+
10
+ # @param level [Symbol] Minimum log level to output (:debug, :info, :warn, :error)
11
+ # @param output [IO] Output destination (default: $stdout)
12
+ # @param format [Symbol] Log format (:simple, :structured, :json)
13
+ def initialize(level: :info, output: $stdout, format: :structured)
14
+ @level = level
15
+ @output = output
16
+ @format = format
17
+ @start_time = Time.now
18
+ end
19
+
20
+ # Log debug message with optional context
21
+ # @param message [String] Log message
22
+ # @param context [Hash] Additional context information
23
+ def debug(message, **context)
24
+ log(:debug, message, context)
25
+ end
26
+
27
+ # Log info message with optional context
28
+ # @param message [String] Log message
29
+ # @param context [Hash] Additional context information
30
+ def info(message, **context)
31
+ log(:info, message, context)
32
+ end
33
+
34
+ # Log warning message with optional context
35
+ # @param message [String] Log message
36
+ # @param context [Hash] Additional context information
37
+ def warn(message, **context)
38
+ log(:warn, message, context)
39
+ end
40
+
41
+ # Log error message with optional context
42
+ # @param message [String] Log message
43
+ # @param context [Hash] Additional context information
44
+ def error(message, **context)
45
+ log(:error, message, context)
46
+ end
47
+
48
+ # Log task build start event
49
+ # @param task_name [String] Name of the task being built
50
+ # @param dependencies [Array] List of task dependencies
51
+ # @param args [Hash] Build arguments for parametrized builds
52
+ def task_build_start(task_name, dependencies: [], args: nil)
53
+ context = {
54
+ task: task_name,
55
+ dependencies: dependencies.size,
56
+ dependency_names: dependencies.map { |dep| dep.is_a?(Hash) ? dep[:klass].inspect : dep.inspect }
57
+ }
58
+ context[:args] = args if args && !args.empty?
59
+
60
+ info("Task build started", **context)
61
+ end
62
+
63
+ # Log task build completion event
64
+ # @param task_name [String] Name of the task that was built
65
+ # @param duration [Float] Build duration in seconds
66
+ def task_build_complete(task_name, duration: nil)
67
+ context = {task: task_name}
68
+ context[:duration_ms] = (duration * 1000).round(2) if duration
69
+ info("Task build completed", **context)
70
+ end
71
+
72
+ # Log task build failure event
73
+ # @param task_name [String] Name of the task that failed
74
+ # @param error [Exception] The error that occurred
75
+ # @param duration [Float] Duration before failure in seconds
76
+ def task_build_failed(task_name, error:, duration: nil)
77
+ context = {
78
+ task: task_name,
79
+ error_class: error.class.name,
80
+ error_message: error.message
81
+ }
82
+ context[:duration_ms] = (duration * 1000).round(2) if duration
83
+ context[:backtrace] = error.backtrace&.first(3) if error.backtrace
84
+ error("Task build failed", **context)
85
+ end
86
+
87
+ # Log dependency resolution event
88
+ # @param task_name [String] Name of the task resolving dependencies
89
+ # @param resolved_count [Integer] Number of dependencies resolved
90
+ def dependency_resolved(task_name, resolved_count:)
91
+ debug("Dependencies resolved",
92
+ task: task_name,
93
+ resolved_dependencies: resolved_count)
94
+ end
95
+
96
+ # Log circular dependency detection
97
+ # @param cycle_path [Array] The circular dependency path
98
+ def circular_dependency_detected(cycle_path)
99
+ error("Circular dependency detected",
100
+ cycle: cycle_path.map { |klass| klass.name || klass.inspect },
101
+ cycle_length: cycle_path.size)
102
+ end
103
+
104
+ private
105
+
106
+ # Core logging method
107
+ # @param level [Symbol] Log level
108
+ # @param message [String] Log message
109
+ # @param context [Hash] Additional context
110
+ def log(level, message, context)
111
+ return unless should_log?(level)
112
+
113
+ case @format
114
+ when :simple
115
+ log_simple(level, message, context)
116
+ when :structured
117
+ log_structured(level, message, context)
118
+ when :json
119
+ log_json(level, message, context)
120
+ end
121
+ end
122
+
123
+ # Check if message should be logged based on current level
124
+ # @param level [Symbol] Message level to check
125
+ # @return [Boolean] True if message should be logged
126
+ def should_log?(level)
127
+ LEVELS[@level] <= LEVELS[level]
128
+ end
129
+
130
+ # Simple log format: [LEVEL] message
131
+ def log_simple(level, message, context)
132
+ @output.puts "[#{level.upcase}] #{message}"
133
+ end
134
+
135
+ # Structured log format with timestamp and context
136
+ def log_structured(level, message, context)
137
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
138
+ elapsed = ((Time.now - @start_time) * 1000).round(1)
139
+
140
+ line = "[#{timestamp}] [#{elapsed}ms] #{level.to_s.upcase.ljust(5)} Taski: #{message}"
141
+
142
+ unless context.empty?
143
+ context_parts = context.map do |key, value|
144
+ "#{key}=#{format_value(value)}"
145
+ end
146
+ line += " (#{context_parts.join(", ")})"
147
+ end
148
+
149
+ @output.puts line
150
+ end
151
+
152
+ # JSON log format for structured logging systems
153
+ def log_json(level, message, context)
154
+ require "json"
155
+
156
+ log_entry = {
157
+ timestamp: Time.now.iso8601(3),
158
+ level: level.to_s,
159
+ logger: "taski",
160
+ message: message,
161
+ elapsed_ms: ((Time.now - @start_time) * 1000).round(1)
162
+ }.merge(context)
163
+
164
+ @output.puts JSON.generate(log_entry)
165
+ end
166
+
167
+ # Format values for structured logging
168
+ def format_value(value)
169
+ case value
170
+ when String
171
+ (value.length > 50) ? "#{value[0..47]}..." : value
172
+ when Array
173
+ (value.size > 5) ? "[#{value[0..4].join(", ")}, ...]" : value.inspect
174
+ when Hash
175
+ (value.size > 3) ? "{#{value.keys[0..2].join(", ")}, ...}" : value.inspect
176
+ else
177
+ value.inspect
178
+ end
179
+ end
180
+ end
181
+
182
+ class << self
183
+ # Get the current logger instance
184
+ # @return [Logger] Current logger instance
185
+ def logger
186
+ @logger ||= Logger.new
187
+ end
188
+
189
+ # Get the current progress display instance (always enabled)
190
+ # @return [ProgressDisplay] Current progress display instance
191
+ def progress_display
192
+ @progress_display ||= ProgressDisplay.new(force_enable: ENV["TASKI_FORCE_PROGRESS"] == "1")
193
+ end
194
+
195
+ # Configure the logger with new settings
196
+ # @param level [Symbol] Log level (:debug, :info, :warn, :error)
197
+ # @param output [IO] Output destination
198
+ # @param format [Symbol] Log format (:simple, :structured, :json)
199
+ def configure_logger(level: :info, output: $stdout, format: :structured)
200
+ @logger = Logger.new(level: level, output: output, format: format)
201
+ end
202
+
203
+ # Set logger to quiet mode (only errors)
204
+ def quiet!
205
+ @logger = Logger.new(level: :error, output: @logger&.instance_variable_get(:@output) || $stdout)
206
+ end
207
+
208
+ # Set logger to verbose mode (all messages)
209
+ def verbose!
210
+ @logger = Logger.new(level: :debug, output: @logger&.instance_variable_get(:@output) || $stdout)
211
+ end
212
+ end
213
+ end