123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753(**************************************************************************)(* *)(* OCamlFormat *)(* *)(* Copyright (c) Facebook, Inc. and its affiliates. *)(* *)(* This source code is licensed under the MIT license found in *)(* the LICENSE file in the root directory of this source tree. *)(* *)(**************************************************************************)openOcamlformat_libopenConfopenCmdlinermoduleDecl=Conf_decltypefile=Stdin|Fileofstringtypet={lib_conf:Conf.t;enable_outside_detected_project:bool;inplace:bool;check:bool;kind:Syntax.toption;inputs:filelist;name:stringoption;output:stringoption;print_config:bool;root:stringoption;disable_conf_files:bool;ignore_invalid_options:bool;ocp_indent_config:bool;config:(string*string)list}letdefault={lib_conf=Conf.default;enable_outside_detected_project=false;inplace=false;check=false;kind=None;inputs=[];name=None;output=None;print_config=false;root=None;disable_conf_files=false;ignore_invalid_options=false;ocp_indent_config=false;config=[]}letglobal_conf=refdefaultletinfo=letdoc="A tool to format OCaml code."inletman=[`SCmdliner.Manpage.s_description;`P"$(tname) automatically formats OCaml code.";`S(Decl.section_nameDecl.Formatting`Valid);`P"Unless otherwise noted, any option \
$(b,--)$(i,option)$(b,=)$(i,VAL) detailed in this section can be \
set in many ways, its value is determined in the following order \
(of increasing priority): the default value is used if no other \
value is specified. The value of a boolean option $(b,--foo) or \
$(b,--no-foo) can be modified in an $(b,.ocamlformat) \
configuration file with '$(b,foo = ){$(b,true),$(b,false)}', it \
can be done for any other option with an '$(b,option = )$(i,VAL)' \
line (*), or using the OCAMLFORMAT environment variable: \
$(b,OCAMLFORMAT=)$(i,option)$(b,=)$(i,VAL)$(b,,)...$(b,,)$(i,option)$(b,=)$(i,VAL), \
or as an optional parameter on the command line, or with a global \
$(b,[@@@ocamlformat \")$(i,option)$(b,=)$(i,VAL)$(b,\"]) attribute \
in the processed file, or with an $(b,[@@ocamlformat \
\")$(i,option)$(b,=)$(i,VAL)$(b,\"]) attribute on expression in \
the processed file.";`P"(*) $(b,.ocamlformat) files in current and all ancestor \
directories for each input file are used, applied from top to \
bottom, overriding the settings each time a file is applied, \
stopping at the project root. If no project root and no \
$(b,ocamlformat) file has been found, and if the option \
$(b,enable-outside-detected-project) is set, the global \
$(b,ocamlformat) file defined in $(b,\\$XDG_CONFIG_HOME) (or in \
$(b,\\$HOME/.config) if $(b,\\$XDG_CONFIG_HOME) is undefined) is \
used.";`P"If the $(b,disable) option is not set, an $(b,.ocamlformat-ignore) \
file specifies files that OCamlFormat should ignore. Each line in \
an $(b,.ocamlformat-ignore) file specifies a filename relative to \
the directory containing the $(b,.ocamlformat-ignore) file. \
Shell-style regular expressions are supported. Lines starting with \
$(b,#) are ignored and can be used as comments.";`P"If the $(b,disable) option is set, an $(b,.ocamlformat-enable) \
file specifies files that OCamlFormat should format even when the \
$(b,disable) option is set. Each line in an \
$(b,.ocamlformat-enable) file specifies a filename relative to the \
directory containing the $(b,.ocamlformat-enable) file. \
Shell-style regular expressions are supported. Lines starting with \
$(b,#) are ignored and can be used as comments.";`S(Decl.section_nameDecl.Operational`Valid);`P"Unless mentioned otherwise non-formatting options cannot be set in \
attributes or $(b,.ocamlformat) files."]inCmd.info"ocamlformat"~version:Version.current~doc~manletkind=Decl.Operationalletdocs=Decl.section_namekind`Validletdeclare_option~setterm=Term.(constset$term)letenable_outside_detected_project=letwitness=String.concat~sep:" or "(List.mapFile_system.project_root_witness~f:(funname->Format.sprintf"$(b,%s)"name))inletdoc=Format.sprintf"Read $(b,.ocamlformat) config files outside the current project when \
no project root has been detected for the input file. The project \
root of an input file is taken to be the nearest ancestor directory \
that contains a %s file. If $(b,.ocamlformat) config files are \
located in the same directory or parents they are applied, if no \
$(b,.ocamlformat) file is found then the global configuration \
defined in $(b,\\$XDG_CONFIG_HOME/.ocamlformat) (or in \
$(b,\\$HOME/.config/.ocamlformat) if $(b,\\$XDG_CONFIG_HOME) is \
undefined) is applied."witnessinTerm.(const(funenable_outside_detected_projectconf->{confwithenable_outside_detected_project})$Arg.(value&flag&info["enable-outside-detected-project"]~doc~docs))letinplace=letdoc="Format in-place, overwriting input file(s)."indeclare_option~set:(funinplaceconf->{confwithinplace})Arg.(value&flag&info["i";"inplace"]~doc~docs)(* Other Flags *)letcheck=letdoc="Check whether the input files already are formatted. Mutually \
exclusive with --inplace and --output."indeclare_option~set:(funcheckconf->{confwithcheck})Arg.(value&flag&info["check"]~doc~docs)letinputs=letdocv="SRC"inletfile_or_dash=letparse,print=Arg.non_dir_fileinletprintfmt=function|Stdin->printfmt"<standard input>"|Filex->printfmtxinletparse=function|"-"->`OkStdin|s->(matchparseswith`Okx->`Ok(Filex)|`Errorx->`Errorx)in(parse,print)inletdoc="Input files. At least one is required, and exactly one without \
$(b,--inplace). If $(b,-) is passed, will read from stdin."inletdefault=[]indeclare_option~set:(funinputsconf->{confwithinputs})Arg.(value&pos_allfile_or_dashdefault&info[]~doc~docv~docs)letkind=letdoc="Parse input as an implementation."inletimpl=(SomeSyntax.Use_file,Arg.info["impl"]~doc~docs)inletdoc="Parse input as an interface."inletintf=(SomeSyntax.Signature,Arg.info["intf"]~doc~docs)inletdoc="Deprecated. Same as $(b,impl)."inletuse_file=(SomeSyntax.Use_file,Arg.info["use-file"]~doc~docs)inletdoc="Parse input as toplevel phrases with their output."inletrepl_file=(SomeSyntax.Repl_file,Arg.info["repl-file"]~doc~docs)inletdoc="Parse input as an odoc documentation."inletdoc_file=(SomeSyntax.Documentation,Arg.info["doc"]~doc~docs)inletdefault=Noneindeclare_option~set:(funkindconf->{confwithkind})Arg.(value&vflagdefault[impl;intf;use_file;repl_file;doc_file])letname=letdocv="NAME"inletdoc="Name of input file for use in error reporting and starting point when \
searching for '.ocamlformat' files. Defaults to the input file name. \
Some options can be specified in configuration files named \
'.ocamlformat' in the same or a parent directory of $(docv), see \
documentation of other options for details."inletdefault=Noneindeclare_option~set:(funnameconf->{confwithname})Arg.(value&opt(somestring)default&info["name"]~doc~docs~docv)letoutput=letdocv="DST"inletdoc="Output file. Mutually exclusive with --inplace. Write to stdout if \
omitted."inletdefault=Noneindeclare_option~set:(funoutputconf->{confwithoutput})Arg.(value&opt(somestring)default&info["o";"output"]~doc~docs~docv)letprint_config=letdoc="Print the configuration determined by the environment variable, the \
configuration files, preset profiles and command line. Attributes are \
not considered. If many input files are specified, only print the \
configuration for the first file. If no input file is specified, print \
the configuration for the root directory if specified, or for the \
current working directory otherwise."indeclare_option~set:(funprint_configconf->{confwithprint_config})Arg.(value&flag&info["print-config"]~doc~docs)letroot=letdocv="DIR"inletdoc="Root of the project. If specified, only take into account .ocamlformat \
configuration files inside $(docv) and its subdirectories."inletdefault=Noneindeclare_option~set:(funrootconf->{confwithroot})Arg.(value&opt(somedir)default&info["root"]~doc~docs~docv)letconfig=letdoc="Aggregate options. Options are specified as a comma-separated list of \
pairs: \
$(i,option)$(b,=)$(i,VAL)$(b,,)...$(b,,)$(i,option)$(b,=)$(i,VAL)."inletenv=Cmd.Env.info"OCAMLFORMAT"inletdefault=[]inletassoc=Arg.(pair~sep:'='stringstring)inletlist_assoc=Arg.(list~sep:','assoc)indeclare_option~set:(funconfigconf->{confwithconfig})Arg.(value&optlist_assocdefault&info["c";"config"]~doc~docs~env)letdisable_conf_files=letdoc="Disable .ocamlformat configuration files."indeclare_option~set:(fundisable_conf_filesconf->{confwithdisable_conf_files})Arg.(value&flag&info["disable-conf-files"]~doc~docs)letignore_invalid_options=letdoc="Ignore invalid options (e.g. in .ocamlformat)."inTerm.(const(funignore_invalid_optionsconf->{confwithignore_invalid_options})$Arg.(value&flag&info["ignore-invalid-option"]~doc~docs))letocp_indent_options_doc=letaliasocp_indentocamlformat=Printf.sprintf"$(b,%s) is an alias for $(b,%s)."ocp_indentocamlformatinletmulti_aliasocp_indentl_ocamlformat=Format.asprintf"$(b,%s) sets %a."ocp_indent(Format.pp_print_list~pp_sep:(funfs()->Format.fprintffs" and ")(funfsx->Format.fprintffs"$(b,%s)"x))l_ocamlformatin[alias"base""let-binding-indent";alias"type""type-decl-indent";alias"in""indent-after-in";multi_alias"with"["function-indent";"match-indent"];alias"match_clause""cases-exp-indent";alias"ppx_stritem_ext""stritem-extension-indent";alias"max_indent""max-indent";multi_alias"strict_with"["function-indent-nested";"match-indent-nested"]]letocp_indent_config=letdoc=letopenFormatinletsupported=matchocp_indent_options_docwith|[]->""|docs->asprintf" %a"(pp_print_list~pp_sep:(funfs()->fprintffs"@ ")(funfss->fprintffs"%s"s))docsinasprintf"Read .ocp-indent configuration files.%s"supportedindeclare_option~set:(funocp_indent_configconf->{confwithocp_indent_config})Arg.(value&flag&info["ocp-indent-config"]~doc~docs)letterms=[Term.(const(funlib_conf_modifconf->{confwithlib_conf=lib_conf_modifconf.lib_conf})$Conf.term);enable_outside_detected_project;inplace;check;kind;inputs;name;output;print_config;root;disable_conf_files;ignore_invalid_options;ocp_indent_config;config]letglobal_term=letcompose(t1:('a->'b)Term.t)(t2:('b->'c)Term.t):('a->'c)Term.t=letopenTerminconst(funf1f2a->f2(f1a))$t1$t2inletterm=List.fold_left~init:(Term.const(funx->x))~f:composetermsintermletset_global_term=declare_option~set:(funconf_modif->global_conf:=conf_modifdefault)global_term(** Do not escape from [build_config] *)exceptionConf_errorofstringletfailwith_user_errors~fromerrors=letopenFormatinletpp_errorppe=pp_print_stringpp(Error.to_stringe)inletpp_errors=pp_print_list~pp_sep:pp_print_newlinepp_errorinletmsg=asprintf"Error while parsing %s:@ %a"frompp_errorserrorsinraise(Conf_errormsg)letupdate_from_ocp_indentcloc(oic:IndentConfig.t)=letconvert_threechoices=function|IndentConfig.Always->`Always|Never->`Never|Auto->`Autoinleteltv=Conf.Elt.makev(`Updated(`Parsed(`Fileloc),None))in{cwithfmt_opts={c.fmt_optswithlet_binding_indent=eltoic.i_base;type_decl_indent=eltoic.i_type;indent_after_in=eltoic.i_in;function_indent=eltoic.i_with;match_indent=eltoic.i_with;cases_exp_indent=eltoic.i_match_clause;stritem_extension_indent=eltoic.i_ppx_stritem_ext;max_indent=eltoic.i_max_indent;function_indent_nested=elt@@convert_threechoicesoic.i_strict_with;match_indent_nested=elt@@convert_threechoicesoic.i_strict_with}}letread_config_file?version_check?disable_conf_attrsconf=function|File_system.Ocp_indentfile->(letfilename=Fpath.to_stringfileintryletocp_indent_conf=IndentConfig.defaultinIn_channel.with_filefilename~f:(funic->letlines=In_channel.input_linesic|>Migrate_ast.Location.of_lines~filenameinlet_ocp_indent_conf,conf,errors=List.fold_leftlines~init:(ocp_indent_conf,conf,[])~f:(fun(ocp_indent_conf,conf,errors){txt=line;loc}->tryletocp_indent_conf=IndentConfig.update_from_stringocp_indent_conflineinletconf=update_from_ocp_indentconflococp_indent_confin(ocp_indent_conf,conf,errors)with|Invalid_argumentewhen!global_conf.ignore_invalid_options->warn~loc"%s"e;(ocp_indent_conf,conf,errors)|Invalid_argumente->(ocp_indent_conf,conf,Error.Unknown(e,None)::errors))inmatchList.reverrorswith|[]->conf|l->failwith_user_errors~from:filenamel)withSys_error_->conf)|File_system.Ocamlformatfile->(letfilename=Fpath.to_stringfileintryIn_channel.with_filefilename~f:(funic->letlines=In_channel.input_linesic|>Migrate_ast.Location.of_lines~filenameinletc,errors=List.fold_leftlines~init:(conf,[])~f:(fun(conf,errors){txt=line;loc}->letfrom=`Filelocinmatchparse_line?version_check?disable_conf_attrsconf~fromlinewith|Okconf->(conf,errors)|Error_when!global_conf.ignore_invalid_options->warn~loc"ignoring invalid options %S"line;(conf,errors)|Errore->(conf,e::errors))inmatchList.reverrorswith|[]->c|l->failwith_user_errors~from:filenamel)withSys_error_->conf)letis_in_listing_file~listings~filename=letdrop_linel=String.is_emptyl||String.is_prefixl~prefix:"#"in(* process deeper files first *)letlistings=List.revlistingsinList.find_maplistings~f:(funlisting_file->letdir,_=Fpath.split_baselisting_fileinletlisting_filename=Fpath.to_stringlisting_fileintryIn_channel.with_filelisting_filename~f:(funch->letlines=In_channel.input_linesch|>Migrate_ast.Location.of_lines~filename:listing_filename|>List.filter~f:(funLocation.{txt=l;_}->not(drop_linel))inList.find_maplines~f:(fun{txt=line;loc}->matchFpath.of_stringlinewith|Okfile_on_current_line->(letf=Fpath.(dir//file_on_current_line)inifFpath.equalfilenamefthenSomelocelsetryletfilename=Fpath.to_stringfilenameinletre=letpathname=trueandanchored=trueinletf=Fpath.to_stringfinletf=ifSys.win32then(* Use only forward slashes in the pattern as
these match both forward and backward
slashes in ocaml-re when using the
[match_backslashes] flag. *)String.concat~sep:"/"(String.split_on_charsf~on:['\\'])elsefinRe.(Glob.glob~pathname~anchored~match_backslashes:Sys.win32f|>compile)inOption.some_if(Re.execprefilename)locwithRe.Glob.Parse_error->warn~loc"pattern %s cannot be parsed."line;None)|Error(`Msgmsg)->warn~loc"%s."msg;None))withSys_errorerr->letloc=Location.in_filelisting_filenameinwarn~loc"%s. Ignoring file."err;None)letupdate_using_envconf=letf(config,errors)(name,value)=matchDecl.updateConf.options~config~from:`Env~name~value~inline:falsewith|Okc->(c,errors)|Errore->(config,e::errors)inletconf,errors=List.fold_left!global_conf.config~init:(conf,[])~finmatchList.reverrorswith|[]->conf|l->failwith_user_errors~from:"OCAMLFORMAT environment variable"lletdiscard_formatter=Format.(formatter_of_out_functions{out_string=(fun___->());out_flush=(fun()->());out_newline=(fun()->());out_spaces=(fun_->());out_indent=(fun_->())})letglobal_lib_term=Term.(const(funconf_modiflib_conf->letnew_global=conf_modif{!global_confwithlib_conf}inglobal_conf:=new_global;new_global.lib_conf)$global_term)letupdate_using_cmdlineinfoconfig=matchCmd.eval_value~err:discard_formatter~help:discard_formatter(Cmd.vinfoglobal_lib_term)with|Ok(`Okconf_modif)->conf_modifconfig|Error_|Ok(`Version|`Help)->configletbuild_config~enable_outside_detected_project~root~file~is_stdin=letvfile=Fpath.vfileinletfile_abs=Fpath.(vfile|>to_absolute|>normalize)inletfs=File_system.make~enable_outside_detected_project~disable_conf_files:!global_conf.disable_conf_files~ocp_indent_config:!global_conf.ocp_indent_config~root~file:file_absin(* [version-check] can be modified by cmdline (evaluated last) but could
lead to errors when parsing the .ocamlformat files (evaluated first).
Similarly, [disable-conf-attrs] could lead to incorrect config. *)letforward_conf=letread_config_file=read_config_file~version_check:false~disable_conf_attrs:falseinList.foldfs.configuration_files~init:Conf.default~f:read_config_file|>update_using_env|>update_using_cmdlineinfoinletconf=letopr_opts={Conf.default.opr_optswithversion_check=forward_conf.opr_opts.version_check;disable_conf_attrs=forward_conf.opr_opts.disable_conf_attrs}in{Conf.defaultwithopr_opts}inletconf=List.foldfs.configuration_files~init:conf~f:read_config_file|>update_using_env|>update_using_cmdlineinfoinif(notis_stdin)&&(not(File_system.has_ocamlformat_filefs))&¬enable_outside_detected_projectthen((letwhy=matchfs.project_rootwith|Someroot->Format.sprintf"no [.ocamlformat] was found within the project (root: %s)"(Fpath.to_string~relativize:trueroot)|None->"no project root was found"inwarn~loc:(Location.in_filefile)"Ocamlformat disabled because [--enable-outside-detected-project] is \
not set and %s"why);Operational.updateconf~f:(funf->{fwithdisable={f.disablewithv=true}}))elseletlistings=ifconf.opr_opts.disable.vthenfs.enable_fileselsefs.ignore_filesinmatchis_in_listing_file~listings~filename:file_abswith|Someloc->letstatus=ifconf.opr_opts.disable.vthen"enabled"else"ignored"inifconf.opr_opts.debug.vthenwarn~loc"%a is %s."Fpath.ppfile_absstatus;Operational.updateconf~f:(funf->{fwithdisable={f.disablewithv=notf.disable.v}})|None->confletbuild_config~enable_outside_detected_project~root~file~is_stdin=tryletconf,warn_now=collect_warnings(fun()->build_config~enable_outside_detected_project~root~file~is_stdin)inifnotconf.opr_opts.quiet.vthenwarn_now();OkconfwithConf_errormsg->Errormsgtypeinput={kind:Syntax.t;name:string;file:file;conf:Conf.t}typeaction=|In_outofinput*stringoption|Inplaceofinputlist|Checkofinputlist|Print_configofConf.tletmake_action~enable_outside_detected_project~rootactioninputs=letmake_file?namekindfile=letname=Option.value~default:filenameinlet+conf=build_config~enable_outside_detected_project~root~file:name~is_stdin:falseinOk{kind;name;file=Filefile;conf}inletmake_stdin?(name="<standard input>")kind=let+conf=build_config~enable_outside_detected_project~root~file:name~is_stdin:falseinOk{kind;name;file=Stdin;conf}inletmake_input=function|`Single_file(kind,name,f)->make_file?namekindf|`Stdin(name,kind)->make_stdin?namekindinletmake_inputs=function|(`Single_file_|`Stdin_)asinp->let+inp=make_inputinpinOk[inp]|`Several_filesfiles->let+inputs=List.fold_leftfiles~init:(Ok[])~f:(funacc(kind,file)->let+acc=accinlet+file=make_filekindfileinOk(file::acc))inOk(List.revinputs)inmatch(action,inputs)with|`Print_config,inputs->letfile,is_stdin=matchinputswith|`Stdin_->("-",true)|`Single_file(_,_,f)->(f,false)|`Several_files((_,f)::_)->(f,false)|`Several_files[]|`No_input->(File_system.root_ocamlformat_file~root|>Fpath.to_string,true)inlet+conf=build_config~enable_outside_detected_project~root~file~is_stdininOk(Print_configconf)|(`No_action|`Output_|`Inplace|`Check),`No_input->Error"Must specify at least one input file, or `-` for stdin"|(`No_action|`Output_),`Several_files_->Error"Must specify exactly one input file without --inplace or --check"|`Inplace,`Stdin_->Error"Cannot specify stdin together with --inplace"|`No_action,((`Single_file_|`Stdin_)asinp)->let+inp=make_inputinpinOk(In_out(inp,None))|`Outputoutput,((`Single_file_|`Stdin_)asinp)->let+inp=make_inputinpinOk(In_out(inp,Someoutput))|`Inplace,((`Single_file_|`Several_files_)asinputs)->let+inputs=make_inputsinputsinOk(Inplaceinputs)|`Check,((`Single_file_|`Several_files_|`Stdin_)asinputs)->let+inputs=make_inputsinputsinOk(Checkinputs)letvalidate_inputs()=match(!global_conf.inputs,!global_conf.kind,!global_conf.name)with|[],_,_->Ok`No_input|[Stdin],None,None->Error"Must specify at least one of --name, --impl or --intf when reading \
from stdin"|[Stdin],Somekind,name->Ok(`Stdin(name,kind))|[Stdin],None,Somename->(matchSyntax.of_fnamenamewith|Somekind->Ok(`Stdin(Somename,kind))|None->Error"Cannot deduce file kind from passed --name. Please specify \
--impl or --intf")|[Filef],Somekind,name->Ok(`Single_file(kind,name,f))|[Filef],None,name->letkind=Option.value~default:fname|>Syntax.of_fname|>Option.value~default:Syntax.Use_fileinOk(`Single_file(kind,name,f))|_::_::_,Some_,_->Error"Cannot specify --impl or --intf with multiple inputs"|_::_::_,_,Some_->Error"Cannot specify --name with multiple inputs"|(_::_::_asinputs),None,None->List.mapinputs~f:(function|Stdin->Error"Cannot specify stdin together with other inputs"|Filef->letkind=Option.value~default:Use_file(Syntax.of_fnamef)inOk(kind,f))|>Result.all|>Result.map~f:(funfiles->`Several_filesfiles)letvalidate_action()=matchList.filter_map~f:(funs->s)[Option.map~f:(funo->(`Outputo,"--output"))!global_conf.output;Option.some_if!global_conf.inplace(`Inplace,"--inplace");Option.some_if!global_conf.check(`Check,"--check");Option.some_if!global_conf.print_config(`Print_config,"--print-config")]with|[]->Ok`No_action|[(action,_)]->Okaction|(_,a1)::(_,a2)::_->Error(Printf.sprintf"Cannot specify %s with %s"a1a2)letvalidate()=letroot=Option.map!global_conf.root~f:Fpath.(funx->vx|>to_absolute|>normalize)inletenable_outside_detected_project=!global_conf.enable_outside_detected_project&&Option.is_nonerootinmatchlet+action=validate_action()inlet+inputs=validate_inputs()inmake_action~enable_outside_detected_project~rootactioninputswith|Errore->`Error(false,e)|Okaction->`Okactionletaction()=Cmd.eval_value(Cmd.vinfoTerm.(ret(constvalidate$set_global_term)))