Files
herolib/lib/lang/rust/rust.v
2025-10-12 12:30:19 +03:00

1219 lines
34 KiB
V

module rust
import os
import incubaid.herolib.core.pathlib
// Reads and combines all Rust files in the given directory
pub fn read_source_code(source_code_path string) !string {
// Get all files in the directory
files := os.ls(source_code_path) or {
return error('Failed to list files in directory: ${err}')
}
// Combine all Rust files into a single source code string
mut source_code := ''
for file in files {
file_path := os.join_path(source_code_path, file)
// Skip directories and non-Rust files
if os.is_dir(file_path) || !file.ends_with('.rs') {
continue
}
// Read the file content
file_content := os.read_file(file_path) or {
println('Failed to read file ${file_path}: ${err}')
continue
}
// Add file content to the combined source code
source_code += '// File: ${file}\n${file_content}\n\n'
}
if source_code == '' {
return error('No Rust files found in directory: ${source_code_path}')
}
return source_code
}
// Determines the crate path from the source code path
pub fn determine_crate_path(source_code_path string) !string {
// Extract the path relative to the src directory
src_index := source_code_path.index('src/') or {
return error('Could not determine crate path: src/ not found in path')
}
mut path_parts := source_code_path[src_index + 4..].split('/')
// Remove the last part (the file name)
if path_parts.len > 0 {
path_parts.delete_last()
}
rel_path := path_parts.join('::')
return 'sal::${rel_path}'
}
// Extracts the module name from a directory path
pub fn extract_module_name_from_path(path string) string {
dir_parts := path.split('/')
return dir_parts[dir_parts.len - 1]
}
// Determines the source package information from a given source path
pub struct SourcePackageInfo {
pub:
name string // Package name
path string // Relative path to the package (for cargo.toml)
module string // Full module path (e.g., herodb::logic)
}
// Detect source package and module information from a path
pub fn detect_source_package(source_path string) !SourcePackageInfo {
// Look for Cargo.toml in parent directories to find the crate root
mut current_path := source_path
mut package_name := ''
mut rel_path := ''
mut module_parts := []string{}
// Extract module name from the directory path
mod_name := extract_module_name_from_path(source_path)
module_parts << mod_name
// Look up parent directories until we find a Cargo.toml
for i := 0; i < 10; i++ { // limit depth to avoid infinite loops
parent_dir := os.dir(current_path)
cargo_path := os.join_path(parent_dir, 'Cargo.toml')
if os.exists(cargo_path) {
// Found the root of the crate
cargo_content := os.read_file(cargo_path) or {
return error('Failed to read Cargo.toml at ${cargo_path}: ${err}')
}
// Extract package name
for line in cargo_content.split('\n') {
if line.contains('name') && line.contains('=') {
parts := line.split('=')
if parts.len > 1 {
package_name = parts[1].trim_space().trim('"').trim("'")
break
}
}
}
// Calculate relative path from current working directory to crate root
current_dir := os.getwd()
rel_path = pathlib.path_relative(parent_dir, current_dir) or {
return error('Failed to get relative path: ${err}')
}
if rel_path == '.' {
rel_path = './'
}
break
}
// Go up one directory
if parent_dir == current_path {
break // We've reached the root
}
// Add directory name to module path parts (in reverse order)
parent_dir_name := os.base(parent_dir)
if parent_dir_name != '' && parent_dir_name != '.' {
module_parts.insert(0, parent_dir_name)
}
current_path = parent_dir
}
if package_name == '' {
// If no Cargo.toml found, use the last directory name as package name
package_name = os.base(os.dir(source_path))
rel_path = '../' // default to parent directory
}
// Construct the full module path
mut module_path := module_parts.join('::')
if module_parts.len >= 2 {
// Use only the last two components for the module path
module_path = module_parts[module_parts.len - 2..].join('::')
}
return SourcePackageInfo{
name: package_name
path: rel_path
module: module_path
}
}
// Build and run a Rust project with an example
pub fn run_example(project_dir string, example_name string) !(string, string) {
// Change to the project directory
os.chdir(project_dir) or { return error('Failed to change directory to project: ${err}') }
// Run cargo build first
build_result := os.execute('cargo build')
if build_result.exit_code != 0 {
return error('Compilation failed. Please fix the following errors and ensure your code is compatible with the existing codebase:\n\n${build_result.output}')
}
// Run the example
run_result := os.execute('cargo run --example ${example_name}')
return build_result.output, run_result.output
}
// Extract function names from wrapper code
fn extract_functions_from_code(code string) []string {
mut functions := []string{}
lines := code.split('\n')
for line in lines {
if line.contains('pub fn ') && !line.contains('//') {
// Extract function name
parts := line.split('pub fn ')
if parts.len > 1 {
name_parts := parts[1].split('(')
if name_parts.len > 0 {
fn_name := name_parts[0].trim_space()
if fn_name != '' {
functions << fn_name
}
}
}
}
}
return functions
}
// Extract function names from Rust file
pub fn list_functions_in_file(file_path string) ![]string {
// Check if file exists
if !os.exists(file_path) {
return error('File not found: ${file_path}')
}
// Read file content
content := os.read_file(file_path) or { return error('Failed to read file: ${err}') }
return extract_functions_from_content(content)
}
// Extract function names from content string
pub fn extract_functions_from_content(content string) []string {
mut functions := []string{}
lines := content.split('\n')
mut in_comment_block := false
mut current_impl := '' // Track the current impl block
mut impl_level := 0 // Track nesting level of braces within impl
for line in lines {
trimmed := line.trim_space()
// Skip comment lines and empty lines
if trimmed.starts_with('//') || trimmed == '' {
continue
}
// Handle block comments
if trimmed.starts_with('/*') {
in_comment_block = true
}
if in_comment_block {
if trimmed.contains('*/') {
in_comment_block = false
}
continue
}
// Check for impl blocks
if trimmed.starts_with('impl ') {
// Extract the struct name from the impl declaration
mut struct_name := ''
// Handle generic impls like "impl<T> StructName<T>"
if trimmed.contains('<') && trimmed.contains('>') {
// Complex case with generics
if trimmed.contains(' for ') {
// Format: impl<T> Trait for StructName<T>
parts := trimmed.split(' for ')
if parts.len > 1 {
struct_parts := parts[1].split('{')
if struct_parts.len > 0 {
struct_name = struct_parts[0].trim_space()
// Remove any generic parameters
if struct_name.contains('<') {
struct_name = struct_name.all_before('<')
}
}
}
} else {
// Format: impl<T> StructName<T>
after_impl := trimmed.all_after('impl')
after_generic := after_impl.all_after('>')
struct_parts := after_generic.split('{')
if struct_parts.len > 0 {
struct_name = struct_parts[0].trim_space()
// Remove any generic parameters
if struct_name.contains('<') {
struct_name = struct_name.all_before('<')
}
}
}
} else {
// Simple case without generics
if trimmed.contains(' for ') {
// Format: impl Trait for StructName
parts := trimmed.split(' for ')
if parts.len > 1 {
struct_parts := parts[1].split('{')
if struct_parts.len > 0 {
struct_name = struct_parts[0].trim_space()
}
}
} else {
// Format: impl StructName
parts := trimmed.split('impl ')
if parts.len > 1 {
struct_parts := parts[1].split('{')
if struct_parts.len > 0 {
struct_name = struct_parts[0].trim_space()
}
}
}
}
current_impl = struct_name
if trimmed.contains('{') {
impl_level = 1
} else {
impl_level = 0
}
continue
}
// Track brace levels to properly handle nested blocks
if current_impl != '' {
// Count opening braces
for c in trimmed {
if c == `{` {
impl_level++
} else if c == `}` {
impl_level--
// If we've closed the impl block, reset current_impl
if impl_level == 0 {
current_impl = ''
break
}
}
}
}
// Look for function declarations
if (trimmed.starts_with('pub fn ') || trimmed.starts_with('fn ')) && !trimmed.contains(';') {
mut fn_name := ''
// Extract function name
if trimmed.starts_with('pub fn ') {
fn_parts := trimmed.split('pub fn ')
if fn_parts.len > 1 {
name_parts := fn_parts[1].split('(')
if name_parts.len > 0 {
fn_name = name_parts[0].trim_space()
}
}
} else {
fn_parts := trimmed.split('fn ')
if fn_parts.len > 1 {
name_parts := fn_parts[1].split('(')
if name_parts.len > 0 {
fn_name = name_parts[0].trim_space()
}
}
}
// Add function name to the list if it's not empty
if fn_name != '' {
if current_impl != '' {
// All functions in an impl block use :: notation
functions << '${current_impl}::${fn_name}'
} else {
// Regular function
functions << fn_name
}
}
}
}
return functions
}
// Extract struct names from Rust file
pub fn list_structs_in_file(file_path string) ![]string {
// Check if file exists
if !os.exists(file_path) {
return error('File not found: ${file_path}')
}
// Read file content
content := os.read_file(file_path) or { return error('Failed to read file: ${err}') }
return extract_structs_from_content(content)
}
// Extract struct names from content string
pub fn extract_structs_from_content(content string) []string {
mut structs := []string{}
lines := content.split('\n')
mut in_comment_block := false
for line in lines {
trimmed := line.trim_space()
// Skip comment lines and empty lines
if trimmed.starts_with('//') {
continue
}
// Handle block comments
if trimmed.starts_with('/*') {
in_comment_block = true
}
if in_comment_block {
if trimmed.contains('*/') {
in_comment_block = false
}
continue
}
// Look for struct declarations
if (trimmed.starts_with('pub struct ') || trimmed.starts_with('struct '))
&& !trimmed.contains(';') {
mut struct_name := ''
// Extract struct name
if trimmed.starts_with('pub struct ') {
struct_parts := trimmed.split('pub struct ')
if struct_parts.len > 1 {
name_parts := struct_parts[1].split('{')
if name_parts.len > 0 {
parts := name_parts[0].split('<')
struct_name = parts[0].trim_space()
}
}
} else {
struct_parts := trimmed.split('struct ')
if struct_parts.len > 1 {
name_parts := struct_parts[1].split('{')
if name_parts.len > 0 {
parts := name_parts[0].split('<')
struct_name = parts[0].trim_space()
}
}
}
// Add struct name to the list if it's not empty
if struct_name != '' {
structs << struct_name
}
}
}
return structs
}
// Extract imports from a Rust file
pub fn extract_imports(file_path string) ![]string {
// Check if file exists
if !os.exists(file_path) {
return error('File not found: ${file_path}')
}
// Read file content
content := os.read_file(file_path) or { return error('Failed to read file: ${err}') }
return extract_imports_from_content(content)
}
// Extract imports from content string
pub fn extract_imports_from_content(content string) []string {
mut imports := []string{}
lines := content.split('\n')
mut in_comment_block := false
for line in lines {
trimmed := line.trim_space()
// Skip comment lines and empty lines
if trimmed.starts_with('//') {
continue
}
// Handle block comments
if trimmed.starts_with('/*') {
in_comment_block = true
}
if in_comment_block {
if trimmed.contains('*/') {
in_comment_block = false
}
continue
}
// Extract use statements
if trimmed.starts_with('use ') && trimmed.ends_with(';') {
import_part := trimmed[4..trimmed.len - 1].trim_space() // Skip 'use ', remove trailing ';', trim spaces
imports << import_part
}
}
return imports
}
// Get module name from file path
pub fn get_module_name(file_path string) string {
// Extract filename from path
filename := os.base(file_path)
// If it's mod.rs, use parent directory name
if filename == 'mod.rs' {
dir := os.dir(file_path)
return os.base(dir)
}
// Otherwise use filename without extension
return filename.all_before('.rs')
}
// List all modules in a directory
pub fn list_modules_in_directory(dir_path string) ![]string {
// Check if directory exists
if !os.exists(dir_path) || !os.is_dir(dir_path) {
return error('Directory not found: ${dir_path}')
}
// Get all files in the directory
files := os.ls(dir_path) or { return error('Failed to list files in directory: ${err}') }
mut modules := []string{}
// Check for mod.rs
if files.contains('mod.rs') {
modules << os.base(dir_path)
}
// Check for Rust files
for file in files {
if file.ends_with('.rs') && file != 'mod.rs' {
modules << file.all_before('.rs')
}
}
// Check for directories that contain mod.rs
for file in files {
file_path := os.join_path(dir_path, file)
if os.is_dir(file_path) {
subfiles := os.ls(file_path) or { continue }
if subfiles.contains('mod.rs') {
modules << file
}
}
}
return modules
}
// Generate an import statement for a module based on current file and target module path
pub fn generate_import_statement(current_file_path string, target_module_path string) !string {
// Attempt to find the project root (directory containing Cargo.toml)
mut project_root := ''
mut current_path := os.dir(current_file_path)
// Find the project root
for i := 0; i < 10; i++ { // Limit depth to avoid infinite loops
cargo_path := os.join_path(current_path, 'Cargo.toml')
if os.exists(cargo_path) {
project_root = current_path
break
}
parent_dir := os.dir(current_path)
if parent_dir == current_path {
break // We've reached the root
}
current_path = parent_dir
}
if project_root == '' {
return error('Could not find project root (Cargo.toml)')
}
// Get package info
pkg_info := detect_source_package(current_file_path) or {
return error('Failed to detect package info: ${err}')
}
// Check if target module is part of the same package
target_pkg_info := detect_source_package(target_module_path) or {
return error('Failed to detect target package info: ${err}')
}
// If same package, generate a relative import
if pkg_info.name == target_pkg_info.name {
// Convert file paths to module paths
current_file_dir := os.dir(current_file_path)
target_file_dir := os.dir(target_module_path)
// Get paths relative to src
current_rel_path := current_file_dir.replace('${project_root}/src/', '')
target_rel_path := target_file_dir.replace('${project_root}/src/', '')
// Convert paths to module format
current_module := current_rel_path.replace('/', '::')
target_module := target_rel_path.replace('/', '::')
// Generate import based on path relationship
if current_module == target_module {
// Same module, import target directly
target_name := get_module_name(target_module_path)
return 'use crate::${target_module}::${target_name};'
} else if current_module.contains(target_module) {
// Target is parent module
target_name := get_module_name(target_module_path)
return 'use super::${target_name};'
} else if target_module.contains(current_module) {
// Target is child module
target_name := get_module_name(target_module_path)
child_path := target_module.replace('${current_module}::', '')
return 'use self::${child_path}::${target_name};'
} else {
// Target is sibling or other module
target_name := get_module_name(target_module_path)
return 'use crate::${target_module}::${target_name};'
}
} else {
// External package
return 'use ${target_pkg_info.name}::${target_pkg_info.module};'
}
}
// Extract dependencies from Cargo.toml
pub fn extract_dependencies(cargo_path string) !map[string]string {
// Check if file exists
if !os.exists(cargo_path) {
return error('Cargo.toml not found: ${cargo_path}')
}
// Read file content
content := os.read_file(cargo_path) or { return error('Failed to read Cargo.toml: ${err}') }
mut dependencies := map[string]string{}
mut in_dependencies_section := false
lines := content.split('\n')
for line in lines {
trimmed := line.trim_space()
// Check for dependencies section
if trimmed == '[dependencies]' {
in_dependencies_section = true
continue
} else if trimmed.starts_with('[') && in_dependencies_section {
// Left dependencies section
in_dependencies_section = false
continue
}
// Extract dependency info
if in_dependencies_section && trimmed != '' {
if trimmed.contains('=') {
eq_pos := trimmed.index('=') or { continue } // Find the first '='
name := trimmed[..eq_pos].trim_space()
mut value := trimmed[eq_pos + 1..].trim_space()
// Remove surrounding quotes if they exist (optional, but good practice for simple strings)
// Note: This won't remove braces for tables, which is desired.
if value.starts_with('"') && value.ends_with('"') {
value = value[1..value.len - 1]
} else if value.starts_with("'") && value.ends_with("'") {
value = value[1..value.len - 1]
}
dependencies[name] = value // Store the potentially complex value string
}
}
}
return dependencies
}
// Get a function declaration from a file by its name
pub fn get_function_from_file(file_path string, function_name string) !string {
// Check if file exists
if !os.exists(file_path) {
return error('File not found: ${file_path}')
}
// Read file content
content := os.read_file(file_path) or { return error('Failed to read file: ${err}') }
return get_function_from_content(content, function_name)
}
// Get a function declaration from a module by its name
pub fn get_function_from_module(module_path string, function_name string) !string {
// Check if directory exists
if !os.exists(module_path) {
return error('Module path not found: ${module_path}')
}
// If it's a directory, look for mod.rs or lib.rs
if os.is_dir(module_path) {
mod_rs_path := os.join_path(module_path, 'mod.rs')
lib_rs_path := os.join_path(module_path, 'lib.rs')
if os.exists(mod_rs_path) {
result := get_function_from_file(mod_rs_path, function_name) or {
if err.msg().contains('Function ${function_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
}
if os.exists(lib_rs_path) { // Changed else if to if
result := get_function_from_file(lib_rs_path, function_name) or {
if err.msg().contains('Function ${function_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
}
// Try to find the function in any Rust file in the directory
files := os.ls(module_path) or {
return error('Failed to list files in module directory: ${err}')
}
for file in files {
if file.ends_with('.rs') {
file_path := os.join_path(module_path, file)
result := get_function_from_file(file_path, function_name) or {
if err.msg().contains('Function ${function_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
// Found it
}
}
return error('Function ${function_name} not found in module ${module_path}')
} else {
// It's a file path, treat it as a direct file
return get_function_from_file(module_path, function_name)
}
}
// Get a function declaration from content by its name
pub fn get_function_from_content(content string, function_name string) !string {
is_method := function_name.contains('::')
mut struct_name := ''
mut method_name := function_name
if is_method {
parts := function_name.split('::')
if parts.len == 2 {
struct_name = parts[0]
method_name = parts[1]
} else {
return error('Invalid method format: ${function_name}')
}
}
lines := content.split('\n')
mut function_declaration := ''
mut brace_level := 0
mut function_start_line_found := false
mut in_impl_block := false // Flag to track if we are inside the correct impl block
mut impl_brace_level := 0 // To know when the impl block ends
for line in lines {
trimmed := line.trim_space()
if trimmed.starts_with('//') {
continue
}
// Skip single-line comments
// Handle finding the correct impl block if it's a method
if is_method && !in_impl_block {
if trimmed.contains('impl') && trimmed.contains(struct_name) {
in_impl_block = true
// Calculate the brace level *before* this impl line
// This is tricky, maybe just track entry/exit
for c in line {
if c == `{` {
impl_brace_level += 1
}
}
continue // Don't process the impl line itself as the start
}
continue // Skip lines until the correct impl block is found
}
// Handle exiting the impl block
if is_method && in_impl_block {
current_line_brace_change := line.count('{') - line.count('}')
if impl_brace_level + current_line_brace_change <= 0 { // Assuming impl starts at level 0 relative to its scope
in_impl_block = false // Exited the impl block
impl_brace_level = 0
}
impl_brace_level += current_line_brace_change
}
// Find the function/method start line
if !function_start_line_found {
mut is_target_line := false
if is_method && in_impl_block {
// Inside the correct impl, look for method
is_target_line = trimmed.contains('fn ${method_name}')
|| trimmed.contains('fn ${method_name}<') // Handle generics
} else if !is_method {
// Look for standalone function
is_target_line = trimmed.contains('fn ${function_name}')
|| trimmed.contains('fn ${function_name}<')
}
if is_target_line {
function_start_line_found = true
function_declaration += line + '\n'
// Count initial braces on the declaration line
for c in line {
if c == `{` {
brace_level++
} else if c == `}` {
brace_level-- // Should ideally not happen on decl line
}
}
// Handle single-line functions like `fn simple() -> i32 { 42 }` or trait methods ending with `;`
if brace_level == 0 && (line.contains('}') || line.contains(';')) {
break // Function definition is complete on this line
}
continue // Move to next line after finding the start
}
}
// If function start found, append lines and track braces
if function_start_line_found {
function_declaration += line + '\n'
// Count braces to determine when the function ends
for c in line {
if c == `{` {
brace_level++
} else if c == `}` {
brace_level--
}
}
// Check if function ended
if brace_level <= 0 { // <= 0 to handle potential formatting issues
break
}
}
}
if function_declaration == '' {
return error('Function ${function_name} not found in content')
}
return function_declaration.trim_space()
}
// Get a struct declaration from a file by its name
pub fn get_struct_from_file(file_path string, struct_name string) !string {
// Check if file exists
if !os.exists(file_path) {
return error('File not found: ${file_path}')
}
// Read file content
content := os.read_file(file_path) or { return error('Failed to read file: ${err}') }
return get_struct_from_content(content, struct_name)
}
// Get a struct declaration from a module by its name
pub fn get_struct_from_module(module_path string, struct_name string) !string {
// Check if directory exists
if !os.exists(module_path) {
return error('Module path not found: ${module_path}')
}
// If it's a directory, look for mod.rs or lib.rs
if os.is_dir(module_path) {
mod_rs_path := os.join_path(module_path, 'mod.rs')
lib_rs_path := os.join_path(module_path, 'lib.rs')
if os.exists(mod_rs_path) {
result := get_struct_from_file(mod_rs_path, struct_name) or {
if err.msg().contains('Struct ${struct_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
}
if os.exists(lib_rs_path) { // Changed else if to if
result := get_struct_from_file(lib_rs_path, struct_name) or {
if err.msg().contains('Struct ${struct_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
}
// Try to find the struct in any Rust file in the directory
files := os.ls(module_path) or {
return error('Failed to list files in module directory: ${err}')
}
for file in files {
if file.ends_with('.rs') {
file_path := os.join_path(module_path, file)
result := get_struct_from_file(file_path, struct_name) or {
if err.msg().contains('Struct ${struct_name} not found') {
'' // Not found error, resolve or block to empty string
} else {
return err // Propagate other errors
}
}
if result != '' {
return result
}
// Found it
}
}
return error('Struct ${struct_name} not found in module ${module_path}')
} else {
// It's a file path, treat it as a direct file
return get_struct_from_file(module_path, struct_name)
}
}
// Get a struct declaration from content by its name
pub fn get_struct_from_content(content string, struct_name string) !string {
lines := content.split('\n')
mut in_comment_block := false
mut brace_level := 0 // Tracks brace level *within* the target struct
mut struct_declaration := ''
mut struct_start_line_found := false
for line in lines {
trimmed := line.trim_space()
if trimmed.starts_with('//') {
continue
}
// Skip single-line comments
// Handle block comments
if trimmed.starts_with('/*') {
in_comment_block = true
}
if in_comment_block {
if trimmed.contains('*/') {
in_comment_block = false
}
continue
}
// Find the struct start line
if !struct_start_line_found {
// Check for `pub struct Name` or `struct Name` followed by { or ;
if (trimmed.starts_with('pub struct ${struct_name}')
|| trimmed.starts_with('struct ${struct_name}'))
&& (trimmed.contains('{') || trimmed.ends_with(';')
|| trimmed.contains(' where ') || trimmed.contains('<')) {
// Basic check to avoid matching struct names that are substrings of others
// Example: Don't match `MyStructExtended` when looking for `MyStruct`
// This is a simplified check, regex might be more robust
name_part := trimmed.all_after('struct ').trim_space()
if name_part.starts_with(struct_name) {
// Check if the character after the name is one that indicates end of name ('{', ';', '<', '(' or whitespace)
char_after := if name_part.len > struct_name.len {
name_part[struct_name.len]
} else {
u8(` `)
}
if char_after == u8(`{`) || char_after == u8(`;`) || char_after == u8(`<`)
|| char_after == u8(`(`) || char_after.is_space() {
struct_start_line_found = true
struct_declaration += line + '\n'
// Count initial braces/check for semicolon on the declaration line
for c in line {
if c == `{` {
brace_level++
} else if c == `}` {
brace_level--
}
// Should not happen on decl line
}
// Handle unit structs ending with semicolon
if trimmed.ends_with(';') {
break // Struct definition is complete on this line
}
// Handle single-line structs like `struct Simple { field: i32 }`
if brace_level == 0 && line.contains('{') && line.contains('}') {
break // Struct definition is complete on this line
}
continue // Move to next line after finding the start
}
}
}
}
// If struct start found, append lines and track braces
if struct_start_line_found {
struct_declaration += line + '\n'
// Count braces to determine when the struct ends
for c in line {
if c == `{` {
brace_level++
} else if c == `}` {
brace_level--
}
}
// Check if struct ended
if brace_level <= 0 { // <= 0 handles potential formatting issues or initial non-zero level
break
}
}
}
if struct_declaration == '' {
return error('Struct ${struct_name} not found in content')
}
return struct_declaration.trim_space()
}
// Struct to hold parsed struct information
pub struct StructInfo {
pub:
struct_name string
fields map[string]string // field_name: field_type
}
// Helper function to parse a Rust struct definition from a string
pub fn parse_rust_struct(definition string) !StructInfo {
mut struct_name := ''
mut fields := map[string]string{}
mut in_struct := false
mut brace_level := 0
lines := definition.split('\n')
for line in lines {
trimmed_line := line.trim_space()
if trimmed_line.starts_with('pub struct') || trimmed_line.starts_with('struct') {
parts := trimmed_line.split(' ')
for i, part in parts {
if part == 'struct' && i + 1 < parts.len {
struct_name = parts[i + 1].split('{')[0].trim_space()
break
}
}
in_struct = true
if trimmed_line.contains('{') {
brace_level++
}
continue
}
if in_struct {
if trimmed_line.contains('{') {
brace_level++
}
if trimmed_line.contains('}') {
brace_level--
if brace_level == 0 {
in_struct = false
break // End of struct definition
}
}
// Inside the struct definition, parse fields (skip comments and attributes)
if brace_level > 0 && !trimmed_line.starts_with('//') && !trimmed_line.starts_with('#[')
&& trimmed_line.contains(':') {
parts := trimmed_line.split(':')
if parts.len >= 2 {
// Extract field name (handle potential 'pub ')
field_name_part := parts[0].trim_space()
mut field_name := '' // Explicitly declare with type
if field_name_part.starts_with('pub ') {
field_name = field_name_part['pub '.len..].trim_space()
} else {
field_name = field_name_part
}
// Extract field type (remove trailing comma)
mut field_type := parts[1..].join(':').trim_space()
if field_type.ends_with(',') {
field_type = field_type[..field_type.len - 1]
}
// Skip attributes or comments if they somehow got here (e.g. line ending comments)
if field_name.starts_with('[') || field_name.starts_with('/')
|| field_name == '' {
continue
}
fields[field_name] = field_type
}
}
}
}
if struct_name == '' {
return error('Could not find struct name in definition')
}
if fields.len == 0 {
return error('Could not parse any fields from the struct definition')
}
return StructInfo{struct_name, fields}
}
// Find the project root directory (the one containing Cargo.toml)
fn find_project_root(path string) string {
mut current_path := path
// If path is a file, get its directory
if !os.is_dir(current_path) {
current_path = os.dir(current_path)
}
// Look up parent directories until we find a Cargo.toml
for i := 0; i < 10; i++ { // Limit depth to avoid infinite loops
cargo_path := os.join_path(current_path, 'Cargo.toml')
if os.exists(cargo_path) {
return current_path
}
parent_dir := os.dir(current_path)
if parent_dir == current_path {
break // We've reached the filesystem root
}
current_path = parent_dir
}
return '' // No project root found
}
// Get module dependency information
pub fn get_module_dependency(importer_path string, module_path string) !ModuleDependency {
// Verify paths exist
if !os.exists(importer_path) {
return error('Importer path does not exist: ${importer_path}')
}
if !os.exists(module_path) {
return error('Module path does not exist: ${module_path}')
}
// Get import statement
import_statement := generate_import_statement(importer_path, module_path)! // Use local function
// Try to find the project roots for both paths
importer_project_root := find_project_root(importer_path)
module_project_root := find_project_root(module_path)
mut dependency := ModuleDependency{
import_statement: import_statement
module_path: module_path
}
// If they're in different projects, we need to extract dependency information
if importer_project_root != module_project_root && module_project_root != '' {
cargo_path := os.join_path(module_project_root, 'Cargo.toml')
if os.exists(cargo_path) {
// Get package info to determine name and version
pkg_info := detect_source_package(module_path) or {
return dependency // Return what we have if we can't get package info
}
dependency.package_name = pkg_info.name
// Extract version from Cargo.toml if possible
dependencies := extract_dependencies(cargo_path) or {
return dependency // Return what we have if we can't extract dependencies
}
// Check if the package is already a dependency
importer_cargo_path := os.join_path(importer_project_root, 'Cargo.toml')
if os.exists(importer_cargo_path) {
importer_dependencies := extract_dependencies(importer_cargo_path) or {
map[string]string{} // Empty map if we can't extract dependencies
}
// Check if package is already a dependency
if pkg_info.name in importer_dependencies {
dependency.is_already_dependency = true
dependency.current_version = importer_dependencies[pkg_info.name]
}
}
// Add cargo dependency line
dependency.cargo_dependency = '${pkg_info.name} = "<version>"' // Placeholder for version
}
} else {
// Same project, no need for external dependency
dependency.is_in_same_project = true
}
return dependency
}
// Information about a module dependency
pub struct ModuleDependency {
pub mut:
import_statement string // The Rust import statement to use
module_path string // Path to the module
package_name string // Name of the package (crate)
cargo_dependency string // Line to add to Cargo.toml
current_version string // Current version if already a dependency
is_already_dependency bool // Whether the package is already a dependency
is_in_same_project bool // Whether the module is in the same project
}