Something went wrong. Try again.
My own corner of monopam
Something went wrong. Try again.
3.4 kB · 100 lines
OCaml
at staging
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101(* Copyright (c) 2024-2026 Thomas Gazagnaire <thomas@gazagnaire.org>
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. *)
type text = Set | Unset | Auto | Unspecifiedtype eol = Lf | Crlftype merge_driver = Text_merge | Binary_merge | Union_merge | Driver of string
(* One [gitattributes] token we care about. [eol=...] also implies the file is text; [binary] is the macro for [-diff -merge -text]. [Merge None] is [!merge], which returns the attribute to unspecified. Anything else is dropped. *)type token = Text of text | Eol of eol | Binary | Merge of merge_driver optiontype rule = { spec : Pathspec.t; base : string; tokens : token list }type t = rule list
let pp ppf t = Fmt.pf ppf "gitattributes[%d]" (List.length t)let empty = []let append = ( @ )
let token_of_string = function | "text" -> Some (Text Set) | "-text" -> Some (Text Unset) | "text=auto" -> Some (Text Auto) | "eol=lf" -> Some (Eol Lf) | "eol=crlf" -> Some (Eol Crlf) | "binary" -> Some Binary | "merge" | "merge=text" -> Some (Merge (Some Text_merge)) | "-merge" | "merge=binary" -> Some (Merge (Some Binary_merge)) | "merge=union" -> Some (Merge (Some Union_merge)) | "!merge" -> Some (Merge None) | s when String.starts_with ~prefix:"merge=" s -> Some (Merge (Some (Driver (String.sub s 6 (String.length s - 6))))) | _ -> None
let ws c = c = ' ' || c = '\t'
let fields line = String.split_on_char ' ' (String.map (fun c -> if ws c then ' ' else c) line) |> List.filter (fun s -> s <> "")
let compile_one ~base raw = let line = String.trim raw in if line = "" || line.[0] = '#' then None else match fields line with | [] -> None | pattern :: attrs -> ( match Pathspec.compile pattern with | None -> None | Some spec -> Some { spec; base; tokens = List.filter_map token_of_string attrs })
let of_file_content ~base content = String.split_on_char '\n' content |> List.filter_map (compile_one ~base)
(* Whether a file is treated as text -- the only thing that decides EOL normalisation. [eol=...] implies text; [binary] forces non-text. *)let apply text = function | Text t -> t | Eol _ -> if text = Unspecified then Set else text | Binary -> Unset | Merge _ -> text
let resolve_text rules ~path = List.fold_left (fun text rule -> if Pathspec.matches rule.spec ~base:rule.base ~path then List.fold_left apply text rule.tokens else text) Unspecified rules
type autocrlf = True | Input | False
let normalizes_eol rules ~autocrlf ~path ~content = let is_text_content () = not (String.contains content '\000') in match resolve_text rules ~path with | Unset -> false | Set -> true | Auto -> is_text_content () | Unspecified -> ( match autocrlf with True | Input -> is_text_content () | False -> false)
let apply_merge driver = function | Merge d -> d | Binary -> Some Binary_merge | Text _ | Eol _ -> driver
let merge_driver rules ~path = let driver = List.fold_left (fun driver rule -> if Pathspec.matches rule.spec ~base:rule.base ~path then List.fold_left apply_merge driver rule.tokens else driver) None rules in Option.value driver ~default:Text_merge