Skip to content

Commit 9a4feed

Browse files
committed
try harder to indent partial parameters as the user would naturally expect
1 parent 844183b commit 9a4feed

File tree

6 files changed

+167
-96
lines changed

6 files changed

+167
-96
lines changed

bin/test/inheritance.t/run.t

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ https://github.com/mustache/spec/pull/75
55

66
$ mustache data.json mypage.mustache
77
<html>
8-
<head>
9-
<title>My page title</title>
10-
</head>
8+
<head>
9+
<title>My page title</title>
10+
</head>
1111
<body>
1212
<h1>Hello world</h1>
1313
</body>
@@ -19,13 +19,13 @@ We also test the indentation of parameter blocks.
1919
$ mustache data.json test-indent-more.mustache
2020
<p>
2121
The test below should be indented in the same way as this line.
22-
This text is not indented in the source,
23-
it should be indented naturally in the output.
22+
This text is not indented in the source,
23+
it should be indented naturally in the output.
2424
</p>
2525

2626
$ mustache data.json test-indent-less.mustache
2727
<p>
2828
The test below should be indented in the same way as this line.
29-
This text is very indented in the source,
30-
it should be indented naturally in the output.
29+
This text is very indented in the source,
30+
it should be indented naturally in the output.
3131
</p>

lib/mustache.ml

Lines changed: 84 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ and erase_locs_partial (p : Locs.partial) : No_locs.partial = {
8484
contents = lazy (Option.map erase_locs (Lazy.force p.contents))
8585
}
8686
and erase_locs_param (pa : Locs.param) : No_locs.param = {
87+
indent = pa.indent;
8788
name = pa.name;
8889
contents = erase_locs pa.contents;
8990
}
@@ -113,6 +114,7 @@ and add_dummy_locs_partial (p : No_locs.partial) : Locs.partial = {
113114
contents = lazy (Option.map add_dummy_locs (Lazy.force p.contents));
114115
}
115116
and add_dummy_locs_param (pa : No_locs.param) : Locs.param = {
117+
indent = pa.indent;
116118
name = pa.name;
117119
contents = add_dummy_locs pa.contents;
118120
}
@@ -315,15 +317,15 @@ module Contexts : sig
315317
val add : t -> Json.value -> t
316318
val find_name : t -> string -> Json.value option
317319
val add_param : t -> Locs.param -> t
318-
val find_param : t -> string -> Locs.t option
320+
val find_param : t -> string -> Locs.param option
319321
end = struct
320322
type t = {
321323
(* nonempty stack of contexts, most recent first *)
322324
stack: Json.value * Json.value list;
323325

324326
(* an associative list of partial parameters
325327
that have been defined *)
326-
params: (string * Locs.t) list;
328+
params: Locs.param list;
327329
}
328330

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

362+
363+
let param_has_name name (p : Locs.param) = String.equal p.name name
364+
360365
(* Note: the template-inheritance specification for Mustache
361366
(https://github.com/mustache/spec/pull/75) mandates that in case
362367
of multi-level inclusion, the "topmost" definition of the
@@ -374,15 +379,15 @@ end = struct
374379
a grandparent), and then late-binding mandates that the
375380
definition "last" in the inheritance chain (so closest to the
376381
start of the rendering) wins.*)
377-
let add_param ctxs { Locs.name; contents } =
378-
if List.mem_assoc name ctxs.params then
382+
let add_param ctxs (param : Locs.param) =
383+
if List.exists (param_has_name param.name) ctxs.params then
379384
(* if the parameter is already bound, the existing binding has precedence *)
380385
ctxs
381386
else
382-
{ctxs with params = (name, contents) :: ctxs.params}
387+
{ctxs with params = param :: ctxs.params}
383388

384389
let find_param ctxs name =
385-
List.assoc_opt name ctxs.params
390+
List.find_opt (param_has_name name) ctxs.params
386391
end
387392

