From 2191dab65726012b057402e13132dd7a062d8440 Mon Sep 17 00:00:00 2001
From: Kevin Amado <kamadorueda@gmail.com>
Date: Fri, 11 Mar 2022 08:57:28 -0500
Subject: [PATCH] nix-fmt: add command

---
 src/nix/flake.cc    | 12 ++++++++++
 src/nix/fmt.cc      | 53 +++++++++++++++++++++++++++++++++++++++++++++
 src/nix/fmt.md      | 53 +++++++++++++++++++++++++++++++++++++++++++++
 tests/fmt.sh        | 28 ++++++++++++++++++++++++
 tests/fmt.simple.sh |  1 +
 tests/local.mk      |  1 +
 6 files changed, 148 insertions(+)
 create mode 100644 src/nix/fmt.cc
 create mode 100644 src/nix/fmt.md
 create mode 100644 tests/fmt.sh
 create mode 100755 tests/fmt.simple.sh

diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 47a380238..9830ce841 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -527,6 +527,16 @@ struct CmdFlakeCheck : FlakeCommand
                             }
                         }
 
+                        else if (name == "formatter") {
+                            state->forceAttrs(vOutput, pos);
+                            for (auto & attr : *vOutput.attrs) {
+                                checkSystemName(attr.name, *attr.pos);
+                                checkApp(
+                                    fmt("%s.%s", name, attr.name),
+                                    *attr.value, *attr.pos);
+                            }
+                        }
+
                         else if (name == "packages" || name == "devShells") {
                             state->forceAttrs(vOutput, pos);
                             for (auto & attr : *vOutput.attrs) {
@@ -1010,6 +1020,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
                     || (attrPath.size() == 1 && (
                             attrPath[0] == "defaultPackage"
                             || attrPath[0] == "devShell"
+                            || attrPath[0] == "formatter"
                             || attrPath[0] == "nixosConfigurations"
                             || attrPath[0] == "nixosModules"
                             || attrPath[0] == "defaultApp"
@@ -1059,6 +1070,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
 
                 else if (
                     (attrPath.size() == 2 && attrPath[0] == "defaultApp") ||
+                    (attrPath.size() == 2 && attrPath[0] == "formatter") ||
                     (attrPath.size() == 3 && attrPath[0] == "apps"))
                 {
                     auto aType = visitor.maybeGetAttr("type");
diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc
new file mode 100644
index 000000000..e5d44bd38
--- /dev/null
+++ b/src/nix/fmt.cc
@@ -0,0 +1,53 @@
+#include "command.hh"
+#include "run.hh"
+
+using namespace nix;
+
+struct CmdFmt : SourceExprCommand {
+    std::vector<std::string> args;
+
+    CmdFmt() { expectArgs({.label = "args", .handler = {&args}}); }
+
+    std::string description() override {
+        return "reformat your code in the standard style";
+    }
+
+    std::string doc() override {
+        return
+          #include "fmt.md"
+          ;
+    }
+
+    Category category() override { return catSecondary; }
+
+    Strings getDefaultFlakeAttrPaths() override {
+        return Strings{"formatter." + settings.thisSystem.get()};
+    }
+
+    Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; }
+
+    void run(ref<Store> store) {
+        auto evalState = getEvalState();
+        auto evalStore = getEvalStore();
+
+        auto installable = parseInstallable(store, ".");
+        auto app = installable->toApp(*evalState).resolve(evalStore, store);
+
+        Strings programArgs{app.program};
+
+        // Propagate arguments from the CLI
+        if (args.empty()) {
+            // Format the current flake out of the box
+            programArgs.push_back(".");
+        } else {
+            // User wants more power, let them decide which paths to include/exclude
+            for (auto &i : args) {
+                programArgs.push_back(i);
+            }
+        }
+
+        runProgramInStore(store, app.program, programArgs);
+    };
+};
+
+static auto r2 = registerCommand<CmdFmt>("fmt");
diff --git a/src/nix/fmt.md b/src/nix/fmt.md
new file mode 100644
index 000000000..1c78bb36f
--- /dev/null
+++ b/src/nix/fmt.md
@@ -0,0 +1,53 @@
+R""(
+
+# Examples
+
+With [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt):
+
+```nix
+# flake.nix
+{
+  outputs = { nixpkgs, self }: {
+    formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt;
+  };
+}
+```
+
+- Format the current flake: `$ nix fmt`
+
+- Format a specific folder or file: `$ nix fmt ./folder ./file.nix`
+
+With [nixfmt](https://github.com/serokell/nixfmt):
+
+```nix
+# flake.nix
+{
+  outputs = { nixpkgs, self }: {
+    formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
+  };
+}
+```
+
+- Format specific files: `$ nix fmt ./file1.nix ./file2.nix`
+
+With [Alejandra](https://github.com/kamadorueda/alejandra):
+
+```nix
+# flake.nix
+{
+  outputs = { nixpkgs, self }: {
+    formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra;
+  };
+}
+```
+
+- Format the current flake: `$ nix fmt`
+
+- Format a specific folder or file: `$ nix fmt ./folder ./file.nix`
+
+# Description
+
+`nix fmt` will rewrite all Nix files (\*.nix) to a canonical format
+using the formatter specified in your flake.
+
+)""
diff --git a/tests/fmt.sh b/tests/fmt.sh
new file mode 100644
index 000000000..7df1c82d3
--- /dev/null
+++ b/tests/fmt.sh
@@ -0,0 +1,28 @@
+source common.sh
+
+set -o pipefail
+
+clearStore
+rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
+
+cp ./simple.nix ./simple.builder.sh ./fmt.simple.sh ./config.nix $TEST_HOME
+
+cd $TEST_HOME
+
+nix fmt --help | grep "Format"
+
+cat << EOF > flake.nix
+{
+  outputs = _: {
+    formatter.$system = {
+      type = "app";
+      program = ./fmt.simple.sh;
+    };
+  };
+}
+EOF
+nix fmt ./file ./folder | grep 'Formatting: ./file ./folder'
+nix flake check
+nix flake show | grep -P 'x86_64-linux|x86_64-darwin'
+
+clearStore
diff --git a/tests/fmt.simple.sh b/tests/fmt.simple.sh
new file mode 100755
index 000000000..4c8c67ebb
--- /dev/null
+++ b/tests/fmt.simple.sh
@@ -0,0 +1 @@
+echo Formatting: "${@}"
diff --git a/tests/local.mk b/tests/local.mk
index 8032fc38a..b42a5bccb 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -77,6 +77,7 @@ nix_tests = \
   post-hook.sh \
   function-trace.sh \
   flake-local-settings.sh \
+  fmt.sh \
   eval-store.sh \
   why-depends.sh \
   import-derivation.sh \