This commit is contained in:
2025-11-23 04:43:08 +01:00
parent 61a3677883
commit 1d4770aca5
9 changed files with 490 additions and 274 deletions

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.core.code
import incubaid.herolib.ui.console
import os
fn main() {
console.print_header('Code Parser Example - lib/core/pathlib Analysis')
console.print_lf(1)
pathlib_dir := os.home_dir() + '/code/github/incubaid/herolib/lib/core/pathlib'
// Step 1: List all V files
console.print_header('1. Listing V Files')
v_files := code.list_v_files(pathlib_dir)!
for file in v_files {
console.print_item(os.base(file))
}
console.print_lf(1)
// Step 2: Parse and analyze each file
console.print_header('2. Parsing Files - Summary')
for v_file_path in v_files {
content := os.read_file(v_file_path)!
vfile := code.parse_vfile(content)!
console.print_item('${os.base(v_file_path)}')
console.print_item(' Module: ${vfile.mod}')
console.print_item(' Imports: ${vfile.imports.len}')
console.print_item(' Structs: ${vfile.structs().len}')
console.print_item(' Functions: ${vfile.functions().len}')
}
console.print_lf(1)
// Step 3: Find Path struct
console.print_header('3. Analyzing Path Struct')
path_code := code.get_type_from_module(pathlib_dir, 'Path')!
console.print_stdout(path_code)
console.print_lf(1)
// Step 4: List all public functions
console.print_header('4. Public Functions in pathlib')
for v_file_path in v_files {
content := os.read_file(v_file_path)!
vfile := code.parse_vfile(content)!
pub_functions := vfile.functions().filter(it.is_pub)
if pub_functions.len > 0 {
console.print_item('From ${os.base(v_file_path)}:')
for f in pub_functions {
console.print_item(' ${f.name}() -> ${f.result.typ.symbol()}')
}
}
}
console.print_lf(1)
console.print_green(' Analysis completed!')
}

View File

