perf: Replaced fd with custom internals.
This commit is contained in:
182
gitignore.odin
Normal file
182
gitignore.odin
Normal file
@@ -0,0 +1,182 @@
|
||||
package findr
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:text/regex"
|
||||
|
||||
is_regex_meta :: proc(c: u8) -> bool {
|
||||
switch c {
|
||||
case '.', '+', '(', ')', '{', '}', '^', '$', '|':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
glob_to_regex :: proc(pattern: string, anchored: bool) -> string {
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb)
|
||||
defer strings.builder_destroy(&sb)
|
||||
|
||||
if anchored {
|
||||
fmt.sbprintf(&sb, "^")
|
||||
} else {
|
||||
fmt.sbprintf(&sb, "(^|/)")
|
||||
}
|
||||
|
||||
i := 0
|
||||
for i < len(pattern) {
|
||||
c := pattern[i]
|
||||
|
||||
if c == '*' {
|
||||
if i + 1 < len(pattern) && pattern[i + 1] == '*' {
|
||||
prev_slash := i == 0 || pattern[i - 1] == '/'
|
||||
at_end := i + 2 >= len(pattern)
|
||||
next_slash := !at_end && pattern[i + 2] == '/'
|
||||
|
||||
if prev_slash && (next_slash || at_end) {
|
||||
if next_slash {
|
||||
i += 3
|
||||
fmt.sbprintf(&sb, "(.*/)?")
|
||||
} else {
|
||||
i += 2
|
||||
fmt.sbprintf(&sb, ".*")
|
||||
}
|
||||
} else {
|
||||
fmt.sbprintf(&sb, "[^/]*")
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
fmt.sbprintf(&sb, "[^/]*")
|
||||
i += 1
|
||||
}
|
||||
} else if c == '?' {
|
||||
fmt.sbprintf(&sb, "[^/]")
|
||||
i += 1
|
||||
} else if c == '[' {
|
||||
append(&sb.buf, '[')
|
||||
i += 1
|
||||
if i < len(pattern) && pattern[i] == '!' {
|
||||
append(&sb.buf, '^')
|
||||
i += 1
|
||||
}
|
||||
if i < len(pattern) && pattern[i] == ']' {
|
||||
append(&sb.buf, ']')
|
||||
i += 1
|
||||
}
|
||||
for i < len(pattern) && pattern[i] != ']' {
|
||||
append(&sb.buf, pattern[i])
|
||||
i += 1
|
||||
}
|
||||
if i < len(pattern) {
|
||||
append(&sb.buf, ']')
|
||||
i += 1
|
||||
}
|
||||
} else if c == '\\' {
|
||||
i += 1
|
||||
if i < len(pattern) {
|
||||
if is_regex_meta(pattern[i]) {
|
||||
append(&sb.buf, '\\')
|
||||
}
|
||||
append(&sb.buf, pattern[i])
|
||||
i += 1
|
||||
}
|
||||
} else if is_regex_meta(c) {
|
||||
append(&sb.buf, '\\')
|
||||
append(&sb.buf, c)
|
||||
i += 1
|
||||
} else {
|
||||
append(&sb.buf, c)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
fmt.sbprintf(&sb, "(/.*)?$")
|
||||
|
||||
s := strings.to_string(sb)
|
||||
result, _ := strings.clone(s)
|
||||
return result
|
||||
}
|
||||
|
||||
Rule :: struct {
|
||||
regex: regex.Regular_Expression,
|
||||
negated: bool,
|
||||
dir_only: bool,
|
||||
}
|
||||
|
||||
Gitignore :: struct {
|
||||
rules: [dynamic]Rule,
|
||||
}
|
||||
|
||||
parse :: proc(content: string) -> Gitignore {
|
||||
gi: Gitignore
|
||||
gi.rules = make([dynamic]Rule)
|
||||
|
||||
remaining := content
|
||||
for {
|
||||
line, ok := strings.split_lines_iterator(&remaining)
|
||||
if !ok do break
|
||||
|
||||
s := strings.trim_space(line)
|
||||
if len(s) == 0 do continue
|
||||
if s[0] == '#' do continue
|
||||
|
||||
negated := false
|
||||
if s[0] == '!' {
|
||||
negated = true
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
if len(s) > 0 && s[0] == '\\' {
|
||||
if len(s) > 1 && (s[1] == '#' || s[1] == '!') {
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
|
||||
dir_only := false
|
||||
if len(s) > 0 && s[len(s) - 1] == '/' {
|
||||
dir_only = true
|
||||
s = s[:len(s) - 1]
|
||||
}
|
||||
|
||||
anchored := false
|
||||
if len(s) > 0 && s[0] == '/' {
|
||||
anchored = true
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
if len(s) == 0 do continue
|
||||
|
||||
regex_str := glob_to_regex(s, anchored)
|
||||
re, err := regex.create(regex_str, {regex.Flag.No_Capture})
|
||||
delete(regex_str)
|
||||
if err != nil do continue
|
||||
|
||||
append(&gi.rules, Rule{
|
||||
regex = re,
|
||||
negated = negated,
|
||||
dir_only = dir_only,
|
||||
})
|
||||
}
|
||||
|
||||
return gi
|
||||
}
|
||||
|
||||
is_ignored :: proc(gi: ^Gitignore, path: string, is_dir: bool) -> bool {
|
||||
matched := false
|
||||
for rule in gi.rules {
|
||||
if rule.dir_only && !is_dir do continue
|
||||
cap, ok := regex.match(rule.regex, path)
|
||||
regex.destroy(cap)
|
||||
if ok {
|
||||
matched = !rule.negated
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
destroy :: proc(gi: ^Gitignore) {
|
||||
for rule in gi.rules {
|
||||
regex.destroy(rule.regex)
|
||||
}
|
||||
delete(gi.rules)
|
||||
}
|
||||
Reference in New Issue
Block a user