(module dr-fuzzy scheme
(provide all-files
make-pattern
build-path-parts-regex
build-file-regex
build-match-result
make-match-result
reload-files!
load-files!
ignored?
how-many-directories-up-to
search
clean-path
add-match-results
path->list
match-result-tagged-path
match-result-path)
(define-struct match-result (tagged-path score path)
#:transparent
#:mutable)
(define-struct run (capture is-inside?) #:mutable)
(define MAX-RESULTS 30)
(define MAX-FILES 5000)
(define max-files-error-msg
(format "Sorry, but there are more than ~a files in the\n current directory and its subdirectories."
MAX-FILES))
(define LEFT-RUN-MARKER "(")
(define RIGHT-RUN-MARKER ")")
(define LEFT-PATTERN-MARKER "(")
(define RIGHT-PATTERN-MARKER ")")
(define IN-BETWEEN-PATTERN "([^/]*?)")
(define START-PATH-PART-REGEX "(?i:^(.*?)")
(define (FILE-SEPARATOR)
(cond
[(equal? (system-path-convention-type) 'windows) "\\"]
[else "/"]))
(define IN-BETWEEN-PATH-PART-REGEX (format "(.*?~a.*?)"
(FILE-SEPARATOR)))
(define END-PATH-PART-REGEX "(.*?)$)")
(define END-FILE-REGEX "(.*)$)")
(define PATTERNS-TO-IGNORE '(#px"^.*\\/\\..*$"
#px"^.*~$"))
(define ALL-FILES empty)
(define (load-files!)
(cond
[(empty? ALL-FILES)
(set! ALL-FILES (all-files "./"))]
[else (void)]))
(define (reload-files!)
(set! ALL-FILES (all-files "./")))
(define (all-files (root-directory (current-directory)))
(local [ (define (get-sub-types a-dir what-to-get)
(filter (λ (file-or-dir)
(what-to-get file-or-dir))
(map (λ (file-or-dir)
(build-path a-dir
file-or-dir))
(reverse (directory-list a-dir)))))
(define (subdirectories current-dir)
(get-sub-types current-dir
directory-exists?))
(define (files current-dir)
(get-sub-types current-dir
(λ (file-or-dir)
(and (file-exists? file-or-dir)
(not (ignored? file-or-dir))))))
(define (all-files-in-directories directories files-so-far)
(cond
[(empty? directories) files-so-far]
[(> (length files-so-far) MAX-FILES)
(error max-files-error-msg)]
[else
(all-files-in-directories
(rest directories)
(append files-so-far
(all-files (first directories))))]))]
(all-files-in-directories (subdirectories root-directory)
(files root-directory))))
(define (ignored? a-file)
(local [(define (matches-any? patterns)
(cond
[(empty? patterns) false]
[(and (path-string? a-file)
(regexp-match (first patterns)
(path->string a-file)))
true]
[else (matches-any? (rest patterns))]))]
(matches-any? PATTERNS-TO-IGNORE)))
(define (build-path-parts-regex path-parts)
(build-regex-from-string (string-join
(map (λ (path-part)
(make-pattern path-part))
path-parts)
IN-BETWEEN-PATH-PART-REGEX)
END-PATH-PART-REGEX))
(define (build-file-regex a-file)
(build-regex-from-string (make-pattern a-file)
END-FILE-REGEX))
(define (build-regex-from-string string-to-be-regex ending)
(string-append START-PATH-PART-REGEX
string-to-be-regex
ending))
(define (make-pattern pattern)
(local [(define (build-piece-of-patter piece)
(string-append LEFT-PATTERN-MARKER
piece
RIGHT-PATTERN-MARKER))
(define (build-the-pattern splitted-pattern accumulator)
(cond
[(and (not (empty? (first splitted-pattern)))
(empty? (rest splitted-pattern)))
(string-append accumulator (build-piece-of-patter
(first splitted-pattern)))]
[else
(build-the-pattern (rest splitted-pattern)
(string-append accumulator
(build-piece-of-patter
(first splitted-pattern))
IN-BETWEEN-PATTERN))]))]
(build-the-pattern (regexp-split (regexp "") pattern)
"")))
(define (path->list the-path)
(filter (λ (path-part)
(and (not (string=? "" path-part))
(not (string=? "." path-part))))
(regexp-split (FILE-SEPARATOR)
(path->string the-path))))
(define (how-many-directories-up-to a-file)
(operation-on-path (λ (path-part accumulator)
(add1 accumulator))
0
a-file))
(define (clean-path the-path)
(operation-on-path (λ (path-part accumulator)
(string-append (path->string path-part)
(FILE-SEPARATOR)
accumulator))
(let ([filename
(file-name-from-path the-path)])
(cond
[(false? filename) ""]
[else (path->string filename)]))
the-path))
(define (operation-on-path operation initial the-path0)
(local [(define (operation-on-path-acc the-path accumulator)
(let-values ([(base name must-be-dir?) (split-path the-path)])
(cond
[(or (equal? name 'same)
(equal? base 'relative))
accumulator]
[(false? must-be-dir?)
(operation-on-path-acc base
accumulator)]
[else
(operation-on-path-acc base
(operation name
accumulator))])))]
(operation-on-path-acc the-path0 initial)))
(define (add-match-results match-path match-file (original-file empty))
(local [ (define (format-result-path path-parts accumulator)
(cond
[(empty? path-parts) accumulator]
[(string=? "" (first path-parts))
(format-result-path (rest path-parts)
accumulator)]
[(equal? (string-ref (first path-parts) 0) #\()
(format-result-path (rest path-parts)
(string-append accumulator
(first path-parts)
(FILE-SEPARATOR)))]
[else
(format-result-path (rest path-parts)
(string-append
accumulator
(string (string-ref (first path-parts) 0))
(FILE-SEPARATOR)))]))]
(cond
[(string=? "" (match-result-tagged-path match-path))
(make-match-result (match-result-tagged-path match-file)
(match-result-score match-file)
original-file)]
[else
(make-match-result
(string-append
(format-result-path
(regexp-split (pregexp (FILE-SEPARATOR))
(match-result-tagged-path match-path))
"")
(match-result-tagged-path match-file))
(* (match-result-score match-path)
(match-result-score match-file))
original-file)])))
(define (search query)
(local [ (define path-regexp
(cond
[(or (string=? "" query)
(false? (path-only query))) empty]
[else
(build-path-parts-regex (path->list
(path-only query)))]))
(define file-regexp
(cond
[(string=? "" query) empty]
[else
(let
([filename (file-name-from-path query)])
(cond
[(false? filename) empty]
[else
(build-file-regex (path->string filename))]))]))
(define (build-result path regexp (real-path empty))
(cond
[(or (string=? path "")
(empty? path)
(empty? regexp)) false]
[else
(let ([the-match (regexp-match regexp path)])
(cond
[(false? the-match) false]
[else
(build-match-result
(regexp-match regexp path)
(how-many-directories-up-to path)
real-path)]))]))
(define (match-file path-result file name)
(cond
[(empty? file-regexp)
(add-match-results path-result
(make-match-result (clean-path name)
0.0
empty)
file)]
[else
(let ([file-result
(build-result (clean-path name)
file-regexp)])
(cond
[(false? file-result) false]
[else
(add-match-results path-result
(build-result (clean-path name)
file-regexp)
file)]))]))
(define (search-all-files files path-matches full-matches)
(cond
[(or (empty? files)
(> (length full-matches) MAX-RESULTS)) full-matches]
[else
(let*-values ([(base name must-be-dir?)
(split-path (first files))]
[(list-of-cached-path-result)
(cond
[(empty? path-regexp)
(list (make-match-result (clean-path base)
1.0
base))]
[else (filter
(λ (result)
(equal? base
(match-result-path result)))
path-matches)])]
[(path-result)
(cond
[(empty? list-of-cached-path-result)
(build-result (clean-path base)
path-regexp
base)]
[else (first list-of-cached-path-result)])])
(cond
[(false? path-result)
(search-all-files (rest files)
path-matches
full-matches)]
[else
(let ([file-result (match-file path-result
(first files)
name)])
(cond
[(false? file-result)
(search-all-files (rest files)
(cons path-result path-matches)
full-matches)]
[else
(search-all-files (rest files)
(cons path-result path-matches)
(cons file-result
full-matches))]))]))]))]
(sort (search-all-files ALL-FILES
empty
empty) #:key match-result-score >)))
(define (build-match-result the-match0 number-of-folders real-path)
(local [ (define (analise-match raw-match runs matched-chars index)
(cond
[(empty? raw-match)
(synthesize-result runs matched-chars)]
[(zero? (modulo index 2))
(analise-match (rest raw-match)
(update-runs runs
(make-run (first raw-match)
true))
(+ matched-chars
(string-length (first raw-match)))
(add1 index))]
[else
(analise-match (rest raw-match)
(update-runs runs
(make-run (first raw-match)
false))
matched-chars
(add1 index))]))
(define (update-runs runs a-run)
(cond
[(and (not (empty? runs))
(equal? (run-is-inside? (last runs))
(run-is-inside? a-run)))
(begin
(set-run-capture! (last runs)
(string-append (run-capture (last runs))
(run-capture a-run)))
runs)]
[(not (string=? "" (run-capture a-run)))
(append runs (list a-run))]
[else runs]))
(define (format-run a-run)
(string-append LEFT-RUN-MARKER
(run-capture a-run)
RIGHT-RUN-MARKER))
(define (total-chars a-string)
(string-length (list->string (remove* (list #\/)
(string->list a-string)))))
(define (synthesize-result runs matched-chars)
(make-match-result
(string-join (map (λ (a-run)
(cond
[(run-is-inside? a-run)
(format-run a-run)]
[else (run-capture a-run)]))
runs) "")
(* (get-a-ratio (count (λ (a-run)
(run-is-inside? a-run))
runs)
(add1 number-of-folders))
(get-a-ratio (total-chars (first the-match0))
matched-chars))
real-path))
(define (get-a-ratio divisor base)
(cond
[(zero? divisor) 1]
[else
(/ base divisor)]))]
(cond
[(or (empty? the-match0)
(false? the-match0)
(string=? "" (first the-match0)))
(make-match-result "" 1 empty)] [else (analise-match (rest the-match0) empty 0 1)]))))