Skip to content

Commit

Permalink
try harder to indent partial parameters as the user would naturally e…
Browse files Browse the repository at this point in the history
…xpect
  • Loading branch information
gasche committed Jan 5, 2021
1 parent 7c3bc53 commit 9e4daeb
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 96 deletions.
14 changes: 7 additions & 7 deletions bin/test/inheritance.t/run.t
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ https://github.com/mustache/spec/pull/75

$ mustache data.json mypage.mustache
<html>
<head>
<title>My page title</title>
</head>
<head>
<title>My page title</title>
</head>
<body>
<h1>Hello world</h1>
</body>
Expand All @@ -19,13 +19,13 @@ We also test the indentation of parameter blocks.
$ mustache data.json test-indent-more.mustache
<p>
The test below should be indented in the same way as this line.
This text is not indented in the source,
it should be indented naturally in the output.
This text is not indented in the source,
it should be indented naturally in the output.
</p>

$ mustache data.json test-indent-less.mustache
<p>
The test below should be indented in the same way as this line.
This text is very indented in the source,
it should be indented naturally in the output.
This text is very indented in the source,
it should be indented naturally in the output.
</p>
138 changes: 84 additions & 54 deletions lib/mustache.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ and erase_locs_partial (p : Locs.partial) : No_locs.partial = {
contents = lazy (Option.map erase_locs (Lazy.force p.contents))
}
and erase_locs_param (pa : Locs.param) : No_locs.param = {
indent = pa.indent;
name = pa.name;
contents = erase_locs pa.contents;
}
Expand Down Expand Up @@ -113,6 +114,7 @@ and add_dummy_locs_partial (p : No_locs.partial) : Locs.partial = {
contents = lazy (Option.map add_dummy_locs (Lazy.force p.contents));
}
and add_dummy_locs_param (pa : No_locs.param) : Locs.param = {
indent = pa.indent;
name = pa.name;
contents = add_dummy_locs pa.contents;
}
Expand Down Expand Up @@ -315,15 +317,15 @@ module Contexts : sig
val add : t -> Json.value -> t
val find_name : t -> string -> Json.value option
val add_param : t -> Locs.param -> t
val find_param : t -> string -> Locs.t option
val find_param : t -> string -> Locs.param option
end = struct
type t = {
(* nonempty stack of contexts, most recent first *)
stack: Json.value * Json.value list;

(* an associative list of partial parameters
that have been defined *)
params: (string * Locs.t) list;
params: Locs.param list;
}

