package octez-shell-libs

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file client_aliases.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
(*****************************************************************************)
(*                                                                           *)
(* 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 *)

let rec try_alternatives input = function
  | [] -> failwith "Could not parse input."
  | (_, f) :: alts -> either_f (f input) (fun () -> try_alternatives input alts)

let parse_alternatives alts input =
  match String.split ~limit:1 ':' input with
  | [_] -> try_alternatives input alts
  | [format; value] -> (
      match List.assoc_opt ~equal:String.equal format alts with
      | Some f -> f value
      | None -> try_alternatives input alts)
  | _ -> assert false
(* cannot happen due to String.split's implementation. *)

module type Entity = sig
  type t

  val encoding : t Data_encoding.t

  val of_source : string -> t tzresult Lwt.t

  val to_source : t -> string tzresult Lwt.t

  val name : string

  include Compare.S with type t := t
end

module type Alias = sig
  type t

  type fresh_param

  val encoding : t Data_encoding.t

  val load : #Client_context.wallet -> (string * t) list tzresult Lwt.t

  val set : #Client_context.wallet -> (string * t) list -> unit tzresult Lwt.t

  val find : #Client_context.wallet -> string -> t tzresult Lwt.t

  val find_opt : #Client_context.wallet -> string -> t option tzresult Lwt.t

  val rev_find : #Client_context.wallet -> t -> string option tzresult Lwt.t

  val rev_find_all : #Client_context.wallet -> t -> string list tzresult Lwt.t

  val name : #Client_context.wallet -> t -> string tzresult Lwt.t

  val mem : #Client_context.wallet -> string -> bool tzresult Lwt.t

  val add :
    force:bool -> #Client_context.wallet -> string -> t -> unit tzresult Lwt.t

  val add_many :
    #Client_context.wallet -> (string * t) list -> unit tzresult Lwt.t

  val del : #Client_context.wallet -> string -> unit tzresult Lwt.t

  val update : #Client_context.wallet -> string -> t -> unit tzresult Lwt.t

  val of_source : string -> t tzresult Lwt.t

  val to_source : t -> string tzresult Lwt.t

  val alias_parameter :
    unit -> (string * t, #Client_context.wallet) Tezos_clic.parameter

  val alias_param :
    ?name:string ->
    ?desc:string ->
    ('a, (#Client_context.wallet as 'b)) Tezos_clic.params ->
    (string * t -> 'a, 'b) Tezos_clic.params

  val aliases_param :
    ?name:string ->
    ?desc:string ->
    ('a, (#Client_context.wallet as 'b)) Tezos_clic.params ->
    ((string * t) list -> 'a, 'b) Tezos_clic.params

  val fresh_alias_param :
    ?name:string ->
    ?desc:string ->
    ('a, (< .. > as 'obj)) Tezos_clic.params ->
    (fresh_param -> 'a, 'obj) Tezos_clic.params

  val force_switch : unit -> (bool, _) Tezos_clic.arg

  val of_fresh :
    #Client_context.wallet -> bool -> fresh_param -> string tzresult Lwt.t

  val parse_source_string : #Client_context.wallet -> string -> t tzresult Lwt.t

  val source_param :
    ?name:string ->
    ?desc:string ->
    ('a, (#Client_context.wallet as 'obj)) Tezos_clic.params ->
    (t -> 'a, 'obj) Tezos_clic.params

  val source_arg :
    ?long:string ->
    ?placeholder:string ->
    ?doc:string ->
    unit ->
    (t option, (#Client_context.wallet as 'obj)) Tezos_clic.arg

  val autocomplete : #Client_context.wallet -> string list tzresult Lwt.t
end

module Alias (Entity : Entity) = struct
  open Client_context
  module Map = Map.Make (String)

  let wallet_encoding : (string * Entity.t) list Data_encoding.encoding =
    let open Data_encoding in
    list (obj2 (req "name" string) (req "value" Entity.encoding))

  type cache = {
    mutable mtime : float option;
        (** [None] if the associated file does not exist; otherwise is the last
            modification time of the associated file. *)
    mutable list_assoc : (string * Entity.t) list;
    mutable map : Entity.t Map.t;
  }

  (** Bindings of wallet to cache. The base directory of wallets are used as
      keys. *)
  type caches = (string * cache) list ref

  let caches : 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. *)
  let peek_cache (wallet : #wallet) =
    List.assoc_opt ~equal:String.equal wallet#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. *)
  let update_assoc key value list =
    let remove key = List.filter (fun (n, _) -> not (String.equal n key)) in
    match value with
    | Some value -> (key, value) :: remove key list
    | None -> remove key list

  (** [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]. *)
  let replace_cache (wallet : #wallet) ?mtime list_assoc =
    let open Lwt_result_syntax in
    let* mtime =
      match mtime with
      | None -> wallet#last_modification_time Entity.name
      | Some mtime -> return mtime
    in
    let map = Map.of_seq (List.to_seq list_assoc) in

    let cache = {mtime; list_assoc; map} in
    caches := update_assoc wallet#get_base_dir (Some cache) !caches ;
    return cache

  (** [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. *)
  let get_cache (wallet : #wallet) =
    let open Lwt_result_syntax in
    let* mtime = wallet#last_modification_time Entity.name in
    let cache = peek_cache wallet in
    match (mtime, cache) with
    | Some fresh_mtime, Some {mtime = Some cache_mtime; _}
      when fresh_mtime = cache_mtime ->
        return (WithExceptions.Option.get ~loc:__LOC__ cache)
    | _ ->
        let* list_assoc =
          wallet#load
            Entity.name
            ~default:([] : (string * Entity.t) list)
            wallet_encoding
        in
        replace_cache wallet ~mtime list_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. *)
  let update_cache (wallet : #wallet) cache key value =
    let open Lwt_result_syntax in
    (match value with
    | Some value ->
        cache.list_assoc <- update_assoc key (Some value) cache.list_assoc ;
        cache.map <- Map.add key value cache.map
    | None ->
        cache.list_assoc <- update_assoc key None cache.list_assoc ;
        cache.map <- Map.remove key cache.map) ;
    let* () = wallet#write Entity.name cache.list_assoc wallet_encoding in
    let* mtime = wallet#last_modification_time Entity.name in
    cache.mtime <- mtime ;
    return_unit

  let load (wallet : #wallet) =
    let open Lwt_result_syntax in
    let* cache = get_cache wallet in
    return cache.list_assoc

  let load_map (wallet : #wallet) =
    let open Lwt_result_syntax in
    let* cache = get_cache wallet in
    return cache.map

  let set (wallet : #wallet) entries =
    let open Lwt_result_syntax in
    let* () = wallet#write Entity.name entries wallet_encoding in
    let* _cache = replace_cache wallet entries in
    return_unit

  let autocomplete wallet =
    let open Lwt_syntax in
    let* r = load wallet in
    match r with
    | Error _ -> return_ok_nil
    | Ok list -> return_ok (List.map fst list)

  let find_opt (wallet : #wallet) name =
    let open Lwt_result_syntax in
    let+ map = load_map wallet in
    Map.find name map

  let find (wallet : #wallet) name =
    let open Lwt_result_syntax in
    let* map = load_map wallet in
    match Map.find name map with
    | Some v -> return v
    | None -> failwith "no %s alias named %s" Entity.name name

  let rev_find (wallet : #wallet) v =
    let open Lwt_result_syntax in
    let+ list = load wallet in
    Option.map fst @@ List.find (fun (_, v') -> Entity.(v = v')) list

  let rev_find_all (wallet : #wallet) v =
    let open Lwt_result_syntax in
    let* list = load wallet in
    return
      (List.filter_map
         (fun (n, v') -> if Entity.(v = v') then Some n else None)
         list)

  let mem (wallet : #wallet) name =
    let open Lwt_result_syntax in
    let+ map = load_map wallet in
    Map.mem name map

  let add ~force (wallet : #wallet) name value =
    let open Lwt_result_syntax in
    let keep = ref false in
    let* cache = get_cache wallet in
    let* () =
      if force then return_unit
      else
        List.iter_es
          (fun (n, v) ->
            if Compare.String.(n = name) && Entity.(v = value) then (
              keep := true ;
              return_unit)
            else if Compare.String.(n = name) && Entity.(v <> value) then
              failwith
                "another %s is already aliased as %s, use --force to update"
                Entity.name
                n
            else if Compare.String.(n <> name) && Entity.(v = value) then
              failwith
                "this %s is already aliased as %s, use --force to insert \
                 duplicate"
                Entity.name
                n
            else return_unit)
          cache.list_assoc
    in
    if !keep then return_unit else update_cache wallet cache name (Some value)

  let add_many (wallet : #wallet) xs =
    let open Lwt_result_syntax in
    let* cache = get_cache wallet in
    let map_to_add = Map.of_seq (List.to_seq xs) in
    cache.map <- Map.union (fun _key x _existing -> Some x) map_to_add cache.map ;
    cache.list_assoc <- List.of_seq (Map.to_seq cache.map) ;
    let* () = wallet#write Entity.name cache.list_assoc wallet_encoding in
    let* mtime = wallet#last_modification_time Entity.name in
    cache.mtime <- mtime ;
    return_unit

  let del (wallet : #wallet) name =
    let open Lwt_result_syntax in
    let* cache = get_cache wallet in
    update_cache wallet cache name None

  let update (wallet : #wallet) name value =
    let open Lwt_result_syntax in
    let* cache = get_cache wallet in
    update_cache wallet cache name (Some value)

  include Entity

  let alias_parameter () =
    let open Lwt_result_syntax in
    Tezos_clic.parameter ~autocomplete (fun cctxt s ->
        let* v = find cctxt s in
        return (s, v))

  let alias_param ?(name = "name")
      ?(desc = "existing " ^ Entity.name ^ " alias") next =
    Tezos_clic.param ~name ~desc (alias_parameter ()) next

  let aliases_parameter () =
    let open Lwt_result_syntax in
    Tezos_clic.parameter ~autocomplete (fun cctxt s ->
        String.split_no_empty ',' s
        |> List.map_es (fun s ->
               let* pkh = find cctxt s in
               return (s, pkh)))

  let aliases_param ?(name = "name")
      ?(desc = "existing " ^ Entity.name ^ " aliases") next =
    Tezos_clic.param ~name ~desc (aliases_parameter ()) next

  type fresh_param = Fresh of string

  let of_fresh (wallet : #wallet) force (Fresh s) =
    let open Lwt_result_syntax in
    let* list = load wallet in
    let* () =
      if force then return_unit
      else
        List.iter_es
          (fun (n, v) ->
            if String.equal n s then
              let* value = Entity.to_source v in
              failwith
                "@[<v 2>The %s alias %s already exists.@,\
                 The current value is %s.@,\
                 Use --force to update@]"
                Entity.name
                n
                value
            else return_unit)
          list
    in
    return s

  let fresh_alias_param ?(name = "new")
      ?(desc = "new " ^ Entity.name ^ " alias") next =
    Tezos_clic.param
      ~name
      ~desc
      (Tezos_clic.parameter (fun (_ : < .. >) s -> Lwt.return_ok (Fresh s)))
      next

  let parse_source_string cctxt s =
    let open Lwt_result_syntax in
    parse_alternatives
      [
        ("alias", fun alias -> find cctxt alias);
        ( "file",
          fun path ->
            let* input = cctxt#read_file path in
            of_source input );
        ("text", of_source);
      ]
      s

  let source_param ?(name = "src") ?(desc = "source " ^ Entity.name) next =
    let desc =
      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."
        desc
        Entity.name
        Entity.name
        Entity.name
        Entity.name
        Entity.name
    in
    Tezos_clic.param ~name ~desc (Tezos_clic.parameter parse_source_string) next

  let source_arg ?(long = "source " ^ Entity.name) ?(placeholder = "src")
      ?(doc = "") () =
    let doc =
      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."
        doc
        Entity.name
        Entity.name
        Entity.name
        Entity.name
        Entity.name
    in
    Tezos_clic.arg
      ~long
      ~placeholder
      ~doc
      (Tezos_clic.parameter parse_source_string)

  let force_switch () =
    Tezos_clic.switch
      ~long:"force"
      ~short:'f'
      ~doc:("overwrite existing " ^ Entity.name)
      ()

  let name (wallet : #wallet) d =
    let open Lwt_result_syntax in
    let* o = rev_find wallet d in
    match o with None -> Entity.to_source d | Some name -> return name
end
OCaml

Innovation. Community. Security.