All Projects → sile → efmt

sile / efmt

Licence: other
Erlang code formatter

Programming Languages

rust
11053 projects
erlang
1774 projects

Labels

Projects that are alternatives of or similar to efmt

lancer
Turn your python code into a hideous mess. Ever heard of Black? This is the opposite.
Stars: ✭ 179 (+842.11%)
Mutual labels:  formatter
pretty print formatter
Pretty Print Formatter for Elixir Logger module -- Colorize Ecto's SQL ouput 🖌️
Stars: ✭ 22 (+15.79%)
Mutual labels:  formatter
StyLua
An opinionated Lua code formatter
Stars: ✭ 479 (+2421.05%)
Mutual labels:  formatter
canonix
Experiment in Nix formatting
Stars: ✭ 18 (-5.26%)
Mutual labels:  formatter
vim-ormolu
Plugin for formatting Haskell source code
Stars: ✭ 35 (+84.21%)
Mutual labels:  formatter
prettier-eslint-atom
DEPRECATED IN FAVOR OF prettier-atom + ESLint integration
Stars: ✭ 64 (+236.84%)
Mutual labels:  formatter
formatters
A javascript library for formatting and manipulating.
Stars: ✭ 14 (-26.32%)
Mutual labels:  formatter
haxe-formatter
Haxe code formatter based on tokentree
Stars: ✭ 58 (+205.26%)
Mutual labels:  formatter
react-put
A flexible formatter and i18n interface for React.
Stars: ✭ 23 (+21.05%)
Mutual labels:  formatter
tox-ini-fmt
Formats your tox.ini files
Stars: ✭ 19 (+0%)
Mutual labels:  formatter
Fmt.jl
Python-style format strings for Julia
Stars: ✭ 31 (+63.16%)
Mutual labels:  formatter
lucene
Node.js lib to transform: lucene query → syntax tree → lucene query
Stars: ✭ 61 (+221.05%)
Mutual labels:  formatter
dockerfile-utils
A library and command line interface for formatting and linting Dockerfiles.
Stars: ✭ 17 (-10.53%)
Mutual labels:  formatter
yapf-online
google/yapf online demo
Stars: ✭ 23 (+21.05%)
Mutual labels:  formatter
autocorrect
Automatically add whitespace between Chinese and half-width characters (alphabetical letters, numerical digits and symbols).
Stars: ✭ 18 (-5.26%)
Mutual labels:  formatter
react-numeric
A react component for formatted number form fields
Stars: ✭ 30 (+57.89%)
Mutual labels:  formatter
unify
Modifies strings to all use the same quote where possible
Stars: ✭ 69 (+263.16%)
Mutual labels:  formatter
sublime-stylefmt
Sublime Text plugin for Stylefmt
Stars: ✭ 49 (+157.89%)
Mutual labels:  formatter
fmt
A code formatter for Racket
Stars: ✭ 44 (+131.58%)
Mutual labels:  formatter
teks
Easily get custom go template based outputs to your command-line tool. Like in docker/kubernetes
Stars: ✭ 41 (+115.79%)
Mutual labels:  formatter

efmt

efmt hex.pm version Documentation Actions Status License

An Erlang code formatter.

Features

  • Opinionated: only maximum line length is configurable by users
  • Emacs Erlang Mode friendly indentation
  • Preserves non-whitespace tokens of the original text as-is
    • Ensures the code after formatting keeps the same semantic meaning
  • Provides a rebar3 plugin: rebar3_efmt
  • Thorough macro support (MACRO_AND_DIRECTIVE.md)

An Formatting Example

Before

-module(example).
-export(
  [fac/1]
).

fac(1) -> 1; fac(N) -> N*fac(N-1).

After

-module(example).
-export([fac/1]).

fac(1) ->
    1;
fac(N) ->
    N * fac(N - 1).

Please refer to FORMAT_RULES.md about the formatting style.

Installation

With Rebar3

Just add the following line to your rebar.config.

{plugins, [rebar3_efmt]}.

Then, you can run the $ rebar3 efmt command.

If you want to provide the default options via rebar.config, please specify an entry that has efmt as the key and efmt's options as the value.

{efmt, [{print_width, 100}]}.  % Sets the maximum line length hint to 100.

Note that rebar3_efmt tries to automatically download a pre-built binary (see the next section) for your environment. However, if there is not a suitable one, you need to build the efmt binary on your own.

Pre-built binaries

Pre-built binaries for Linux and MacOS are available in the releases page.