388393
let raise_err loc kind =
@@ -474,34 +479,64 @@ module Render = struct
474479
?(strict = true)
475480
(buf : Buffer.t) (m : Locs.t) (js : Json.t)
476481
=
477-
let print_indent indent =
478-
for _ = 0 to indent - 1 do
479-
Buffer.add_char buf ' '
480-
done
482+
let beginning_of_line = ref true in
483+
484+
let print_indented buf indent line =
485+
assert (indent >= 0);
486+
if String.equal line ""
487+
then ()
488+
else begin
489+
for _i = 1 to indent do Buffer.add_char buf ' ' done;
490+
Buffer.add_string buf line;
491+
beginning_of_line := false;
492+
end
481493
in
482494

483-
let beginning_of_line = ref true in
495+
let print_dedented buf dedent line =
496+
assert (dedent >= 0);
497+
let rec print_from i =
498+
if i = String.length line then ()
499+
else if i < dedent && (match line.[i] with ' ' | '\t' -> true | _ -> false)
500+
then print_from (i + 1)
501+
else begin
502+
Buffer.add_substring buf line i (String.length line - i);
503+
beginning_of_line := false;
504+
end
505+
in
506+
print_from 0
507+
in
484508

485-
let align indent =
486-
if !beginning_of_line then (
487-
print_indent indent;
488-
beginning_of_line := false
489-
)
509+
let print_line indent line =
510+
if not !beginning_of_line then
511+
Buffer.add_string buf line
512+
else begin
513+
if indent >= 0
514+
then print_indented buf indent line
515+
else print_dedented buf (-indent) line;
516+
end
517+
in
518+
519+
let print_newline buf =
520+
Buffer.add_char buf '\n';
521+
beginning_of_line := true
490522
in
491523

492524
let print_indented_string indent s =
493525
let lines = String.split_on_char '\n' s in
494-
align indent; Buffer.add_string buf (List.hd lines);
526+
print_line indent (List.hd lines);
495527
List.iter (fun line ->
496-
Buffer.add_char buf '\n';
497-
beginning_of_line := true;
498-
if line <> "" then (
499-
align indent;
500-
Buffer.add_string buf line;
501-
)
528+
print_newline buf;
529+
print_line indent line
502530
) (List.tl lines)
503531
in
504532

533+
let print_interpolated indent data =
534+
(* per the specification, interpolated data should be spliced into the
535+
document, with further lines *not* indented specifically; this effect
536+
is obtained by calling print_line on the (possibly multiline) data. *)
537+
print_line indent data
538+
in
539+
505540
let rec render indent m (ctxs : Contexts.t) =
506541
let loc = m.loc in
507542
match m.desc with
@@ -510,12 +545,12 @@ module Render = struct
510545
print_indented_string indent s
511546

512547
| Escaped name ->
513-
align indent;
514-
Buffer.add_string buf (escape_html (Lookup.str ~strict ~loc ~key:name ctxs))
548+
print_interpolated indent
549+
(escape_html (Lookup.str ~strict ~loc ~key:name ctxs))
515550

516551
| Unescaped name ->
517-
align indent;
518-
Buffer.add_string buf (Lookup.str ~strict ~loc ~key:name ctxs)
552+
print_interpolated indent
553+
(Lookup.str ~strict ~loc ~key:name ctxs)
519554

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

548-
| Param { name; contents } ->
583+
| Param default_param ->
549584
let param =
550-
match Lookup.param ctxs ~loc ~key:name with
551-
| None ->
552-
(* The "contents" of the partial parameter is to be used as
553-
default content, if the parameter was not explicitly passed
554-
by one of the partials in scope. *)
555-
contents
556-
| Some param ->
557-
param
585+
match Lookup.param ctxs ~loc ~key:default_param.name with
586+
| Some passed_param -> passed_param
587+
| None -> default_param
558588
in
559-
render indent param ctxs
589+
render (indent + default_param.indent - param.indent) param.contents ctxs
560590

