Skip to content

Commit

Permalink
Add 'line-endings' option to specify line endings used in the formatt…
Browse files Browse the repository at this point in the history
…ed output (#1703)
  • Loading branch information
nojb authored Jul 16, 2021
1 parent 1c4960b commit 70dc63d
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 4 deletions.
1 change: 1 addition & 0 deletions .ocamlformat
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ break-cases = fit
margin = 77
parse-docstrings = true
wrap-comments = true
line-endings = lf
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
- Break the line and reindent the cursor when pressing <ENTER>
(#1639, #1685, @gpetiot) (#1687, @bcc32)

+ Add 'line-endings=lf|crlf' option to specify the line endings used in the
formatted output. (#1703, @nojb)

#### Internal

+ A script `tools/build-mingw64.sh` is provided to build a native Windows
Expand Down
15 changes: 12 additions & 3 deletions bin/ocamlformat/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,19 @@ let format ?output_file ~kind ~input_name ~source conf opts =
Translation_unit.parse_and_format kind ?output_file ~input_name ~source
conf opts

let write_all output_file ~data =
Out_channel.with_file ~binary:true output_file ~f:(fun oc ->
Out_channel.output_string oc data )

let to_output_file output_file data =
match output_file with
| None -> Out_channel.output_string Out_channel.stdout data
| Some output_file -> Out_channel.write_all output_file ~data
| None ->
Out_channel.flush Out_channel.stdout ;
Out_channel.set_binary_mode Out_channel.stdout true ;
Out_channel.output_string Out_channel.stdout data ;
Out_channel.flush Out_channel.stdout ;
Out_channel.set_binary_mode Out_channel.stdout false
| Some output_file -> write_all output_file ~data

let source_from_file = function
| Conf.Stdin -> In_channel.input_all In_channel.stdin
Expand All @@ -52,7 +61,7 @@ let run_action action opts =
match result with
| Ok formatted ->
if not (String.equal formatted source) then
Out_channel.write_all input_file ~data:formatted ;
write_all input_file ~data:formatted ;
Ok ()
| Error e -> Error (fun () -> print_error conf opts e)
in
Expand Down
16 changes: 15 additions & 1 deletion lib/Conf.ml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type t =
; let_binding_indent: int
; let_binding_spacing: [`Compact | `Sparse | `Double_semicolon]
; let_module: [`Compact | `Sparse]
; line_endings: [`Lf | `Crlf]
; margin: int
; match_indent: int
; match_indent_nested: [`Always | `Auto | `Never]
Expand Down Expand Up @@ -86,7 +87,7 @@ let warn_raw, collect_warnings =
let delayed_warning_list = ref [] in
let warn_ s =
if !delay_warning then delayed_warning_list := s :: !delayed_warning_list
else Format.eprintf "%s" s
else Format.eprintf "%s%!" s
in
let collect_warnings f =
let old_flag, old_list = (!delay_warning, !delayed_warning_list) in
Expand Down Expand Up @@ -830,6 +831,16 @@ module Formatting = struct
let msg = concrete_syntax_preserved_msg in
C.removed_option ~names ~version ~msg

let line_endings =
let doc = "Line endings used." in
let all =
[ ("lf", `Lf, "$(b,lf) uses Unix line endings.")
; ("crlf", `Crlf, "$(b,crlf) uses Windows line endings.") ]
in
C.choice ~names:["line-endings"] ~all ~doc ~allow_inline:false ~section
(fun conf x -> {conf with line_endings= x})
(fun conf -> conf.line_endings)

let margin =
let docv = "COLS" in
let doc = "Format code to fit within $(docv) columns." in
Expand Down Expand Up @@ -1437,6 +1448,7 @@ let ocamlformat_profile =
; let_binding_indent= 2
; let_binding_spacing= `Compact
; let_module= `Compact
; line_endings= `Lf
; margin= 80
; match_indent= 0
; match_indent_nested= `Never
Expand Down Expand Up @@ -1508,6 +1520,7 @@ let conventional_profile =
; let_binding_indent= C.default Formatting.let_binding_indent
; let_binding_spacing= C.default Formatting.let_binding_spacing
; let_module= C.default Formatting.let_module
; line_endings= C.default Formatting.line_endings
; margin= C.default Formatting.margin
; match_indent= C.default Formatting.match_indent
; match_indent_nested= C.default Formatting.match_indent_nested
Expand Down Expand Up @@ -1632,6 +1645,7 @@ let janestreet_profile =
; let_binding_indent= 2
; let_binding_spacing= `Double_semicolon
; let_module= `Sparse
; line_endings= `Lf
; margin= 90
; match_indent= 0
; match_indent_nested= `Never
Expand Down
1 change: 1 addition & 0 deletions lib/Conf.mli
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type t =
; let_binding_indent: int
; let_binding_spacing: [`Compact | `Sparse | `Double_semicolon]
; let_module: [`Compact | `Sparse]
; line_endings: [`Lf | `Crlf]
; margin: int (** Format code to fit within [margin] columns. *)
; match_indent: int
; match_indent_nested: [`Always | `Auto | `Never]
Expand Down
26 changes: 26 additions & 0 deletions lib/Translation_unit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,30 @@ let parse_result ?(f = Ast_passes.Ast0.Parse.ast) fragment conf ~source
| exception exn -> Error (Error.Invalid_source {exn; input_name})
| parsed -> Ok parsed

let normalize_eol ~line_endings s =
let buf = Buffer.create (String.length s) in
let rec loop seen_cr i =
if i = String.length s then (
if seen_cr then Buffer.add_char buf '\r' ;
Buffer.contents buf )
else
match (s.[i], line_endings) with
| '\r', _ ->
if seen_cr then Buffer.add_char buf '\r' ;
loop true (i + 1)
| '\n', `Crlf ->
Buffer.add_string buf "\r\n" ;
loop false (i + 1)
| '\n', `Lf ->
Buffer.add_char buf '\n' ;
loop false (i + 1)
| c, _ ->
if seen_cr then Buffer.add_char buf '\r' ;
Buffer.add_char buf c ;
loop false (i + 1)
in
loop false 0

let parse_and_format (type a b) (fg0 : a Ast_passes.Ast0.t)
(fgN : b Ast_passes.Ast_final.t) ?output_file ~input_name ~source conf
opts =
Expand All @@ -421,6 +445,8 @@ let parse_and_format (type a b) (fg0 : a Ast_passes.Ast0.t)
let parsed = {parsed with ast= Ast_passes.run fg0 fgN parsed.ast} in
format fg0 fgN ?output_file ~input_name ~prev_source:source ~parsed conf
opts
>>= fun formatted ->
Ok (normalize_eol ~line_endings:conf.Conf.line_endings formatted)

let parse_and_format = function
| Syntax.Structure -> parse_and_format Structure Structure
Expand Down
5 changes: 5 additions & 0 deletions ocamlformat-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ OPTIONS (CODE FORMATTING STYLE)
... = and before the in if the module declaration does not fit on
a single line. The default value is compact.

--line-endings={lf|crlf}
Line endings used. lf uses Unix line endings. crlf uses Windows
line endings. The default value is lf. Cannot be set in
attributes.

-m COLS, --margin=COLS
Format code to fit within COLS columns. The default value is 80.
Cannot be set in attributes.
Expand Down
24 changes: 24 additions & 0 deletions test/passing/dune.inc
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,30 @@
(package ocamlformat)
(action (diff tests/compact_lists_arrays.ml compact_lists_arrays.ml.output)))

(rule
(deps tests/.ocamlformat )
(package ocamlformat)
(action
(with-outputs-to crlf_to_crlf.ml.output
(run %{bin:ocamlformat} --line-endings=crlf %{dep:tests/crlf_to_crlf.ml}))))

(rule
(alias runtest)
(package ocamlformat)
(action (diff tests/crlf_to_crlf.ml.ref crlf_to_crlf.ml.output)))

(rule
(deps tests/.ocamlformat )
(package ocamlformat)
(action
(with-outputs-to crlf_to_lf.ml.output
(run %{bin:ocamlformat} --line-endings=lf %{dep:tests/crlf_to_lf.ml}))))

(rule
(alias runtest)
(package ocamlformat)
(action (diff tests/crlf_to_lf.ml.ref crlf_to_lf.ml.output)))

(rule
(deps tests/.ocamlformat )
(package ocamlformat)
Expand Down
41 changes: 41 additions & 0 deletions test/passing/tests/crlf_to_crlf.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
let _ = {|
foo

bar
|}

(** This is verbatim:
{v
o o
/\ /\
/\ /\
v}
This is preformated code:
{[
let verbatim s =
s |> String.split_lines |> List.map ~f:String.strip
|> fun s -> list s "@," Fmt.str
]} *)

(** Lists:
list with short lines:
- x
list with long lines:
- xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
xxx xxx xxx xxx xxx xxx
list with sub lists:
{ul
{- xxx
- a
}
} *)
1 change: 1 addition & 0 deletions test/passing/tests/crlf_to_crlf.ml.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--line-endings=crlf
41 changes: 41 additions & 0 deletions test/passing/tests/crlf_to_crlf.ml.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
let _ = {|
foo

bar
|}

(** This is verbatim:

{v
o o
/\ /\
/\ /\
v}

This is preformated code:

{[
let verbatim s =
s |> String.split_lines |> List.map ~f:String.strip
|> fun s -> list s "@," Fmt.str
]} *)

(** Lists:

list with short lines:

- x

list with long lines:

- xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
xxx xxx xxx xxx xxx xxx

list with sub lists:

{ul
{- xxx

- a
}
} *)
41 changes: 41 additions & 0 deletions test/passing/tests/crlf_to_lf.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
let _ = {|
foo

bar
|}

(** This is verbatim:
{v
o o
/\ /\
/\ /\
v}
This is preformated code:
{[
let verbatim s =
s |> String.split_lines |> List.map ~f:String.strip
|> fun s -> list s "@," Fmt.str
]} *)

(** Lists:
list with short lines:
- x
list with long lines:
- xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
xxx xxx xxx xxx xxx xxx
list with sub lists:
{ul
{- xxx
- a
}
} *)
1 change: 1 addition & 0 deletions test/passing/tests/crlf_to_lf.ml.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--line-endings=lf
41 changes: 41 additions & 0 deletions test/passing/tests/crlf_to_lf.ml.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
let _ = {|
foo

bar
|}

(** This is verbatim:

{v
o o
/\ /\
/\ /\
v}

This is preformated code:

{[
let verbatim s =
s |> String.split_lines |> List.map ~f:String.strip
|> fun s -> list s "@," Fmt.str
]} *)

(** Lists:

list with short lines:

- x

list with long lines:

- xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
xxx xxx xxx xxx xxx xxx

list with sub lists:

{ul
{- xxx

- a
}
} *)
1 change: 1 addition & 0 deletions test/passing/tests/print_config.ml.ref
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ max-indent=68 (profile ocamlformat (file tests/.ocamlformat:1))
match-indent-nested=never (profile ocamlformat (file tests/.ocamlformat:1))
match-indent=0 (profile ocamlformat (file tests/.ocamlformat:1))
margin=77 (file tests/.ocamlformat:3)
line-endings=lf (profile ocamlformat (file tests/.ocamlformat:1))
let-module=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-spacing=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-indent=2 (profile ocamlformat (file tests/.ocamlformat:1))
Expand Down
1 change: 1 addition & 0 deletions test/passing/tests/verbose1.ml.ref
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ max-indent=68 (profile ocamlformat (file tests/.ocamlformat:1))
match-indent-nested=never (profile ocamlformat (file tests/.ocamlformat:1))
match-indent=0 (profile ocamlformat (file tests/.ocamlformat:1))
margin=77 (file tests/.ocamlformat:3)
line-endings=lf (profile ocamlformat (file tests/.ocamlformat:1))
let-module=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-spacing=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-indent=2 (profile ocamlformat (file tests/.ocamlformat:1))
Expand Down
1 change: 1 addition & 0 deletions test/passing/tests/verbose2.ml.ref
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ max-indent=68 (profile ocamlformat (file tests/.ocamlformat:1))
match-indent-nested=never (profile ocamlformat (file tests/.ocamlformat:1))
match-indent=0 (profile ocamlformat (file tests/.ocamlformat:1))
margin=77 (file tests/.ocamlformat:3)
line-endings=lf (profile ocamlformat (file tests/.ocamlformat:1))
let-module=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-spacing=compact (profile ocamlformat (file tests/.ocamlformat:1))
let-binding-indent=2 (profile ocamlformat (file tests/.ocamlformat:1))
Expand Down

0 comments on commit 70dc63d

Please sign in to comment.