commit 6904097171ec878bdfacc34bd8c8f108592dedc0 Author: Brian McGee Date: Sat Dec 23 12:50:47 2023 +0000 feat: initial import diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..631de79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# editors +.idea + +# nix +result* +repl-result-* + +# direnv +/.direnv + +# devshell +/.data diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..fd2155d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Nits Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6a851c1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,187 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": ["nixpkgs"], + "systems": "systems" + }, + "locked": { + "lastModified": 1700815693, + "narHash": "sha256-JtKZEQUzosrCwDsLgm+g6aqbP1aseUl1334OShEAS3s=", + "owner": "numtide", + "repo": "devshell", + "rev": "7ad1c417c87e98e56dcef7ecd0e0a2f2e5669d51", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1698882062, + "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-root": { + "locked": { + "lastModified": 1692742795, + "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=", + "owner": "srid", + "repo": "flake-root", + "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937", + "type": "github" + }, + "original": { + "owner": "srid", + "repo": "flake-root", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gomod2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": ["nixpkgs"] + }, + "locked": { + "lastModified": 1699950847, + "narHash": "sha256-xN/yVtqHb7kimHA/WvQFrEG5WS38t0K+A/W+j/WhQWM=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "05c993c9a5bd55a629cd45ed49951557b7e9c61a", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1702312524, + "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a9bf124c46ef298113270b1f84a164865987a91c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1698611440, + "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "flake-root": "flake-root", + "gomod2nix": "gomod2nix", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": ["nixpkgs"] + }, + "locked": { + "lastModified": 1699786194, + "narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "e82f32aa7f06bbbd56d7b12186d555223dc399d1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8a4ad1a --- /dev/null +++ b/flake.nix @@ -0,0 +1,39 @@ +{ + description = "Treefmt"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-root.url = "github:srid/flake-root"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + devshell = { + url = "github:numtide/devshell"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + gomod2nix = { + url = "github:nix-community/gomod2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs @ {flake-parts, ...}: + flake-parts.lib.mkFlake + { + inherit inputs; + } + { + imports = [ + ./nix + ]; + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + }; +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5b427b7 --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module github.com/numtide/treefmt + +go 1.21 + +require ( + github.com/BurntSushi/toml v1.3.2 + github.com/adrg/xdg v0.4.0 + github.com/alecthomas/kong v0.8.1 + github.com/charmbracelet/log v0.3.1 + github.com/gobwas/glob v0.2.3 + github.com/juju/errors v1.0.0 + github.com/stretchr/testify v1.8.4 + github.com/vmihailenco/msgpack/v5 v5.4.1 + github.com/ztrue/shutdown v0.1.1 + go.etcd.io/bbolt v1.3.8 + golang.org/x/sync v0.5.0 +) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/lipgloss v0.9.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8346b8b --- /dev/null +++ b/go.sum @@ -0,0 +1,73 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= +github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA= +github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY= +github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= +github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= +github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= +github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= +github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/ztrue/shutdown v0.1.1 h1:GKR2ye2OSQlq1GNVE/s2NbrIMsFdmL+NdR6z6t1k+Tg= +github.com/ztrue/shutdown v0.1.1/go.mod h1:hcMWcM2SwIsQk7Wb49aYme4tX66x6iLzs07w1OYAQLw= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..3dbee60 --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,81 @@ +schema = 3 + +[mod] + [mod."github.com/BurntSushi/toml"] + version = "v1.3.2" + hash = "sha256-FIwyH67KryRWI9Bk4R8s1zFP0IgKR4L66wNQJYQZLeg=" + [mod."github.com/adrg/xdg"] + version = "v0.4.0" + hash = "sha256-zGjkdUQmrVqD6rMO9oDY+TeJCpuqnHyvkPCaXDlac/U=" + [mod."github.com/alecthomas/kong"] + version = "v0.8.1" + hash = "sha256-170mjSrLNC+0W1KhXltaa+YWYgt5gJQEcfssepcyh4E=" + [mod."github.com/aymanbagabas/go-osc52/v2"] + version = "v2.0.1" + hash = "sha256-6Bp0jBZ6npvsYcKZGHHIUSVSTAMEyieweAX2YAKDjjg=" + [mod."github.com/charmbracelet/lipgloss"] + version = "v0.9.1" + hash = "sha256-AHbabOymgDRIXsMBgJHS25/GgBWT54oGbd15EBWKeZc=" + [mod."github.com/charmbracelet/log"] + version = "v0.3.1" + hash = "sha256-Er60POPID2eNrRZnBHxoI4yHn0mIKnXYftGKSslbXx0=" + [mod."github.com/davecgh/go-spew"] + version = "v1.1.1" + hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" + [mod."github.com/go-logfmt/logfmt"] + version = "v0.6.0" + hash = "sha256-RtIG2qARd5sT10WQ7F3LR8YJhS8exs+KiuUiVf75bWg=" + [mod."github.com/gobwas/glob"] + version = "v0.2.3" + hash = "sha256-hYHMUdwxVkMOjSKjR7UWO0D0juHdI4wL8JEy5plu/Jc=" + [mod."github.com/juju/errors"] + version = "v1.0.0" + hash = "sha256-9uZ0wNf44ilzLsvXqOsmFUpNOBFAVadj6+ZH8+QMDMk=" + [mod."github.com/lucasb-eyer/go-colorful"] + version = "v1.2.0" + hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" + [mod."github.com/mattn/go-isatty"] + version = "v0.0.18" + hash = "sha256-QpIn0DSggtBn2ocyj0RlXDKLK5F5KZG1/ogzrqBCjF8=" + [mod."github.com/mattn/go-runewidth"] + version = "v0.0.15" + hash = "sha256-WP39EU2UrQbByYfnwrkBDoKN7xzXsBssDq3pNryBGm0=" + [mod."github.com/muesli/reflow"] + version = "v0.3.0" + hash = "sha256-Pou2ybE9SFSZG6YfZLVV1Eyfm+X4FuVpDPLxhpn47Cc=" + [mod."github.com/muesli/termenv"] + version = "v0.15.2" + hash = "sha256-Eum/SpyytcNIchANPkG4bYGBgcezLgej7j/+6IhqoMU=" + [mod."github.com/pmezard/go-difflib"] + version = "v1.0.0" + hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=" + [mod."github.com/rivo/uniseg"] + version = "v0.2.0" + hash = "sha256-GLj0jiGrT03Ept4V6FXCN1yeZ/b6PpS3MEXK6rYQ8Eg=" + [mod."github.com/stretchr/testify"] + version = "v1.8.4" + hash = "sha256-MoOmRzbz9QgiJ+OOBo5h5/LbilhJfRUryvzHJmXAWjo=" + [mod."github.com/vmihailenco/msgpack/v5"] + version = "v5.4.1" + hash = "sha256-pDplX6xU6UpNLcFbO1pRREW5vCnSPvSU+ojAwFDv3Hk=" + [mod."github.com/vmihailenco/tagparser/v2"] + version = "v2.0.0" + hash = "sha256-M9QyaKhSmmYwsJk7gkjtqu9PuiqZHSmTkous8VWkWY0=" + [mod."github.com/ztrue/shutdown"] + version = "v0.1.1" + hash = "sha256-+ygx5THHu9g+vBAn6b63tV35bvQGdRyto4pLhkontJI=" + [mod."go.etcd.io/bbolt"] + version = "v1.3.8" + hash = "sha256-ekKy8198B2GfPldHLYZnvNjID6x07dUPYKgFx84TgVs=" + [mod."golang.org/x/exp"] + version = "v0.0.0-20231006140011-7918f672742d" + hash = "sha256-2SO1etTQ6UCUhADR5sgvDEDLHcj77pJKCIa/8mGDbAo=" + [mod."golang.org/x/sync"] + version = "v0.5.0" + hash = "sha256-EAKeODSsct5HhXPmpWJfulKSCkuUu6kkDttnjyZMNcI=" + [mod."golang.org/x/sys"] + version = "v0.13.0" + hash = "sha256-/+RDZ0a0oEfJ0k304VqpJpdrl2ZXa3yFlOxy4mjW7w0=" + [mod."gopkg.in/yaml.v3"] + version = "v3.0.1" + hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU=" diff --git a/internal/cache/cache.go b/internal/cache/cache.go new file mode 100644 index 0000000..d10469e --- /dev/null +++ b/internal/cache/cache.go @@ -0,0 +1,144 @@ +package cache + +import ( + "context" + "crypto/sha1" + "encoding/base32" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/adrg/xdg" + "github.com/juju/errors" + "github.com/vmihailenco/msgpack/v5" + bolt "go.etcd.io/bbolt" +) + +const ( + modifiedBucket = "modified" +) + +var db *bolt.DB + +func Open(treeRoot string, clean bool) (err error) { + // determine a unique and consistent db name for the tree root + h := sha1.New() + h.Write([]byte(treeRoot)) + digest := h.Sum(nil) + + name := base32.StdEncoding.EncodeToString(digest) + path, err := xdg.CacheFile(fmt.Sprintf("treefmt/eval-cache/%v.db", name)) + + // bust the cache if specified + if clean { + err := os.Remove(path) + if errors.Is(err, os.ErrNotExist) { + err = nil + } else if err != nil { + return errors.Annotate(err, "failed to clear cache") + } + } + + if err != nil { + return errors.Annotate(err, "could not resolve local path for the cache") + } + + db, err = bolt.Open(path, 0o600, nil) + if err != nil { + return errors.Annotate(err, "failed to open cache") + } + + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte(modifiedBucket)) + if errors.Is(err, bolt.ErrBucketExists) { + return nil + } + return err + }) + + return +} + +func Close() error { + return db.Close() +} + +func ChangeSet(ctx context.Context, root string, pathsCh chan<- string) error { + return db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(modifiedBucket)) + + return filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return errors.Annotate(err, "failed to walk path") + } else if ctx.Err() != nil { + return ctx.Err() + } else if info.IsDir() { + // todo what about symlinks? + return nil + } + + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + // skip symlinks + return nil + } + + b := bucket.Get([]byte(path)) + + var cached FileInfo + + if b != nil { + if err = msgpack.Unmarshal(b, &cached); err != nil { + return errors.Annotatef(err, "failed to unmarshal cache info for path '%v'", path) + } + } + + changedOrNew := !(cached.Modified == info.ModTime() && cached.Size == info.Size()) + + if !changedOrNew { + // no change + return nil + } + + // pass on the path + pathsCh <- path + return nil + }) + }) +} + +func WriteModTime(paths []string) error { + if len(paths) == 0 { + return nil + } + + return db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(modifiedBucket)) + + for _, path := range paths { + if path == "" { + continue + } + pathInfo, err := os.Stat(path) + if err != nil { + return err + } + + cacheInfo := FileInfo{ + Size: pathInfo.Size(), + Modified: pathInfo.ModTime(), + } + + bytes, err := msgpack.Marshal(cacheInfo) + if err != nil { + return errors.Annotate(err, "failed to marshal mod time") + } + + if err = bucket.Put([]byte(path), bytes); err != nil { + return errors.Annotate(err, "failed to put mode time") + } + } + + return nil + }) +} diff --git a/internal/cache/types.go b/internal/cache/types.go new file mode 100644 index 0000000..d78e953 --- /dev/null +++ b/internal/cache/types.go @@ -0,0 +1,8 @@ +package cache + +import "time" + +type FileInfo struct { + Size int64 + Modified time.Time +} diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..8007a31 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,29 @@ +package cli + +import "github.com/charmbracelet/log" + +var Cli struct { + Log LogOptions `embed:""` + + ConfigFile string `type:"existingfile" default:"./treefmt.toml"` + TreeRoot string `type:"existingdir" default:"."` + ClearCache bool `short:"c" help:"Reset the evaluation cache. Use in case the cache is not precise enough"` + + Format Format `cmd:"" default:"."` +} + +type LogOptions struct { + Verbosity int `name:"verbose" short:"v" type:"counter" default:"0" env:"LOG_LEVEL" help:"Set the verbosity of logs e.g. -vv"` +} + +func (lo *LogOptions) ConfigureLogger() { + log.SetReportTimestamp(false) + + if lo.Verbosity == 0 { + log.SetLevel(log.WarnLevel) + } else if lo.Verbosity == 1 { + log.SetLevel(log.InfoLevel) + } else if lo.Verbosity >= 2 { + log.SetLevel(log.DebugLevel) + } +} diff --git a/internal/cli/format.go b/internal/cli/format.go new file mode 100644 index 0000000..bae2eb4 --- /dev/null +++ b/internal/cli/format.go @@ -0,0 +1,155 @@ +package cli + +import ( + "context" + "fmt" + "time" + + "github.com/numtide/treefmt/internal/cache" + "github.com/numtide/treefmt/internal/format" + + "github.com/charmbracelet/log" + "github.com/juju/errors" + "github.com/ztrue/shutdown" + "golang.org/x/sync/errgroup" +) + +type Format struct{} + +func (f *Format) Run() error { + start := time.Now() + + Cli.Log.ConfigureLogger() + + l := log.WithPrefix("format") + + defer func() { + if err := cache.Close(); err != nil { + l.Errorf("failed to close cache: %v", err) + } + }() + + // create an overall context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // register shutdown hook + shutdown.Add(cancel) + + // read config + cfg, err := format.ReadConfigFile(Cli.ConfigFile) + if err != nil { + return errors.Annotate(err, "failed to read config file") + } + + // init formatters + for name, formatter := range cfg.Formatters { + if err = formatter.Init(name); err != nil { + return errors.Annotatef(err, "failed to initialise formatter: %v", name) + } + } + + ctx = format.RegisterFormatters(ctx, cfg.Formatters) + + if err = cache.Open(Cli.TreeRoot, Cli.ClearCache); err != nil { + return err + } + + // + pendingCh := make(chan string, 1024) + completedCh := make(chan string, 1024) + + ctx = format.SetCompletedChannel(ctx, completedCh) + + // + eg, ctx := errgroup.WithContext(ctx) + + // start the formatters + for name := range cfg.Formatters { + formatter := cfg.Formatters[name] + eg.Go(func() error { + return formatter.Run(ctx) + }) + } + + // determine paths to be formatted + pathsCh := make(chan string, 1024) + + // update cache as paths are completed + eg.Go(func() error { + batchSize := 1024 + batch := make([]string, batchSize) + + var pending, completed int + + LOOP: + for { + select { + case _, ok := <-pendingCh: + if ok { + pending += 1 + } else if pending == completed { + break LOOP + } + + case path, ok := <-completedCh: + if !ok { + break LOOP + } + batch = append(batch, path) + if len(batch) == batchSize { + if err := cache.WriteModTime(batch); err != nil { + return err + } + batch = batch[:0] + } + + completed += 1 + + if completed == pending { + close(completedCh) + } + } + } + + // final flush + if err := cache.WriteModTime(batch); err != nil { + return err + } + + println(fmt.Sprintf("%v files changed in %v", completed, time.Now().Sub(start))) + return nil + }) + + eg.Go(func() error { + count := 0 + + for path := range pathsCh { + // todo cycle detection in Befores + for _, formatter := range cfg.Formatters { + if formatter.Wants(path) { + pendingCh <- path + count += 1 + formatter.Put(path) + } + } + } + + for _, formatter := range cfg.Formatters { + formatter.Close() + } + + if count == 0 { + close(completedCh) + } + + return nil + }) + + eg.Go(func() error { + defer close(pathsCh) + return cache.ChangeSet(ctx, Cli.TreeRoot, pathsCh) + }) + + return eg.Wait() +} diff --git a/internal/format/config.go b/internal/format/config.go new file mode 100644 index 0000000..6b786c3 --- /dev/null +++ b/internal/format/config.go @@ -0,0 +1,12 @@ +package format + +import "github.com/BurntSushi/toml" + +type Config struct { + Formatters map[string]*Formatter `toml:"formatter"` +} + +func ReadConfigFile(path string) (cfg *Config, err error) { + _, err = toml.DecodeFile(path, &cfg) + return +} diff --git a/internal/format/config_test.go b/internal/format/config_test.go new file mode 100644 index 0000000..56c0b43 --- /dev/null +++ b/internal/format/config_test.go @@ -0,0 +1,122 @@ +package format + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConfig(t *testing.T) { + as := require.New(t) + + cfg, err := ReadConfigFile("../../test/treefmt.toml") + as.NoError(err, "failed to read config file") + + as.NotNil(cfg) + + // python + python, ok := cfg.Formatters["python"] + as.True(ok, "python formatter not found") + as.Equal("black", python.Command) + as.Nil(python.Options) + as.Equal([]string{"*.py"}, python.Includes) + as.Nil(python.Excludes) + + // elm + elm, ok := cfg.Formatters["elm"] + as.True(ok, "elm formatter not found") + as.Equal("elm-format", elm.Command) + as.Equal([]string{"--yes"}, elm.Options) + as.Equal([]string{"*.elm"}, elm.Includes) + as.Nil(elm.Excludes) + + // go + golang, ok := cfg.Formatters["go"] + as.True(ok, "go formatter not found") + as.Equal("gofmt", golang.Command) + as.Equal([]string{"-w"}, golang.Options) + as.Equal([]string{"*.go"}, golang.Includes) + as.Nil(golang.Excludes) + + // haskell + haskell, ok := cfg.Formatters["haskell"] + as.True(ok, "haskell formatter not found") + as.Equal("ormolu", haskell.Command) + as.Equal([]string{ + "--ghc-opt", "-XBangPatterns", + "--ghc-opt", "-XPatternSynonyms", + "--ghc-opt", "-XTypeApplications", + "--mode", "inplace", + "--check-idempotence", + }, haskell.Options) + as.Equal([]string{"*.hs"}, haskell.Includes) + as.Equal([]string{"examples/haskell/"}, haskell.Excludes) + + // nix + nix, ok := cfg.Formatters["nix"] + as.True(ok, "nix formatter not found") + as.Equal("nixpkgs-fmt", nix.Command) + as.Nil(nix.Options) + as.Equal([]string{"*.nix"}, nix.Includes) + as.Equal([]string{"examples/nix/sources.nix"}, nix.Excludes) + + // ruby + ruby, ok := cfg.Formatters["ruby"] + as.True(ok, "ruby formatter not found") + as.Equal("rufo", ruby.Command) + as.Equal([]string{"-x"}, ruby.Options) + as.Equal([]string{"*.rb"}, ruby.Includes) + as.Nil(ruby.Excludes) + + // prettier + prettier, ok := cfg.Formatters["prettier"] + as.True(ok, "prettier formatter not found") + as.Equal("prettier", prettier.Command) + as.Equal([]string{"--write"}, prettier.Options) + as.Equal([]string{ + "*.css", + "*.html", + "*.js", + "*.json", + "*.jsx", + "*.md", + "*.mdx", + "*.scss", + "*.ts", + "*.yaml", + }, prettier.Includes) + as.Equal([]string{"CHANGELOG.md"}, prettier.Excludes) + + // rust + // rust, ok := cfg.Formatters["rust"] + // as.True(ok, "rust formatter not found") + // as.Equal("rustfmt", rust.Command) + // as.Equal([]string{"--edition", "2018"}, rust.Options) + // as.Equal([]string{"*.rs"}, rust.Includes) + // as.Nil(rust.Excludes) + + // shell + shell, ok := cfg.Formatters["shell"] + as.True(ok, "shell formatter not found") + as.Equal("/bin/sh", shell.Command) + as.Equal([]string{ + "-euc", + `# First lint all the scripts +shellcheck "$@" + +# Then format them +shfmt -i 2 -s -w "$@" + `, + "--", + }, shell.Options) + as.Equal([]string{"*.sh"}, shell.Includes) + as.Nil(shell.Excludes) + + // terraform + terraform, ok := cfg.Formatters["terraform"] + as.True(ok, "terraform formatter not found") + as.Equal("terraform", terraform.Command) + as.Equal([]string{"fmt"}, terraform.Options) + as.Equal([]string{"*.tf"}, terraform.Includes) + as.Nil(terraform.Excludes) +} diff --git a/internal/format/context.go b/internal/format/context.go new file mode 100644 index 0000000..207ca4a --- /dev/null +++ b/internal/format/context.go @@ -0,0 +1,36 @@ +package format + +import ( + "context" +) + +const ( + formattersKey = "formatters" + completedChKey = "completedCh" +) + +func RegisterFormatters(ctx context.Context, formatters map[string]*Formatter) context.Context { + return context.WithValue(ctx, formattersKey, formatters) +} + +func GetFormatters(ctx context.Context) map[string]*Formatter { + return ctx.Value(formattersKey).(map[string]*Formatter) +} + +func SetCompletedChannel(ctx context.Context, completedCh chan string) context.Context { + return context.WithValue(ctx, completedChKey, completedCh) +} + +func MarkFormatComplete(ctx context.Context, path string) { + ctx.Value(completedChKey).(chan string) <- path +} + +func ForwardPath(ctx context.Context, path string, names []string) { + if len(names) == 0 { + return + } + formatters := GetFormatters(ctx) + for _, name := range names { + formatters[name].Put(path) + } +} diff --git a/internal/format/format.go b/internal/format/format.go new file mode 100644 index 0000000..8d446b3 --- /dev/null +++ b/internal/format/format.go @@ -0,0 +1,161 @@ +package format + +import ( + "context" + "os/exec" + "strings" + "time" + + "github.com/charmbracelet/log" + "github.com/gobwas/glob" + "github.com/juju/errors" +) + +type Formatter struct { + Name string + Command string + Options []string + Includes []string + Excludes []string + Before []string + + log *log.Logger + + // globs for matching against paths + includes []glob.Glob + excludes []glob.Glob + + inbox chan string + + batch []string + batchSize int +} + +func (f *Formatter) Init(name string) error { + f.Name = name + f.log = log.WithPrefix("format | " + name) + + f.inbox = make(chan string, 1024) + + f.batchSize = 1024 + f.batch = make([]string, f.batchSize) + f.batch = f.batch[:0] + + // todo refactor common code below + if len(f.Includes) > 0 { + for _, pattern := range f.Includes { + if !strings.Contains(pattern, "/") { + pattern = "**/" + pattern + } + g, err := glob.Compile(pattern) + if err != nil { + return errors.Annotatef(err, "failed to compile include pattern '%v' for formatter '%v'", pattern, f.Name) + } + f.includes = append(f.includes, g) + } + } + + if len(f.Excludes) > 0 { + for _, pattern := range f.Excludes { + if !strings.Contains(pattern, "/") { + pattern = "**/" + pattern + } + g, err := glob.Compile(pattern) + if err != nil { + return errors.Annotatef(err, "failed to compile exclude pattern '%v' for formatter '%v'", pattern, f.Name) + } + f.excludes = append(f.excludes, g) + } + } + + return nil +} + +func (f *Formatter) Wants(path string) bool { + if PathMatches(path, f.excludes) { + return false + } + return PathMatches(path, f.includes) +} + +func (f *Formatter) Put(path string) { + f.inbox <- path +} + +func (f *Formatter) Run(ctx context.Context) (err error) { +LOOP: + for { + select { + case <-ctx.Done(): + err = ctx.Err() + break LOOP + + case path, ok := <-f.inbox: + if !ok { + break LOOP + } + + // add to the current batch + f.batch = append(f.batch, path) + + if len(f.batch) == f.batchSize { + // drain immediately + if err := f.apply(ctx); err != nil { + break LOOP + } + } + } + } + + if err != nil { + return + } + + // final flush + return f.apply(ctx) +} + +func (f *Formatter) apply(ctx context.Context) error { + // empty check + if len(f.batch) == 0 { + return nil + } + + // construct args, starting with config + args := f.Options + + // append each file path + for _, path := range f.batch { + args = append(args, path) + } + + start := time.Now() + cmd := exec.CommandContext(ctx, f.Command, args...) + + if _, err := cmd.CombinedOutput(); err != nil { + // todo log output + return err + } + + f.log.Infof("%v files processed in %v", len(f.batch), time.Now().Sub(start)) + + // mark completed or forward on + if len(f.Before) == 0 { + for _, path := range f.batch { + MarkFormatComplete(ctx, path) + } + } else { + for _, path := range f.batch { + ForwardPath(ctx, path, f.Before) + } + } + + // reset batch + f.batch = f.batch[:0] + + return nil +} + +func (f *Formatter) Close() { + close(f.inbox) +} diff --git a/internal/format/glob.go b/internal/format/glob.go new file mode 100644 index 0000000..e883fb6 --- /dev/null +++ b/internal/format/glob.go @@ -0,0 +1,15 @@ +package format + +import ( + "github.com/gobwas/glob" +) + +func PathMatches(path string, globs []glob.Glob) bool { + for idx := range globs { + if globs[idx].Match(path) { + return true + } + } + + return false +} diff --git a/internal/log/writer.go b/internal/log/writer.go new file mode 100644 index 0000000..25daa88 --- /dev/null +++ b/internal/log/writer.go @@ -0,0 +1,21 @@ +package log + +import ( + "bufio" + "bytes" + + "github.com/charmbracelet/log" +) + +type Writer struct { + Log *log.Logger +} + +func (l *Writer) Write(p []byte) (n int, err error) { + scanner := bufio.NewScanner(bytes.NewReader(p)) + for scanner.Scan() { + line := scanner.Text() + l.Log.Debug(line) + } + return len(p), nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ca6d3f9 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/alecthomas/kong" + "github.com/numtide/treefmt/internal/cli" +) + +func main() { + ctx := kong.Parse(&cli.Cli) + ctx.FatalIfErrorf(ctx.Run()) +} diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 0000000..8216515 --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,5 @@ +{lib, ...}: { + perSystem = {self', ...}: { + checks = with lib; mapAttrs' (n: nameValuePair "package-${n}") self'.packages; + }; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..8d2322e --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,10 @@ +{inputs, ...}: { + imports = [ + inputs.flake-root.flakeModule + ./checks.nix + ./devshell.nix + ./nixpkgs.nix + ./packages.nix + ./treefmt.nix + ]; +} diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 0000000..391bd14 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,58 @@ +{inputs, ...}: { + imports = [ + inputs.devshell.flakeModule + ]; + + config.perSystem = { + pkgs, + config, + ... + }: { + config.devshells.default = { + env = [ + { + name = "GOROOT"; + value = pkgs.go + "/share/go"; + } + { + name = "LD_LIBRARY_PATH"; + value = "$DEVSHELL_DIR/lib"; + } + ]; + + packages = with pkgs; [ + # golang + go + go-tools + delve + golangci-lint + + # formatters for testing + + elmPackages.elm-format + haskellPackages.cabal-fmt + haskellPackages.ormolu + mdsh + nixpkgs-fmt + nodePackages.prettier + python3.pkgs.black + rufo + rustfmt + shellcheck + shfmt + terraform + ]; + + commands = [ + { + category = "development"; + package = pkgs.gomod2nix; + } + { + category = "development"; + package = pkgs.enumer; + } + ]; + }; + }; +} diff --git a/nix/nixpkgs.nix b/nix/nixpkgs.nix new file mode 100644 index 0000000..fd370ca --- /dev/null +++ b/nix/nixpkgs.nix @@ -0,0 +1,16 @@ +{inputs, ...}: { + perSystem = {system, ...}: { + # customise nixpkgs instance + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ + inputs.gomod2nix.overlays.default + ]; + config = { + # for terraform + # todo make this more specific + allowUnfree = true; + }; + }; + }; +} diff --git a/nix/packages.nix b/nix/packages.nix new file mode 100644 index 0000000..dd695b9 --- /dev/null +++ b/nix/packages.nix @@ -0,0 +1,42 @@ +{inputs, ...}: { + imports = [ + inputs.flake-parts.flakeModules.easyOverlay + ]; + + perSystem = { + self', + inputs', + lib, + pkgs, + ... + }: { + packages = rec { + treefmt = inputs'.gomod2nix.legacyPackages.buildGoApplication rec { + pname = "treefmt"; + version = "0.0.1+dev"; + + # ensure we are using the same version of go to build with + inherit (pkgs) go; + + src = ../.; + modules = ../gomod2nix.toml; + + ldflags = [ + "-X 'build.Name=${pname}'" + "-X 'build.Version=${version}'" + ]; + + meta = with lib; { + description = "treefmt: one CLI to format your repo"; + homepage = "https://github.com/numtide/treefmt"; + license = licenses.mit; + mainProgram = "treefmt"; + }; + }; + + default = treefmt; + }; + + overlayAttrs = self'.packages; + }; +} diff --git a/nix/treefmt.nix b/nix/treefmt.nix new file mode 100644 index 0000000..ed6fc52 --- /dev/null +++ b/nix/treefmt.nix @@ -0,0 +1,32 @@ +{inputs, ...}: { + imports = [ + inputs.treefmt-nix.flakeModule + ]; + perSystem = {config, ...}: { + treefmt.config = { + inherit (config.flake-root) projectRootFile; + flakeCheck = true; + flakeFormatter = true; + programs = { + alejandra.enable = true; + deadnix.enable = true; + gofumpt.enable = true; + prettier.enable = true; + statix.enable = true; + }; + + settings.formatter.prettier.options = ["--tab-width" "4"]; + }; + + devshells.default = { + commands = [ + { + category = "formatting"; + name = "fmt"; + help = "format the repo"; + command = "nix fmt"; + } + ]; + }; + }; +} diff --git a/test/echo.toml b/test/echo.toml new file mode 100644 index 0000000..9e3295c --- /dev/null +++ b/test/echo.toml @@ -0,0 +1,3 @@ +[formatter.echo] +command = "echo" +includes = [ "*.*" ] \ No newline at end of file diff --git a/test/examples/elm/elm.json b/test/examples/elm/elm.json new file mode 100644 index 0000000..2e54076 --- /dev/null +++ b/test/examples/elm/elm.json @@ -0,0 +1,22 @@ +{ + "type": "application", + "source-directories": ["src"], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/test/examples/elm/src/Main.elm b/test/examples/elm/src/Main.elm new file mode 100644 index 0000000..401f267 --- /dev/null +++ b/test/examples/elm/src/Main.elm @@ -0,0 +1,31 @@ +module Main exposing (Msg(..), main, update, view) + +import Browser +import Html exposing (Html, button, div, text) +import Html.Events exposing (onClick) + + +main = + Browser.sandbox { init = 0, update = update, view = view } + + +type Msg + = Increment + | Decrement + + +update msg model = + case msg of + Increment -> + model + 1 + + Decrement -> + model - 1 + + +view model = + div [] + [ button [ onClick Decrement ] [ text "-" ] + , div [] [ text (String.fromInt model) ] + , button [ onClick Increment ] [ text "+" ] + ] diff --git a/test/examples/go/go.mod b/test/examples/go/go.mod new file mode 100644 index 0000000..4f96884 --- /dev/null +++ b/test/examples/go/go.mod @@ -0,0 +1,3 @@ +module hello + +go 1.15 diff --git a/test/examples/go/main.go b/test/examples/go/main.go new file mode 100644 index 0000000..c048119 --- /dev/null +++ b/test/examples/go/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} diff --git a/test/examples/haskell-frontend/CHANGELOG.md b/test/examples/haskell-frontend/CHANGELOG.md new file mode 100644 index 0000000..f928d5e --- /dev/null +++ b/test/examples/haskell-frontend/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for haskell + +## 0.1.0.0 -- YYYY-mm-dd + +- First version. Released on an unsuspecting world. diff --git a/test/examples/haskell-frontend/Main.hs b/test/examples/haskell-frontend/Main.hs new file mode 100644 index 0000000..65ae4a0 --- /dev/null +++ b/test/examples/haskell-frontend/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "Hello, Haskell!" diff --git a/test/examples/haskell-frontend/Setup.hs b/test/examples/haskell-frontend/Setup.hs new file mode 100644 index 0000000..e8ef27d --- /dev/null +++ b/test/examples/haskell-frontend/Setup.hs @@ -0,0 +1,3 @@ +import Distribution.Simple + +main = defaultMain diff --git a/test/examples/haskell-frontend/haskell-frontend.cabal b/test/examples/haskell-frontend/haskell-frontend.cabal new file mode 100644 index 0000000..2997318 --- /dev/null +++ b/test/examples/haskell-frontend/haskell-frontend.cabal @@ -0,0 +1,25 @@ +cabal-version: >=1.10 +-- Initial package description 'haskell.cabal' generated by 'cabal init'. +-- For further documentation, see http://haskell.org/cabal/users-guide/ + +name: haskell-frontend +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +-- license: +license-file: LICENSE +author: Andika Demas Riyandi +maintainer: andika.riyan@gmail.com +-- copyright: +-- category: +build-type: Simple +extra-source-files: CHANGELOG.md + +executable haskell-frontend + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: base >=4.14 && <4.15 + -- hs-source-dirs: + default-language: Haskell2010 diff --git a/test/examples/haskell/CHANGELOG.md b/test/examples/haskell/CHANGELOG.md new file mode 100644 index 0000000..f928d5e --- /dev/null +++ b/test/examples/haskell/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for haskell + +## 0.1.0.0 -- YYYY-mm-dd + +- First version. Released on an unsuspecting world. diff --git a/test/examples/haskell/Foo.hs b/test/examples/haskell/Foo.hs new file mode 100644 index 0000000..2ece54e --- /dev/null +++ b/test/examples/haskell/Foo.hs @@ -0,0 +1,4 @@ +module Foo where + +foo :: IO () +foo = putStrLn "Hello, Riyan!" diff --git a/test/examples/haskell/Main.hs b/test/examples/haskell/Main.hs new file mode 100644 index 0000000..07a0935 --- /dev/null +++ b/test/examples/haskell/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "Hello, Riyan!" diff --git a/test/examples/haskell/Nested/Foo.hs b/test/examples/haskell/Nested/Foo.hs new file mode 100644 index 0000000..43dea3c --- /dev/null +++ b/test/examples/haskell/Nested/Foo.hs @@ -0,0 +1,4 @@ +module Nested.Foo where + +foo :: IO () +foo = putStrLn "Hello, Riyan!" diff --git a/test/examples/haskell/Setup.hs b/test/examples/haskell/Setup.hs new file mode 100644 index 0000000..e8ef27d --- /dev/null +++ b/test/examples/haskell/Setup.hs @@ -0,0 +1,3 @@ +import Distribution.Simple + +main = defaultMain diff --git a/test/examples/haskell/haskell.cabal b/test/examples/haskell/haskell.cabal new file mode 100644 index 0000000..a7d1fa5 --- /dev/null +++ b/test/examples/haskell/haskell.cabal @@ -0,0 +1,25 @@ +cabal-version: >=1.10 +-- Initial package description 'haskell.cabal' generated by 'cabal init'. +-- For further documentation, see http://haskell.org/cabal/users-guide/ + +name: haskell +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +-- license: +license-file: LICENSE +author: Andika Demas Riyandi +maintainer: andika.riyan@gmail.com +-- copyright: +-- category: +build-type: Simple +extra-source-files: CHANGELOG.md + +executable haskell + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: base >=4.14 && <4.15 + -- hs-source-dirs: + default-language: Haskell2010 diff --git a/test/examples/haskell/treefmt.toml b/test/examples/haskell/treefmt.toml new file mode 100644 index 0000000..6865944 --- /dev/null +++ b/test/examples/haskell/treefmt.toml @@ -0,0 +1,10 @@ +[formatter.haskell] +command = "ormolu" +options = [ + "--ghc-opt", "-XBangPatterns", + "--ghc-opt", "-XPatternSynonyms", + "--ghc-opt", "-XTypeApplications", + "--mode", "inplace", + "--check-idempotence", +] +includes = ["Foo.hs"] \ No newline at end of file diff --git a/test/examples/html/index.html b/test/examples/html/index.html new file mode 100644 index 0000000..201633d --- /dev/null +++ b/test/examples/html/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +

Hi!

+ + diff --git a/test/examples/html/scripts/.gitkeep b/test/examples/html/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/examples/javascript/source/hello.js b/test/examples/javascript/source/hello.js new file mode 100644 index 0000000..4d352ce --- /dev/null +++ b/test/examples/javascript/source/hello.js @@ -0,0 +1,65 @@ +const helloFactory = function ({ React }) { + const { string, func } = React.PropTypes; + + return function Hello(props) { + // React wants propTypes & defaultProps + // to be static. + Hello.propTypes = { + word: string, + mode: string, + + actions: React.PropTypes.shape({ + setWord: func.isRequired, + setMode: func.isRequired, + }), + }; + + return { + props, // set props + + componentDidUpdate() { + this.refs.wordInput.getDOMNode().focus(); + }, + + render() { + const { word, mode } = this.props; + + const { setMode, setWord } = this.props.actions; + + const styles = { + displayMode: { + display: mode === "display" ? "inline" : "none", + }, + + editMode: { + display: mode === "edit" ? "inline" : "none", + }, + }; + + const onKeyUp = function (e) { + if (e.key !== "Enter") return; + + setWord(e.target.value); + setMode("display"); + }; + + return ( +

+ Hello,  + setMode("edit")}> + {word}! + + +

+ ); + }, + }; + }; +}; + +export default helloFactory; diff --git a/test/examples/nix/sources.nix b/test/examples/nix/sources.nix new file mode 100644 index 0000000..dccca92 --- /dev/null +++ b/test/examples/nix/sources.nix @@ -0,0 +1,242 @@ +# This file has been generated by Niv. +let + # + # The fetchers. fetch_ fetches specs of type . + # + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true + then + builtins_fetchurl + { + inherit (spec) url sha256; + name = name'; + } + else + pkgs.fetchurl { + inherit (spec) url sha256; + name = name'; + }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true + then + builtins_fetchTarball + { + name = name'; + inherit (spec) url sha256; + } + else + pkgs.fetchzip { + name = name'; + inherit (spec) url sha256; + }; + + fetch_git = name: spec: + let + ref = + spec.ref + or ( + if spec ? branch + then "refs/heads/${spec.branch}" + else if spec ? tag + then "refs/tags/${spec.tag}" + else abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + in + builtins.fetchGit { + url = spec.repo; + inherit (spec) rev; + inherit ref; + }; + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: + throw + '' [${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: + throw + '' [${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: ( + concatMapStrings + (s: + if builtins.isList s + then "-" + else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath + then import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + if ! builtins.hasAttr "type" spec + then abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" + then fetch_file pkgs name spec + else if spec.type == "tarball" + then fetch_tarball pkgs name spec + else if spec.type == "git" + then fetch_git name spec + else if spec.type == "local" + then fetch_local spec + else if spec.type == "builtin-tarball" + then fetch_builtin-tarball name + else if spec.type == "builtin-url" + then fetch_builtin-url name + else abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = + stringAsChars + (c: + if ((builtins.match "[a-zA-Z0-9]" c) == null) + then "_" + else c) + name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" + then drv + else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" + then /. + ersatz + else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = + builtins.mapAttrs + or ( + f: set: + with builtins; + listToAttrs (map + (attr: { + name = attr; + value = f attr set.${attr}; + }) + (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: + if first > last + then [ ] + else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: + if cond + then as + else { }; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = + { url + , name ? null + , sha256 + , + } @ attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" + then fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = + { url + , name ? null + , sha256 + , + } @ attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" + then fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json + then ./sources.json + else null + , sources ? if (sourcesFile == null) + then { } + else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + , + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; +in +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/test/examples/python/main.py b/test/examples/python/main.py new file mode 100644 index 0000000..79fe90b --- /dev/null +++ b/test/examples/python/main.py @@ -0,0 +1,12 @@ +from flask import Flask + +app = Flask(__name__) + + +@app.route("/") +def hello_world(): + return "Hello world" + + +if __name__ == "__main__": + app.run() diff --git a/test/examples/python/requirements.txt b/test/examples/python/requirements.txt new file mode 100644 index 0000000..200fdf3 --- /dev/null +++ b/test/examples/python/requirements.txt @@ -0,0 +1 @@ +Flask==0.12.1 \ No newline at end of file diff --git a/test/examples/python/virtualenv_proxy.py b/test/examples/python/virtualenv_proxy.py new file mode 100644 index 0000000..cef7ff4 --- /dev/null +++ b/test/examples/python/virtualenv_proxy.py @@ -0,0 +1,104 @@ +import datetime +import os +import sys +import traceback + +if sys.version_info[0] == 3: + + def to_str(value): + return value.decode(sys.getfilesystemencoding()) + + def execfile(path, global_dict): + """Execute a file""" + with open(path, "r") as f: + code = f.read() + code = code.replace("\r\n", "\n") + "\n" + exec(code, global_dict) + +else: + + def to_str(value): + return value.encode(sys.getfilesystemencoding()) + + +def log(txt): + """Logs fatal errors to a log file if WSGI_LOG env var is defined""" + log_file = os.environ.get("WSGI_LOG") + if log_file: + f = open(log_file, "a+") + try: + f.write("%s: %s" % (datetime.datetime.now(), txt)) + finally: + f.close() + + +def get_wsgi_handler(handler_name): + if not handler_name: + raise Exception("WSGI_ALT_VIRTUALENV_HANDLER env var must be set") + + if not isinstance(handler_name, str): + handler_name = to_str(handler_name) + + module_name, _, callable_name = handler_name.rpartition(".") + should_call = callable_name.endswith("()") + callable_name = callable_name[:-2] if should_call else callable_name + name_list = [(callable_name, should_call)] + handler = None + last_tb = "" + + while module_name: + try: + handler = __import__(module_name, fromlist=[name_list[0][0]]) + last_tb = "" + for name, should_call in name_list: + handler = getattr(handler, name) + if should_call: + handler = handler() + break + except ImportError: + module_name, _, callable_name = module_name.rpartition(".") + should_call = callable_name.endswith("()") + callable_name = callable_name[:-2] if should_call else callable_name + name_list.insert(0, (callable_name, should_call)) + handler = None + last_tb = ": " + traceback.format_exc() + + if handler is None: + raise ValueError('"%s" could not be imported%s' % (handler_name, last_tb)) + + return handler + + +activate_this = os.getenv("WSGI_ALT_VIRTUALENV_ACTIVATE_THIS") +if not activate_this: + raise Exception("WSGI_ALT_VIRTUALENV_ACTIVATE_THIS is not set") + + +def get_virtualenv_handler(): + log("Activating virtualenv with %s\n" % activate_this) + execfile(activate_this, dict(__file__=activate_this)) + + log("Getting handler %s\n" % os.getenv("WSGI_ALT_VIRTUALENV_HANDLER")) + handler = get_wsgi_handler(os.getenv("WSGI_ALT_VIRTUALENV_HANDLER")) + log("Got handler: %r\n" % handler) + return handler + + +def get_venv_handler(): + log("Activating venv with executable at %s\n" % activate_this) + import site + + sys.executable = activate_this + old_sys_path, sys.path = sys.path, [] + + site.main() + + sys.path.insert(0, "") + for item in old_sys_path: + if item not in sys.path: + sys.path.append(item) + + log("Getting handler %s\n" % os.getenv("WSGI_ALT_VIRTUALENV_HANDLER")) + handler = get_wsgi_handler(os.getenv("WSGI_ALT_VIRTUALENV_HANDLER")) + log("Got handler: %r\n" % handler) + return handler diff --git a/test/examples/ruby/bundler.rb b/test/examples/ruby/bundler.rb new file mode 100644 index 0000000..6a3a14f --- /dev/null +++ b/test/examples/ruby/bundler.rb @@ -0,0 +1,452 @@ +# frozen_string_literal: true +require "fileutils" +require "pathname" +require "rbconfig" +require "thread" +require "bundler/environment_preserver" +require "bundler/gem_remote_fetcher" +require "bundler/rubygems_ext" +require "bundler/rubygems_integration" +require "bundler/version" +require "bundler/constants" +require "bundler/current_ruby" +require "bundler/errors" + +module Bundler + environment_preserver = EnvironmentPreserver.new(ENV, %w(PATH GEM_PATH)) + ORIGINAL_ENV = environment_preserver.restore + ENV.replace(environment_preserver.backup) + SUDO_MUTEX = Mutex.new + + autoload :Definition, "bundler/definition" + autoload :Dependency, "bundler/dependency" + autoload :DepProxy, "bundler/dep_proxy" + autoload :Deprecate, "bundler/deprecate" + autoload :Dsl, "bundler/dsl" + autoload :EndpointSpecification, "bundler/endpoint_specification" + autoload :Environment, "bundler/environment" + autoload :Env, "bundler/env" + autoload :Fetcher, "bundler/fetcher" + autoload :GemHelper, "bundler/gem_helper" + autoload :GemHelpers, "bundler/gem_helpers" + autoload :Graph, "bundler/graph" + autoload :Index, "bundler/index" + autoload :Installer, "bundler/installer" + autoload :Injector, "bundler/injector" + autoload :LazySpecification, "bundler/lazy_specification" + autoload :LockfileParser, "bundler/lockfile_parser" + autoload :MatchPlatform, "bundler/match_platform" + autoload :Mirror, "bundler/mirror" + autoload :Mirrors, "bundler/mirror" + autoload :RemoteSpecification, "bundler/remote_specification" + autoload :Resolver, "bundler/resolver" + autoload :Retry, "bundler/retry" + autoload :RubyVersion, "bundler/ruby_version" + autoload :RubyDsl, "bundler/ruby_dsl" + autoload :Runtime, "bundler/runtime" + autoload :Settings, "bundler/settings" + autoload :SharedHelpers, "bundler/shared_helpers" + autoload :SpecSet, "bundler/spec_set" + autoload :StubSpecification, "bundler/stub_specification" + autoload :Source, "bundler/source" + autoload :SourceList, "bundler/source_list" + autoload :RubyGemsGemInstaller, "bundler/rubygems_gem_installer" + autoload :UI, "bundler/ui" + + class << self + attr_writer :bundle_path + + def configure + @configured ||= configure_gem_home_and_path + end + + def ui + (defined?(@ui) && @ui) || (self.ui = UI::Silent.new) + end + + def ui=(ui) + Bundler.rubygems.ui = ui ? UI::RGProxy.new(ui) : nil + @ui = ui + end + + # Returns absolute path of where gems are installed on the filesystem. + def bundle_path + @bundle_path ||= Pathname.new(settings.path).expand_path(root) + end + + # Returns absolute location of where binstubs are installed to. + def bin_path + @bin_path ||= begin + path = settings[:bin] || "bin" + path = Pathname.new(path).expand_path(root).expand_path + SharedHelpers.filesystem_access(path) { |p| FileUtils.mkdir_p(p) } + path + end + end + + def setup(*groups) + # Return if all groups are already loaded + return @setup if defined?(@setup) + + definition.validate_ruby! + + if groups.empty? + # Load all groups, but only once + @setup = load.setup + else + load.setup(*groups) + end + end + + def require(*groups) + setup(*groups).require(*groups) + end + + def load + @load ||= Runtime.new(root, definition) + end + + def environment + Bundler::Environment.new(root, definition) + end + + # Returns an instance of Bundler::Definition for given Gemfile and lockfile + # + # @param unlock [Hash, Boolean, nil] Gems that have been requested + # to be updated or true if all gems should be updated + # @return [Bundler::Definition] + def definition(unlock = nil) + @definition = nil if unlock + @definition ||= begin + configure + upgrade_lockfile + Definition.build(default_gemfile, default_lockfile, unlock) + end + end + + def locked_gems + return @locked_gems if defined?(@locked_gems) + if Bundler.default_lockfile.exist? + lock = Bundler.read_file(Bundler.default_lockfile) + @locked_gems = LockfileParser.new(lock) + else + @locked_gems = nil + end + end + + def ruby_scope + "#{Bundler.rubygems.ruby_engine}/#{Bundler.rubygems.config_map[:ruby_version]}" + end + + def user_bundle_path + Pathname.new(Bundler.rubygems.user_home).join(".bundle") + end + + def home + bundle_path.join("bundler") + end + + def install_path + home.join("gems") + end + + def specs_path + bundle_path.join("specifications") + end + + def cache + bundle_path.join("cache/bundler") + end + + def user_cache + user_bundle_path.join("cache") + end + + def root + @root ||= begin + default_gemfile.dirname.expand_path + rescue GemfileNotFound + bundle_dir = default_bundle_dir + raise GemfileNotFound, "Could not locate Gemfile or .bundle/ directory" unless bundle_dir + Pathname.new(File.expand_path("..", bundle_dir)) + end + end + + def app_config_path + if ENV["BUNDLE_APP_CONFIG"] + Pathname.new(ENV["BUNDLE_APP_CONFIG"]).expand_path(root) + else + root.join(".bundle") + end + end + + def app_cache(custom_path = nil) + path = custom_path || root + path.join(settings.app_cache_path) + end + + def tmp(name = Process.pid.to_s) + Pathname.new(Dir.mktmpdir(["bundler", name])) + end + + def rm_rf(path) + FileUtils.remove_entry_secure(path) if path && File.exist?(path) + end + + def settings + return @settings if defined?(@settings) + @settings = Settings.new(app_config_path) + rescue GemfileNotFound + @settings = Settings.new(Pathname.new(".bundle").expand_path) + end + + # @return [Hash] Environment present before Bundler was activated + def original_env + ORIGINAL_ENV.clone + end + + # @deprecated Use `original_env` instead + # @return [Hash] Environment with all bundler-related variables removed + def clean_env + env = original_env + + if env.key?("BUNDLE_ORIG_MANPATH") + env["MANPATH"] = env["BUNDLE_ORIG_MANPATH"] + end + + env.delete_if { |k, _| k[0, 7] == "BUNDLE_" } + + if env.key?("RUBYOPT") + env["RUBYOPT"] = env["RUBYOPT"].sub "-rbundler/setup", "" + end + + if env.key?("RUBYLIB") + rubylib = env["RUBYLIB"].split(File::PATH_SEPARATOR) + rubylib.delete(File.expand_path("..", __FILE__)) + env["RUBYLIB"] = rubylib.join(File::PATH_SEPARATOR) + end + + env + end + + def with_original_env + with_env(original_env) { yield } + end + + def with_clean_env + with_env(clean_env) { yield } + end + + def clean_system(*args) + with_clean_env { Kernel.system(*args) } + end + + def clean_exec(*args) + with_clean_env { Kernel.exec(*args) } + end + + def default_gemfile + SharedHelpers.default_gemfile + end + + def default_lockfile + SharedHelpers.default_lockfile + end + + def default_bundle_dir + SharedHelpers.default_bundle_dir + end + + def system_bindir + # Gem.bindir doesn't always return the location that Rubygems will install + # system binaries. If you put '-n foo' in your .gemrc, Rubygems will + # install binstubs there instead. Unfortunately, Rubygems doesn't expose + # that directory at all, so rather than parse .gemrc ourselves, we allow + # the directory to be set as well, via `bundle config bindir foo`. + Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir + end + + def requires_sudo? + return @requires_sudo if defined?(@requires_sudo_ran) + + sudo_present = which "sudo" if settings.allow_sudo? + + if sudo_present + # the bundle path and subdirectories need to be writable for Rubygems + # to be able to unpack and install gems without exploding + path = bundle_path + path = path.parent until path.exist? + + # bins are written to a different location on OS X + bin_dir = Pathname.new(Bundler.system_bindir) + bin_dir = bin_dir.parent until bin_dir.exist? + + # if any directory is not writable, we need sudo + files = [path, bin_dir] | Dir[path.join("build_info/*").to_s] | Dir[path.join("*").to_s] + sudo_needed = files.any? { |f| !File.writable?(f) } + end + + @requires_sudo_ran = true + @requires_sudo = settings.allow_sudo? && sudo_present && sudo_needed + end + + def mkdir_p(path) + if requires_sudo? + sudo "mkdir -p '#{path}'" unless File.exist?(path) + else + SharedHelpers.filesystem_access(path, :write) do |p| + FileUtils.mkdir_p(p) + end + end + end + + def which(executable) + if File.file?(executable) && File.executable?(executable) + executable + elsif paths = ENV["PATH"] + quote = '"'.freeze + paths.split(File::PATH_SEPARATOR).find do |path| + path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote) + executable_path = File.expand_path(executable, path) + return executable_path if File.file?(executable_path) && File.executable?(executable_path) + end + end + end + + def sudo(str) + SUDO_MUTEX.synchronize do + prompt = "\n\n" + <<-PROMPT.gsub(/^ {6}/, "").strip + " " + Your user account isn't allowed to install to the system Rubygems. + You can cancel this installation and run: + + bundle install --path vendor/bundle + + to install the gems into ./vendor/bundle/, or you can enter your password + and install the bundled gems to Rubygems using sudo. + + Password: + PROMPT + + `sudo -p "#{prompt}" #{str}` + end + end + + def read_file(file) + File.open(file, "rb", &:read) + end + + def load_marshal(data) + Marshal.load(data) + rescue => e + raise MarshalError, "#{e.class}: #{e.message}" + end + + def load_gemspec(file, validate = false) + @gemspec_cache ||= {} + key = File.expand_path(file) + @gemspec_cache[key] ||= load_gemspec_uncached(file, validate) + # Protect against caching side-effected gemspecs by returning a + # new instance each time. + @gemspec_cache[key].dup if @gemspec_cache[key] + end + + def load_gemspec_uncached(file, validate = false) + path = Pathname.new(file) + # Eval the gemspec from its parent directory, because some gemspecs + # depend on "./" relative paths. + SharedHelpers.chdir(path.dirname.to_s) do + contents = path.read + spec = if contents[0..2] == "---" # YAML header + eval_yaml_gemspec(path, contents) + else + eval_gemspec(path, contents) + end + return unless spec + spec.loaded_from = path.expand_path.to_s + Bundler.rubygems.validate(spec) if validate + spec + end + end + + def clear_gemspec_cache + @gemspec_cache = {} + end + + def git_present? + return @git_present if defined?(@git_present) + @git_present = Bundler.which("git") || Bundler.which("git.exe") + end + + def reset! + @definition = nil + end + + private + + def eval_yaml_gemspec(path, contents) + # If the YAML is invalid, Syck raises an ArgumentError, and Psych + # raises a Psych::SyntaxError. See psyched_yaml.rb for more info. + Gem::Specification.from_yaml(contents) + rescue YamlLibrarySyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception + eval_gemspec(path, contents) + end + + def eval_gemspec(path, contents) + eval(contents, TOPLEVEL_BINDING, path.expand_path.to_s) + rescue ScriptError, StandardError => e + original_line = e.backtrace.find { |line| line.include?(path.to_s) } + msg = String.new + msg << "There was a #{e.class} while loading #{path.basename}: \n#{e.message}" + msg << " from\n #{original_line}" if original_line + msg << "\n" + + if e.is_a?(LoadError) && RUBY_VERSION >= "1.9" + msg << "\nDoes it try to require a relative path? That's been removed in Ruby 1.9." + end + + raise GemspecError, msg + end + + def configure_gem_home_and_path + blank_home = ENV["GEM_HOME"].nil? || ENV["GEM_HOME"].empty? + if settings[:disable_shared_gems] + ENV["GEM_PATH"] = "" + elsif blank_home || Bundler.rubygems.gem_dir != bundle_path.to_s + possibles = [Bundler.rubygems.gem_dir, Bundler.rubygems.gem_path] + paths = possibles.flatten.compact.uniq.reject(&:empty?) + ENV["GEM_PATH"] = paths.join(File::PATH_SEPARATOR) + end + + configure_gem_home + bundle_path + end + + def configure_gem_home + # TODO: This mkdir_p is only needed for JRuby <= 1.5 and should go away (GH #602) + begin + FileUtils.mkdir_p bundle_path.to_s + rescue + nil + end + + ENV["GEM_HOME"] = File.expand_path(bundle_path, root) + Bundler.rubygems.clear_paths + end + + def upgrade_lockfile + lockfile = default_lockfile + return unless lockfile.exist? && lockfile.read(3) == "---" + Bundler.ui.warn "Detected Gemfile.lock generated by 0.9, deleting..." + lockfile.rmtree + end + + # @param env [Hash] + def with_env(env) + backup = ENV.to_hash + ENV.replace(env) + yield + ensure + ENV.replace(backup) + end + end +end diff --git a/test/examples/rust/Cargo.toml b/test/examples/rust/Cargo.toml new file mode 100644 index 0000000..64190c6 --- /dev/null +++ b/test/examples/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust" +version = "0.1.0" +authors = ["Andika Demas Riyandi "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test/examples/rust/src/main.rs b/test/examples/rust/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/test/examples/rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/test/examples/shell/foo.sh b/test/examples/shell/foo.sh new file mode 100755 index 0000000..8d5fe8c --- /dev/null +++ b/test/examples/shell/foo.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +hello() { + echo "Hello" +} + +hello diff --git a/test/examples/terraform/main.tf b/test/examples/terraform/main.tf new file mode 100644 index 0000000..799e8de --- /dev/null +++ b/test/examples/terraform/main.tf @@ -0,0 +1,4 @@ + +resource "my_resource" "xxx" { + option = [1, 2, 3] +} diff --git a/test/examples/terraform/two.tf b/test/examples/terraform/two.tf new file mode 100644 index 0000000..d111967 --- /dev/null +++ b/test/examples/terraform/two.tf @@ -0,0 +1,4 @@ + +resource "other_resource" "xxx" { + xxx = "xxx" +} diff --git a/test/treefmt.toml b/test/treefmt.toml new file mode 100644 index 0000000..f5ac8aa --- /dev/null +++ b/test/treefmt.toml @@ -0,0 +1,82 @@ +# One CLI to format the code tree - https://github.com/numtide/treefmt + +[formatter.python] +command = "black" +includes = ["*.py"] + +[formatter.elm] +command = "elm-format" +options = ["--yes"] +includes = ["*.elm"] + +[formatter.go] +command = "gofmt" +options = ["-w"] +includes = ["*.go"] + +[formatter.haskell] +command = "ormolu" +options = [ + "--ghc-opt", "-XBangPatterns", + "--ghc-opt", "-XPatternSynonyms", + "--ghc-opt", "-XTypeApplications", + "--mode", "inplace", + "--check-idempotence", +] +includes = ["*.hs"] +excludes = ["examples/haskell/"] + +[formatter.nix] +command = "nixpkgs-fmt" +includes = ["*.nix"] +# Act as an example on how to exclude specific files +excludes = ["examples/nix/sources.nix"] + +[formatter.ruby] +command = "rufo" +options = ["-x"] +includes = ["*.rb"] + +[formatter.prettier] +command = "prettier" +options = ["--write"] +includes = [ + "*.css", + "*.html", + "*.js", + "*.json", + "*.jsx", + "*.md", + "*.mdx", + "*.scss", + "*.ts", + "*.yaml", +] +excludes = ["CHANGELOG.md"] + +[formatter.rust] +command = "rustfmt" +options = ["--edition", "2018"] +includes = ["*.rs"] + +[formatter.shell] +command = "/bin/sh" +options = [ + "-euc", + """ +# First lint all the scripts +shellcheck "$@" + +# Then format them +shfmt -i 2 -s -w "$@" + """, + "--", # bash swallows the second argument when using -c +] +includes = ["*.sh"] + +[formatter.terraform] +# Careful, only terraform 1.3.0 or later accept a list of files. +# see https://github.com/numtide/treefmt/issues/97 +command = "terraform" +options = ["fmt"] +includes = ["*.tf"] \ No newline at end of file