let start js = {
Expand Down Expand Up @@ -357,6 +359,9 @@ end = struct
| [] -> None
| top :: rest -> find_name { ctxs with stack = (top, rest) } name


let param_has_name name (p : Locs.param) = String.equal p.name name

(* Note: the template-inheritance specification for Mustache
(https://github.com/mustache/spec/pull/75) mandates that in case
of multi-level inclusion, the "topmost" definition of the
Expand All @@ -374,15 +379,15 @@ end = struct
a grandparent), and then late-binding mandates that the
definition "last" in the inheritance chain (so closest to the
start of the rendering) wins.*)
let add_param ctxs { Locs.name; contents } =
if List.mem_assoc name ctxs.params then
let add_param ctxs (param : Locs.param) =
if List.exists (param_has_name param.name) ctxs.params then
(* if the parameter is already bound, the existing binding has precedence *)
ctxs
else
{ctxs with params = (name, contents) :: ctxs.params}
{ctxs with params = param :: ctxs.params}

let find_param ctxs name =
List.assoc_opt name ctxs.params
List.find_opt (param_has_name name) ctxs.params
end

let raise_err loc kind =
Expand Down Expand Up @@ -474,34 +479,64 @@ module Render = struct
?(strict = true)
(buf : Buffer.t) (m : Locs.t) (js : Json.t)
=
let print_indent indent =
for _ = 0 to indent - 1 do
Buffer.add_char buf ' '
done
let beginning_of_line = ref true in

let print_indented buf indent line =
assert (indent >= 0);
if String.equal line ""
then ()
else begin
for _i = 1 to indent do Buffer.add_char buf ' ' done;
Buffer.add_string buf line;
beginning_of_line := false;
end
in

let beginning_of_line = ref true in
let print_dedented buf dedent line =
assert (dedent >= 0);
let rec print_from i =
if i = String.length line then ()
else if i < dedent && (match line.[i] with ' ' | '\t' -> true | _ -> false)
then print_from (i + 1)
else begin
Buffer.add_substring buf line i (String.length line - i);
beginning_of_line := false;
end
in
print_from 0
in

let align indent =
if !beginning_of_line then (
print_indent indent;
beginning_of_line := false
)
let print_line indent line =
if not !beginning_of_line then
Buffer.add_string buf line
else begin
if indent >= 0
then print_indented buf indent line
else print_dedented buf (-indent) line;
end
in

let print_newline buf =
Buffer.add_char buf '\n';
beginning_of_line := true
in

let print_indented_string indent s =
let lines = String.split_on_char '\n' s in
align indent; Buffer.add_string buf (List.hd lines);
print_line indent (List.hd lines);
List.iter (fun line ->
Buffer.add_char buf '\n';
beginning_of_line := true;
if line <> "" then (
align indent;
Buffer.add_string buf line;
)
print_newline buf;
print_line indent line
) (List.tl lines)
in

let print_interpolated indent data =
(* per the specification, interpolated data should be spliced into the
document, with further lines *not* indented specifically; this effect
is obtained by calling print_line on the (possibly multiline) data. *)
print_line indent data
in

let rec render indent m (ctxs : Contexts.t) =
let loc = m.loc in
match m.desc with
Expand All @@ -510,12 +545,12 @@ module Render = struct
print_indented_string indent s

| Escaped name ->
align indent;
Buffer.add_string buf (escape_html (Lookup.str ~strict ~loc ~key:name ctxs))
print_interpolated indent
(escape_html (Lookup.str ~strict ~loc ~key:name ctxs))

| Unescaped name ->
align indent;
Buffer.add_string buf (Lookup.str ~strict ~loc ~key:name ctxs)
print_interpolated indent
(Lookup.str ~strict ~loc ~key:name ctxs)

| Inverted_section s ->
if Lookup.inverted ctxs ~loc ~key:s.name
Expand Down Expand Up @@ -545,18 +580,13 @@ module Render = struct
render (indent + partial_indent) partial ctxs
end

| Param { name; contents } ->
| Param default_param ->
let param =
match Lookup.param ctxs ~loc ~key:name with
| None ->
(* The "contents" of the partial parameter is to be used as
default content, if the parameter was not explicitly passed
by one of the partials in scope. *)
contents
| Some param ->
param
match Lookup.param ctxs ~loc ~key:default_param.name with
| Some passed_param -> passed_param
| None -> default_param
in
render indent param ctxs
render (indent + default_param.indent - param.indent) param.contents ctxs

| Comment _c -> ()

Expand Down Expand Up @@ -598,11 +628,11 @@ module Without_locations = struct
concat (List.map ms ~f:go)
| Partial {indent; name; params; contents} ->
let params =
Option.map (List.map ~f:(fun {name; contents} -> (name, go contents))) params
Option.map (List.map ~f:(fun {indent; name; contents} -> (indent, name, go contents))) params
in
partial indent name ?params contents
| Param { name; contents } ->
param name (go contents)
partial ?indent:(Some indent) name ?params contents
| Param { indent; name; contents } ->
param ?indent:(Some indent) name (go contents)

module Infix = struct
let (^) y x = Concat [x; y]
Expand All @@ -615,24 +645,24 @@ module Without_locations = struct
let inverted_section n c = Inverted_section { name = n ; contents = c }
let partial ?(indent = 0) n ?params c =
let params =
Option.map (List.map ~f:(fun (name, contents) -> {name; contents})) params in
Option.map (List.map ~f:(fun (indent, name, contents) -> {indent; name; contents})) params in
Partial { indent ; name = n ; params; contents = c }
let param n c = Param { name = n; contents = c }
let param ?(indent=0) n c = Param { indent; name = n; contents = c }
let concat t = Concat t
let comment s = Comment s

let rec expand_partials (partials : name -> t option) : t -> t =
let section ~inverted =
if inverted then inverted_section else section
in
let partial indent name ?params contents =
let partial ?indent name ?params contents =
let contents' = lazy (
match Lazy.force contents with
| None -> Option.map (expand_partials partials) (partials name)
| Some t_opt -> Some t_opt
)
in
partial ~indent name ?params contents'
partial ?indent name ?params contents'
in
fold ~string:raw ~section ~escaped ~unescaped ~partial ~param ~comment ~concat