@@ -0,0 +1,339 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.core.flows
import incubaid.herolib.core.redisclient
import incubaid.herolib.ui.console
import incubaid.herolib.data.ourtime
import time
fn main() {
mut cons := console.new()
console.print_header('Flow Runner Test Suite')
console.print_lf(1)
// Test 1: Basic Flow Execution
console.print_item('Test 1: Basic Flow with Successful Steps')
test_basic_flow()!
console.print_lf(1)
// Test 2: Error Handling
console.print_item('Test 2: Error Handling with Error Steps')
test_error_handling()!
console.print_lf(1)
// Test 3: Multiple Next Steps
console.print_item('Test 3: Multiple Next Steps')
test_multiple_next_steps()!
console.print_lf(1)
// Test 4: Redis State Retrieval
console.print_item('Test 4: Redis State Retrieval and JSON')
test_redis_state()!
console.print_lf(1)
// Test 5: Complex Flow Chain
console.print_item('Test 5: Complex Flow Chain')
test_complex_flow()!
console.print_lf(1)
console.print_header('All Tests Completed Successfully!')
}
fn test_basic_flow() ! {
mut redis := redisclient.core_get()!
redis.flushdb()!
mut coordinator := flows.new(
name: 'test_basic_flow',
redis: redis,
ai: none
)!
// Step 1: Initialize
mut step1 := coordinator.step_new(
name: 'initialize'
description: 'Initialize test environment'
f: fn (mut s flows.Step) ! {
println(' Step 1: Initializing...')
s.context['init_time'] = ourtime.now().str()
}
)!
// Step 2: Process
mut step2 := coordinator.step_new(
name: 'process'
description: 'Process data'
f: fn (mut s flows.Step) ! {
println(' Step 2: Processing...')
s.context['processed'] = 'true'
}
)!
// Step 3: Finalize
mut step3 := coordinator.step_new(
name: 'finalize'
description: 'Finalize results'
f: fn (mut s flows.Step) ! {
println(' Step 3: Finalizing...')
s.context['status'] = 'completed'
}
)!
step1.next_step_add(step2)
step2.next_step_add(step3)
coordinator.run()!
// Verify Redis state
state := coordinator.get_all_steps_state()!
assert state.len >= 3, 'Expected at least 3 steps in Redis'
for step_state in state {
assert step_state['status'] == 'success', 'Expected all steps to be successful'
}
println(' Test 1 PASSED: All steps executed successfully')
coordinator.clear_redis()!
}
fn test_error_handling() ! {
mut redis := redisclient.core_get()!
redis.flushdb()!
mut coordinator := flows.new(
name: 'test_error_flow',
redis: redis,
ai: none
)!
// Error step
mut error_recovery := coordinator.step_new(
name: 'error_recovery'
description: 'Recover from error'
f: fn (mut s flows.Step) ! {
println(' Error Step: Executing recovery...')
s.context['recovered'] = 'true'
}
)!
// Main step that fails
mut main_step := coordinator.step_new(
name: 'failing_step'
description: 'This step will fail'
f: fn (mut s flows.Step) ! {
println(' Main Step: Intentionally failing...')
return error('Simulated error for testing')
}
)!
main_step.error_step_add(error_recovery)
// Run and expect error
coordinator.run() or {
println(' Error caught as expected: ${err.msg()}')
}
// Verify error state in Redis
error_state := coordinator.get_step_state('failing_step')!
assert error_state['status'] == 'error', 'Expected step to be in error state'
recovery_state := coordinator.get_step_state('error_recovery')!
assert recovery_state['status'] == 'success', 'Expected error step to execute'
println(' Test 2 PASSED: Error handling works correctly')
coordinator.clear_redis()!
}
fn test_multiple_next_steps() ! {
mut redis := redisclient.core_get()!
redis.flushdb()!
mut coordinator := flows.new(
name: 'test_parallel_steps',
redis: redis,
ai: none
)!
// Parent step
mut parent := coordinator.step_new(
name: 'parent_step'
description: 'Parent step with multiple children'
f: fn (mut s flows.Step) ! {
println(' Parent Step: Executing...')
}
)!
// Child steps
mut child1 := coordinator.step_new(
name: 'child_step_1'
description: 'First child'
f: fn (mut s flows.Step) ! {
println(' Child Step 1: Executing...')
}
)!
mut child2 := coordinator.step_new(
name: 'child_step_2'
description: 'Second child'
f: fn (mut s flows.Step) ! {
println(' Child Step 2: Executing...')
}
)!
mut child3 := coordinator.step_new(
name: 'child_step_3'
description: 'Third child'
f: fn (mut s flows.Step) ! {
println(' Child Step 3: Executing...')
}
)!
// Add multiple next steps
parent.next_step_add(child1)
parent.next_step_add(child2)
parent.next_step_add(child3)
coordinator.run()!
// Verify all steps executed
all_states := coordinator.get_all_steps_state()!
assert all_states.len >= 4, 'Expected 4 steps to execute'
println(' Test 3 PASSED: Multiple next steps executed sequentially')
coordinator.clear_redis()!
}
fn test_redis_state() ! {
mut redis := redisclient.core_get()!
redis.flushdb()!
mut coordinator := flows.new(
name: 'test_redis_state',
redis: redis,
ai: none
)!
mut step1 := coordinator.step_new(
name: 'redis_test_step'
description: 'Test Redis state storage'
f: fn (mut s flows.Step) ! {
println(' Executing step with context...')
s.context['user'] = 'test_user'
s.context['action'] = 'test_action'
}
)!
coordinator.run()!
// Retrieve state from Redis
step_state := coordinator.get_step_state('redis_test_step')!
println(' Step state in Redis:')
for key, value in step_state {
println(' ${key}: ${value}')
}
// Verify fields
assert step_state['name'] == 'redis_test_step', 'Step name mismatch'
assert step_state['status'] == 'success', 'Step status should be success'
assert step_state['description'] == 'Test Redis state storage', 'Description mismatch'
// Verify JSON is stored
if json_data := step_state['json'] {
println(' JSON data stored in Redis: ${json_data[0..50]}...')
}
// Verify log count
logs_count := step_state['logs_count'] or { '0' }
println(' Logs count: ${logs_count}')
println(' Test 4 PASSED: Redis state correctly stored and retrieved')
coordinator.clear_redis()!
}
fn test_complex_flow() ! {
mut redis := redisclient.core_get()!
redis.flushdb()!
mut coordinator := flows.new(
name: 'test_complex_flow',
redis: redis,
ai: none
)!
// Step 1: Validate
mut validate := coordinator.step_new(
name: 'validate_input'
description: 'Validate input parameters'
f: fn (mut s flows.Step) ! {
println(' Validating input...')
s.context['validated'] = 'true'
}
)!
// Step 2: Transform (next step after validate)
mut transform := coordinator.step_new(
name: 'transform_data'
description: 'Transform input data'
f: fn (mut s flows.Step) ! {
println(' Transforming data...')
s.context['transformed'] = 'true'
}
)!
// Step 3a: Save to DB (next step after transform)
mut save_db := coordinator.step_new(
name: 'save_to_database'
description: 'Save data to database'
f: fn (mut s flows.Step) ! {
println(' Saving to database...')
s.context['saved'] = 'true'
}
)!
// Step 3b: Send notification (next step after transform)
mut notify := coordinator.step_new(
name: 'send_notification'
description: 'Send notification'
f: fn (mut s flows.Step) ! {
println(' Sending notification...')
s.context['notified'] = 'true'
}
)!
// Step 4: Cleanup (final step)
mut cleanup := coordinator.step_new(
name: 'cleanup'
description: 'Cleanup resources'
f: fn (mut s flows.Step) ! {
println(' Cleaning up...')
s.context['cleaned'] = 'true'
}
)!
// Build the flow chain
validate.next_step_add(transform)
transform.next_step_add(save_db)
transform.next_step_add(notify)
save_db.next_step_add(cleanup)
notify.next_step_add(cleanup)
coordinator.run()!
// Verify all steps executed
all_states := coordinator.get_all_steps_state()!
println(' Total steps executed: ${all_states.len}')
for state in all_states {
name := state['name'] or { 'unknown' }
status := state['status'] or { 'unknown' }
duration := state['duration'] or { '0' }
println(' - ${name}: ${status} (${duration}ms)')
}
assert all_states.len >= 5, 'Expected at least 5 steps'
println(' Test 5 PASSED: Complex flow executed successfully')
coordinator.clear_redis()!
}

