taski 0.2.1 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e44ea93464ceaf8994d761cec75cf4d32df7cdb8f90d25b4cfcae5be7f8a586
4
- data.tar.gz: 8a1b5fae85bbffb28756f2e13b3f98c2cb8023c76346b5da722ac55e87ef3cc5
3
+ metadata.gz: da68d3dc067cdf14dd29a438cbf88cc7b00769000682417dc3395eb63f1a1659
4
+ data.tar.gz: a5ec661eeea77a73f77b07cbc09dfc4aef88fce2e856bac0366a5e32782356cc
5
5
  SHA512:
6
- metadata.gz: '037285b1bc19edd6d674f6748674fa8443c0cb40d5199de86bdfa3696c1597308d69ff5236a7114c43cf666a27e62f5c4111c5a36301736c4fdac98cdd65b4c4'
7
- data.tar.gz: afa967d630b56f9de1f4974879533572d06c42d574dce6bea69da9635ce69e06251af555168ff0b3475e95d5a1d6d07e8951e6052f0f6e04291d07b15d23239b
6
+ metadata.gz: 68b3c5ddaf63a71067fea5f3c6fdc47c80e6a020f0e7bd417f8cc03e8f595aa4a3f0b16826bf3d8a65935959ee7a243074c51db4bb766c296d25c73817fe90a8
7
+ data.tar.gz: ed2a3f80b1a5987c9a2ce8d410c6594c4edd35483a1be9507de0565582facba12e238f2862ae5964af27180431c21b3b80d00887aab8525ee89af2bfb91a507a
data/README.md CHANGED
@@ -99,7 +99,7 @@ EnvironmentConfig.build
99
99
 
100
100
  ### When to Use Each API
101
101
 
102
- - **Define API**: Best for dynamic runtime dependencies. Cannot contain side effects in definition blocks.
102
+ - **Define API**: Best for dynamic runtime dependencies. Cannot contain side effects in definition blocks. Dependencies are analyzed at class definition time, not runtime.
103
103
  - **Exports API**: Ideal for static dependencies. Supports side effects in build methods.
104
104
 
105
105
  | Use Case | API | Example |
@@ -109,6 +109,8 @@ EnvironmentConfig.build
109
109
  | Side effects | Exports | Database connections, I/O |
110
110
  | Conditional processing | Define | Algorithm selection |
111
111
 
112
+ **Note**: Define API analyzes dependencies when the class is defined. Conditional dependencies like `ENV['USE_NEW'] ? TaskA : TaskB` will only include the task selected at class definition time, not runtime.
113
+
112
114
  ## ✨ Key Features
113
115
 
114
116
  - **Automatic Dependency Resolution**: Dependencies detected through static analysis
@@ -116,6 +118,81 @@ EnvironmentConfig.build
116
118
  - **Circular Dependency Detection**: Clear error messages with detailed paths
117
119
  - **Granular Execution**: Build individual tasks or complete graphs
118
120
  - **Memory Management**: Built-in reset mechanisms
121
+ - **Progress Display**: Visual feedback with spinners and output capture
122
+ - **Dependency Tree Visualization**: Visual representation of task relationships
123
+
124
+ ### Dependency Tree Visualization
125
+
126
+ Visualize task dependencies with the `tree` method:
127
+
128
+ ```ruby
129
+ class Database < Taski::Task
130
+ exports :connection
131
+ def build; @connection = "db-conn"; end
132
+ end
133
+
134
+ class Cache < Taski::Task
135
+ exports :redis_url
136
+ def build; @redis_url = "redis://localhost"; end
137
+ end
138
+
139
+ class Config < Taski::Task
140
+ exports :settings
141
+ def build
142
+ @settings = {
143
+ database: Database.connection,
144
+ cache: Cache.redis_url
145
+ }
146
+ end
147
+ end
148
+
149
+ class WebServer < Taski::Task
150
+ def build
151
+ puts "Starting with #{Config.settings}"
152
+ end
153
+ end
154
+
155
+ puts WebServer.tree
156
+ # => WebServer
157
+ # => └── Config
158
+ # => ├── Database
159
+ # => └── Cache
160
+ ```
161
+
162
+ ### Progress Display
163
+
164
+ Taski provides visual feedback during task execution with animated spinners and real-time output capture:
165
+
166
+ ```ruby
167
+ class LongRunningTask < Taski::Task
168
+ def build
169
+ puts "Starting process..."
170
+ sleep(1.0)
171
+ puts "Processing data..."
172
+ puts "Almost done..."
173
+ sleep(0.5)
174
+ puts "Completed!"
175
+ end
176
+ end
177
+
178
+ LongRunningTask.build
179
+ # During execution shows:
180
+ # ⠧ LongRunningTask
181
+ # Starting process...
182
+ # Processing data...
183
+ # Almost done...
184
+ # Completed!
185
+ #
186
+ # After completion shows:
187
+ # ✅ LongRunningTask (1500ms)
188
+ ```
189
+
190
+ **Progress Display Features:**
191
+ - **Spinner Animation**: Dots-style spinner during task execution
192
+ - **Output Capture**: Real-time display of task output (last 5 lines)
193
+ - **Status Indicators**: ✅ for success, ❌ for failure
194
+ - **Execution Time**: Shows task duration after completion
195
+ - **TTY Detection**: Clean output when redirected to files
119
196
 