Expand Down Expand Up @@ -683,10 +713,10 @@ module With_locations = struct
concat ~loc (List.map ms ~f:go)
| Partial p ->
let params =
Option.map (List.map ~f:(fun {name; contents} -> (name, go contents))) p.params in
partial ~loc p.indent p.name ?params p.contents
| Param { name; contents } ->
param ~loc name (go contents)
Option.map (List.map ~f:(fun {indent; name; contents} -> (indent, name, go contents))) p.params in
partial ~loc ?indent:(Some p.indent) p.name ?params p.contents
| Param { indent; name; contents } ->
param ~loc ?indent:(Some indent) name (go contents)
module Infix = struct
let (^) t1 t2 = { desc = Concat [t1; t2]; loc = dummy_loc }
Expand All @@ -703,27 +733,27 @@ module With_locations = struct
loc }
let partial ~loc ?(indent = 0) n ?params c =
let params =
Option.map (List.map ~f:(fun (name, contents) -> {name; contents})) params in
Option.map (List.map ~f:(fun (indent, name, contents) -> {indent; name; contents})) params in
{ desc = Partial { indent; name = n; params; contents = c };
loc }
let concat ~loc t = { desc = Concat t; loc }
let comment ~loc s = { desc = Comment s; loc }
let param ~loc n c =
{ desc = Param { name = n; contents = c };
let param ~loc ?(indent = 0) n c =
{ desc = Param { indent; name = n; contents = c };
loc }
let rec expand_partials (partials : name -> t option) : t -> t =
let section ~loc ~inverted =
if inverted then inverted_section ~loc else section ~loc
in
let partial ~loc indent name ?params contents =
let partial ~loc ?indent name ?params contents =
let contents' = lazy (
match Lazy.force contents with
| None -> Option.map (expand_partials partials) (partials name)
| Some t_opt -> Some t_opt
)
in
partial ~loc ~indent name ?params contents'
partial ~loc ?indent name ?params contents'
in
fold ~string:raw ~section ~escaped ~unescaped ~partial ~param ~comment ~concat
Expand Down
24 changes: 14 additions & 10 deletions lib/mustache.mli
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ and partial =
params: param list option;
contents: t option Lazy.t }
and param =
{ name: name;
{ indent: int;
name: name;
contents: t }

type loc =
Expand Down Expand Up @@ -141,8 +142,8 @@ val fold : string: (string -> 'a) ->
section: (inverted:bool -> dotted_name -> 'a -> 'a) ->
escaped: (dotted_name -> 'a) ->
unescaped: (dotted_name -> 'a) ->
partial: (int -> name -> ?params:(name * 'a) list -> t option Lazy.t -> 'a) ->
param: (name -> 'a -> 'a) ->
partial: (?indent:int -> name -> ?params:(int * name * 'a) list -> t option Lazy.t -> 'a) ->
param: (?indent:int -> name -> 'a -> 'a) ->
comment: (string -> 'a) ->
concat:('a list -> 'a) ->
t -> 'a
Expand Down Expand Up @@ -186,10 +187,11 @@ val section : dotted_name -> t -> t
{{/box}}
]}
*)
val partial : ?indent:int -> name -> ?params:(name * t) list -> t option Lazy.t -> t
val partial :
?indent:int -> name -> ?params:(int * name * t) list -> t option Lazy.t -> t

(** [{{$foo}} {{/foo}}] *)
val param: name -> t -> t
val param : ?indent:int -> name -> t -> t

(** [{{! this is a comment}}] *)
val comment : string -> t
Expand Down Expand Up @@ -223,7 +225,8 @@ module With_locations : sig
params: param list option;
contents: t option Lazy.t }
and param =
{ name: name;
{ indent: int;
name: name;
contents: t }
and t =
{ loc : loc;
Expand Down Expand Up @@ -296,8 +299,8 @@ module With_locations : sig
section: (loc:loc -> inverted:bool -> dotted_name -> 'a -> 'a) ->
escaped: (loc:loc -> dotted_name -> 'a) ->
unescaped: (loc:loc -> dotted_name -> 'a) ->
partial: (loc:loc -> int -> name -> ?params:(name * 'a) list -> t option Lazy.t -> 'a) ->
param: (loc:loc -> name -> 'a -> 'a) ->
partial: (loc:loc -> ?indent:int -> name -> ?params:(int * name * 'a) list -> t option Lazy.t -> 'a) ->
param: (loc:loc -> ?indent:int -> name -> 'a -> 'a) ->
comment: (loc:loc -> string -> 'a) ->
concat:(loc:loc -> 'a list -> 'a) ->
t -> 'a
Expand Down Expand Up @@ -339,10 +342,11 @@ module With_locations : sig
{{/box}}
]}
*)
val partial : loc:loc -> ?indent:int -> name -> ?params:(name * t) list -> t option Lazy.t -> t
val partial :
loc:loc -> ?indent:int -> name -> ?params:(int * name * t) list -> t option Lazy.t -> t

(** [{{$foo}} {{/foo}}] *)
val param: loc:loc -> name -> t -> t
val param : loc:loc -> ?indent:int -> name -> t -> t

(** [{{! this is a comment}}] *)
val comment : loc:loc -> string -> t
Expand Down
Loading

0 comments on commit 9e4daeb

Please sign in to comment.