32 个版本 (18 个重大更改)
0.18.1 | 2024 年 8 月 1 日 |
---|---|
0.18.0 | 2024 年 7 月 31 日 |
0.17.0 | 2024 年 7 月 19 日 |
0.15.0 | 2024 年 2 月 18 日 |
0.1.1 | 2021 年 11 月 29 日 |
#133 in 开发工具
每月 459 次下载
265KB
7.5K SLoC
efmt
Erlang 代码格式化工具。
在线演示.
功能
- 有偏见的格式化工具
- 没有配置选项
- 如果项目(例如,
case
块、列表、记录)在原始代码中包含换行符,则将在多行模式下处理这些换行符
- Emacs Erlang 模式 友好的缩进,有一些例外
- 保留原始文本的非空白标记不变
- 确保格式化后的代码保持相同的语义意义
- 提供 rebar3 插件: rebar3_efmt
- 彻底支持宏(MACRO_AND_DIRECTIVE.md)
格式化示例
之前
-module(example).
-export(
[fac/1]
).
fac(1)->
1;fac(N )
-> N*fac(
N-1).
之后
-module(example).
-export([fac/1]).
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
安装
使用 Rebar3
只需将以下行添加到您的 rebar.config
文件中。
{project_plugins, [rebar3_efmt]}.
然后,您可以通过运行以下命令来运行 $ rebar3 efmt
。
如果您想通过 rebar.config
提供默认选项,请指定一个以 efmt
为键,以 efmt
的选项为值的条目。
{efmt, [{exclude_file, "rebar.config"}]}.
请注意,rebar3_efmt
尝试自动为您的工作环境下载预构建的二进制文件(请参阅下一节)。但是,如果没有合适的版本,您需要自己构建 efmt
二进制文件。
预构建的二进制文件
Linux 和 MacOS 的预构建二进制文件可在 发布页面 上找到。
// An example to download the binary for Linux.
$ VERSION=0.18.0
$ curl -L https://github.com/sile/efmt/releases/download/${VERSION}/efmt-${VERSION}.x86_64-unknown-linux-musl -o efmt
$ chmod +x efmt
$ ./efmt
使用 Cargo
如果您已安装 cargo
(Rust 的包管理器),可以使用以下命令安装 efmt
$ cargo install efmt
$ efmt
用法
格式化Erlang文件(例如,在上述示例中,example.erl
位于当前目录)
$ efmt example.erl # or `rebar3 efmt example.erl`
// You can specify multiple files.
$ efmt example.erl rebar.config ...
检查原始文本与格式化文本之间的差异
$ efmt -c example.erl # or `rebar3 efmt -c example.erl`
--- a/example.erl
+++ b/example.erl
@@ -1,9 +1,8 @@
-module(example).
--export(
- [fac/1]
-).
+-export([fac/1]).
-fac(1)->
-1;fac(N )
--> N*fac(
-N-1).
+
+fac(1) ->
+ 1;
+fac(N) ->
+ 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
用格式化后的文件覆盖原始文件
$ efmt -w example.erl # or `rebar3 efmt -w example.erl`
// As with `-c` option, you can omit the filename arg.
$ emf -w
有关其他命令行选项,请参阅帮助文档
// Short doc.
$ efmt -h # or `rebar3 efmt -h`
// Long doc.
$ efmt --help # or `rebar3 efmt --help`
如何避免某些区域被格式化
如果您想在输入文本中保留某些区域的外观,请使用以下注释:@efmt:off
和@efmt:on
foo() ->
%% @efmt:off
LargeList =
[1,2,3,...,
998,999,1000],
%% @efmt:on
bar(LargeList).
编辑器集成
- Emacs: emacs-format-all-the-code
- VSCode: 扩展
- Sublime Text: Formatter
与其他Erlang格式化器的差异
由于我对其他Erlang格式化器不太熟悉,并且erlfmt
的README.md已经提供了各种格式化器之间的良好比较表,因此我只在这里描述efmt
和erlfmt
之间的差异。
请注意,在以下示例中,我使用了efmt-v0.11.0
和erlfmt-v1.0.0
。
格式化风格
我认为efmt
的格式化风格与erlfmt
有很大不同。在我看来,这是您决定选择哪一个时的一个重要点。如果您喜欢erlfmt
的风格,那就没问题。我建议使用erlfmt
。但是,如果您喜欢efmt
的风格,那就欢迎使用efmt
。
在这里挑选所有不同点是一项艰巨的任务。所以我只给你一些格式化代码示例,希望它们能给你一个感觉。
原始代码
-module(foo).
-spec hello(term(), integer()) ->
{ok, integer()} | {error, Reason :: term()} |
timeout.
hello({_, _, A, _,
[B, _, C]}, D) -> {ok,
A + B +
C + D};
hello(Error, X) when not is_integer(X);
is_atom(X) ->
{error, Error};
hello(#record{foo=[_,_],
bar=#{qux := 10}}, World) ->
World.
让我们看看erlfmt
和efmt
是如何格式化上述代码的。
erlfmt
格式化代码
$erlfmt foo.erl
-module(foo).
-spec hello(term(), integer()) ->
{ok, integer()}
| {error, Reason :: term()}
| timeout.
hello({_, _, A, _, [B, _, C]}, D) ->
{ok,
A + B +
C + D};
hello(Error, X) when
not is_integer(X);
is_atom(X)
->
{error, Error};
hello(
#record{
foo = [_, _],
bar = #{qux := 10}
},
World
) ->
World.
efmt
格式化代码
$efmt foo.erl
-module(foo).
-spec hello(term(), integer()) ->
{ok, integer()} |
{error, Reason :: term()} |
timeout.
hello({_,
_,
A,
_,
[B, _, C]},
D) ->
{ok, A + B +
C + D};
hello(Error, X)
when not is_integer(X);
is_atom(X) ->
{error, Error};
hello(#record{
foo = [_, _],
bar = #{qux := 10}
},
World) ->
World.
无行宽限制
与erlfmt
不同,efmt
不提供确保格式化代码的每一行都位于指定行宽(列)内的功能。
错误处理
erlfmt
似乎会在检测到语法错误后尝试格式化代码的剩余部分。相比之下,efmt
一旦检测到错误就会中断。
例如,让我们格式化以下代码。
-module(bar).
invalid_fun() ->
: foo,
ok.
valid_fun
()->
ok.
使用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)
使用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)
宏处理
efmt
尽可能地像Erlang预处理器一样处理宏。
因此,它可以涵盖广泛的复杂情况。让我们格式化以下基于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.
使用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.
使用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: '?'
格式化速度
以下基准测试比较了格式化OTP-24源分布中所有".erl"文件所需的时间。
// 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.ac.cn/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: 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
开发阶段
erlfmt
已经发布了稳定版本(v1),但efmt
还没有。也许在发布v1之前,efmt
的风格的一些部分可能会发生变化。
限制
有一些限制,未来不计划解决
- 仅支持UTF-8文件
- 不处理解析转换
- 也就是说,如果解析转换在您的Erlang代码中引入了自定义语法,
efmt
可能会失败
- 也就是说,如果解析转换在您的Erlang代码中引入了自定义语法,
- 不处理
-include().
和-include_lib().
指令- 这些包含文件中定义的宏被展开为(虚拟的)原子。
依赖项
~7–20MB
~217K SLoC