123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)(* Tezos Command line interface - Local Storage for Configuration *)letrectry_alternativesinput=function|[]->failwith"Could not parse input."|(_,f)::alts->either_f(finput)(fun()->try_alternativesinputalts)letparse_alternativesaltsinput=matchString.split~limit:1':'inputwith|[_]->try_alternativesinputalts|[format;value]->(matchList.assoc_opt~equal:String.equalformataltswith|Somef->fvalue|None->try_alternativesinputalts)|_->assertfalse(* cannot happen due to String.split's implementation. *)moduletypeEntity=sigtypetvalencoding:tData_encoding.tvalof_source:string->ttzresultLwt.tvalto_source:t->stringtzresultLwt.tvalname:stringincludeCompare.Swithtypet:=tendmoduletypeAlias=sigtypettypefresh_paramvalencoding:tData_encoding.tvalload:#Client_context.wallet->(string*t)listtzresultLwt.tvalset:#Client_context.wallet->(string*t)list->unittzresultLwt.tvalfind:#Client_context.wallet->string->ttzresultLwt.tvalfind_opt:#Client_context.wallet->string->toptiontzresultLwt.tvalrev_find:#Client_context.wallet->t->stringoptiontzresultLwt.tvalrev_find_all:#Client_context.wallet->t->stringlisttzresultLwt.tvalname:#Client_context.wallet->t->stringtzresultLwt.tvalmem:#Client_context.wallet->string->booltzresultLwt.tvaladd:force:bool->#Client_context.wallet->string->t->unittzresultLwt.tvaladd_many:#Client_context.wallet->(string*t)list->unittzresultLwt.tvaldel:#Client_context.wallet->string->unittzresultLwt.tvalupdate:#Client_context.wallet->string->t->unittzresultLwt.tvalof_source:string->ttzresultLwt.tvalto_source:t->stringtzresultLwt.tvalalias_parameter:unit->(string*t,#Client_context.wallet)Tezos_clic.parametervalalias_param:?name:string->?desc:string->('a,(#Client_context.walletas'b))Tezos_clic.params->(string*t->'a,'b)Tezos_clic.paramsvalaliases_param:?name:string->?desc:string->('a,(#Client_context.walletas'b))Tezos_clic.params->((string*t)list->'a,'b)Tezos_clic.paramsvalfresh_alias_param:?name:string->?desc:string->('a,(<..>as'obj))Tezos_clic.params->(fresh_param->'a,'obj)Tezos_clic.paramsvalforce_switch:unit->(bool,_)Tezos_clic.argvalof_fresh:#Client_context.wallet->bool->fresh_param->stringtzresultLwt.tvalparse_source_string:#Client_context.wallet->string->ttzresultLwt.tvalsource_param:?name:string->?desc:string->('a,(#Client_context.walletas'obj))Tezos_clic.params->(t->'a,'obj)Tezos_clic.paramsvalsource_arg:?long:string->?placeholder:string->?doc:string->unit->(toption,(#Client_context.walletas'obj))Tezos_clic.argvalautocomplete:#Client_context.wallet->stringlisttzresultLwt.tendmoduleAlias(Entity:Entity)=structopenClient_contextmoduleMap=Map.Make(String)letwallet_encoding:(string*Entity.t)listData_encoding.encoding=letopenData_encodinginlist(obj2(req"name"string)(req"value"Entity.encoding))typecache={mutablemtime:floatoption;(** [None] if the associated file does not exist; otherwise is the last
modification time of the associated file. *)mutablelist_assoc:(string*Entity.t)list;mutablemap:Entity.tMap.t;}(** Bindings of wallet to cache. The base directory of wallets are used as
keys. *)typecaches=(string*cache)listrefletcaches:caches=ref[](** [peek_cache wallet] returns {Some v} if the binding of [wallet] in the
cache is {v}, or {None} if no binding for [wallet] exists. *)letpeek_cache(wallet:#wallet)=List.assoc_opt~equal:String.equalwallet#get_base_dir!caches(** [update_assoc key value list] returns a list containing the same bindings
as [list], except for the bindings of [key]. If [value] is {None}, the
bindings are removed if it exists; otherwise, if [value] is {Some v} then
the bindings of [key] are replaced by one binding of [key] to {v} in the
resulting list. *)letupdate_assockeyvaluelist=letremovekey=List.filter(fun(n,_)->not(String.equalnkey))inmatchvaluewith|Somevalue->(key,value)::removekeylist|None->removekeylist(** [replace_cache wallet ?mtime list_assoc] replaces the cache bind to
[wallet] by a new cache {cache}. If [mtime] is {Some mt}, then
{cache.mtime = mt}; otherwise, {cache.mtime} is generated by
[wallet#last_modification_time]. *)letreplace_cache(wallet:#wallet)?mtimelist_assoc=letopenLwt_result_syntaxinlet*mtime=matchmtimewith|None->wallet#last_modification_timeEntity.name|Somemtime->returnmtimeinletmap=Map.of_seq(List.to_seqlist_assoc)inletcache={mtime;list_assoc;map}incaches:=update_assocwallet#get_base_dir(Somecache)!caches;returncache(** [get_cache wallet] reloads the cache bind to [wallet] if the associated
file does not exist or if its last modification time changed; then
returns it. *)letget_cache(wallet:#wallet)=letopenLwt_result_syntaxinlet*mtime=wallet#last_modification_timeEntity.nameinletcache=peek_cachewalletinmatch(mtime,cache)with|Somefresh_mtime,Some{mtime=Somecache_mtime;_}whenfresh_mtime=cache_mtime->return(WithExceptions.Option.get~loc:__LOC__cache)|_->let*list_assoc=wallet#loadEntity.name~default:([]:(string*Entity.t)list)wallet_encodinginreplace_cachewallet~mtimelist_assoc(** [update_cache wallet cache key value] updates the cache bind to
[wallet] and the associated file with a cache containing the same
bindings as [cache], except for the bindings of [key]. If [value] is
{None}, the bindings are removed if it exists; otherwise, if [value] is
{Some v}, then the bindings of [key] are replaced by one binding of [key]
to {v} in the resulting cache. *)letupdate_cache(wallet:#wallet)cachekeyvalue=letopenLwt_result_syntaxin(matchvaluewith|Somevalue->cache.list_assoc<-update_assockey(Somevalue)cache.list_assoc;cache.map<-Map.addkeyvaluecache.map|None->cache.list_assoc<-update_assockeyNonecache.list_assoc;cache.map<-Map.removekeycache.map);let*()=wallet#writeEntity.namecache.list_assocwallet_encodinginlet*mtime=wallet#last_modification_timeEntity.nameincache.mtime<-mtime;return_unitletload(wallet:#wallet)=letopenLwt_result_syntaxinlet*cache=get_cachewalletinreturncache.list_assocletload_map(wallet:#wallet)=letopenLwt_result_syntaxinlet*cache=get_cachewalletinreturncache.mapletset(wallet:#wallet)entries=letopenLwt_result_syntaxinlet*()=wallet#writeEntity.nameentrieswallet_encodinginlet*_cache=replace_cachewalletentriesinreturn_unitletautocompletewallet=letopenLwt_syntaxinlet*r=loadwalletinmatchrwith|Error_->return_ok_nil|Oklist->return_ok(List.mapfstlist)letfind_opt(wallet:#wallet)name=letopenLwt_result_syntaxinlet+map=load_mapwalletinMap.findnamemapletfind(wallet:#wallet)name=letopenLwt_result_syntaxinlet*map=load_mapwalletinmatchMap.findnamemapwith|Somev->returnv|None->failwith"no %s alias named %s"Entity.namenameletrev_find(wallet:#wallet)v=letopenLwt_result_syntaxinlet+list=loadwalletinOption.mapfst@@List.find(fun(_,v')->Entity.(v=v'))listletrev_find_all(wallet:#wallet)v=letopenLwt_result_syntaxinlet*list=loadwalletinreturn(List.filter_map(fun(n,v')->ifEntity.(v=v')thenSomenelseNone)list)letmem(wallet:#wallet)name=letopenLwt_result_syntaxinlet+map=load_mapwalletinMap.memnamemapletadd~force(wallet:#wallet)namevalue=letopenLwt_result_syntaxinletkeep=reffalseinlet*cache=get_cachewalletinlet*()=ifforcethenreturn_unitelseList.iter_es(fun(n,v)->ifCompare.String.(n=name)&&Entity.(v=value)then(keep:=true;return_unit)elseifCompare.String.(n=name)&&Entity.(v<>value)thenfailwith"another %s is already aliased as %s, use --force to update"Entity.namenelseifCompare.String.(n<>name)&&Entity.(v=value)thenfailwith"this %s is already aliased as %s, use --force to insert \
duplicate"Entity.namenelsereturn_unit)cache.list_associnif!keepthenreturn_unitelseupdate_cachewalletcachename(Somevalue)letadd_many(wallet:#wallet)xs=letopenLwt_result_syntaxinlet*cache=get_cachewalletinletmap_to_add=Map.of_seq(List.to_seqxs)incache.map<-Map.union(fun_keyx_existing->Somex)map_to_addcache.map;cache.list_assoc<-List.of_seq(Map.to_seqcache.map);let*()=wallet#writeEntity.namecache.list_assocwallet_encodinginlet*mtime=wallet#last_modification_timeEntity.nameincache.mtime<-mtime;return_unitletdel(wallet:#wallet)name=letopenLwt_result_syntaxinlet*cache=get_cachewalletinupdate_cachewalletcachenameNoneletupdate(wallet:#wallet)namevalue=letopenLwt_result_syntaxinlet*cache=get_cachewalletinupdate_cachewalletcachename(Somevalue)includeEntityletalias_parameter()=letopenLwt_result_syntaxinTezos_clic.parameter~autocomplete(funcctxts->let*v=findcctxtsinreturn(s,v))letalias_param?(name="name")?(desc="existing "^Entity.name^" alias")next=Tezos_clic.param~name~desc(alias_parameter())nextletaliases_parameter()=letopenLwt_result_syntaxinTezos_clic.parameter~autocomplete(funcctxts->String.split_no_empty','s|>List.map_es(funs->let*pkh=findcctxtsinreturn(s,pkh)))letaliases_param?(name="name")?(desc="existing "^Entity.name^" aliases")next=Tezos_clic.param~name~desc(aliases_parameter())nexttypefresh_param=Freshofstringletof_fresh(wallet:#wallet)force(Freshs)=letopenLwt_result_syntaxinlet*list=loadwalletinlet*()=ifforcethenreturn_unitelseList.iter_es(fun(n,v)->ifString.equalnsthenlet*value=Entity.to_sourcevinfailwith"@[<v 2>The %s alias %s already exists.@,\
The current value is %s.@,\
Use --force to update@]"Entity.namenvalueelsereturn_unit)listinreturnsletfresh_alias_param?(name="new")?(desc="new "^Entity.name^" alias")next=Tezos_clic.param~name~desc(Tezos_clic.parameter(fun(_:<..>)s->Lwt.return_ok(Freshs)))nextletparse_source_stringcctxts=letopenLwt_result_syntaxinparse_alternatives[("alias",funalias->findcctxtalias);("file",funpath->let*input=cctxt#read_filepathinof_sourceinput);("text",of_source);]sletsource_param?(name="src")?(desc="source "^Entity.name)next=letdesc=Format.asprintf"%s\n\
Can be a %s name, a file or a raw %s literal. If the parameter is not \
the name of an existing %s, the client will look for a file \
containing a %s, and if it does not exist, the argument will be read \
as a raw %s.\n\
Use 'alias:<name>', 'file:<path>' or 'text:<literal>' to disable \
autodetect."descEntity.nameEntity.nameEntity.nameEntity.nameEntity.nameinTezos_clic.param~name~desc(Tezos_clic.parameterparse_source_string)nextletsource_arg?(long="source "^Entity.name)?(placeholder="src")?(doc="")()=letdoc=Format.asprintf"%s\n\
Can be a %s name, a file or a raw %s literal. If the parameter is not \
the name of an existing %s, the client will look for a file \
containing a %s, and if it does not exist, the argument will be read \
as a raw %s.\n\
Use 'alias:<name>', 'file:<path>' or 'text:<literal>' to disable \
autodetect."docEntity.nameEntity.nameEntity.nameEntity.nameEntity.nameinTezos_clic.arg~long~placeholder~doc(Tezos_clic.parameterparse_source_string)letforce_switch()=Tezos_clic.switch~long:"force"~short:'f'~doc:("overwrite existing "^Entity.name)()letname(wallet:#wallet)d=letopenLwt_result_syntaxinlet*o=rev_findwalletdinmatchowithNone->Entity.to_sourced|Somename->returnnameend