123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182(********************************************************************************)(* crs - A tool for managing code review comments embedded in source code *)(* Copyright (C) 2024-2025 Mathieu Barbin <mathieu.barbin@gmail.com> *)(* *)(* This file is part of crs. *)(* *)(* crs is free software; you can redistribute it and/or modify it under the *)(* terms of the GNU Lesser General Public License as published by the Free *)(* Software Foundation either version 3 of the License, or any later version, *)(* with the LGPL-3.0 Linking Exception. *)(* *)(* crs is distributed in the hope that it will be useful, but WITHOUT ANY *)(* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS *)(* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License and *)(* the file `NOTICE.md` at the root of this repository for more details. *)(* *)(* You should have received a copy of the GNU Lesser General Public License *)(* and the LGPL-3.0 Linking Exception along with this library. If not, see *)(* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively. *)(********************************************************************************)(* This module is derived from Iron (v0.9.114.44+47), file
* [./hg/cr_comment.ml], which is released under Apache 2.0:
*
* Copyright (c) 2016-2017 Jane Street Group, LLC <opensource-contacts@janestreet.com>
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* See the file `NOTICE.md` at the root of this repository for more details.
*
* Changes:
*
* - Migrate to this file only the part that relates to the parsing of the 1st line.
* - Remove dependency to [Core] - make small adjustments to use [Base] instead.
* - Replace [Unresolved_name] by [Vcs.User_handle].
* - Remove alternate names and aliases resolution.
* - Remove support for in-file `Properties.
* - Remove support for extra headers.
* - Remove support for attributes.
* - Remove assignee computation (left as external work).
* - Replace [is_xcr] by a variant type [Status.t].
* - Make [reporter] mandatory.
* - Rename [Processed] to [Header].
* - Remove support for 'v' separator in CR comment
* - Include the leading '-' char in due's [Loc.t].
* - Migrate from [Re2] to [ocaml-re].
*)(* : and @ have other meanings in CR comments *)letword_t=Re.compl[Re.char' ';Re.char'\t';Re.char'\n';Re.char':';Re.char'@'];;letwhitespace=Re.alt[Re.char' ';Re.char'\n';Re.char'\t']moduleRe_helper=structtypet={re:Re.re;status:int;qualifier:int;reporter:int;recipient:int}endletmake_re_helper()=letstatus_g="status"inletqualifier_g="qualifier"inletreporter_g="reporter"inletrecipient_g="recipient"inletre=Re.(whole_string(seq[repwhitespace;group~name:status_g(seq[opt(char'X');str"CR"]);opt(seq[char'-';group~name:qualifier_g(alt[repndigit6(Some6);str"soon";str"someday"])]);rep1whitespace;group~name:reporter_g(rep1word_t);opt(seq[rep1whitespace;str"for";rep1whitespace;group~name:recipient_g(rep1word_t)]);repwhitespace;char':';repany]))|>Re.compileinletgroups=Re.group_namesre|>Map.of_alist_exn(moduleString)inletfind_group~name=Map.find_exngroupsnamein{Re_helper.re;status=find_group~name:status_g;qualifier=find_group~name:qualifier_g;reporter=find_group~name:reporter_g;recipient=find_group~name:recipient_g};;letre_helper=lazy(make_re_helper())letparse~file_cache~content_start_offset~content=let(let*)af=Or_error.binda~fintryletre_helper=Lazy.forcere_helperinlet*m=matchRe.exec_optre_helper.recontentwith|None->Or_error.error"Invalid CR comment"contentString.sexp_of_t|Somem->Or_error.returnminletgetindex=matchRe.Group.get_optmindexwith|None->None|Somev->letstart,stop=Re.Group.offsetmindexinletloc=Loc.of_file_range~file_cache~range:{start=content_start_offset+start;stop=content_start_offset+stop}inSome(v,loc)inletreporter=matchgetre_helper.reporterwith|None->assertfalse(* Mandatory in the [regexp]. *)|Some(reporter,loc)->{Loc.Txt.txt=Vcs.User_handle.vreporter;loc}inletstatus=matchgetre_helper.statuswith|None->assertfalse(* Mandatory in the [regexp]. *)|Some(status,loc)->lettxt:Cr_comment.Status.t=matchstatuswith|"CR"->CR|"XCR"->XCR|_->assertfalse(* Cannot be parsed according to the [regexp]. *)in{Loc.Txt.txt;loc}inletrecipient=Option.map(getre_helper.recipient)~f:(fun(user,loc)->{Loc.Txt.txt=Vcs.User_handle.vuser;loc})inletqualifier=matchgetre_helper.qualifierwith|None->{Loc.Txt.txt=Cr_comment.Qualifier.None;loc=status.loc}|Some("soon",loc)->{Loc.Txt.txt=Cr_comment.Qualifier.Soon;loc}|Some("someday",loc)->{Loc.Txt.txt=Cr_comment.Qualifier.Someday;loc}|Some(_,loc)->(* dated CR -> CR-someday *){Loc.Txt.txt=Cr_comment.Qualifier.Someday;loc}inOr_error.return(Cr_comment.Private.Header.create~status~qualifier~reporter~recipient)with|exn->(* This catches e.g. other invalid inputs such as invalid user names. *)Or_error.error"could not process CR"(content,exn)[%sexp_of:string*exn][@coverageoff];;