Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file helpers_async.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287open!Coreopen!Asyncopen!Expect_test_helpers_coreopen!Expect_test_helpers_asyncincludeHelpers_async_intfletpopulatepaths=Deferred.List.iter~how:`Sequentialpaths~f:(funpath->ifString.is_suffixpath~suffix:"/"thenrun"mkdir"["-p";path]else(let%bind()=run"mkdir"["-p";Filename.dirnamepath]inrun"touch"[path]));;letcompare_by_number_of_slashesab=Comparable.liftInt.compare~f:(String.count~f:(Char.equal'/'))ab;;letapply_if_changedfx=lety=fxinifString.equalxythenNoneelseSomey;;letreplace_path_prefixstring~pattern~with_=matchString.chop_prefixstring~prefix:patternwith|None->string|Somesuffix->ifString.is_emptysuffix||String.is_prefixsuffix~prefix:"/"thenwith_^suffixelsestring;;lethome_to_tildestring=replace_path_prefixstring~pattern:"/home"~with_:"~"lettilde_to_homestring=replace_path_prefixstring~pattern:"~"~with_:"/home"letescapestring=(* Brute force conversion of [Sys.quote]-style escaping to backslash escaping. *)String.concat_mapstring~f:(funchar->letraw=String.of_charcharinifString.equalraw(Sys.quoteraw)thenrawelse"\\"^raw);;letunescapestring=matchList.fold(String.to_liststring)~init:(`Normal,[])~f:(fun(mode,acc)char->matchmode,charwith|`Normal,'\\'->`Backslash,acc|`Normal,char->`Normal,char::acc|`Backslash,char->`Normal,char::acc)with|`Normal,acc->Ok(String.of_char_list(List.revacc))|`Backslash,_->error_s[%sexp"unterminated backslash escape"];;letwith_double_slash_examplepaths=List.concat[paths;paths|>List.max_elt~compare:compare_by_number_of_slashes|>Option.map~f:(String.substr_replace_all~pattern:"/"~with_:"//")|>Option.to_list];;letwith_absolute_examplespaths=List.concat[paths;List.mappaths~f:(sprintf"/%s")];;letwith_home_examplespaths=List.concat[paths;List.filter_mappaths~f:(apply_if_changedhome_to_tilde)];;letevery_prefixstring=List.init(String.lengthstring+1)~f:(funlength->String.prefixstringlength);;letwith_all_prefixesstrings=strings|>List.concat_map~f:every_prefix|>List.dedup_and_sort~compare:String.compare;;letwith_escapedstrings=List.concat[List.mapstrings~f:escape;List.findstrings~f:(fununescaped->letescaped=escapeunescapedinnot(String.equalescapedunescaped))|>Option.to_list];;letargs_of_pathspaths=paths|>with_double_slash_example|>with_escaped|>with_absolute_examples|>with_home_examples|>with_all_prefixes;;(* This is a messy and inefficient way to capture the stdout output of a command. It is
currently handy because [Command_test_helpers] only tests completion by printing. It
would be handier to just get a string list as output. *)letoutput_of~expect_outputf=letprior_output=expect_output()inletresult=Or_error.try_withfinletpending_output=expect_output()inprint_stringprior_output;matchresultwith|Ok()->pending_output|Errorerror->error|>Error.tag_s~tag:[%sexp{pending_output:string}]|>Error.raise;;letcompletionsparamarg~expect_output=(* [Command_test_helpers.complete] prints each completion on a line, followed by a final
line containing the exit status of the simulated command. *)output_of~expect_output(fun()->Command_test_helpers.completeparam~args:[arg])|>String.split_lines|>List.drop_last_exn;;letwith_env~key~dataf=letoriginal=Unix.getenvkeyinUnix.putenv~key~data;Exn.protect~f~finally:(fun()->matchoriginalwith|None->Unix.unsetenvkey|Somestring->Unix.putenv~key~data:string);;letrecremove_duplicate_slashesstring=ifString.is_substringstring~substring:"//"thenremove_duplicate_slashes(String.substr_replace_allstring~pattern:"//"~with_:"/")elsestring;;letvalidate(typea)(modulePath:Pathwithtypet=a)string=Or_error.try_with(fun()->ignore(Path.of_stringstring:Path.t));;moduleBash_action=structtypet=|Empty|Chooseofstringlist|Extendofstring|Finishofstring[@@derivingequal](* For readability, we uniformly quote printed inputs and outputs of completion using
[sprintf "%S"]. *)letto_string_hum=function|Empty->"Empty"|Choosestrings->strings|>List.map~f:(sprintf"%S")|>String.concat~sep:", "|>sprintf"Choose: %s"|Extendstring->sprintf"Extend: %S"string|Finishstring->sprintf"Finish: %S"string;;(* Simulates bash's heuristics after running a custom completion script. *)letof_completion~argcompletion=matchcompletionwith|[]->Empty|[single]->Finish(single:string)|multiple->letprefix=String.common_prefixmultipleinifString.is_emptyprefix||String.equalprefixargthenChoose(multiple:stringlist)elseExtend(prefix:string);;letcheck_no_slash_except_trailing_slashnames=require[%here](List.for_allnames~f:(funname->not(String.mem(String.chop_suffix_if_existsname~suffix:"/")'/')))~if_false_then_print_s:(lazy[%sexp"menu item contains non-trailing slash"]);;letcheck_completion_extends_input~escaped_inputs~output=List.findescaped_inputs~f:(funescaped_input->matchunescapeescaped_inputwith|Error_->false|Okinput->not(String.is_prefixoutput~prefix:(remove_duplicate_slashesinput)))|>Option.iter~f:(funinput->print_cr[%here][%sexp"completion changed the input",(input:string)]);;letcheckpath_maction~args=matchactionwith|Empty->()|Choosenames->(matchnames|>List.map~f:unescape|>Or_error.combine_errorswith|Errorerror->print_cr[%here][%sexp"invalid escape or quotation",(error:Error.t)]|Okunescaped_names->check_no_slash_except_trailing_slashunescaped_names)|Extendstring|Finishstring->(matchunescapestringwith|Errorerror->print_cr[%here][%sexp"invalid escape or quotation",(error:Error.t)]|Okunescaped->(matchvalidatepath_m(tilde_to_homeunescaped)with|Errorerror->print_cr[%here][%sexp"invalid completion",(error:Error.t)]|Ok()->check_completion_extends_input~escaped_inputs:args~output:unescaped));;endletshould_print_actionaction=not(Bash_action.equalactionEmpty)letshould_print_stringpath_mstring=String.is_emptystring||Result.is_ok(validatepath_m(tilde_to_homestring));;letshould_printpath_mstringsaction=should_print_actionaction||List.existsstrings~f:(should_print_stringpath_m);;letcomplete_argparamarg~tmp~expect_output=with_env~key:"ROOT_FOR_FILE_PATH_TESTING"~data:tmp(fun()->with_env~key:"HOME"~data:"/home"(fun()->arg|>completionsparam~expect_output|>Bash_action.of_completion~arg));;(* any list six or longer, show first two + ellipsis + last two *)letwith_ellipsislist~ellipsis=matchlist,List.revlistwith|(head1::head2::_head3::_head4::_head5::_head6::_,tail1::tail2::_tail3::_tail4::_tail5::_tail6::_)->[head1;head2;ellipsis;tail2;tail1]|_->list;;letcomplete_pathspath_mparampaths~tmp~expect_output=paths|>args_of_paths|>List.map~f:(funarg->complete_argparamarg~tmp~expect_output,arg)|>List.Assoc.group~equal:Bash_action.equal|>List.iter~f:(fun(action,args)->ifshould_printpath_margsactionthen(print_newline();args|>List.map~f:(sprintf"%S")(* quote as in [Bash_action.to_string_hum] *)|>with_ellipsis~ellipsis:"..."|>List.iter~f:print_endline;print_endline(Bash_action.to_string_humaction));Bash_action.checkpath_maction~args);;letpaths=[".fe/";"app/tool/jbuild";"app/tool/tool.ml";"app/tool/tool.mli";"app/tool/tool_intf.ml";"lib/code/jbuild";"lib/code/code.ml";"lib/code/code.mli";"lib/code/code_intf.ml";"libmap.sexp";"home/file";"home/dir/";"home/s\\ash/";"home/qu\"te/";"home/sp ce/";"home/t'ck/"];;lettest_arg_type(typea)(modulePath:Pathwithtypet=a)~expect_output=within_temp_dir(fun()->let%bindtmp=Sys.getcwd()inlet%bind()=populatepathsinletparam=letopenCommand.Paraminanon("PATH"%:Path.arg_type)incomplete_paths(modulePath)parampaths~tmp~expect_output;return());;