561591
| Comment _c -> ()
562592

@@ -598,11 +628,11 @@ module Without_locations = struct
598628
concat (List.map ms ~f:go)
599629
| Partial {indent; name; params; contents} ->
600630
let params =
601-
Option.map (List.map ~f:(fun {name; contents} -> (name, go contents))) params
631+
Option.map (List.map ~f:(fun {indent; name; contents} -> (indent, name, go contents))) params
602632
in
603-
partial indent name ?params contents
604-
| Param { name; contents } ->
605-
param name (go contents)
633+
partial ?indent:(Some indent) name ?params contents
634+
| Param { indent; name; contents } ->
635+
param ?indent:(Some indent) name (go contents)
606636

607637
module Infix = struct
608638
let (^) y x = Concat [x; y]
@@ -615,24 +645,24 @@ module Without_locations = struct
615645
let inverted_section n c = Inverted_section { name = n ; contents = c }
616646
let partial ?(indent = 0) n ?params c =
617647
let params =
618-
Option.map (List.map ~f:(fun (name, contents) -> {name; contents})) params in
648+
Option.map (List.map ~f:(fun (indent, name, contents) -> {indent; name; contents})) params in
619649
Partial { indent ; name = n ; params; contents = c }
620-
let param n c = Param { name = n; contents = c }
650+
let param ?(indent=0) n c = Param { indent; name = n; contents = c }
621651
let concat t = Concat t
622652
let comment s = Comment s
623653

624654
let rec expand_partials (partials : name -> t option) : t -> t =
625655
let section ~inverted =
626656
if inverted then inverted_section else section
627657
in
628-
let partial indent name ?params contents =
658+
let partial ?indent name ?params contents =
629659
let contents' = lazy (
630660
match Lazy.force contents with
631661
| None -> Option.map (expand_partials partials) (partials name)
632662
| Some t_opt -> Some t_opt
633663
)
634664
in
635-
partial ~indent name ?params contents'
665+
partial ?indent name ?params contents'
636666
in
637667
fold ~string:raw ~section ~escaped ~unescaped ~partial ~param ~comment ~concat
638668

@@ -683,10 +713,10 @@ module With_locations = struct
683713
concat ~loc (List.map ms ~f:go)
684714
| Partial p ->
685715
let params =
686-
Option.map (List.map ~f:(fun {name; contents} -> (name, go contents))) p.params in
687-
partial ~loc p.indent p.name ?params p.contents
688-
| Param { name; contents } ->
689-
param ~loc name (go contents)
716+
Option.map (List.map ~f:(fun {indent; name; contents} -> (indent, name, go contents))) p.params in
717+
partial ~loc ?indent:(Some p.indent) p.name ?params p.contents
718+
| Param { indent; name; contents } ->
719+
param ~loc ?indent:(Some indent) name (go contents)
690720
691721
module Infix = struct
692722
let (^) t1 t2 = { desc = Concat [t1; t2]; loc = dummy_loc }
@@ -703,27 +733,27 @@ module With_locations = struct
703733
loc }
704734
let partial ~loc ?(indent = 0) n ?params c =
705735
let params =
706-
Option.map (List.map ~f:(fun (name, contents) -> {name; contents})) params in
736+
Option.map (List.map ~f:(fun (indent, name, contents) -> {indent; name; contents})) params in
707737
{ desc = Partial { indent; name = n; params; contents = c };
708738
loc }
709739
let concat ~loc t = { desc = Concat t; loc }
710740
let comment ~loc s = { desc = Comment s; loc }
711-
let param ~loc n c =
712-
{ desc = Param { name = n; contents = c };
741+
let param ~loc ?(indent = 0) n c =
742+
{ desc = Param { indent; name = n; contents = c };
713743
loc }
714744
715745
let rec expand_partials (partials : name -> t option) : t -> t =
716746
let section ~loc ~inverted =
717747
if inverted then inverted_section ~loc else section ~loc
718748
in
719-
let partial ~loc indent name ?params contents =
749+
let partial ~loc ?indent name ?params contents =
720750
let contents' = lazy (
721751
match Lazy.force contents with
722752
| None -> Option.map (expand_partials partials) (partials name)
723753
| Some t_opt -> Some t_opt
724754
)
725755
in
726-
partial ~loc ~indent name ?params contents'
756+
partial ~loc ?indent name ?params contents'
727757
in
728758
fold ~string:raw ~section ~escaped ~unescaped ~partial ~param ~comment ~concat
729759

