This commit is contained in:
2025-11-25 05:44:58 +01:00
parent 03d9e97008
commit de7e1abcba
6 changed files with 94 additions and 108 deletions

View File

@@ -1,14 +1,24 @@
module codewalker
// new creates a CodeWalker instance with default ignore patterns
pub fn new() CodeWalker {
return CodeWalker{
scoped_ignore: ScopedIgnore{}
}
@[params]
pub struct FileMapArgs {
pub mut:
path string
content string
content_read bool = true // If false, file content not read from disk
// Include if matches any wildcard pattern (* = any sequence)
filter []string
// Exclude if matches any wildcard pattern
filter_ignore []string
}
// filemap creates FileMap from path or content (convenience function)
// filemap_get creates FileMap from path or content string
pub fn filemap(args FileMapArgs) !FileMap {
mut cw := new()
return cw.filemap_get(args)
if args.path != '' {
return filemap_get_from_path(args.path, args.content_read)!
} else if args.content != '' {
return filemap_get_from_content(args.content)!
} else {
return error('Either path or content must be provided')
}
}

View File

@@ -1,5 +1,7 @@
module codewalker
import arrays
// Default ignore patterns based on .gitignore conventions
const default_gitignore = '
.git/
@@ -45,3 +47,11 @@ Thumbs.db
*.temp
*.log
'
pub fn find_ignore_patterns() []string {
mut patterns := default_gitignore.split_into_lines()
patterns.sort()
patterns = arrays.uniq(patterns)
return patterns
}

View File

@@ -14,61 +14,3 @@ pub:
category string
filename string
}
// ScopedIgnore handles directory-scoped .gitignore/.heroignore patterns
pub struct ScopedIgnore {
pub mut:
// Map of directory -> list of patterns
// Empty string key for root level patterns
patterns map[string][]string
}
// Add patterns for a specific directory scope
pub fn (mut si ScopedIgnore) add_for_scope(scope string, patterns_text string) {
mut scope_key := scope
if scope == '' {
scope_key = '/'
}
if scope_key !in si.patterns {
si.patterns[scope_key] = []string{}
}
for line in patterns_text.split_into_lines() {
line_trimmed := line.trim_space()
if line_trimmed != '' && !line_trimmed.starts_with('#') {
si.patterns[scope_key] << gitignore_pattern_to_regex(line_trimmed)
}
}
}
// Check if a relative path should be ignored
pub fn (si ScopedIgnore) is_ignored(relpath string) bool {
// Check all scopes that could apply to this path
path_parts := relpath.split('/')
// Check root level patterns
if '/' in si.patterns {
for pattern in si.patterns['/'] {
if relpath.match_regex(pattern) { // Use match_regex here
return true
}
}
}
// Check directory-scoped patterns
for i := 0; i < path_parts.len; i++ {
scope := path_parts[..i].join('/')
if scope != '' && scope in si.patterns {
// Check if remaining path matches patterns in this scope
remaining := path_parts[i..].join('/')
for pattern in si.patterns[scope] {
if remaining.match_regex(pattern) {
return true
}
}
}
}
return false
}

View File

@@ -2,38 +2,8 @@ module codewalker
import incubaid.herolib.core.pathlib
// CodeWalker walks directories and parses file content
pub struct CodeWalker {
pub mut:
scoped_ignore ScopedIgnore
}
@[params]
pub struct FileMapArgs {
pub mut:
path string
content string
content_read bool = true // If false, file content not read from disk
}
// parse extracts FileMap from formatted content string
pub fn (mut cw CodeWalker) parse(content string) !FileMap {
return cw.filemap_get_from_content(content)
}
// filemap_get creates FileMap from path or content string
pub fn (mut cw CodeWalker) filemap_get(args FileMapArgs) !FileMap {
if args.path != '' {
return cw.filemap_get_from_path(args.path, args.content_read)!
} else if args.content != '' {
return cw.filemap_get_from_content(args.content)!
} else {
return error('Either path or content must be provided')
}
}
// filemap_get_from_path reads directory and creates FileMap, respecting ignore patterns
fn (mut cw CodeWalker) filemap_get_from_path(path string, content_read bool) !FileMap {
fn filemap_get_from_path(path string, content_read bool) !FileMap {
mut dir := pathlib.get(path)
if !dir.exists() || !dir.is_dir() {
return error('Directory "${path}" does not exist')
@@ -43,17 +13,7 @@ fn (mut cw CodeWalker) filemap_get_from_path(path string, content_read bool) !Fi
source: path
}
// Load .gitignore and .heroignore files first to build scoped ignores
cw.scoped_ignore = ScopedIgnore{}
cw.load_ignore_files(path)!
// Combine default patterns with custom ignore patterns
mut ignore_patterns := get_default_ignore_patterns()
// Add any root-level custom patterns
if '/' in cw.scoped_ignore.patterns {
ignore_patterns << cw.scoped_ignore.patterns['/']
}
// List all files using pathlib with both default and custom ignore patterns
mut file_list := dir.list(

View File

@@ -2,6 +2,7 @@ module pathlib
import os
import incubaid.herolib.core.texttools
import incubaid.herolib.core.texttools.regext
import time
import crypto.md5
import rand
@@ -292,6 +293,69 @@ pub fn (path Path) parent_find(tofind string) !Path {
return path2.parent_find(tofind)
}
// parent_find_advanced walks up the directory tree, collecting all items that match tofind
// pattern until it encounters an item matching the stop pattern.
// Both tofind and stop use matcher filter format supporting wildcards:
// - '*.txt' matches any .txt file
// - 'src*' matches anything starting with 'src'
// - '.git' matches exactly '.git'
// - '*test*' matches anything containing 'test'
//
// Returns all found paths before hitting the stop condition.
// If stop is never found, continues until reaching filesystem root.
//
// Examples:
// // Find all 'test_*.v' files until reaching '.git' directory
// tests := my_path.parent_find_advanced('test_*.v', '.git')!
//
// // Find any 'Makefile*' until hitting 'node_modules'
// makefiles := my_path.parent_find_advanced('Makefile*', 'node_modules')!
//
// // Find '*.md' files until reaching '.git'
// docs := my_path.parent_find_advanced('*.md', '.git')!
pub fn (path Path) parent_find_advanced(tofind string, stop string) ![]Path {
// Start from current path or its parent if it's a file
mut search_path := path
if search_path.is_file() {
search_path = search_path.parent()!
}
// Create matchers from filter patterns
tofind_matcher := regext.new(filter: [tofind])!
stop_matcher := regext.new(filter: [stop])!
mut found_paths := []Path{}
mut current := search_path
for {
// List contents of current directory
mut items := os.ls(current.path) or { []string{} }
// Check each item in the directory
for item in items {
// Check if this is the stop pattern - if yes, halt and return
if stop_matcher.match(item) {
return found_paths
}
// Check if this matches what we're looking for
if tofind_matcher.match(item) {
full_path := os.join_path(current.path, item)
mut found_path := get(full_path)
if found_path.exists() {
found_paths << found_path
}
}
}
// Try to move to parent directory
current = current.parent() or {
// Reached filesystem root, return what we found
return found_paths
}
}
}
// delete
pub fn (mut path Path) rm() ! {
return path.delete()