123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268(*---------------------------------------------------------------------------
Copyright (c) 2020 The b0 programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)openB0_std(* Keys *)moduleKey=structtypet=V:'atyped->tand'atyped={default:'aoption;doc:string;id:'aType.Id.t;name:string;pp_value:'aFmt.t;scope:B0_scope.t;untyped:t;}let[@inline]uidk=Type.Id.uidk.idletdefs=refString.Map.emptyletaddk=defs:=String.Map.addk.namek.untyped!defs(* Typed keys *)letkind="key"letmake?(doc="undocumented")?defaultname~pp_value=letid=Type.Id.make()inletname,scope=B0_scope.current_make_unique_qualified_name~defs:!defs~kindnameinletreck={default;doc;id;name;pp_value;scope;untyped}anduntyped=Vkinaddk;kletmake_tag?docname=make?docname~default:false~pp_value:Fmt.boolletnamek=k.nameletdefaultk=k.defaultletget_defaultk=Option.getk.defaultletdock=k.docletpp_valuek=k.pp_value(* Existential keys *)letequal(Vk0)(Vk1)=Int.equal(uidk0)(uidk1)letcompare(Vk0)(Vk1)=Int.compare(uidk0)(uidk1)letcompare_by_name(Vk0)(Vk1)=String.compare(namek0)(namek1)letpp_name_str=Fmt.st[`Fg`Yellow]letpp_nameppfk=pp_name_strppfk.nameletppppf(Vk)=pp_name_strppfk.name(* Lookup keys by name *)letfindn=matchString.Map.findn!defswith|exceptionNot_found->None|k->Somekletgetn=matchfindnwith|Somev->v|None->Fmt.invalid_arg"No meta key named %s"nletget_or_suggestn=matchfindnwith|Somev->Okv|None->letadd_suggkvacc=ifString.edit_distancekn<=2thenv::accelseaccinError(List.rev(String.Map.foldadd_sugg!defs[]))letget_or_hintn=matchget_or_suggestnwith|Ok_asv->v|Errorsuggs->letkind=Fmt.any"meta key"andhint=Fmt.did_you_meaninletpp=Fmt.unknown'~kindpp_name_str~hintinletname(Vk)=namekinFmt.error"@[%a@]"pp(n,List.mapnamesuggs)letfoldfacc=matchB0_scope.current_is_root()with|true->letadd_vacc=fvaccinString.Map.foldadd!defsacc|false->letprefix=B0_scope.current_scope_prefix()inletaddkvacc=ifString.starts_with~prefixkthenfvaccelseaccinString.Map.foldadd!defsaccletlist()=List.sortcompare_by_name(foldList.cons[])letget_list_or_hint~all_if_emptynames=ifall_if_empty&&names=[]thenOk(list())elseletrecloopvses=function|[]->ifes<>[]thenError(String.concat"\n"(List.reves))elseOk(List.revvs)|n::ns->matchget_or_hintnwith|Okv->loop(v::vs)esns|Errore->loopvs(e::es)nsinloop[][]namesendtype'akey='aKey.typedleterr_no_defaultk=Fmt.invalid_arg"Key %a has no default value"Key.pp_namekleterr_no_such_key_namek=Fmt.invalid_arg"Key %a not found in map"Key.pp_name_strkleterr_no_such_keyk=Fmt.invalid_arg"Key %a not found in map"Key.pp_namek(* Metadadta *)typebinding=B:'akey*'a->bindingmoduleM=Map.Make(Int)typet=bindingM.tletempty=M.empty(* Predicates *)letis_empty=M.is_emptyletmemkm=M.mem(Key.uidk)mlethas_tag:boolkey->t->bool=funkm->matchKey.defaultkwith|None->err_no_defaultk|Somedefault->matchM.find_opt(Key.uidk)mwith|None->default|Some(B(k',v))->matchType.Id.provably_equalk.Key.idk'.Key.idwith|SomeType.Equal->v|None->assertfalse(* Adding and removing *)letaddkvm=M.add(Key.uidk)(B(k,v))mlettagkm=addktruemletadd_somekom=matchowithNone->m|Somev->addkvmletadd_some_or_defaultkom=matchk.Key.defaultwith|None->err_no_defaultk|Somedefault->addk(matchowithNone->default|Somev->v)mletadd_if_undefkvm=letupdate=functionNone->Some(B(k,v))|Some_asb->binM.update(Key.uidk)updatemletoverridem~by=letoverride__by=SomebyinM.unionoverridembyletremovekm=M.remove(Key.uidk)m(* Lookup *)letfind:typea.akey->t->aoption=funkm->matchM.find_opt(Key.uidk)mwith|None->None|Some(B(k',v))->matchType.Id.provably_equalk.Key.idk'.Key.idwith|SomeType.Equal->Somev|None->assertfalseletfind_or_default:typea.akey->t->a=funkm->matchk.Key.defaultwith|None->err_no_defaultk|Somedefault->matchM.find_opt(Key.uidk)mwith|None->default|Some(B(k',v))->matchType.Id.provably_equalk.Key.idk'.Key.idwith|SomeType.Equal->v|None->assertfalseletgetkm=matchfindkmwith|Somev->v|None->err_no_such_keyk(* Bindings *)letfind_bindingkm=M.find_opt(Key.uidk)mletfind_binding_by_namenm=matchKey.findnwith|None->None|Some(Key.Vk)->M.find_opt(Key.uidk)mletget_bindingkm=matchfind_bindingkmwith|None->err_no_such_keyk|Somev->vletget_binding_by_namenm=matchfind_binding_by_namenmwith|None->err_no_such_key_namen|Somev->vletpp_bindingppf(B(k,v))=Fmt.fieldk.Key.nameFun.idk.Key.pp_valueppfv(* Traversing *)letfoldfmacc=M.fold(fun_bacc->fbacc)macc(* Formatting *)letppppfm=(* The circumvolution here is to print in key name order. *)letadd_binding_(B(k,v)asb)acc=String.Map.add(Key.namek)baccinletbindings=M.foldadd_bindingmString.Map.emptyin(Fmt.vbox@@Fmt.iter_bindingsString.Map.iter(Fmt.usingsndpp_binding))ppfbindingsletpp_non_emptyppfm=ifM.is_emptymthen()else(Fmt.cutppf();ppppfm)(* Standard keys *)letstring_list=Fmt.(list~sep:spstring)letstring_list_keyk~doc=Key.makek~doc~pp_value:string_listletstring_key?defaultk~doc=Key.makek?default~doc~pp_value:Fmt.string(* End-user information. *)let()=B0_scope.open_lib~module':__MODULE__"meta"letauthors=string_list_key"authors"~doc:"Author list"letdescription=string_key"description"~doc:"Description"letdescription_tags=string_list_key"descr-tags"~doc:"Description tags"lethomepage=string_key"homepage"~doc:"Homepage URI"letissues=string_key"issues"~doc:"Issue tracker URI"typespdxid=stringletlicenses=string_list_key"licenses"~doc:"License list (SPDX ids)"letmaintainers=string_list_key"maintainers"~doc:"Maintainer list"letonline_doc=string_key"online-doc"~doc:"Online documentation URI"letrepo=string_key"repo"~doc:"VCS source repository URI"letsynopsis=string_key"synopsis"~default:"Undocumented"~doc:"One line synopsis"(* Entity tags *)letbench=Key.make_tag"bench"~doc:"Benchmarking entity"letbuild=Key.make_tag"build"~doc:"A build system entity"letdeprecated=Key.make_tag"deprecated"~doc:"Deprecated entity"letdev=Key.make_tag"dev"~doc:"Development entity"letdoc=Key.make_tag"doc"~doc:"Documentation entity"letexe=Key.make_tag"exe"~doc:"Executable entity"lettest=Key.make_tag"test"~doc:"Testing entity"letlib=Key.make_tag"lib"~doc:"Library entity"letlong=Key.make_tag"long"~doc:"Entity is associated to a lengthy process"letpublic=Key.make_tag"public"~doc:"Public entity"letrun=Key.make_tag"run"~doc:"Entity should be part of a run in a given context."letsample=Key.make_tag"sample"~doc:"Demonstration entity."letwarning=string_key"warning"~doc:"A warning output when the entity is used"letexe_file=letdoc="Absolute file path to a built executable."inletpp_value=Fmt.any"<built value>"inKey.make"exe-file"~doc~pp_valuelettool_name=letdoc="Executable tool name without platform specific extension"inKey.make"tool-name"~doc~pp_value:Fmt.stringlet()=B0_scope.close()