// An example to download the binary for Linux.
$ curl -L https://github.com/sile/efmt/releases/download/${VERSION}/efmt-${VERSION}.x86_64-unknown-linux-musl -o efmt
$ chmod +x efmt
$ ./efmt

With Cargo

If you have installed cargo (the package manager for Rust), you can install efmt with the following command:

$ cargo install efmt
$ efmt

Usage

Formats an Erlang file (assuming example.erl in the above example is located in the current directory):

$ efmt example.erl  # or `rebar3 efmt example.erl`

// You can specify multiple files.
$ efmt example.erl rebar.config ...

Checks diff between the original text and the formatted one:

$ efmt -c example.erl  # or `rebar3 efmt -c example.erl`
...
    1   1    | -module(example).
    2        |--export(
    3        |-  [fac/1]
    4        |-).
        2    |+-export([fac/1]).
    5   3    |
    6        |-fac(1) -> 1; fac(N) -> N*fac(N-1).
        4    |+fac(1) ->
        5    |+    1;
        6    |+fac(N) ->
        7    |+    N * fac(N - 1).
...

// If you omit the filename, all the Erlang-like files (i.e., `*.{erl, hrl, app.src}` and `rebar.config`)
// are included in the target (if you're in a git repository the files specified by `.gitignore` are excluded).
$ efmt -c

Overwrites the original file with the formatted one:

$ efmt -w example.erl  # or `rebar3 efmt -w example.erl`

// As with `-c` option, you can omit the filename arg.
$ emf -w

For the other command-line options, please see the help document:

// Short doc.
$ efmt -h  # or `rebar3 efmt -h`

// Long doc.
$ efmt --help  # or `rebar3 efmt --help`

How to keep some areas from being formatted

If you want to keep the style of some areas in your input text, please use @efmt:off and @efmt:on comments as follows:

foo() ->
    %% @efmt:off
    LargeList =
      [1,2,3,...,
       998,999,1000],
    %% @efmt:on

    bar(LargeList).

Editor Integrations

TODO (contribution welcome)

Differences with other Erlang formatters

Since I'm not familiar with other Erlang formatters, and the README.md of erlfmt already provides a good comparison table among various formatters, I only describe the differences between efmt and erlfmt here.

Note that in the following examples, I used efmt-v0.1.0 and erlfmt-v1.0.0.

Formatting style

I think the formatting style of efmt is much different from erlfmt. IMO, this is a major point when you decide which one you should choose. If you like the erlfmt style. It's okay. I recommend using erlfmt. But, if you like the efmt style. It's welcomed. Please use efmt.

It's hard work to pick up all difference points here. So I just give you some formatted code examples and hope they give you a sense.

Original code

-module(foo).

-spec hello(term(), integer()) -> {ok, integer()} | {error, Reason :: term()}.
hello({_, _, A, _, [B, _, C]}, D) ->
    {ok, A + B + C + D};
hello(Error, X) when not is_integer(X) ->
    {error, Error}.

Let's set --print-width (the maximum line length) to 30, and see how erlfmt and efmt format the above code if line-wrapping is inevitable.

erlfmt formatted code

$ erlfmt foo.erl --print-width 30

-module(foo).

-spec hello(
    term(), integer()
) ->
    {ok, integer()}
    | {error,
        Reason :: term()}.
hello(
    {_, _, A, _, [B, _, C]}, D
) ->
    {ok, A + B + C + D};
hello(Error, X) when
    not is_integer(X)
->
    {error, Error}.

efmt formatted code

$ efmt foo.erl --print-width 30

-module(foo).

-spec hello(term(),
            integer()) ->
          {ok, integer()} |
          {error,
           Reason :: term()}.
hello({_, _, A, _, [B, _, C]},
      D) ->
    {ok, A + B + C + D};
hello(Error, X)
  when not is_integer(X) ->
    {error, Error}.

Error handling

erlfmt seems to try formatting the remaining part of code even if it detected a syntax error. In contrast, efmt aborts once it detects an error.

For instance, let's format the following code.

-module(bar).

invalid_fun() ->
    : foo,
ok.

valid_fun
()->
ok.

Using erlfmt:

$ erlfmt bar.erl
-module(bar).

invalid_fun() ->
    : foo,
ok.

valid_fun() ->
    ok.
bar.erl:4:5: syntax error before: ':'
// `valid_fun/0` was formatted and the program exited with 0 (success)

Using efmt:

$ efmt bar.erl
[2021-11-28T11:30:06Z ERROR efmt] Failed to format "bar.erl"
    Parse failed:
    --> bar.erl:4:5
    4 |     : foo,
      |     ^ unexpected token

