Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for YAML + option for non-strict mode + change sections behaviour #45

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
(name mustache_cli)
(public_name mustache)
(modules mustache_cli)
(libraries mustache ezjsonm))
(libraries mustache ezjsonm yaml))
130 changes: 109 additions & 21 deletions bin/mustache_cli.ml
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
let apply_mustache json_data template_data =
let env = Ezjsonm.from_string json_data
and tmpl =
try Mustache.of_string template_data
with Mustache.Parse_error err ->
Format.eprintf "%a@." Mustache.pp_error err;
exit 3
in
Mustache.render tmpl env |> print_endline
type format = [`Json | `Yaml]

exception EnvParseError of string

let env_of_json (env : string) : Mustache.Env.t =
try
Ezjsonm.from_string env
with Ezjsonm.Parse_error (_, msg) ->
raise (EnvParseError msg)

let env_of_yaml (env : string) : Mustache.Env.t =
match Yaml.of_string env with
| Ok (#Mustache.Env.t as env) ->
env
| Ok _ ->
raise (EnvParseError "YAML document root must be a list or map")
| Error (`Msg msg) ->
raise (EnvParseError msg)

let env_of (format : format) (env : string): Mustache.Env.t =
match format with
| `Json -> env_of_json env
| `Yaml -> env_of_yaml env

let load_file f =
let ic = open_in f in
Expand All @@ -16,18 +30,92 @@ let load_file f =
close_in ic;
(Bytes.to_string s)

let run json_filename template_filename =
let j = load_file json_filename
and t = load_file template_filename
in
print_endline j;
apply_mustache j t
type args = {
format : format;
strict : bool;
envname : string;
mstname : string;
}

let usage =
"Usage: mustache [-f json|yaml] [-s] filename.{json,yaml} filename.mustache\n"

let usage () =
print_endline "Usage: mustache-cli json_filename template_filename"
let arg_format (format : string) =
match String.lowercase_ascii format with
| "json" -> `Json
| "yaml" -> `Yaml
| _ -> raise (Arg.Bad (Printf.sprintf "invalid format: %s" format))

let () =
match Sys.argv with
| [| _ ; json_filename ; template_filename |]
-> run json_filename template_filename
| _ -> usage ()
let args =
let format = ref None in
let strict = ref false in
let extra = ref [] in

let specs = [
("-f", Arg.String (fun s -> format := Some (arg_format s)),
": set input format for the environment (= [json|yaml])");
("-s", Arg.Set strict,
": interpret the template in strict mode");
] in

try
begin try
Arg.parse_argv
Sys.argv specs (fun x -> extra := x :: !extra) usage;
with
| Arg.Bad msg ->
let msg =
if String.contains msg '\n'
then String.sub msg 0 (String.index msg '\n')
else msg in

let msg =
if String.contains msg ':' then
let index = String.index msg ':' in
String.sub msg (index+1) (String.length msg - (index+1))
else msg in

raise (Arg.Bad (String.trim msg))
end;

let envname, mstname =
match !extra with
| [mstname; envname] -> (envname, mstname)
| _ -> raise (Arg.Bad "the program expects exactly 2 mandatory arguments") in

let format =
match !format with
| Some format -> format
| None -> begin
match String.lowercase_ascii (Filename.extension envname) with
| ".json" -> `Json
| ".yml" | ".yaml" -> `Yaml
| _ -> raise (Arg.Bad "cannot guess environment format from extension")
end in

let strict = !strict in

{ format; strict; envname; mstname; }

with
| Arg.Bad msg ->
Format.eprintf "%s: %s@.@.%s@."
(Filename.basename Sys.argv.(0)) msg (Arg.usage_string specs usage);
exit 1

| Arg.Help _ ->
Format.eprintf "%s@." (Arg.usage_string specs usage);
exit 1
in

try
let env = env_of args.format (load_file args.envname) in
let tmpl = Mustache.of_string (load_file args.mstname) in

Format.printf "%s%!"
(Mustache.render ~strict:args.strict tmpl env)

with Mustache.Parse_error err ->
Format.eprintf "%a@." Mustache.pp_error err;
exit 1
20 changes: 10 additions & 10 deletions lib/mustache.ml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ include Mustache_types
module List = ListLabels
module String = StringLabels

module Json = struct
module Env = struct
type value =
[ `Null
| `Bool of bool
Expand Down Expand Up @@ -206,13 +206,13 @@ let () =

module Contexts : sig
type t
val start : Json.value -> t
val top : t -> Json.value
val add : t -> Json.value -> t
val find_name : t -> string -> Json.value option
val start : Env.value -> t
val top : t -> Env.value
val add : t -> Env.value -> t
val find_name : t -> string -> Env.value option
end = struct
(* a nonempty stack of contexts, most recent first *)
type t = Json.value * Json.value list
type t = Env.value * Env.value list

let start js = (js, [])

Expand Down Expand Up @@ -255,7 +255,7 @@ module Lookup = struct
| Some _ as result -> result

let dotted_name ?(strict=true) ctxs ~key =
let rec lookup (js : Json.value) ~key =
let rec lookup (js : Env.value) ~key =
match key with
| [] -> Some js
| n :: ns ->
Expand Down Expand Up @@ -364,7 +364,7 @@ module Without_locations = struct
let render_buf
?(strict = true)
?(partials = fun _ -> None)
(buf : Buffer.t) (m : No_locs.t) (js : Json.t)
(buf : Buffer.t) (m : No_locs.t) (js : Env.t)
=
let print_indent indent =
for _ = 0 to indent - 1 do
Expand Down Expand Up @@ -431,9 +431,9 @@ module Without_locations = struct
| Concat templates ->
List.iter (fun x -> render' indent x ctxs) templates

in render' 0 (expand_partials partials m) (Contexts.start (Json.value js))
in render' 0 (expand_partials partials m) (Contexts.start (Env.value js))

let render ?strict ?partials (m : t) (js : Json.t) =
let render ?strict ?partials (m : t) (js : Env.t) =
let buf = Buffer.create 0 in
render_buf ?strict ?partials buf m js ;
Buffer.contents buf
Expand Down
14 changes: 7 additions & 7 deletions lib/mustache.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exception Missing_partial of string

[@@@warning "-30"]

module Json : sig (** Compatible with Ezjsonm *)
module Env : sig (** Compatible with Ezjsonm / Yaml *)
type value =
[ `Null
| `Bool of bool
Expand Down Expand Up @@ -85,23 +85,23 @@ val to_string : t -> string
val render_fmt :
?strict:bool ->
?partials:(name -> t option) ->
Format.formatter -> t -> Json.t -> unit
Format.formatter -> t -> Env.t -> unit

(** [render_buf buf template json] renders [template], filling it
with data from [json], printing it to the buffer [buf].
See {!render_fmt} for the optional arguments. *)
val render_buf :
?strict:bool ->
?partials:(name -> t option) ->
Buffer.t -> t -> Json.t -> unit
Buffer.t -> t -> Env.t -> unit

(** [render template json] renders [template], filling it
with data from [json], and returns the resulting string.
See {!render_fmt} for the optional arguments. *)
val render :
?strict:bool ->
?partials:(name -> t option) ->
t -> Json.t -> string
t -> Env.t -> string

(** [fold template] is the composition of [f] over parts of [template], called
in order of occurrence, where each [f] is one of the labelled arguments
Expand Down Expand Up @@ -217,23 +217,23 @@ module With_locations : sig
val render_fmt :
?strict:bool ->
?partials:(name -> t option) ->
Format.formatter -> t -> Json.t -> unit
Format.formatter -> t -> Env.t -> unit

(** [render_buf buf template json] renders [template], filling it
with data from [json], printing it to the buffer [buf].
See {!render_fmt} for the optional arguments. *)
val render_buf :
?strict:bool ->
?partials:(name -> t option) ->
Buffer.t -> t -> Json.t -> unit
Buffer.t -> t -> Env.t -> unit

(** [render template json] renders [template], filling it
with data from [json], and returns the resulting string.
See {!render_fmt} for the optional arguments. *)
val render :
?strict:bool ->
?partials:(name -> t option) ->
t -> Json.t -> string
t -> Env.t -> string

(** [fold template] is the composition of [f] over parts of [template], called
in order of occurrence, where each [f] is one of the labelled arguments
Expand Down
1 change: 1 addition & 0 deletions mustache.opam
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ depends: [
"ocaml" {>= "4.08.0"}
"dune" {>= "1.4.0"}
"ezjsonm"
"yaml"
"menhir" {>= "20180703"}
"ounit" {with-test}
]
Expand Down