120
197
  ### Granular Task Execution
121
198
 
@@ -172,6 +249,29 @@ WebServer.clean
172
249
  # => Database disconnected
173
250
  ```
174
251
 
252
+ ### Clean Method Idempotency
253
+
254
+ **Important**: The `clean` method must be idempotent - safe to call multiple times without errors.
255
+
256
+ ```ruby
257
+ class FileTask < Taski::Task
258
+ exports :output_file
259
+
260
+ def build
261
+ @output_file = '/tmp/data.csv'
262
+ File.write(@output_file, process_data)
263
+ end
264
+
265
+ def clean
266
+ # ❌ Bad: Raises error if file doesn't exist
267
+ # File.delete(@output_file)
268
+
269
+ # ✅ Good: Check before delete
270
+ File.delete(@output_file) if File.exist?(@output_file)
271
+ end
272
+ end
273
+ ```
274
+
175
275
  ### Error Handling
176
276
 
177
277
  ```ruby
@@ -182,7 +282,7 @@ rescue Taski::CircularDependencyError => e
182
282
  end
183
283
  # => Circular dependency: Circular dependency detected!
184
284
  # => Cycle: TaskA → TaskB → TaskA
185
- # =>
285
+ # =>
186
286
  # => The dependency chain is:
187
287
  # => 1. TaskA is trying to build → TaskB
188
288
  # => 2. TaskB is trying to build → TaskA
@@ -222,4 +322,4 @@ MIT License
222
322
 
223
323
  ---
224
324
 
225
- **Taski** - Build dependency graphs with elegant Ruby code. 🚀
325
+ **Taski** - Build dependency graphs with elegant Ruby code. 🚀
@@ -0,0 +1,57 @@
1
+ # Taski Examples
2
+
3
+ Learn Taski through practical examples, from basic concepts to advanced patterns.
4
+
5
+ ## Getting Started
6
+
7
+ Start with these examples in order:
8
+
9
+ ### 1. **[quick_start.rb](quick_start.rb)** - Your First Taski Program
10
+ - Basic task definition with Exports API
11
+ - Automatic dependency resolution
12
+ - Simple task execution
13
+
14
+ ```bash
15
+ ruby examples/quick_start.rb
16
+ ```
17
+
18
+ ### 2. **[progress_demo.rb](progress_demo.rb)** - Rich CLI Progress Display
19
+ - Animated spinner with ANSI colors
20
+ - Real-time output capture and 5-line tail
21
+ - Production build scenarios
22
+ - TTY detection for clean file output
23
+
24
+ ```bash
25
+ # Interactive mode with rich spinner
26
+ ruby examples/progress_demo.rb
27
+
28
+ # Clean output mode (no spinner)
29
+ ruby examples/progress_demo.rb > build.log 2>&1
30
+ cat build.log
31
+ ```
32
+
33
+ ### 3. **[advanced_patterns.rb](advanced_patterns.rb)** - Complex Dependency Patterns
34
+ - Mixed Exports API and Define API usage
35
+ - Environment-specific dependencies
36
+ - Feature flags and conditional logic
37
+ - Task reset and rebuild scenarios
38
+
39
+ ```bash
40
+ ruby examples/advanced_patterns.rb
41
+ ```
42
+
43
+ ## Key Concepts Demonstrated
44
+
45
+ - **Exports API**: Static dependencies with `exports :property`
46
+ - **Define API**: Dynamic dependencies with `define :property, -> { ... }`
47
+ - **Progress Display**: Rich terminal output with spinners and colors
48
+ - **Output Capture**: Tail-style display of task output
49
+ - **Environment Configuration**: Different behavior based on runtime settings
50
+ - **Error Handling**: Graceful failure with progress indicators
51
+
52
+ ## Next Steps
53
+
54
+ After exploring these examples:
55
+ - Read the main documentation
56
+ - Examine the test files for more usage patterns
57
+ - Check out the source code in `lib/taski/`
@@ -1,8 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
- # Complex example from README showing both APIs
2
+ # frozen_string_literal: true
3
+
4
+ # Taski Advanced Patterns
5
+ #
6
+ # This example demonstrates advanced Taski patterns:
7
+ # - Mixed usage of Exports API and Define API
8
+ # - Environment-specific dependency resolution
9
+ # - Feature flag integration with dynamic dependencies
10
+ # - Task reset and rebuild scenarios
11
+ # - Conditional dependency evaluation
12
+ #
13
+ # Run: ruby examples/advanced_patterns.rb
3
14
 
4
15
  require_relative "../lib/taski"
5
16
 
17
+ puts "⚡ Advanced Taski Patterns"
18
+ puts "=" * 40
19
+
6
20
  # Mock classes for the example
