diff --git a/tools/dune b/tools/dune index 4f5057ca..7b747b57 100644 --- a/tools/dune +++ b/tools/dune @@ -5,7 +5,12 @@ (libraries cmdliner bos fmt mirage-crypto ptime mte vif)) (executable - (public_name gen_signatures_registry) - (name gen_signatures_registry) - (modules gen_signatures_registry) - (libraries angstrom bos fmt)) + (public_name gen_registry_files) + (name gen_registry_files) + (modules gen_registry_files) + (libraries recfile_parser bos fmt)) + +(library + (name recfile_parser) + (modules recfile_parser) + (libraries angstrom bos cmdliner fmt)) diff --git a/tools/gen_registry_files.ml b/tools/gen_registry_files.ml new file mode 100644 index 00000000..ebaae034 --- /dev/null +++ b/tools/gen_registry_files.ml @@ -0,0 +1,89 @@ +module Signature_code = struct + type t = { + number: int32; + name: string; + comment: string; + } +end + +let parse_signature_codes records = + records + |> List.filter_map (fun l -> + match l with + | a :: b :: c :: _ -> ( + let open Recfile_parser in + match a.k = "Number" && b.k = "Name" && c.k = "Comment" with + | false -> None + | true -> + Some + Signature_code. + { + number= Int32.of_int (int_of_string a.v); + name= String.lowercase_ascii b.v; + comment= c.v; + }) + | _ -> None) + |> List.filter (fun v -> v.Signature_code.number >= 1000_l) + +let pp_taler_signatures_ml ppf purposes = + let header = + {|(* This file was generated from the GANA database: + https://git-www.gnunet.org/gana.git/tree/gnunet-signatures/registry.rec *)|} + in + let pp ppf Signature_code.{ number; name; comment } = + Fmt.pf ppf "(** %s *)\nlet %s : int32 = %ld_l\n\n" comment name number + in + Fmt.pf ppf "%s\n\n%a@." header (Fmt.list ~sep:Fmt.nop pp) purposes; + () + +let download ~tmp ~url = + let open Bos in + let res = + OS.Cmd.run + Cmd.( + v "curl" % "--silent" % "--show-error" % "-o" % tmp % "-X" % "GET" % url) + in + match res with + | Error (`Msg s) -> Fmt.failwith "download failure: %s" s + | Ok () -> () + +let signatures ~output = + let url = + "https://git-www.gnunet.org/gana.git/plain/gnunet-signatures/registry.rec" + in + let tmp_file = Bos.OS.File.tmp "registry.rec.%s" |> Result.get_ok in + download ~tmp:(Fpath.to_string tmp_file) ~url; + let content = Bos.OS.File.read tmp_file |> Result.get_ok in + match Recfile_parser.parse content with + | Error msg -> Fmt.failwith "Recfile_parser parse error: %s" msg + | Ok records -> + let purposes = parse_signature_codes records in + let module_content = Fmt.str "%a" pp_taler_signatures_ml purposes in + Bos.OS.File.write (Fpath.v output) module_content |> Result.get_ok + +(* --- *) +open Cmdliner +open Cmdliner.Term.Syntax + +let output = + let doc = "output file" in + Arg.(required & opt (some filepath) None & info [ "o"; "output" ] ~doc) + +let signatures_cmd = + let doc = + "Generate taler_signatures.ml from GANA gnunet-signatures registry" + in + Cmd.make (Cmd.info "signatures" ~doc) + @@ + let+ output = output in + signatures ~output + +let cli = + let info = + let doc = "Tool to generate OCaml module from GANA registries" in + Cmd.info "gen_registry_files" ~doc + in + Cmd.group info [ signatures_cmd ] + +let main () = Cmd.eval cli +let () = if !Sys.interactive then () else exit (main ()) diff --git a/tools/gen_signatures_registry.ml b/tools/gen_signatures_registry.ml deleted file mode 100644 index dc6a8ede..00000000 --- a/tools/gen_signatures_registry.ml +++ /dev/null @@ -1,118 +0,0 @@ -module Recfile = struct - (* rudimentary recfile parser - https://www.gnu.org/software/recutils/manual/recutils.html#The-Rec-Format *) - open Angstrom - - type field = { - k: string; - v: string; - } - - type record = field list - - let newline = char '\n' - let is_newline = function '\n' -> true | _ -> false - - let field_name = - let first_char = - satisfy (function 'a' .. 'z' | 'A' .. 'Z' | '%' -> true | _ -> false) - in - let subsequent_char = - satisfy (function - | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> true - | _ -> false) - in - lift2 - (fun hd tl -> String.of_seq (List.to_seq (hd :: tl))) - first_char (many subsequent_char) - - (* todo handle '\' escape and '+' on next line *) - let field_value = take_till is_newline <* newline - - let field = - let blank = satisfy (function ' ' | '\t' -> true | _ -> false) in - let blanks = skip_many1 blank in - lift3 (fun k () v -> { k; v }) field_name (char ':' *> blanks) field_value - - let blank = newline *> return () - let comment = (char '#' *> take_till is_newline <* newline) *> return () - let record = many1 field - - let records = - let sep = - (* at least one blank line *) - skip_many comment *> blank *> skip_many (comment <|> blank) - in - sep_by1 sep record - - let recfile : record list t = - skip_many (comment <|> blank) *> records <* skip_many (comment <|> blank) - - let parse s = parse_string ~consume:All recfile s -end - -let url = - "https://git-www.gnunet.org/gana.git/plain/gnunet-signatures/registry.rec" - -let registry_file = "registry.rec" - -let download () = - let open Bos in - let res = - OS.Cmd.run - Cmd.( - v "curl" - % "--silent" - % "--show-error" - % "-o" - % registry_file - % "-X" - % "GET" - % url) - in - match res with - | Error (`Msg s) -> Fmt.failwith "download failure: %s" s - | Ok () -> () - -let read_file file = In_channel.with_open_bin file In_channel.input_all - -type purpose = { - number: int32; - name: string; - comment: string; -} - -let parse_purposes records = - records - |> List.filter_map (fun l -> - match l with - | a :: b :: c :: _ -> ( - let open Recfile in - match a.k = "Number" && b.k = "Name" && c.k = "Comment" with - | false -> None - | true -> - Some - { - number= Int32.of_int (int_of_string a.v); - name= String.lowercase_ascii b.v; - comment= c.v; - }) - | _ -> None) - |> List.filter (fun v -> v.number >= 1000_l) - -let () = - download (); - let content = read_file registry_file in - match Recfile.parse content with - | Error msg -> Fmt.failwith "Recfile parse error: %s" msg - | Ok records -> - let purposes = parse_purposes records in - let header = - {|(* This file was generated from the GANA database: - https://git-www.gnunet.org/gana.git/tree/gnunet-signatures/registry.rec *)|} - in - let pp ppf { number; name; comment } = - Fmt.pf ppf "(** %s *)\nlet %s : int32 = %ld_l\n\n" comment name number - in - Fmt.pr "%s\n\n%a@." header (Fmt.list ~sep:Fmt.nop pp) purposes; - () diff --git a/tools/offline.ml b/tools/offline.ml index 78c99b9e..461492c7 100644 --- a/tools/offline.ml +++ b/tools/offline.ml @@ -1,13 +1,7 @@ (* TODO - better cmd doc - change arguments: '_' -> '-' - all management operations: /management/wire /management/wire/disable - -> how to build WireSetupMessage? - we need additional data to craft master_sig - maybe supposed to be found in the full payto-uri? /management/aml-officers -> /aml diff --git a/tools/recfile_parser.ml b/tools/recfile_parser.ml new file mode 100644 index 00000000..94882a2f --- /dev/null +++ b/tools/recfile_parser.ml @@ -0,0 +1,51 @@ +(* very rudimentary recfile parser + https://www.gnu.org/software/recutils/manual/recutils.html#The-Rec-Format *) + +open Angstrom + +type field = { + k: string; + v: string; +} + +type record = field list + +let newline = char '\n' +let is_newline = function '\n' -> true | _ -> false + +let field_name = + let first_char = + satisfy (function 'a' .. 'z' | 'A' .. 'Z' | '%' -> true | _ -> false) + in + let subsequent_char = + satisfy (function + | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_' -> true + | _ -> false) + in + lift2 + (fun hd tl -> String.of_seq (List.to_seq (hd :: tl))) + first_char (many subsequent_char) + +(* todo handle '\' escape and '+' on next line *) +let field_value = take_till is_newline <* newline + +let field = + let blank = satisfy (function ' ' | '\t' -> true | _ -> false) in + let blanks = skip_many1 blank in + lift3 (fun k () v -> { k; v }) field_name (char ':' *> blanks) field_value + +let blank = newline *> return () +let comment = (char '#' *> take_till is_newline <* newline) *> return () +let record = many1 field + +let records = + let sep = + (* at least one blank line *) + skip_many comment *> blank *> skip_many (comment <|> blank) + in + sep_by1 sep record + +let recfile : record list t = + skip_many (comment <|> blank) *> records <* skip_many (comment <|> blank) + +let parse s = parse_string ~consume:All recfile s