From 77e2a55df2ef696909c5d72a2ff6951cd7ccbfdc Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Tue, 18 Aug 2026 16:12:38 +0000 Subject: [PATCH] Add snippets --- bin/dune | 2 +- bin/main.ml | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- lib/dune | 1 + lib/octx.ml | 5 +++-- lib/snippets.ml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 file(s) changed, 121 insertion(s)(+), 11 deletion(s)(-) diff --git a/bin/dune b/bin/dune --- a/bin/dune +++ b/bin/dune @@ -1,4 +1,4 @@ (executable (public_name octx) (name main) - (libraries octx ppxlib cmdliner)) + (libraries octx xdg ppxlib cmdliner)) diff --git a/bin/main.ml b/bin/main.ml --- a/bin/main.ml +++ b/bin/main.ml @@ -1,6 +1,57 @@ open Cmdliner open Cmdliner.Term.Syntax +let list = + let docv = "LIST" in + let doc = "List all of the currently available snippets." in + Cmdliner.Arg.(value & flag & info ~doc ~docv [ "list" ]) + +let snippet_name = + let docv = "NAME" in + let doc = "The snippet name to print." in + Cmdliner.Arg.(value & pos 0 (some string) None & info ~doc ~docv []) + +let snippet_cmd = + let doc = "Paste snippets from your library into your code." in + let man = + [ + `S Manpage.s_description; + `P + "Snippets can be stored in an ~/.local/state/octx.json file as a list \ + of name-snippet pairs."; + `S Manpage.s_examples; + `P "The following is an example file."; + `Pre + (Jsont_bytesrw.encode_string ~format:Jsont.Indent Octx.Snippets.jsont + Octx.Snippets.default + |> Result.get_ok); + ] + in + Cmd.make (Cmd.info "snippet" ~doc ~man) + @@ + let+ list = list and+ snippet_name = snippet_name in + let xdg = Xdg.create ~env:Sys.getenv_opt () in + let snippets = + Filename.concat (Xdg.state_dir xdg) "octx.json" |> Octx.Snippets.load + in + match list with + | true -> + Format.eprintf "Snippet library:\n%!"; + Format.printf "%a\n%!" Jsont.(pp_value Octx.Snippets.jsont ()) snippets + | false -> ( + match snippet_name with + | None -> + Format.eprintf "You must provide a snippet name\n%!"; + exit 1 + | Some snippet_name -> ( + match + List.find (fun s -> s.Octx.Snippets.name = snippet_name) snippets + with + | exception Not_found -> + Format.eprintf "Snippet %s not found\n%!" snippet_name; + exit 1 + | Octx.Snippets.{ snippet; _ } -> Format.printf "%s\n%!" snippet)) + let input = let docv = "INPUT" in let doc = @@ -39,16 +90,11 @@ let doc = "Any cases that are found in the structure are inverted." in Cmdliner.Arg.(value & flag & info ~doc ~docv [ "invert" ]) let run_cmd = - let doc = "OCaml Code-generation Toolbox (octx)." in + let doc = "Derive from and transform existing OCaml code" in let man = [ `S Manpage.s_description; `P - "Octx is an all-in-one tool for generating some useful bits of OCaml \ - code from other OCaml code. In pratice, it is a bunch of ppxes \ - crammed into one binary alongside other useful OCaml code \ - transformers."; - `P "When supplying derivers, there is limited support for passing \ deriving arguments to the underlying ppx. The syntax is \ `deriver_name1:arg1=v1:arg2, deriver_name2'. That is,each argument is \ @@ -57,7 +103,7 @@ `S Manpage.s_bugs; `P "Email bug reports to ."; ] in - Cmd.make (Cmd.info "octx" ~version:"%%VERSION%%" ~doc ~man) + Cmd.make (Cmd.info "run" ~doc ~man) @@ let+ input and+ derivers and+ as_str and+ as_sig and+ invert in let derivers = @@ -88,5 +134,23 @@ | fname -> Octx.Input.File fname in Octx.run ~invert ~derivers ?kind input -let main () = Cmd.eval run_cmd +let octx = + let doc = "OCaml Code-generation Toolbox (octx)." in + let man = + [ + `S Manpage.s_description; + `P + "Octx is an all-in-one tool for generating some useful bits of OCaml \ + code from other OCaml code. In pratice, it is a bunch of ppxes \ + crammed into one binary alongside other useful OCaml code \ + transformers."; + `S Manpage.s_bugs; + `P "Email bug reports to ."; + ] + in + Cmd.group + (Cmd.info "octx" ~version:"%%VERSION%%" ~doc ~man) + [ run_cmd; snippet_cmd ] + +let main () = Cmd.eval octx let () = if !Sys.interactive then () else exit (main ()) diff --git a/lib/dune b/lib/dune --- a/lib/dune +++ b/lib/dune @@ -6,6 +6,7 @@ ppxlib ppx_deriving_yojson ppx_deriving_yaml ppx_sexp_conv + jsont.bytesrw ppx_deriving_jsont ppx_deriving.show ppx_deriving.ord diff --git a/lib/octx.ml b/lib/octx.ml --- a/lib/octx.ml +++ b/lib/octx.ml @@ -1,5 +1,6 @@ open Ppxlib module Transformations = Transformations +module Snippets = Snippets module Kind = struct type t = Structure | Signature @@ -172,8 +173,8 @@ | _ -> si in List.map map ast -let run ?(invert = false) ?(derivers = []) ?(transformations = []) ?kind - (input : Input.t) = +let run ?(invert = false) ?(derivers = []) ?(transformations = []) + ?(snippets = []) ?kind (input : Input.t) = let input_name = Input.input_name input in let ast = match (kind, input) with diff --git a/lib/snippets.ml b/lib/snippets.ml new file mode 100644 --- /dev/null +++ b/lib/snippets.ml @@ -0,0 +1,44 @@ +type snippet = { name : string; snippet : string } + +let snippet_jsont = + let make name snippet = { name; snippet } in + Jsont.Object.map ~kind:"Snippet" make + |> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.name) + |> Jsont.Object.mem "snippet" Jsont.string ~enc:(fun t -> t.snippet) + |> Jsont.Object.finish + +type t = snippet list + +let jsont = Jsont.list snippet_jsont + +let cmdliner_flag = + { + name = "cmdliner-flag"; + snippet = + {|let cmdliner_flag = + let doc = _ in + Arg.(value & flag & info [ _ ] ~docv:_ ~doc)|}; + } + +let cmdliner_option = + { + name = "cmdliner-opt"; + snippet = + {|let cmdliner_opt = + let doc = _ in + Arg.(value & opt _ _ & info [ _ ] ~doc ~docv:_)|}; + } + +let default = [ cmdliner_flag; cmdliner_option ] + +let load path = + try + In_channel.with_open_bin path @@ fun ic -> + match + Jsont_bytesrw.decode jsont (Bytesrw.Bytes.Reader.of_in_channel ic) + with + | Ok v -> v + | Error msg -> + Format.eprintf "Failed to load snippets: %s" msg; + exit 1 + with Sys_error _ -> default -- tangled.sh