...
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
module codewalker
|
||||
|
||||
import arrays
|
||||
import os
|
||||
import incubaid.herolib.core.pathlib
|
||||
|
||||
// Default ignore patterns based on .gitignore conventions
|
||||
const default_gitignore = '
|
||||
@@ -48,10 +50,65 @@ Thumbs.db
|
||||
*.log
|
||||
'
|
||||
|
||||
pub fn find_ignore_patterns() []string {
|
||||
// find_ignore_patterns collects all .gitignore patterns from current directory up to repository root
|
||||
//
|
||||
// Walks up the directory tree using parent_find_advanced to locate all .gitignore files,
|
||||
// stopping when it encounters the .git directory (repository root).
|
||||
// Patterns are collected from:
|
||||
// 1. Default ignore patterns (built-in)
|
||||
// 2. All .gitignore files found from current directory to repository root
|
||||
// 3. Filter out comments (lines starting with '#') and empty lines
|
||||
//
|
||||
// Parameters:
|
||||
// - start_path: Optional starting directory path (defaults to current working directory if empty)
|
||||
//
|
||||
// Returns:
|
||||
// - Combined, sorted, unique ignore patterns from all sources
|
||||
// - Error if path operations fail (file not found, permission denied, etc.)
|
||||
//
|
||||
// Examples:
|
||||
// // Use current working directory
|
||||
// patterns := find_ignore_patterns()!
|
||||
//
|
||||
// // Use specific project directory
|
||||
// patterns := find_ignore_patterns('/home/user/myproject')!
|
||||
pub fn find_ignore_patterns(start_path string) ![]string {
|
||||
mut patterns := default_gitignore.split_into_lines()
|
||||
|
||||
// Use provided path or current working directory
|
||||
mut search_from := start_path
|
||||
if search_from == '' { // If an empty string was passed for start_path, use current working directory
|
||||
search_from = os.getwd()
|
||||
}
|
||||
|
||||
mut current_path := pathlib.get(search_from)
|
||||
|
||||
// Find all .gitignore files up the tree until we hit .git directory (repo root)
|
||||
mut gitignore_paths := current_path.parent_find_advanced('.gitignore', '.git')!
|
||||
|
||||
// Read and collect patterns from all found .gitignore files
|
||||
for mut gitignore_path in gitignore_paths {
|
||||
if gitignore_path.is_file() {
|
||||
content := gitignore_path.read() or {
|
||||
// Skip files that can't be read (permission issues, etc.)
|
||||
continue
|
||||
}
|
||||
|
||||
gitignore_lines := content.split_into_lines()
|
||||
for line in gitignore_lines {
|
||||
trimmed := line.trim_space()
|
||||
|
||||
// Skip empty lines and comment lines
|
||||
if trimmed != '' && !trimmed.starts_with('#') {
|
||||
patterns << trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort and get unique patterns to remove duplicates
|
||||
patterns.sort()
|
||||
patterns = arrays.uniq(patterns)
|
||||
|
||||
|
||||
return patterns
|
||||
}
|
||||
|
||||
@@ -13,25 +13,17 @@ fn filemap_get_from_path(path string, content_read bool) !FileMap {
|
||||
source: path
|
||||
}
|
||||
|
||||
|
||||
ignore_patterns := find_ignore_patterns(path)!
|
||||
|
||||
// List all files using pathlib with both default and custom ignore patterns
|
||||
mut file_list := dir.list(
|
||||
recursive: true
|
||||
ignore_default: true
|
||||
regex_ignore: ignore_patterns
|
||||
recursive: true
|
||||
filter_ignore: ignore_patterns
|
||||
)!
|
||||
|
||||
// Process files with additional scoped ignore checking
|
||||
for mut file in file_list.paths {
|
||||
if file.is_file() {
|
||||
relpath := file.path_relative(path)!
|
||||
|
||||
// Check scoped ignore patterns (from .gitignore/.heroignore in subdirectories)
|
||||
if cw.scoped_ignore.is_ignored(relpath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if content_read {
|
||||
content := file.read()!
|
||||
fm.content[relpath] = content
|
||||
@@ -44,84 +36,8 @@ fn filemap_get_from_path(path string, content_read bool) !FileMap {
|
||||
return fm
|
||||
}
|
||||
|
||||
// load_ignore_files reads .gitignore and .heroignore files and builds scoped patterns
|
||||
fn (mut cw CodeWalker) load_ignore_files(root_path string) ! {
|
||||
mut root := pathlib.get(root_path)
|
||||
if !root.is_dir() {
|
||||
return
|
||||
}
|
||||
|
||||
// List all files to find ignore files
|
||||
mut all_files := root.list(
|
||||
recursive: true
|
||||
ignore_default: false
|
||||
)!
|
||||
|
||||
for mut p in all_files.paths {
|
||||
if p.is_file() {
|
||||
name := p.name()
|
||||
if name == '.gitignore' || name == '.heroignore' {
|
||||
relpath := p.path_relative(root_path)!
|
||||
// Get the directory containing this ignore file
|
||||
mut scope := relpath
|
||||
if scope.contains('/') {
|
||||
scope = scope.all_before_last('/')
|
||||
} else {
|
||||
scope = ''
|
||||
}
|
||||
|
||||
content := p.read()!
|
||||
cw.scoped_ignore.add_for_scope(scope, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse_header robustly extracts block type and filename from header line
|
||||
// Handles variable `=` count, spaces, and case-insensitivity
|
||||
// Example: ` ===FILE: myfile.txt ===` → $(BlockKind.file, "myfile.txt")
|
||||
fn parse_header(line string) !(BlockKind, string) {
|
||||
cleaned := line.trim_space()
|
||||
|
||||
// Must have = and content
|
||||
if !cleaned.contains('=') {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Strip leading and trailing = (any count), preserving spaces between
|
||||
mut content := cleaned.trim_left('=').trim_space()
|
||||
content = content.trim_right('=').trim_space()
|
||||
|
||||
if content.len == 0 {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Check for END marker
|
||||
if content.to_lower() == 'end' {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Parse FILE or FILECHANGE
|
||||
if content.contains(':') {
|
||||
kind_str := content.all_before(':').to_lower().trim_space()
|
||||
filename := content.all_after(':').trim_space()
|
||||
|
||||
if filename.len < 1 {
|
||||
return error('Invalid filename: empty after colon')
|
||||
}
|
||||
|
||||
match kind_str {
|
||||
'file' { return BlockKind.file, filename }
|
||||
'filechange' { return BlockKind.filechange, filename }
|
||||
else { return BlockKind.end, '' }
|
||||
}
|
||||
}
|
||||
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// filemap_get_from_content parses FileMap from string with ===FILE:name=== format
|
||||
fn (mut cw CodeWalker) filemap_get_from_content(content string) !FileMap {
|
||||
fn filemap_get_from_content(content string) !FileMap {
|
||||
mut fm := FileMap{}
|
||||
|
||||
mut current_kind := BlockKind.end
|
||||
44
lib/ai/codewalker/parser.v
Normal file
44
lib/ai/codewalker/parser.v
Normal file
@@ -0,0 +1,44 @@
|
||||
module codewalker
|
||||
|
||||
// parse_header robustly extracts block type and filename from header line
|
||||
// Handles variable `=` count, spaces, and case-insensitivity
|
||||
// Example: ` ===FILE: myfile.txt ===` → $(BlockKind.file, "myfile.txt")
|
||||
fn parse_header(line string) !(BlockKind, string) {
|
||||
cleaned := line.trim_space()
|
||||
|
||||
// Must have = and content
|
||||
if !cleaned.contains('=') {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Strip leading and trailing = (any count), preserving spaces between
|
||||
mut content := cleaned.trim_left('=').trim_space()
|
||||
content = content.trim_right('=').trim_space()
|
||||
|
||||
if content.len == 0 {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Check for END marker
|
||||
if content.to_lower() == 'end' {
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
|
||||
// Parse FILE or FILECHANGE
|
||||
if content.contains(':') {
|
||||
kind_str := content.all_before(':').to_lower().trim_space()
|
||||
filename := content.all_after(':').trim_space()
|
||||
|
||||
if filename.len < 1 {
|
||||
return error('Invalid filename: empty after colon')
|
||||
}
|
||||
|
||||
match kind_str {
|
||||
'file' { return BlockKind.file, filename }
|
||||
'filechange' { return BlockKind.filechange, filename }
|
||||
else { return BlockKind.end, '' }
|
||||
}
|
||||
}
|
||||
|
||||
return BlockKind.end, ''
|
||||
}
|
||||
Reference in New Issue
Block a user