View File

@@ -1,3 +0,0 @@
module code
pub type Value = string

View File

@@ -1,247 +0,0 @@
# Code Review and Improvement Plan for HeroLib Code Module
## Overview
The HeroLib `code` module provides utilities for parsing and generating V language code. It's designed to be a lightweight alternative to `v.ast` for code analysis and generation across multiple languages. While the module has good foundational structure, there are several areas that need improvement.
## Issues Identified
### 1. Incomplete TypeScript Generation Support
- The `typescript()` method exists in some models but lacks comprehensive implementation
- Missing TypeScript generation for complex types (arrays, maps, results)
- No TypeScript interface generation for structs
### 2. Template System Issues
- Some templates are empty (e.g., `templates/function/method.py`, `templates/comment/comment.py`)
- Template usage is inconsistent across the codebase
- No clear separation between V and other language templates
### 3. Missing Parser Documentation Examples
- README.md mentions codeparser but doesn't show how to use the parser from this module
- No clear examples of parsing V files or modules
### 4. Incomplete Type Handling
- The `parse_type` function doesn't handle all V language types comprehensively
- Missing support for function types, sum types, and complex generics
- No handling of optional types (`?Type`)
### 5. Code Structure and Consistency
- Some functions lack proper error handling
- Inconsistent naming conventions in test files
- Missing documentation for several key functions
## Improvement Plan
### 1. Complete TypeScript Generation Implementation
**What needs to be done:**
- Implement comprehensive TypeScript generation in `model_types.v`
- Add TypeScript generation for all type variants
- Create proper TypeScript interface generation in `model_struct.v`
**Specific fixes:**
```v
// In model_types.v, improve the typescript() method:
pub fn (t Type) typescript() string {
return match t {
Map { 'Record<string, ${t.typ.typescript()}>' }
Array { '${t.typ.typescript()}[]' }
Object { t.name }
Result { 'Promise<${t.typ.typescript()}>' } // Better representation for async operations
Boolean { 'boolean' }
Integer { 'number' }
Alias { t.name }
String { 'string' }
Function { '(...args: any[]) => any' } // More appropriate for function types
Void { 'void' }
}
}
// In model_struct.v, improve the typescript() method:
pub fn (s Struct) typescript() string {
name := texttools.pascal_case(s.name)
fields := s.fields.map(it.typescript()).join('\n ')
return 'export interface ${name} {\n ${fields}\n}'
}
```
### 2. Fix Template System
**What needs to be done:**
- Remove empty Python template files
- Ensure all templates are properly implemented
- Add template support for other languages
**Specific fixes:**
- Delete `templates/function/method.py` and `templates/comment/comment.py` if they're not needed
- Add proper TypeScript templates for struct and interface generation
- Create consistent template naming conventions
### 3. Improve Parser Documentation
**What needs to be done:**
- Add clear examples in README.md showing how to use the parser
- Document the parsing functions with practical examples
**Specific fixes:**
Add to README.md:
```markdown
## Parsing V Code
The code module provides utilities to parse V code into structured models:
```v
import incubaid.herolib.core.code
// Parse a V file
content := os.read_file('example.v') or { panic(err) }
vfile := code.parse_vfile(content) or { panic(err) }
// Access parsed information
println('Module: ${vfile.mod}')
println('Number of functions: ${vfile.functions().len}')
println('Number of structs: ${vfile.structs().len}')
// Parse individual components
function := code.parse_function(fn_code_string) or { panic(err) }
struct_ := code.parse_struct(struct_code_string) or { panic(err) }
```
### 4. Complete Type Handling
**What needs to be done:**
- Extend `parse_type` to handle more complex V types
- Add support for optional types (`?Type`)
- Improve generic type parsing
**Specific fixes:**
```v
// In model_types.v, enhance parse_type function:
pub fn parse_type(type_str string) Type {
mut type_str_trimmed := type_str.trim_space()
// Handle optional types
if type_str_trimmed.starts_with('?') {
return Optional{parse_type(type_str_trimmed.all_after('?'))}
}
// Handle function types
if type_str_trimmed.starts_with('fn ') {
// Parse function signature
return Function{}
}
// Handle sum types
if type_str_trimmed.contains('|') {
types := type_str_trimmed.split('|').map(parse_type(it.trim_space()))
return Sum{types}
}
// Existing parsing logic...
}
```
### 5. Code Structure Improvements
**What needs to be done:**
- Add proper error handling to all parsing functions
- Standardize naming conventions
- Improve documentation consistency
**Specific fixes:**
- Add error checking in `parse_function`, `parse_struct`, and other parsing functions
- Ensure all public functions have clear documentation comments
- Standardize test function names
## Module Generation to Other Languages
### Current Implementation
The current code shows basic TypeScript generation support, but it's incomplete. The generation should:
1. **Support multiple languages**: The code structure allows for multi-language generation, but only TypeScript has partial implementation
2. **Use templates consistently**: All language generation should use the template system
3. **Separate language-specific code**: Each language should have its own generation module
### What Needs to Move to Other Modules
**TypeScript Generation Module:**
- Move all TypeScript-specific generation code to a new `typescript` module
- Create TypeScript templates for structs, interfaces, and functions
- Add proper TypeScript formatting support
**Example Structure:**
```
lib/core/code/
├── model_types.v # Core type models (language agnostic)
├── model_struct.v # Core struct/function models (language agnostic)
└── typescript/ # TypeScript-specific generation
├── generator.v # TypeScript generation logic
└── templates/ # TypeScript templates
```
### Parser Usage Examples (to add to README.md)
```v
// Parse a V file into a structured representation
content := os.read_file('mymodule/example.v') or { panic(err) }
vfile := code.parse_vfile(content)!
// Extract all functions
functions := vfile.functions()
println('Found ${functions.len} functions')
// Extract all structs
structs := vfile.structs()
for s in structs {
println('Struct: ${s.name}')
for field in s.fields {
println(' Field: ${field.name} (${field.typ.symbol()})')
}
}
// Find a specific function
if greet_fn := vfile.get_function('greet') {
println('Found function: ${greet_fn.name}')
println('Parameters: ${greet_fn.params.map(it.name)}')
println('Returns: ${greet_fn.result.typ.symbol()}')
}
// Parse a function from string
fn_code := '
pub fn add(a int, b int) int {
return a + b
}
'
function := code.parse_function(fn_code)!
println('Parsed function: ${function.name}')
```
## Summary of Required Actions
1. **Implement complete TypeScript generation** across all model types
2. **Remove empty template files** and organize templates properly
3. **Enhance type parsing** to handle optional types, function types, and sum types
4. **Add comprehensive parser documentation** with practical examples to README.md
5. **Create language-specific generation modules** to separate concerns
6. **Improve error handling** in all parsing functions
7. **Standardize documentation and naming** conventions across the module
These improvements will make the code module more robust, easier to use, and better prepared for multi-language code generation.

View File

@@ -6,4 +6,4 @@ pub struct Example {
result Value
}
// pub type Value = string
pub type Value = string

View File

@@ -19,17 +19,30 @@ pub mut:
current_step string // links to steps dict
steps map[string]&Step
logger logger.Logger
ai aiclient.AIClient
ai ?aiclient.AIClient
redis ?&redisclient.Redis
}
pub fn new() !Coordinator {
@[params]
pub struct CoordinatorArgs {
pub mut:
name string @[required]
redis ?&redisclient.Redis
ai ?aiclient.AIClient = none
}
pub fn new(args CoordinatorArgs) !Coordinator {
ai := args.ai
return Coordinator{
name: args.name
logger: logger.new(path: '/tmp/flowlogger')!
ai: aiclient.new()!
ai: ai
redis: args.redis
}
}
@[params]
pub struct StepNewArgs {
pub mut:
@@ -37,8 +50,8 @@ pub mut:
description string
f fn (mut s Step) ! @[required]
context map[string]string
error_steps []Step
next_steps []Step
error_steps []string
next_steps []string
error string
params paramsparser.Params
}

View File

@@ -9,7 +9,7 @@ pub fn (mut c Coordinator) run() ! {
}
// Run a single step, including error and next steps
pub fn (mut c Coordinator) run_step(mut step Step) ! {
pub fn (mut c Coordinator) run_step(mut step &Step) ! {
// Initialize step
step.status = .running
step.started_at = ostime.now().unix_milli()
@@ -17,8 +17,8 @@ pub fn (mut c Coordinator) run_step(mut step Step) ! {
// Log step start
step.log(
level: .info
message: 'Step "${step.name}" started'
logtype: .stdout
log: 'Step "${step.name}" started'
)!
// Execute main step function
@@ -30,13 +30,16 @@ pub fn (mut c Coordinator) run_step(mut step Step) ! {
step.store_redis()!
step.log(
level: .error
message: 'Step "${step.name}" failed: ${err.msg()}'
logtype: .error
log: 'Step "${step.name}" failed: ${err.msg()}'
)!
// Run error steps if any
if step.error_steps.len > 0 {
for mut error_step in step.error_steps {
for error_step_name in step.error_steps {
mut error_step := c.steps[error_step_name] or {
return error('Error step "${error_step_name}" not found in coordinator "${c.name}"')
}
c.run_step(mut error_step)!
}
}
@@ -50,13 +53,16 @@ pub fn (mut c Coordinator) run_step(mut step Step) ! {
step.store_redis()!
step.log(
level: .info
message: 'Step "${step.name}" completed successfully'
logtype: .stdout
log: 'Step "${step.name}" completed successfully'
)!
// Run next steps if any
if step.next_steps.len > 0 {
for mut next_step in step.next_steps {
for next_step_name in step.next_steps {
mut next_step := c.steps[next_step_name] or {
return error('Next step "${next_step_name}" not found in coordinator "${c.name}"')
}
c.run_step(mut next_step)!
}
}
@@ -64,7 +70,7 @@ pub fn (mut c Coordinator) run_step(mut step Step) ! {
// Get step state from redis
pub fn (c Coordinator) get_step_state(step_name string) !map[string]string {
if redis := c.redis {
if mut redis := c.redis {
return redis.hgetall('flow:${c.name}:${step_name}')!
}
return error('Redis not configured')
@@ -73,7 +79,7 @@ pub fn (c Coordinator) get_step_state(step_name string) !map[string]string {
// Get all steps state from redis (for UI dashboard)
pub fn (c Coordinator) get_all_steps_state() ![]map[string]string {
mut states := []map[string]string{}
if redis := c.redis {
if mut redis := c.redis {
pattern := 'flow:${c.name}:*'
keys := redis.keys(pattern)!
for key in keys {
@@ -85,7 +91,7 @@ pub fn (c Coordinator) get_all_steps_state() ![]map[string]string {
}
pub fn (c Coordinator) clear_redis() ! {
if redis := c.redis {
if mut redis := c.redis {
pattern := 'flow:${c.name}:*'
keys := redis.keys(pattern)!
for key in keys {

View File

@@ -2,6 +2,8 @@ module flows
import incubaid.herolib.data.paramsparser
import incubaid.herolib.core.logger
import time as ostime
import json
pub enum StepStatus {
pending
@@ -21,23 +23,71 @@ pub mut:
description string
main_step fn (mut s Step) ! @[required]
context map[string]string
error_steps []Step
next_steps []Step
error_steps []string
next_steps []string
error string
logs []logger.LogItem
params paramsparser.Params
coordinator &Coordinator
}
pub fn (mut s Step) error_step_add(s2 Step) {
s.error_steps << s2
pub fn (mut s Step) error_step_add(s2 &Step) {
s.error_steps << s2.name
}
pub fn (mut s Step) next_step_add(s2 Step) {
s.next_steps << s2
pub fn (mut s Step) next_step_add(s2 &Step) {
s.next_steps << s2.name
}
pub fn (mut s Step) log(l logger.LogItemArgs) ! {
mut l2 := s.coordinator.logger.log(l)!
s.logs << l2
}
pub fn (mut s Step) store_redis() ! {
if mut redis := s.coordinator.redis {
key := 'flow:${s.coordinator.name}:${s.name}'
redis.hset(key, 'name', s.name)!
redis.hset(key, 'description', s.description)!
redis.hset(key, 'status', s.status.str())!
redis.hset(key, 'error', s.error_msg)!
redis.hset(key, 'logs_count', s.logs.len.str())!
redis.hset(key, 'started_at', s.started_at.str())!
redis.hset(key, 'finished_at', s.finished_at.str())!
redis.hset(key, 'json', s.to_json()!)!
// Set expiration to 24 hours
redis.expire(key, 86400)!
}
}
@[json: id]
pub struct StepJSON {
pub:
name string
description string
status string
error string
logs_count int
started_at i64
finished_at i64
duration i64 // milliseconds
}
pub fn (s Step) to_json() !string {
duration := s.finished_at - s.started_at
step_json := StepJSON{
name: s.name
description: s.description
status: s.status.str()
error: s.error_msg
logs_count: s.logs.len
started_at: s.started_at
finished_at: s.finished_at
duration: duration
}
return json.encode(step_json)
}