7
21
  class ProductionDB < Taski::Task
8
22
  exports :connection_string
@@ -81,8 +95,6 @@ class Application < Taski::Task
81
95
  end
82
96
 
83
97
  # Test different environments
84
- puts "=== Complex Example ==="
85
-
86
98
  puts "\n1. Development Environment (default):"
87
99
  ENV.delete("RAILS_ENV")
88
100
  ENV.delete("FEATURE_REDIS_CACHE")
@@ -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,8 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
- # Quick Start example from README
2
+ # frozen_string_literal: true
3
+
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
3
12
 
4
13
  require_relative "../lib/taski"
5
14
 
15
+ puts "🚀 Taski Quick Start"
16
+ puts "=" * 30
17
+
6
18
  # Simple static dependency using Exports API
7
19
  class DatabaseSetup < Taski::Task
8
20
  exports :connection_string
@@ -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
@@ -38,7 +38,7 @@ module Taski
38
38
  method_node = find_method_node(result.value, method_name, line_number)
39
39
 
40
40
  if method_node
41
- visitor = TaskDependencyVisitor.new
41
+ visitor = TaskDependencyVisitor.new(klass)
42
42
  visitor.visit(method_node)
43
43
  dependencies = visitor.dependencies
44
44
  end
@@ -96,9 +96,10 @@ module Taski
96
96
  class TaskDependencyVisitor < Prism::Visitor
97
97
  attr_reader :dependencies
98
98
 
99
- def initialize
99
+ def initialize(context_class = nil)
100
100
  @dependencies = []
101
101
  @constant_cache = {}
102
+ @context_class = context_class
102
103
  end
103
104
 
104
105
  def visit_constant_read_node(node)
@@ -135,14 +136,19 @@ module Taski
135
136
  return @dependencies << cached_result if cached_result # Cached positive result
136
137
 
137
138
  begin
139
+ resolved_class = nil
140
+
141
+ # 1. Try absolute reference first (existing logic)
138
142
  if Object.const_defined?(const_name)
139
- klass = Object.const_get(const_name)
140
- if klass.is_a?(Class) && klass < Taski::Task
141
- @constant_cache[const_name] = klass
142
- @dependencies << klass
143
- else
144
- @constant_cache[const_name] = false
145
- end
143
+ resolved_class = Object.const_get(const_name)
144
+ # 2. Try relative reference within namespace context
145
+ elsif @context_class
146
+ resolved_class = resolve_relative_constant(const_name)
147
+ end
148
+
149
+ if resolved_class&.is_a?(Class) && resolved_class < Taski::Task
150
+ @constant_cache[const_name] = resolved_class
151
+ @dependencies << resolved_class
146
152
  else
147
153
  @constant_cache[const_name] = false
148
154
  end
@@ -151,6 +157,32 @@ module Taski
151
157
  end
152
158
  end
153
159
 
160
+ def resolve_relative_constant(const_name)
161
+ return nil unless @context_class
162
+
163
+ # Get the namespace from the context class
164
+ namespace = get_namespace_from_class(@context_class)
165
+ return nil unless namespace
166
+
167
+ # Try to resolve the constant within the namespace
168
+ full_const_name = "#{namespace}::#{const_name}"
169
+ Object.const_get(full_const_name) if Object.const_defined?(full_const_name)
170
+ rescue NameError, ArgumentError
171
+ nil
172
+ end
173
+
174
+ def get_namespace_from_class(klass)
175
+ # Extract namespace from class name (e.g., "A::AB" -> "A")
176
+ class_name = klass.name
177
+ return nil unless class_name&.include?("::")
178
+
179
+ # Split by "::" and take all but the last part
180
+ parts = class_name.split("::")
181
+ return nil if parts.length <= 1 # No namespace
182
+
183
+ parts[0..-2].join("::")
184
+ end
185
+
154
186
  def extract_constant_path(node)
155
187
  case node
156
188
  when Prism::ConstantReadNode
data/lib/taski/logger.rb CHANGED
@@ -48,11 +48,16 @@ module Taski
48
48
  # Log task build start event
49
49
  # @param task_name [String] Name of the task being built
50
50
  # @param dependencies [Array] List of task dependencies
51
- def task_build_start(task_name, dependencies: [])
52
- info("Task build started",
51
+ # @param args [Hash] Build arguments for parametrized builds
52
+ def task_build_start(task_name, dependencies: [], args: nil)
53
+ context = {
53
54
  task: task_name,
54
55
  dependencies: dependencies.size,
55
- dependency_names: dependencies.map { |dep| dep.is_a?(Hash) ? dep[:klass].inspect : dep.inspect })
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)
56
61
  end
57
62
 
58
63
  # Log task build completion event
@@ -181,6 +186,12 @@ module Taski
181
186
  @logger ||= Logger.new
182
187
  end
183
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
+
184
195
  # Configure the logger with new settings
185
196
  # @param level [Symbol] Log level (:debug, :info, :warn, :error)
186
197
  # @param output [IO] Output destination