Error: Failed to format the following files:
- bar.erl
// The program exited with 1 (error)

Macro handling

efmt, as much as possible, processes macros as the Erlang preprocessor does.

Thus, it can cover a wide range of tricky cases. Let's format the following code which is based on a macro usage in sile/jsone/src/jsone.erl:

-module(baz).

-ifdef('OTP_RELEASE').
%% The 'OTP_RELEASE' macro introduced at OTP-21,
%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.
-define(CAPTURE_STACKTRACE, :__StackTrace).
-define(GET_STACKTRACE, __StackTrace).
-else.
-define(CAPTURE_STACKTRACE,).
-define(GET_STACKTRACE, erlang:get_stacktrace()).
-endif.

decode(Json, Options) ->
try
{ok, Value, Remainings} = try_decode(Json, Options),
check_decode_remainings(Remainings),
Value
catch
error:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE ->
erlang:raise(error, Reason, [StackItem])
end.

Using efmt:

$ efmt baz.erl
-module(baz).

-ifdef('OTP_RELEASE').
%% The 'OTP_RELEASE' macro introduced at OTP-21,
%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.
-define(CAPTURE_STACKTRACE, :__StackTrace).
-define(GET_STACKTRACE, __StackTrace).
-else.
-define(CAPTURE_STACKTRACE, ).
-define(GET_STACKTRACE, erlang:get_stacktrace()).
-endif.

decode(Json, Options) ->
    try
        {ok, Value, Remainings} = try_decode(Json, Options),
        check_decode_remainings(Remainings),
        Value
    catch
        error:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE->
            erlang:raise(error, Reason, [StackItem])
    end.

Using erlfmt:

$ erlfmt baz.erl
baz.erl:6:29: syntax error before: ':'
-module(baz).

-ifdef('OTP_RELEASE').
%% The 'OTP_RELEASE' macro introduced at OTP-21,
%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.
-define(CAPTURE_STACKTRACE, :__StackTrace).
-define(GET_STACKTRACE, __StackTrace).
-else.
-define(CAPTURE_STACKTRACE,).
-define(GET_STACKTRACE, erlang:get_stacktrace()).
-endif.

decode(Json, Options) ->
try
{ok, Value, Remainings} = try_decode(Json, Options),
check_decode_remainings(Remainings),
Value
catch
error:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE ->
erlang:raise(error, Reason, [StackItem])
end.
baz.erl:19:50: syntax error before: '?'

Formatting speed

The following benchmark compares the time to format all "*.erl" files contained in the OTP-24 source distribution.

// OS and CPU spec.
$ uname -a
Linux TABLET-GC0A6KVD 5.10.16.3-microsoft-standard-WSL2 #1 SMP Fri Apr 2 22:23:49 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ cat /proc/cpuinfo | grep 'model name' | head -1
model name      : 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz

// Downloads OTP source code. There are 3,737 "*.erl" files.
$ wget https://erlang.org/download/otp_src_24.1.tar.gz
$ tar zxvf otp_src_24.1.tar.gz
$ cd otp_src_24.1/
$ find . -name '*.erl' | wc -l
3737

// Erlang version: Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

// erlfmt: 17.30s
$ time erlfmt (find . -name '*.erl') > /dev/null 2> /dev/null
________________________________________________________
Executed in   17.30 secs
   usr time   97.73 secs
   sys time   10.20 secs

// efmt (w/o include cache): 15.10s
$ time efmt --parallel $(find . -name '*.erl') > /dev/null 2> /dev/null
________________________________________________________
Executed in   15.10 secs
   usr time   98.83 secs
   sys time    9.67 secs

// efmt (w/ include cache): 5.84s
$ time efmt --parallel $(find . -name '*.erl') > /dev/null 2> /dev/null
________________________________________________________
Executed in    5.84 secs
   usr time   43.88 secs
   sys time    1.28 secs

Note that efmt needs to process --include and --include_lib to collect macro definitions in the included files. Once an include file is processed, efmt stores the result into a cache file under .efmt/cache/ dir. The efmt second execution in the above benchmark just reused the cached results instead of processing hole include files. So the execution time was much faster than the first execution.

Development phase

erlfmt has released the stable version (v1), but efmt hasn't. Perhaps some parts of the efmt style will change in future releases until it releases v1.

Limitations

There are some limitations that are not planned to be addressed in the future:

  • Only supports UTF-8 files
  • Doesn't process parse transforms
    • That is, if a parse transform has introduced custom syntaxes in your Erlang code, efmt could fail
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].