{"id":13667995,"url":"https://github.com/sile/efmt","last_synced_at":"2025-04-13T05:03:09.334Z","repository":{"id":37044044,"uuid":"95031959","full_name":"sile/efmt","owner":"sile","description":"Erlang code formatter","archived":false,"fork":false,"pushed_at":"2025-03-08T13:24:19.000Z","size":3490,"stargazers_count":71,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-04T05:06:16.705Z","etag":null,"topics":["erlang","formatter","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sile.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-21T18:01:53.000Z","updated_at":"2025-03-08T13:24:23.000Z","dependencies_parsed_at":"2024-01-14T16:15:50.458Z","dependency_job_id":"cc2b548e-552f-4b28-aa48-0e220bc75e1d","html_url":"https://github.com/sile/efmt","commit_stats":{"total_commits":532,"total_committers":3,"mean_commits":"177.33333333333334","dds":0.003759398496240629,"last_synced_commit":"41f5936292bbf93c2f2e3af45fd0d6d17ed5cc5e"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sile%2Fefmt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sile%2Fefmt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sile%2Fefmt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sile%2Fefmt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sile","download_url":"https://codeload.github.com/sile/efmt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248665748,"owners_count":21142123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","formatter","rust"],"created_at":"2024-08-02T07:00:58.611Z","updated_at":"2025-04-13T05:03:09.309Z","avatar_url":"https://github.com/sile.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"efmt\n====\n\n[![efmt](https://img.shields.io/crates/v/efmt.svg)](https://crates.io/crates/efmt)\n[![hex.pm version](https://img.shields.io/hexpm/v/rebar3_efmt.svg)](https://hex.pm/packages/rebar3_efmt)\n[![vscode version](https://img.shields.io/vscode-marketplace/v/sile.efmt.svg?label=vscode)](https://marketplace.visualstudio.com/items?itemName=sile.efmt)\n[![Documentation](https://docs.rs/efmt/badge.svg)](https://docs.rs/efmt)\n[![Actions Status](https://github.com/sile/efmt/workflows/CI/badge.svg)](https://github.com/sile/efmt/actions)\n![License](https://img.shields.io/crates/l/efmt)\n\nAn Erlang code formatter.\n\n[Online demo](https://sile.github.io/efmt/examples/efmt.html).\n\nFeatures\n--------\n\n- An opinionated formatter\n  - No configuration options\n  - If items (e.g., `case` blocks, lists, records) contain newlines in the original code, those are processed in multi-line mode\n- [Emacs Erlang Mode](https://www.erlang.org/doc/apps/tools/erlang_mode_chapter.html) friendly indentation with some exceptions\n- Preserves non-whitespace tokens of the original text as-is\n  - Ensures the code after formatting keeps the same semantic meaning\n- Provides a rebar3 plugin: [rebar3_efmt](https://hex.pm/packages/rebar3_efmt)\n- Thorough macro support ([MACRO_AND_DIRECTIVE.md](MACRO_AND_DIRECTIVE.md))\n\nAn Formatting Example\n---------------------\n\n### Before\n\n```erlang\n-module(example).\n-export(\n  [fac/1]\n).\n\nfac(1)-\u003e\n1;fac(N   )\n-\u003e N*fac(\nN-1).\n```\n\n### After\n\n```erlang\n-module(example).\n-export([fac/1]).\n\n\nfac(1) -\u003e\n    1;\nfac(N) -\u003e\n    N * fac(N - 1).\n```\n\nInstallation\n------------\n\n### With [Rebar3](https://github.com/erlang/rebar3)\n\nJust add the following line to your `rebar.config`.\n\n```erlang\n{project_plugins, [rebar3_efmt]}.\n```\n\nThen, you can run the `$ rebar3 efmt` command.\n\nIf you want to provide the default options via `rebar.config`,\nplease specify an entry that has `efmt` as the key and `efmt`'s options as the value.\n```erlang\n{efmt, [{exclude_file, \"rebar.config\"}]}.\n```\n\nNote that `rebar3_efmt` tries to automatically download a pre-built binary (see the next section) for your environment.\nHowever, if there is not a suitable one, you need to build the `efmt` binary on your own.\n\n### Pre-built binaries\n\nPre-built binaries for Linux and MacOS are available in [the releases page](https://github.com/sile/efmt/releases).\n\n```console\n// An example to download the binary for Linux.\n$ VERSION=0.19.1\n$ curl -L https://github.com/sile/efmt/releases/download/${VERSION}/efmt-${VERSION}.x86_64-unknown-linux-musl -o efmt\n$ chmod +x efmt\n$ ./efmt\n```\n\n### With [Cargo](https://doc.rust-lang.org/cargo/)\n\nIf you have installed `cargo` (the package manager for Rust), you can install `efmt` with the following command:\n```console\n$ cargo install efmt\n$ efmt\n```\n\nUsage\n-----\n\nFormats an Erlang file (assuming `example.erl` in the above example is located in the current directory):\n```console\n$ efmt example.erl  # or `rebar3 efmt example.erl`\n\n// You can specify multiple files.\n$ efmt example.erl rebar.config ...\n```\n\nChecks diff between the original text and the formatted one:\n```console\n$ efmt -c example.erl  # or `rebar3 efmt -c example.erl`\n--- a/example.erl\n+++ b/example.erl\n@@ -1,9 +1,8 @@\n -module(example).\n--export(\n-  [fac/1]\n-).\n+-export([fac/1]).\n\n-fac(1)-\u003e\n-1;fac(N   )\n--\u003e N*fac(\n-N-1).\n+\n+fac(1) -\u003e\n+    1;\n+fac(N) -\u003e\n+    N * fac(N - 1).\n\n\n// If you omit the filename, all the Erlang-like files (i.e., `*.{erl, hrl, app.src}` and `rebar.config`)\n// are included in the target (if you're in a git repository the files specified by `.gitignore` are excluded).\n$ efmt -c\n```\n\nOverwrites the original file with the formatted one:\n```console\n$ efmt -w example.erl  # or `rebar3 efmt -w example.erl`\n\n// As with `-c` option, you can omit the filename arg.\n$ emf -w\n```\n\nFor the other command-line options, please see the help document:\n```console\n// Short doc.\n$ efmt -h  # or `rebar3 efmt -h`\n\n// Long doc.\n$ efmt --help  # or `rebar3 efmt --help`\n```\n\n### How to keep some areas from being formatted\n\nIf you want to keep the style of some areas in your input text,\nplease use `@efmt:off` and `@efmt:on` comments as follows:\n\n```erlang\nfoo() -\u003e\n    %% @efmt:off\n    LargeList =\n      [1,2,3,...,\n       998,999,1000],\n    %% @efmt:on\n\n    bar(LargeList).\n```\n\nEditor Integrations\n-------------------\n\n- Emacs: [emacs-format-all-the-code](https://github.com/lassik/emacs-format-all-the-code)\n- VSCode: [extension](https://marketplace.visualstudio.com/items?itemName=sile.efmt)\n- Sublime Text: [Formatter](https://packagecontrol.io/packages/Formatter)\n\nDifferences with other Erlang formatters\n-----------------------------------------\n\nSince I'm not familiar with other Erlang formatters, and [the README.md of `erlfmt`](https://github.com/WhatsApp/erlfmt/blob/main/README.md) already provides a good comparison table among various formatters, I only describe the differences between `efmt` and `erlfmt` here.\n\nNote that in the following examples, I used `efmt-v0.11.0` and `erlfmt-v1.0.0`.\n\n### Formatting style\n\nI think the formatting style of `efmt` is much different from `erlfmt`.\nIMO, this is a major point when you decide which one you should choose.\nIf you like the `erlfmt` style. It's okay. I recommend using `erlfmt`.\nBut, if you like the `efmt` style. It's welcomed. Please use `efmt`.\n\nIt's hard work to pick up all difference points here.\nSo I just give you some formatted code examples and hope they give you a sense.\n\n#### Original code\n\n```erlang\n-module(foo).\n\n-spec hello(term(), integer()) -\u003e\n {ok, integer()} | {error, Reason :: term()} |\n          timeout.\nhello({_, _, A, _,\n [B, _, C]}, D) -\u003e {ok,\nA + B +\nC + D};\nhello(Error, X) when not is_integer(X);\n                     is_atom(X) -\u003e\n    {error, Error};\nhello(#record{foo=[_,_],\nbar=#{qux := 10}}, World) -\u003e\n    World.\n```\n\nLet's see how `erlfmt` and `efmt` format the above code.\n\n#### `erlfmt` formatted code\n\n`$ erlfmt foo.erl`\n```erlang\n-module(foo).\n\n-spec hello(term(), integer()) -\u003e\n    {ok, integer()}\n    | {error, Reason :: term()}\n    | timeout.\nhello({_, _, A, _, [B, _, C]}, D) -\u003e\n    {ok,\n        A + B +\n            C + D};\nhello(Error, X) when\n    not is_integer(X);\n    is_atom(X)\n-\u003e\n    {error, Error};\nhello(\n    #record{\n        foo = [_, _],\n        bar = #{qux := 10}\n    },\n    World\n) -\u003e\n    World.\n```\n\n#### `efmt` formatted code\n\n`$ efmt foo.erl`\n```erlang\n-module(foo).\n\n\n-spec hello(term(), integer()) -\u003e\n          {ok, integer()} |\n          {error, Reason :: term()} |\n          timeout.\nhello({_,\n       _,\n       A,\n       _,\n       [B, _, C]},\n      D) -\u003e\n    {ok, A + B +\n         C + D};\nhello(Error, X)\n  when not is_integer(X);\n       is_atom(X) -\u003e\n    {error, Error};\nhello(#record{\n        foo = [_, _],\n        bar = #{qux := 10}\n       },\n      World) -\u003e\n    World.\n```\n\n### No line width limit\n\nUnlike `erlfmt`, `efmt` doesn't provide a feature to ensure each line of the formatted code is within a specified line width (columns).\n\n### Error handling\n\n`erlfmt` seems to try formatting the remaining part of code even if it detected a syntax error.\nIn contrast, `efmt` aborts once it detects an error.\n\nFor instance, let's format the following code.\n```erlang\n-module(bar).\n\ninvalid_fun() -\u003e\n    : foo,\nok.\n\nvalid_fun\n()-\u003e\nok.\n```\n\nUsing `erlfmt`:\n```console\n$ erlfmt bar.erl\n-module(bar).\n\ninvalid_fun() -\u003e\n    : foo,\nok.\n\nvalid_fun() -\u003e\n    ok.\nbar.erl:4:5: syntax error before: ':'\n// `valid_fun/0` was formatted and the program exited with 0 (success)\n```\n\nUsing `efmt`:\n```console\n$ efmt bar.erl\n[2021-11-28T11:30:06Z ERROR efmt] Failed to format \"bar.erl\"\n    Parse failed:\n    --\u003e bar.erl:4:5\n    4 |     : foo,\n      |     ^ unexpected token\n\nError: Failed to format the following files:\n- bar.erl\n// The program exited with 1 (error)\n```\n\n### Macro handling\n\n`efmt`, as much as possible, processes macros as the Erlang preprocessor does.\n\nThus, it can cover a wide range of tricky cases.\nLet's format the following code which is based on a macro usage in [sile/jsone/src/jsone.erl](https://github.com/sile/jsone/blob/master/src/jsone.erl):\n```erlang\n-module(baz).\n\n-ifdef('OTP_RELEASE').\n%% The 'OTP_RELEASE' macro introduced at OTP-21,\n%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.\n-define(CAPTURE_STACKTRACE, :__StackTrace).\n-define(GET_STACKTRACE, __StackTrace).\n-else.\n-define(CAPTURE_STACKTRACE,).\n-define(GET_STACKTRACE, erlang:get_stacktrace()).\n-endif.\n\ndecode(Json, Options) -\u003e\ntry\n{ok, Value, Remainings} = try_decode(Json, Options),\ncheck_decode_remainings(Remainings),\nValue\ncatch\nerror:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE -\u003e\nerlang:raise(error, Reason, [StackItem])\nend.\n```\n\nUsing `efmt`:\n```console\n$ efmt baz.erl\n-module(baz).\n\n-ifdef('OTP_RELEASE').\n%% The 'OTP_RELEASE' macro introduced at OTP-21,\n%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.\n-define(CAPTURE_STACKTRACE, :__StackTrace).\n-define(GET_STACKTRACE, __StackTrace).\n-else.\n-define(CAPTURE_STACKTRACE, ).\n-define(GET_STACKTRACE, erlang:get_stacktrace()).\n-endif.\n\ndecode(Json, Options) -\u003e\n    try\n        {ok, Value, Remainings} = try_decode(Json, Options),\n        check_decode_remainings(Remainings),\n        Value\n    catch\n        error:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE-\u003e\n            erlang:raise(error, Reason, [StackItem])\n    end.\n```\n\nUsing `erlfmt`:\n```console\n$ erlfmt baz.erl\nbaz.erl:6:29: syntax error before: ':'\n-module(baz).\n\n-ifdef('OTP_RELEASE').\n%% The 'OTP_RELEASE' macro introduced at OTP-21,\n%% so we can use it for detecting whether the Erlang compiler supports new try/catch syntax or not.\n-define(CAPTURE_STACKTRACE, :__StackTrace).\n-define(GET_STACKTRACE, __StackTrace).\n-else.\n-define(CAPTURE_STACKTRACE,).\n-define(GET_STACKTRACE, erlang:get_stacktrace()).\n-endif.\n\ndecode(Json, Options) -\u003e\ntry\n{ok, Value, Remainings} = try_decode(Json, Options),\ncheck_decode_remainings(Remainings),\nValue\ncatch\nerror:{badmatch, {error, {Reason, [StackItem]}}} ?CAPTURE_STACKTRACE -\u003e\nerlang:raise(error, Reason, [StackItem])\nend.\nbaz.erl:19:50: syntax error before: '?'\n```\n\n### Formatting speed\n\nThe following benchmark compares the time to format all \"*.erl\" files contained in the OTP-24 source distribution.\n```console\n// OS and CPU spec.\n$ uname -a\nLinux 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\n$ cat /proc/cpuinfo | grep 'model name' | head -1\nmodel name      : 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz\n\n// Downloads OTP source code. There are 3,737 \"*.erl\" files.\n$ wget https://erlang.org/download/otp_src_24.1.tar.gz\n$ tar zxvf otp_src_24.1.tar.gz\n$ cd otp_src_24.1/\n$ find . -name '*.erl' | wc -l\n3737\n\n// Erlang version: Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]\n\n// erlfmt: 17.30s\n$ time erlfmt (find . -name '*.erl') \u003e /dev/null 2\u003e /dev/null\n________________________________________________________\nExecuted in   17.30 secs\n   usr time   97.73 secs\n   sys time   10.20 secs\n\n// efmt: 5.84s\n$ time efmt --parallel $(find . -name '*.erl') \u003e /dev/null 2\u003e /dev/null\n________________________________________________________\nExecuted in    5.84 secs\n   usr time   43.88 secs\n   sys time    1.28 secs\n```\n\n### Development phase\n\n`erlfmt` has released the stable version (v1), but `efmt` hasn't.\nPerhaps some parts of the `efmt` style will change in future releases until it releases v1.\n\nLimitations\n-----------\n\nThere are some limitations that are not planned to be addressed in the future:\n- Only supports UTF-8 files\n- Doesn't process parse transforms\n  - That is, if a parse transform has introduced custom syntaxes in your Erlang code, `efmt` could fail\n- Doesn't process `-include().` and `-include_lib().` directives\n  - Macros defined in those include files are expanded to (dummy) atoms.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsile%2Fefmt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsile%2Fefmt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsile%2Fefmt/lists"}