lib/mustache.mli

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ and partial =
3838
params: param list option;
3939
contents: t option Lazy.t }
4040
and param =
41-
{ name: name;
41+
{ indent: int;
42+
name: name;
4243
contents: t }
4344

4445
type loc =
@@ -139,8 +140,8 @@ val fold : string: (string -> 'a) ->
139140
section: (inverted:bool -> dotted_name -> 'a -> 'a) ->
140141
escaped: (dotted_name -> 'a) ->
141142
unescaped: (dotted_name -> 'a) ->
142-
partial: (int -> name -> ?params:(name * 'a) list -> t option Lazy.t -> 'a) ->
143-
param: (name -> 'a -> 'a) ->
143+
partial: (?indent:int -> name -> ?params:(int * name * 'a) list -> t option Lazy.t -> 'a) ->
144+
param: (?indent:int -> name -> 'a -> 'a) ->
144145
comment: (string -> 'a) ->
145146
concat:('a list -> 'a) ->
146147
t -> 'a
@@ -184,10 +185,11 @@ val section : dotted_name -> t -> t
184185
{{/box}}
185186
]}
186187
*)
187-
val partial : ?indent:int -> name -> ?params:(name * t) list -> t option Lazy.t -> t
188+
val partial :
189+
?indent:int -> name -> ?params:(int * name * t) list -> t option Lazy.t -> t
188190

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

192194
(** [{{! this is a comment}}] *)
193195
val comment : string -> t
@@ -221,7 +223,8 @@ module With_locations : sig
221223
params: param list option;
222224
contents: t option Lazy.t }
223225
and param =
224-
{ name: name;
226+
{ indent: int;
227+
name: name;
225228
contents: t }
226229
and t =
227230
{ loc : loc;
@@ -294,8 +297,8 @@ module With_locations : sig
294297
section: (loc:loc -> inverted:bool -> dotted_name -> 'a -> 'a) ->
295298
escaped: (loc:loc -> dotted_name -> 'a) ->
296299
unescaped: (loc:loc -> dotted_name -> 'a) ->
297-
partial: (loc:loc -> int -> name -> ?params:(name * 'a) list -> t option Lazy.t -> 'a) ->
298-
param: (loc:loc -> name -> 'a -> 'a) ->
300+
partial: (loc:loc -> ?indent:int -> name -> ?params:(int * name * 'a) list -> t option Lazy.t -> 'a) ->
301+
param: (loc:loc -> ?indent:int -> name -> 'a -> 'a) ->
299302
comment: (loc:loc -> string -> 'a) ->
300303
concat:(loc:loc -> 'a list -> 'a) ->
301304
t -> 'a
@@ -337,10 +340,11 @@ module With_locations : sig
337340
{{/box}}
338341
]}
339342
*)
340-
val partial : loc:loc -> ?indent:int -> name -> ?params:(name * t) list -> t option Lazy.t -> t
343+
val partial :
344+
loc:loc -> ?indent:int -> name -> ?params:(int * name * t) list -> t option Lazy.t -> t
341345

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

345349
(** [{{! this is a comment}}] *)
346350
val comment : loc:loc -> string -> t

0 commit comments

Comments
 (0)