[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: muesli\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    labels:\n      - \"dependencies\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    labels:\n      - \"dependencies\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\non: [push, pull_request]\n\njobs:\n  build:\n    strategy:\n      matrix:\n        go-version: [~1.23, ^1]\n        os: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n    env:\n      GO111MODULE: \"on\"\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v6.0.0\n        with:\n          go-version: ${{ matrix.go-version }}\n\n      - name: Checkout code\n        uses: actions/checkout@v5\n\n      - name: Download Go modules\n        run: go mod download\n\n      - name: Build\n        run: go build -v ./...\n\n      - name: Test\n        run: go test ./...\n"
  },
  {
    "path": ".github/workflows/goreleaser.yml",
    "content": "name: goreleaser\n\non:\n  pull_request:\n  push:\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - name: Set up Go\n        uses: actions/setup-go@v6.0.0\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        with:\n          version: latest\n          args: release --snapshot --skip publish --skip sign --clean\n"
  },
  {
    "path": ".github/workflows/lint-soft.yml",
    "content": "name: lint-soft\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\npermissions:\n  contents: read\n  # Optional: allow read access to pull request. Use with `only-new-issues` option.\n  pull-requests: read\n\njobs:\n  golangci:\n    name: lint-soft\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions/setup-go@v6.0.0\n        with:\n          go-version: stable\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v8\n        with:\n          # Optional: golangci-lint command line arguments.\n          args: --config .golangci-soft.yml --issues-exit-code=0\n          # Optional: show only new issues if it's a pull request. The default value is `false`.\n          only-new-issues: true\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: lint\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\npermissions:\n  contents: read\n  # Optional: allow read access to pull request. Use with `only-new-issues` option.\n  pull-requests: read\n\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions/setup-go@v6.0.0\n        with:\n          go-version: stable\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v8\n        with:\n          # Optional: golangci-lint command line arguments.\n          #args:\n          # Optional: show only new issues if it's a pull request. The default value is `false`.\n          only-new-issues: true\n"
  },
  {
    "path": ".github/workflows/manpage.yml",
    "content": "name: manpage\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  manpage:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v6.0.0\n        with:\n          go-version: 1.23\n\n      - name: Checkout code\n        uses: actions/checkout@v5\n\n      - name: Download Go modules\n        run: go mod download\n\n      - name: Build\n        run: go build -v -tags mango\n\n      - name: Generate man-page\n        run: ./duf > duf.1\n\n      - name: Commit\n        uses: stefanzweifel/git-auto-commit-action@v4\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          commit_message: \"docs: update man page\"\n          branch: master\n          commit_user_name: mango 🤖\n          commit_user_email: actions@github.com\n          commit_author: mango 🤖 <actions@github.com>\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\nduf\n\ndist/\n"
  },
  {
    "path": ".golangci-soft.yml",
    "content": "version: \"2\"\nrun:\n  tests: false\nlinters:\n  enable:\n    - exhaustive\n    - goconst\n    - godot\n    - godox\n    - gomoddirectives\n    - goprintffuncname\n    - misspell\n    - mnd\n    - nakedret\n    - nestif\n    - noctx\n    - nolintlint\n    - prealloc\n    - wrapcheck\n  disable:\n    - errcheck\n    - govet\n    - ineffassign\n    - staticcheck\n    - unused\n  exclusions:\n    generated: lax\n    presets:\n      - common-false-positives\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  exclusions:\n    generated: lax\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  tests: false\nlinters:\n  enable:\n    - bodyclose\n    - gosec\n    - nilerr\n    - predeclared\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - tparallel\n    - unconvert\n    - unparam\n    - whitespace\n  exclusions:\n    generated: lax\n    presets:\n      - common-false-positives\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0\nformatters:\n  enable:\n    - goimports\n  exclusions:\n    generated: lax\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "version: 2\n\nenv:\n  - CGO_ENABLED=0\n\nbefore:\n  hooks:\n    - go mod tidy\n\nbuilds:\n  - binary: duf\n    flags:\n      - -trimpath\n    ldflags: -s -w -X main.Version={{ .Version }} -X main.CommitSHA={{ .Commit }}\n    goos:\n      - linux\n      - freebsd\n      - openbsd\n      - darwin\n      - windows\n    goarch:\n      - amd64\n      - arm64\n      - 386\n      - arm\n      - ppc64le\n    goarm:\n      - 6\n      - 7\n    ignore:\n      - goos: windows\n        goarm: \"6\"\n      - goos: windows\n        goarm: \"7\"\n\narchives:\n  - format_overrides:\n      - goos: windows\n        formats: ['zip']\n    name_template: >-\n      {{- .ProjectName }}_\n      {{- .Version }}_\n      {{- .Os }}_\n      {{- if eq .Arch \"amd64\" }}x86_64\n      {{- else if eq .Arch \"386\" }}i386\n      {{- else }}{{ .Arch }}{{ end }}\n      {{- if .Arm }}v{{ .Arm }}{{ end -}}\n    files:\n      - duf.1\n\nnfpms:\n  - ids:\n      - duf\n    vendor: muesli\n    homepage: \"https://fribbledom.com/\"\n    maintainer: \"Christian Muehlhaeuser <muesli@gmail.com>\"\n    description: \"Disk Usage/Free Utility\"\n    license: MIT\n    formats:\n      - apk\n      - deb\n      - rpm\n    bindir: /usr/bin\n\nhomebrew_casks:\n  - repository:\n      owner: muesli\n      name: homebrew-tap\n    commit_author:\n      name: \"Christian Muehlhaeuser\"\n      email: \"muesli@gmail.com\"\n    homepage: \"https://fribbledom.com/\"\n    description: \"Disk Usage/Free Utility\"\n    manpages:\n      - duf.1\n    skip_upload: true\n\nsigns:\n  - artifacts: checksum\n\nchecksum:\n  name_template: \"checksums.txt\"\nsnapshot:\n  version_template: \"{{ .Tag }}-next\"\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n      - \"^docs:\"\n      - \"^test:\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Christian Muehlhaeuser\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\nPortions of duf's code are copied and modified from\nhttps://github.com/shirou/gopsutil.\n\ngopsutil is distributed under BSD license reproduced below.\n\nCopyright (c) 2014, WAKAYAMA Shirou\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n * Neither the name of the gopsutil authors nor the names of its contributors\n   may be used to endorse or promote products derived from this software without\n   specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# duf\n\n[![Latest Release](https://img.shields.io/github/release/muesli/duf.svg?style=for-the-badge)](https://github.com/muesli/duf/releases)\n[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](https://pkg.go.dev/github.com/muesli/duf)\n[![Software License](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](/LICENSE)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/muesli/duf/build.yml?style=for-the-badge&branch=master)](https://github.com/muesli/duf/actions)\n[![Go ReportCard](https://goreportcard.com/badge/github.com/muesli/duf?style=for-the-badge)](https://goreportcard.com/report/muesli/duf)\n\nDisk Usage/Free Utility (Linux, BSD, macOS & Windows)\n\n![duf](/duf.png)\n\n## Features\n\n- [x] User-friendly, colorful output\n- [x] Adjusts to your terminal's theme & width\n- [x] Sort the results according to your needs\n- [x] Groups & filters devices\n- [x] Can conveniently output JSON\n\n## Installation\n\n### Packages\n\n#### Linux\n- Arch Linux: `pacman -S duf`\n- Ubuntu (22.04 and later) / Debian (12 and later): `apt install duf`\n- Fedora Linux: `dnf install duf`\n- Nix: `nix-env -iA nixpkgs.duf`\n- Void Linux: `xbps-install -S duf`\n- Gentoo Linux: `emerge sys-fs/duf`\n- Solus: `eopkg it duf`\n- [Packages](https://github.com/muesli/duf/releases) in Alpine, Debian & RPM formats\n\n#### BSD\n- FreeBSD: `pkg install duf`\n- OpenBSD: `pkg_add duf`\n\n#### macOS\n- with [Homebrew](https://brew.sh/): `brew install duf`\n- with [MacPorts](https://www.macports.org): `sudo port selfupdate && sudo port install duf`\n\n#### Windows\n- with [Chocolatey](https://chocolatey.org/): `choco install duf`\n- with [scoop](https://scoop.sh/): `scoop install duf`\n\n#### Android\n- Android (via termux): `pkg install duf`\n\n### Binaries\n- [Binaries](https://github.com/muesli/duf/releases) for Linux, FreeBSD, OpenBSD, macOS, Windows\n\n### From source\n\nMake sure you have a working Go environment (Go 1.23 or higher is required).\nSee the [install instructions](https://golang.org/doc/install.html).\n\nCompiling duf is easy, simply run:\n\n    git clone https://github.com/muesli/duf.git\n    cd duf\n    go build\n\n## Usage\n\nYou can simply start duf without any command-line arguments:\n\n    duf\n\nIf you supply arguments, duf will only list specific devices & mount points:\n\n    duf /home /some/file\n\nIf you want to list everything (including pseudo, duplicate, inaccessible file systems):\n\n    duf --all\n\n### Filtering\n\nYou can show and hide specific tables:\n\n    duf --only local,network,fuse,special,loops,binds\n    duf --hide local,network,fuse,special,loops,binds\n\nYou can also show and hide specific filesystems:\n\n    duf --only-fs tmpfs,vfat\n    duf --hide-fs tmpfs,vfat\n\n...or specific mount points:\n\n    duf --only-mp /,/home,/dev\n    duf --hide-mp /,/home,/dev\n\nWildcards inside quotes work:\n\n    duf --only-mp '/sys/*,/dev/*'\n\n### Display options\n\nSort the output:\n\n    duf --sort size\n\nValid keys are: `mountpoint`, `size`, `used`, `avail`, `usage`, `inodes`,\n`inodes_used`, `inodes_avail`, `inodes_usage`, `type`, `filesystem`.\n\nShow or hide specific columns:\n\n    duf --output mountpoint,size,usage\n\nValid keys are: `mountpoint`, `size`, `used`, `avail`, `usage`, `inodes`,\n`inodes_used`, `inodes_avail`, `inodes_usage`, `type`, `filesystem`.\n\nList inode information instead of block usage:\n\n    duf --inodes\n\nIf duf doesn't detect your terminal's colors correctly, you can set a theme:\n\n    duf --theme light\n\n### Color-coding & Thresholds\n\nduf highlights the availability & usage columns in red, green, or yellow,\ndepending on how much space is still available. You can set your own thresholds:\n\n    duf --avail-threshold=\"10G,1G\"\n    duf --usage-threshold=\"0.5,0.9\"\n\n### Bonus\n\nIf you prefer your output as JSON:\n\n    duf --json\n\n## Troubleshooting\n\nUsers of `oh-my-zsh` should be aware that it already defines an alias called\n`duf`, which you will have to remove in order to use `duf`:\n\n    unalias duf\n\n## Feedback\n\nGot some feedback or suggestions? Please open an issue or drop me a note!\n\n* [Twitter](https://twitter.com/mueslix)\n* [The Fediverse](https://mastodon.social/@fribbledom)\n"
  },
  {
    "path": "duf.1",
    "content": ".TH DUF 1 \"2025-09-30\" \"duf\" \"Disk Usage/Free Utility\"\n.SH NAME\nduf - Disk Usage/Free Utility\n.SH SYNOPSIS\n\\fBduf\\fP [\\fIoptions\\&.\\&.\\&.\\fP] [\\fIargument\\&.\\&.\\&.\\fP]\n.SH DESCRIPTION\nSimple Disk Usage/Free Utility\\&.\n.PP\nFeatures:\n.PP\n.RS\n.IP \\(bu 3\nUser-friendly, colorful output\\&.\n.IP \\(bu 3\nAdjusts to your terminal's theme & width\\&.\n.IP \\(bu 3\nSort the results according to your needs\\&.\n.IP \\(bu 3\nGroups & filters devices\\&.\n.IP \\(bu 3\nCan conveniently output JSON\\&.\n.SH OPTIONS\n.TP\n\\fB--all\\fP\ninclude pseudo, duplicate, inaccessible file systems\n.TP\n\\fB--avail-threshold\\fP\nspecifies the coloring threshold (yellow, red) of the avail column, must be integer with optional SI prefixes\n.TP\n\\fB--hide\\fP\nhide specific devices, separated with commas: local, network, fuse, special, loops, binds\n.TP\n\\fB--hide-fs\\fP\nhide specific filesystems, separated with commas\n.TP\n\\fB--hide-mp\\fP\nhide specific mount points, separated with commas (supports wildcards)\n.TP\n\\fB-h, --human-readable\\fP\nignored, just for df compatibility\n.TP\n\\fB--inodes\\fP\nlist inode information instead of block usage\n.TP\n\\fB--json\\fP\noutput all devices in JSON format\n.TP\n\\fB--only\\fP\nshow only specific devices, separated with commas: local, network, fuse, special, loops, binds\n.TP\n\\fB--only-fs\\fP\nonly specific filesystems, separated with commas\n.TP\n\\fB--only-mp\\fP\nonly specific mount points, separated with commas (supports wildcards)\n.TP\n\\fB--output\\fP\noutput fields: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\n.TP\n\\fB--sort\\fP\nsort output by: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\n.TP\n\\fB--style\\fP\nstyle: unicode, ascii\n.TP\n\\fB--theme\\fP\ncolor themes: dark, light, ansi\n.TP\n\\fB--usage-threshold\\fP\nspecifies the coloring threshold (yellow, red) of the usage bars as a floating point number from 0 to 1\n.TP\n\\fB--version\\fP\ndisplay version\n.TP\n\\fB--warnings\\fP\noutput all warnings to STDERR\n.TP\n\\fB--width\\fP\nmax output width\n.SH USAGE\nYou can simply start duf without any command-line arguments:\n.PP\n.PP\n  $ duf\n.PP\n.PP\nIf you supply arguments, duf will only list specific devices & mount points:\n.PP\n.PP\n  $ duf /home /some/file\n.PP\n.PP\nIf you want to list everything (including pseudo, duplicate, inaccessible file systems):\n.PP\n.PP\n  $ duf --all\n.PP\n.PP\nYou can show and hide specific tables:\n.PP\n.PP\n  $ duf --only local,network,fuse,special,loops,binds\n.PP\n  $ duf --hide local,network,fuse,special,loops,binds\n.PP\n.PP\nYou can also show and hide specific filesystems:\n.PP\n.PP\n  $ duf --only-fs tmpfs,vfat\n.PP\n  $ duf --hide-fs tmpfs,vfat\n.PP\n.PP\n\\&.\\&.\\&.or specific mount points:\n.PP\n.PP\n  $ duf --only-mp /,/home,/dev\n.PP\n  $ duf --hide-mp /,/home,/dev\n.PP\n.PP\nWildcards inside quotes work:\n.PP\n.PP\n  $ duf --only-mp '/sys/*,/dev/*'\n.PP\n.PP\nSort the output:\n.PP\n.PP\n  $ duf --sort size\n.PP\n.PP\nValid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\\&.\n.PP\n.PP\nShow or hide specific columns:\n.PP\n.PP\n  $ duf --output mountpoint,size,usage\n.PP\n.PP\nValid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\\&.\n.PP\n.PP\nList inode information instead of block usage:\n.PP\n.PP\n  $ duf --inodes\n.PP\n.PP\nIf duf doesn't detect your terminal's colors correctly, you can set a theme:\n.PP\n.PP\n  $ duf --theme light\n.PP\n.PP\nduf highlights the availability & usage columns in red, green, or yellow, depending on how much space is still available\\&. You can set your own thresholds:\n.PP\n.PP\n  $ duf --avail-threshold=\"10G,1G\"\n.PP\n  $ duf --usage-threshold=\"0\\&.5,0\\&.9\"\n.PP\n.PP\nIf you prefer your output as JSON:\n.PP\n.PP\n  $ duf --json\n.PP\n.SH NOTES\nPortions of duf's code are copied and modified from https://github\\&.com/shirou/gopsutil\\&.\n.PP\ngopsutil was written by WAKAYAMA Shirou and is distributed under BSD-3-Clause\\&.\n.SH AUTHORS\nduf was written by Christian Muehlhaeuser <https://github\\&.com/muesli/duf>\n.SH COPYRIGHT\nCopyright (C) 2020-2022 Christian Muehlhaeuser <https://github\\&.com/muesli>\n.PP\nReleased under MIT license\\&.\n"
  },
  {
    "path": "filesystems.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc findMounts(mounts []Mount, path string) ([]Mount, error) {\n\tvar err error\n\tpath, err = filepath.Abs(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpath, err = filepath.EvalSymlinks(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = os.Stat(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar m []Mount\n\tfor _, v := range mounts {\n\t\tif path == v.Device {\n\t\t\treturn []Mount{v}, nil\n\t\t}\n\n\t\tif strings.HasPrefix(path, v.Mountpoint) {\n\t\t\tvar nm []Mount\n\n\t\t\t// keep all entries that are as close or closer to the target\n\t\t\tfor _, mv := range m {\n\t\t\t\tif len(mv.Mountpoint) >= len(v.Mountpoint) {\n\t\t\t\t\tnm = append(nm, mv)\n\t\t\t\t}\n\t\t\t}\n\t\t\tm = nm\n\n\t\t\t// add entry only if we didn't already find something closer\n\t\t\tif len(nm) == 0 || len(v.Mountpoint) >= len(nm[0].Mountpoint) {\n\t\t\t\tm = append(m, v)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc deviceType(m Mount) string {\n\tif isNetworkFs(m) {\n\t\treturn networkDevice\n\t}\n\tif isSpecialFs(m) {\n\t\treturn specialDevice\n\t}\n\tif isFuseFs(m) {\n\t\treturn fuseDevice\n\t}\n\n\treturn localDevice\n}\n\n// remote: [ \"nfs\", \"smbfs\", \"cifs\", \"ncpfs\", \"afs\", \"coda\", \"ftpfs\", \"mfs\", \"sshfs\", \"fuse.sshfs\", \"nfs4\" ]\n// special: [ \"tmpfs\", \"devpts\", \"devtmpfs\", \"proc\", \"sysfs\", \"usbfs\", \"devfs\", \"fdescfs\", \"linprocfs\" ]\n"
  },
  {
    "path": "filesystems_darwin.go",
    "content": "//go:build darwin\n// +build darwin\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isNetworkFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isSpecialFs(m Mount) bool {\n\treturn m.Fstype == \"devfs\"\n}\n\nfunc isHiddenFs(m Mount) bool {\n\treturn false\n}\n"
  },
  {
    "path": "filesystems_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isNetworkFs(m Mount) bool {\n\tfs := []string{\"nfs\", \"smbfs\"}\n\n\tfor _, v := range fs {\n\t\tif m.Fstype == v {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isSpecialFs(m Mount) bool {\n\tfs := []string{\"devfs\", \"tmpfs\", \"linprocfs\", \"linsysfs\", \"fdescfs\", \"procfs\"}\n\n\tfor _, v := range fs {\n\t\tif m.Fstype == v {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isHiddenFs(m Mount) bool {\n\treturn false\n}\n"
  },
  {
    "path": "filesystems_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage main\n\nimport \"strings\"\n\n//nolint:revive\nconst (\n\t// man statfs\n\tADFS_SUPER_MAGIC      = 0xadf5\n\tAFFS_SUPER_MAGIC      = 0xADFF\n\tAUTOFS_SUPER_MAGIC    = 0x0187\n\tBDEVFS_MAGIC          = 0x62646576\n\tBEFS_SUPER_MAGIC      = 0x42465331\n\tBFS_MAGIC             = 0x1BADFACE\n\tBINFMTFS_MAGIC        = 0x42494e4d\n\tBPF_FS_MAGIC          = 0xcafe4a11\n\tBTRFS_SUPER_MAGIC     = 0x9123683E\n\tCGROUP_SUPER_MAGIC    = 0x27e0eb\n\tCGROUP2_SUPER_MAGIC   = 0x63677270\n\tCIFS_MAGIC_NUMBER     = 0xFF534D42\n\tCODA_SUPER_MAGIC      = 0x73757245\n\tCOH_SUPER_MAGIC       = 0x012FF7B7\n\tCONFIGFS_MAGIC        = 0x62656570\n\tCRAMFS_MAGIC          = 0x28cd3d45\n\tDEBUGFS_MAGIC         = 0x64626720\n\tDEVFS_SUPER_MAGIC     = 0x1373\n\tDEVPTS_SUPER_MAGIC    = 0x1cd1\n\tEFIVARFS_MAGIC        = 0xde5e81e4\n\tEFS_SUPER_MAGIC       = 0x00414A53\n\tEXT_SUPER_MAGIC       = 0x137D\n\tEXT2_OLD_SUPER_MAGIC  = 0xEF51\n\tEXT2_SUPER_MAGIC      = 0xEF53\n\tEXT3_SUPER_MAGIC      = 0xEF53\n\tEXT4_SUPER_MAGIC      = 0xEF53\n\tFUSE_SUPER_MAGIC      = 0x65735546\n\tFUTEXFS_SUPER_MAGIC   = 0xBAD1DEA\n\tHFS_SUPER_MAGIC       = 0x4244\n\tHFSPLUS_SUPER_MAGIC   = 0x482b\n\tHOSTFS_SUPER_MAGIC    = 0x00c0ffee\n\tHPFS_SUPER_MAGIC      = 0xF995E849\n\tHUGETLBFS_MAGIC       = 0x958458f6\n\tISOFS_SUPER_MAGIC     = 0x9660\n\tJFFS2_SUPER_MAGIC     = 0x72b6\n\tJFS_SUPER_MAGIC       = 0x3153464a\n\tMINIX_SUPER_MAGIC     = 0x137F /* orig. minix */\n\tMINIX_SUPER_MAGIC2    = 0x138F /* 30 char minix */\n\tMINIX2_SUPER_MAGIC    = 0x2468 /* minix V2 */\n\tMINIX2_SUPER_MAGIC2   = 0x2478 /* minix V2, 30 char names */\n\tMINIX3_SUPER_MAGIC    = 0x4d5a /* minix V3 fs, 60 char names */\n\tMQUEUE_MAGIC          = 0x19800202\n\tMSDOS_SUPER_MAGIC     = 0x4d44\n\tNCP_SUPER_MAGIC       = 0x564c\n\tNFS_SUPER_MAGIC       = 0x6969\n\tNILFS_SUPER_MAGIC     = 0x3434\n\tNTFS_SB_MAGIC         = 0x5346544e\n\tOCFS2_SUPER_MAGIC     = 0x7461636f\n\tOPENPROM_SUPER_MAGIC  = 0x9fa1\n\tPIPEFS_MAGIC          = 0x50495045\n\tPROC_SUPER_MAGIC      = 0x9fa0\n\tPSTOREFS_MAGIC        = 0x6165676C\n\tQNX4_SUPER_MAGIC      = 0x002f\n\tQNX6_SUPER_MAGIC      = 0x68191122\n\tRAMFS_MAGIC           = 0x858458f6\n\tREISERFS_SUPER_MAGIC  = 0x52654973\n\tROMFS_MAGIC           = 0x7275\n\tSELINUX_MAGIC         = 0xf97cff8c\n\tSMACK_MAGIC           = 0x43415d53\n\tSMB_SUPER_MAGIC       = 0x517B\n\tSMB2_MAGIC_NUMBER     = 0xfe534d42\n\tSOCKFS_MAGIC          = 0x534F434B\n\tSQUASHFS_MAGIC        = 0x73717368\n\tSYSFS_MAGIC           = 0x62656572\n\tSYSV2_SUPER_MAGIC     = 0x012FF7B6\n\tSYSV4_SUPER_MAGIC     = 0x012FF7B5\n\tTMPFS_MAGIC           = 0x01021994\n\tTRACEFS_MAGIC         = 0x74726163\n\tUDF_SUPER_MAGIC       = 0x15013346\n\tUFS_MAGIC             = 0x00011954\n\tUSBDEVICE_SUPER_MAGIC = 0x9fa2\n\tV9FS_MAGIC            = 0x01021997\n\tVXFS_SUPER_MAGIC      = 0xa501FCF5\n\tXENFS_SUPER_MAGIC     = 0xabba1974\n\tXENIX_SUPER_MAGIC     = 0x012FF7B4\n\tXFS_SUPER_MAGIC       = 0x58465342\n\t_XIAFS_SUPER_MAGIC    = 0x012FD16D\n\n\tAFS_SUPER_MAGIC             = 0x5346414F\n\tAUFS_SUPER_MAGIC            = 0x61756673\n\tANON_INODE_FS_SUPER_MAGIC   = 0x09041934\n\tCEPH_SUPER_MAGIC            = 0x00C36400\n\tECRYPTFS_SUPER_MAGIC        = 0xF15F\n\tFAT_SUPER_MAGIC             = 0x4006\n\tFHGFS_SUPER_MAGIC           = 0x19830326\n\tFUSEBLK_SUPER_MAGIC         = 0x65735546\n\tFUSECTL_SUPER_MAGIC         = 0x65735543\n\tGFS_SUPER_MAGIC             = 0x1161970\n\tGPFS_SUPER_MAGIC            = 0x47504653\n\tMTD_INODE_FS_SUPER_MAGIC    = 0x11307854\n\tINOTIFYFS_SUPER_MAGIC       = 0x2BAD1DEA\n\tISOFS_R_WIN_SUPER_MAGIC     = 0x4004\n\tISOFS_WIN_SUPER_MAGIC       = 0x4000\n\tJFFS_SUPER_MAGIC            = 0x07C0\n\tKAFS_SUPER_MAGIC            = 0x6B414653\n\tLUSTRE_SUPER_MAGIC          = 0x0BD00BD0\n\tNFSD_SUPER_MAGIC            = 0x6E667364\n\tPANFS_SUPER_MAGIC           = 0xAAD7AAEA\n\tRPC_PIPEFS_SUPER_MAGIC      = 0x67596969\n\tSECURITYFS_SUPER_MAGIC      = 0x73636673\n\tUFS_BYTESWAPPED_SUPER_MAGIC = 0x54190100\n\tVMHGFS_SUPER_MAGIC          = 0xBACBACBC\n\tVZFS_SUPER_MAGIC            = 0x565A4653\n\tZFS_SUPER_MAGIC             = 0x2FC12FC1\n)\n\n// coreutils/src/stat.c\nvar fsTypeMap = map[int64]string{\n\tADFS_SUPER_MAGIC:            \"adfs\",                /* 0xADF5 local */\n\tAFFS_SUPER_MAGIC:            \"affs\",                /* 0xADFF local */\n\tAFS_SUPER_MAGIC:             \"afs\",                 /* 0x5346414F remote */\n\tANON_INODE_FS_SUPER_MAGIC:   \"anon-inode FS\",       /* 0x09041934 local */\n\tAUFS_SUPER_MAGIC:            \"aufs\",                /* 0x61756673 remote */\n\tAUTOFS_SUPER_MAGIC:          \"autofs\",              /* 0x0187 local */\n\tBEFS_SUPER_MAGIC:            \"befs\",                /* 0x42465331 local */\n\tBDEVFS_MAGIC:                \"bdevfs\",              /* 0x62646576 local */\n\tBFS_MAGIC:                   \"bfs\",                 /* 0x1BADFACE local */\n\tBINFMTFS_MAGIC:              \"binfmt_misc\",         /* 0x42494E4D local */\n\tBTRFS_SUPER_MAGIC:           \"btrfs\",               /* 0x9123683E local */\n\tCEPH_SUPER_MAGIC:            \"ceph\",                /* 0x00C36400 remote */\n\tCGROUP_SUPER_MAGIC:          \"cgroupfs\",            /* 0x0027E0EB local */\n\tCIFS_MAGIC_NUMBER:           \"cifs\",                /* 0xFF534D42 remote */\n\tCODA_SUPER_MAGIC:            \"coda\",                /* 0x73757245 remote */\n\tCOH_SUPER_MAGIC:             \"coh\",                 /* 0x012FF7B7 local */\n\tCRAMFS_MAGIC:                \"cramfs\",              /* 0x28CD3D45 local */\n\tDEBUGFS_MAGIC:               \"debugfs\",             /* 0x64626720 local */\n\tDEVFS_SUPER_MAGIC:           \"devfs\",               /* 0x1373 local */\n\tDEVPTS_SUPER_MAGIC:          \"devpts\",              /* 0x1CD1 local */\n\tECRYPTFS_SUPER_MAGIC:        \"ecryptfs\",            /* 0xF15F local */\n\tEFS_SUPER_MAGIC:             \"efs\",                 /* 0x00414A53 local */\n\tEXT_SUPER_MAGIC:             \"ext\",                 /* 0x137D local */\n\tEXT2_SUPER_MAGIC:            \"ext2/ext3\",           /* 0xEF53 local */\n\tEXT2_OLD_SUPER_MAGIC:        \"ext2\",                /* 0xEF51 local */\n\tFAT_SUPER_MAGIC:             \"fat\",                 /* 0x4006 local */\n\tFHGFS_SUPER_MAGIC:           \"fhgfs\",               /* 0x19830326 remote */\n\tFUSEBLK_SUPER_MAGIC:         \"fuseblk\",             /* 0x65735546 remote */\n\tFUSECTL_SUPER_MAGIC:         \"fusectl\",             /* 0x65735543 remote */\n\tFUTEXFS_SUPER_MAGIC:         \"futexfs\",             /* 0x0BAD1DEA local */\n\tGFS_SUPER_MAGIC:             \"gfs/gfs2\",            /* 0x1161970 remote */\n\tGPFS_SUPER_MAGIC:            \"gpfs\",                /* 0x47504653 remote */\n\tHFS_SUPER_MAGIC:             \"hfs\",                 /* 0x4244 local */\n\tHFSPLUS_SUPER_MAGIC:         \"hfsplus\",             /* 0x482b local */\n\tHPFS_SUPER_MAGIC:            \"hpfs\",                /* 0xF995E849 local */\n\tHUGETLBFS_MAGIC:             \"hugetlbfs\",           /* 0x958458F6 local */\n\tMTD_INODE_FS_SUPER_MAGIC:    \"inodefs\",             /* 0x11307854 local */\n\tINOTIFYFS_SUPER_MAGIC:       \"inotifyfs\",           /* 0x2BAD1DEA local */\n\tISOFS_SUPER_MAGIC:           \"isofs\",               /* 0x9660 local */\n\tISOFS_R_WIN_SUPER_MAGIC:     \"isofs\",               /* 0x4004 local */\n\tISOFS_WIN_SUPER_MAGIC:       \"isofs\",               /* 0x4000 local */\n\tJFFS_SUPER_MAGIC:            \"jffs\",                /* 0x07C0 local */\n\tJFFS2_SUPER_MAGIC:           \"jffs2\",               /* 0x72B6 local */\n\tJFS_SUPER_MAGIC:             \"jfs\",                 /* 0x3153464A local */\n\tKAFS_SUPER_MAGIC:            \"k-afs\",               /* 0x6B414653 remote */\n\tLUSTRE_SUPER_MAGIC:          \"lustre\",              /* 0x0BD00BD0 remote */\n\tMINIX_SUPER_MAGIC:           \"minix\",               /* 0x137F local */\n\tMINIX_SUPER_MAGIC2:          \"minix (30 char.)\",    /* 0x138F local */\n\tMINIX2_SUPER_MAGIC:          \"minix v2\",            /* 0x2468 local */\n\tMINIX2_SUPER_MAGIC2:         \"minix v2 (30 char.)\", /* 0x2478 local */\n\tMINIX3_SUPER_MAGIC:          \"minix3\",              /* 0x4D5A local */\n\tMQUEUE_MAGIC:                \"mqueue\",              /* 0x19800202 local */\n\tMSDOS_SUPER_MAGIC:           \"msdos\",               /* 0x4D44 local */\n\tNCP_SUPER_MAGIC:             \"novell\",              /* 0x564C remote */\n\tNFS_SUPER_MAGIC:             \"nfs\",                 /* 0x6969 remote */\n\tNFSD_SUPER_MAGIC:            \"nfsd\",                /* 0x6E667364 remote */\n\tNILFS_SUPER_MAGIC:           \"nilfs\",               /* 0x3434 local */\n\tNTFS_SB_MAGIC:               \"ntfs\",                /* 0x5346544E local */\n\tOPENPROM_SUPER_MAGIC:        \"openprom\",            /* 0x9FA1 local */\n\tOCFS2_SUPER_MAGIC:           \"ocfs2\",               /* 0x7461636f remote */\n\tPANFS_SUPER_MAGIC:           \"panfs\",               /* 0xAAD7AAEA remote */\n\tPIPEFS_MAGIC:                \"pipefs\",              /* 0x50495045 remote */\n\tPROC_SUPER_MAGIC:            \"proc\",                /* 0x9FA0 local */\n\tPSTOREFS_MAGIC:              \"pstorefs\",            /* 0x6165676C local */\n\tQNX4_SUPER_MAGIC:            \"qnx4\",                /* 0x002F local */\n\tQNX6_SUPER_MAGIC:            \"qnx6\",                /* 0x68191122 local */\n\tRAMFS_MAGIC:                 \"ramfs\",               /* 0x858458F6 local */\n\tREISERFS_SUPER_MAGIC:        \"reiserfs\",            /* 0x52654973 local */\n\tROMFS_MAGIC:                 \"romfs\",               /* 0x7275 local */\n\tRPC_PIPEFS_SUPER_MAGIC:      \"rpc_pipefs\",          /* 0x67596969 local */\n\tSECURITYFS_SUPER_MAGIC:      \"securityfs\",          /* 0x73636673 local */\n\tSELINUX_MAGIC:               \"selinux\",             /* 0xF97CFF8C local */\n\tSMB_SUPER_MAGIC:             \"smb\",                 /* 0x517B remote */\n\tSMB2_MAGIC_NUMBER:           \"smb2\",                /* 0xfe534d42 remote */\n\tSOCKFS_MAGIC:                \"sockfs\",              /* 0x534F434B local */\n\tSQUASHFS_MAGIC:              \"squashfs\",            /* 0x73717368 local */\n\tSYSFS_MAGIC:                 \"sysfs\",               /* 0x62656572 local */\n\tSYSV2_SUPER_MAGIC:           \"sysv2\",               /* 0x012FF7B6 local */\n\tSYSV4_SUPER_MAGIC:           \"sysv4\",               /* 0x012FF7B5 local */\n\tTMPFS_MAGIC:                 \"tmpfs\",               /* 0x01021994 local */\n\tUDF_SUPER_MAGIC:             \"udf\",                 /* 0x15013346 local */\n\tUFS_MAGIC:                   \"ufs\",                 /* 0x00011954 local */\n\tUFS_BYTESWAPPED_SUPER_MAGIC: \"ufs\",                 /* 0x54190100 local */\n\tUSBDEVICE_SUPER_MAGIC:       \"usbdevfs\",            /* 0x9FA2 local */\n\tV9FS_MAGIC:                  \"v9fs\",                /* 0x01021997 local */\n\tVMHGFS_SUPER_MAGIC:          \"vmhgfs\",              /* 0xBACBACBC remote */\n\tVXFS_SUPER_MAGIC:            \"vxfs\",                /* 0xA501FCF5 local */\n\tVZFS_SUPER_MAGIC:            \"vzfs\",                /* 0x565A4653 local */\n\tXENFS_SUPER_MAGIC:           \"xenfs\",               /* 0xABBA1974 local */\n\tXENIX_SUPER_MAGIC:           \"xenix\",               /* 0x012FF7B4 local */\n\tXFS_SUPER_MAGIC:             \"xfs\",                 /* 0x58465342 local */\n\t_XIAFS_SUPER_MAGIC:          \"xia\",                 /* 0x012FD16D local */\n\tZFS_SUPER_MAGIC:             \"zfs\",                 /* 0x2FC12FC1 local */\n}\n\n/*\nvar localMap = map[int64]bool{\n\tAFS_SUPER_MAGIC:      true,\n\tBTRFS_SUPER_MAGIC:    true,\n\tEXT_SUPER_MAGIC:      true,\n\tEXT2_OLD_SUPER_MAGIC: true,\n\tEXT2_SUPER_MAGIC:     true,\n\tFAT_SUPER_MAGIC:      true,\n\tHPFS_SUPER_MAGIC:     true,\n\tMSDOS_SUPER_MAGIC:    true,\n\tNTFS_SB_MAGIC:        true,\n\tREISERFS_SUPER_MAGIC: true,\n\tUDF_SUPER_MAGIC:      true,\n\tXFS_SUPER_MAGIC:      true,\n\tZFS_SUPER_MAGIC:      true,\n}\n*/\n\nvar networkMap = map[int64]bool{\n\tCIFS_MAGIC_NUMBER: true,\n\tNFS_SUPER_MAGIC:   true,\n\tSMB_SUPER_MAGIC:   true,\n\tSMB2_MAGIC_NUMBER: true,\n}\n\nvar specialMap = map[int64]bool{\n\tAUTOFS_SUPER_MAGIC:     true,\n\tBINFMTFS_MAGIC:         true,\n\tBPF_FS_MAGIC:           true,\n\tCGROUP_SUPER_MAGIC:     true,\n\tCGROUP2_SUPER_MAGIC:    true,\n\tCONFIGFS_MAGIC:         true,\n\tDEBUGFS_MAGIC:          true,\n\tDEVPTS_SUPER_MAGIC:     true,\n\tEFIVARFS_MAGIC:         true,\n\tFUSECTL_SUPER_MAGIC:    true,\n\tHUGETLBFS_MAGIC:        true,\n\tMQUEUE_MAGIC:           true,\n\tPROC_SUPER_MAGIC:       true,\n\tPSTOREFS_MAGIC:         true,\n\tSECURITYFS_SUPER_MAGIC: true,\n\tSYSFS_MAGIC:            true,\n\tTMPFS_MAGIC:            true,\n\tTRACEFS_MAGIC:          true,\n}\n\n/*\nfunc isLocalFs(m Mount) bool {\n\treturn localMap[int64(m.Stat().Type)] //nolint:unconvert\n}\n*/\n\nfunc isFuseFs(m Mount) bool {\n\treturn m.Stat().Type == FUSEBLK_SUPER_MAGIC ||\n\t\tm.Stat().Type == FUSE_SUPER_MAGIC\n}\n\nfunc isNetworkFs(m Mount) bool {\n\treturn networkMap[int64(m.Stat().Type)] //nolint:unconvert\n}\n\nfunc isSpecialFs(m Mount) bool {\n\tif m.Device == \"nsfs\" {\n\t\treturn true\n\t}\n\n\treturn specialMap[int64(m.Stat().Type)] //nolint:unconvert\n}\n\nfunc isHiddenFs(m Mount) bool {\n\tswitch m.Device {\n\tcase \"shm\":\n\t\treturn true\n\tcase \"overlay\":\n\t\treturn true\n\t}\n\n\tswitch m.Fstype {\n\tcase \"autofs\":\n\t\treturn true\n\tcase \"squashfs\":\n\t\tif strings.HasPrefix(m.Mountpoint, \"/snap\") {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "filesystems_openbsd.go",
    "content": "//go:build openbsd\n// +build openbsd\n\npackage main\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isNetworkFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isSpecialFs(m Mount) bool {\n\treturn m.Fstype == \"devfs\"\n}\n\nfunc isHiddenFs(m Mount) bool {\n\treturn false\n}\n"
  },
  {
    "path": "filesystems_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/windows/registry\"\n)\n\nconst (\n\tWindowsSandboxMountPointRegistryPath = `Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints2\\CPC\\LocalMOF`\n)\n\nvar windowsSandboxMountPoints = loadRegisteredWindowsSandboxMountPoints()\n\nfunc loadRegisteredWindowsSandboxMountPoints() (ret map[string]struct{}) {\n\tret = make(map[string]struct{})\n\tkey, err := registry.OpenKey(registry.CURRENT_USER, WindowsSandboxMountPointRegistryPath, registry.READ)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tkeyInfo, err := key.Stat()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tmountPoints, err := key.ReadValueNames(int(keyInfo.ValueCount))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tfor _, val := range mountPoints {\n\t\tret[val] = struct{}{}\n\t}\n\treturn ret\n}\n\nfunc isFuseFs(m Mount) bool {\n\t//FIXME: implement\n\treturn false\n}\n\nfunc isNetworkFs(m Mount) bool {\n\t_, ok := m.Metadata.(*NetResource)\n\treturn ok\n}\n\nfunc isSpecialFs(m Mount) bool {\n\t_, ok := windowsSandboxMountPoints[m.Mountpoint]\n\treturn ok\n}\n\nfunc isHiddenFs(m Mount) bool {\n\treturn false\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/muesli/duf\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/IGLOU-EU/go-wildcard v1.0.3\n\tgithub.com/jedib0t/go-pretty/v6 v6.6.8\n\tgithub.com/mattn/go-runewidth v0.0.19\n\tgithub.com/muesli/mango v0.2.0\n\tgithub.com/muesli/mango-pflag v0.2.0\n\tgithub.com/muesli/roff v0.1.0\n\tgithub.com/muesli/termenv v0.16.0\n\tgithub.com/spf13/pflag v1.0.10\n\tgolang.org/x/sys v0.35.0\n\tgolang.org/x/term v0.34.0\n)\n\nrequire (\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.2.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgolang.org/x/text v0.22.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU0Nou0=\ngithub.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=\ngithub.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=\ngithub.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=\ngithub.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=\ngithub.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=\ngithub.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k=\ngithub.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y=\ngithub.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=\ngithub.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=\ngolang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=\ngolang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=\ngolang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "groups.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n)\n\nconst (\n\tlocalDevice   = \"local\"\n\tnetworkDevice = \"network\"\n\tfuseDevice    = \"fuse\"\n\tspecialDevice = \"special\"\n\tloopsDevice   = \"loops\"\n\tbindsMount    = \"binds\"\n)\n\n// FilterOptions contains all filters.\ntype FilterOptions struct {\n\tHiddenDevices map[string]struct{}\n\tOnlyDevices   map[string]struct{}\n\n\tHiddenFilesystems map[string]struct{}\n\tOnlyFilesystems   map[string]struct{}\n\n\tHiddenMountPoints map[string]struct{}\n\tOnlyMountPoints   map[string]struct{}\n}\n\n// renderTables renders all tables.\nfunc renderTables(m []Mount, filters FilterOptions, opts TableOptions) {\n\tdeviceMounts := make(map[string][]Mount)\n\thasOnlyDevices := len(filters.OnlyDevices) != 0\n\n\t_, hideLocal := filters.HiddenDevices[localDevice]\n\t_, hideNetwork := filters.HiddenDevices[networkDevice]\n\t_, hideFuse := filters.HiddenDevices[fuseDevice]\n\t_, hideSpecial := filters.HiddenDevices[specialDevice]\n\t_, hideLoops := filters.HiddenDevices[loopsDevice]\n\t_, hideBinds := filters.HiddenDevices[bindsMount]\n\n\t_, onlyLocal := filters.OnlyDevices[localDevice]\n\t_, onlyNetwork := filters.OnlyDevices[networkDevice]\n\t_, onlyFuse := filters.OnlyDevices[fuseDevice]\n\t_, onlySpecial := filters.OnlyDevices[specialDevice]\n\t_, onlyLoops := filters.OnlyDevices[loopsDevice]\n\t_, onlyBinds := filters.OnlyDevices[bindsMount]\n\n\t// sort/filter devices\n\tfor _, v := range m {\n\t\tif len(filters.OnlyFilesystems) != 0 {\n\t\t\t// skip not onlyFs\n\t\t\tif _, ok := filters.OnlyFilesystems[strings.ToLower(v.Fstype)]; !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\t// skip hideFs\n\t\t\tif _, ok := filters.HiddenFilesystems[strings.ToLower(v.Fstype)]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// skip hidden devices\n\t\tif isHiddenFs(v) && !*all {\n\t\t\tcontinue\n\t\t}\n\n\t\t// skip bind-mounts\n\t\tif strings.Contains(v.Opts, \"bind\") {\n\t\t\tif (hasOnlyDevices && !onlyBinds) || (hideBinds && !*all) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// skip loop devices\n\t\tif strings.HasPrefix(v.Device, \"/dev/loop\") {\n\t\t\tif (hasOnlyDevices && !onlyLoops) || (hideLoops && !*all) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// skip special devices\n\t\tif v.Blocks == 0 && !*all {\n\t\t\tcontinue\n\t\t}\n\n\t\t// skip zero size devices\n\t\tif v.BlockSize == 0 && !*all {\n\t\t\tcontinue\n\t\t}\n\n\t\t// skip not only mount point\n\t\tif len(filters.OnlyMountPoints) != 0 {\n\t\t\tif !findInKey(v.Mountpoint, filters.OnlyMountPoints) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// skip hidden mount point\n\t\tif len(filters.HiddenMountPoints) != 0 {\n\t\t\tif findInKey(v.Mountpoint, filters.HiddenMountPoints) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tt := deviceType(v)\n\t\tdeviceMounts[t] = append(deviceMounts[t], v)\n\t}\n\n\t// print tables\n\tfor _, devType := range groups {\n\t\tmounts := deviceMounts[devType]\n\n\t\tshouldPrint := *all\n\t\tif !shouldPrint {\n\t\t\tswitch devType {\n\t\t\tcase localDevice:\n\t\t\t\tshouldPrint = (hasOnlyDevices && onlyLocal) || (!hasOnlyDevices && !hideLocal)\n\t\t\tcase networkDevice:\n\t\t\t\tshouldPrint = (hasOnlyDevices && onlyNetwork) || (!hasOnlyDevices && !hideNetwork)\n\t\t\tcase fuseDevice:\n\t\t\t\tshouldPrint = (hasOnlyDevices && onlyFuse) || (!hasOnlyDevices && !hideFuse)\n\t\t\tcase specialDevice:\n\t\t\t\tshouldPrint = (hasOnlyDevices && onlySpecial) || (!hasOnlyDevices && !hideSpecial)\n\t\t\t}\n\t\t}\n\n\t\tif shouldPrint {\n\t\t\tprintTable(devType, mounts, opts)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\twildcard \"github.com/IGLOU-EU/go-wildcard\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/muesli/termenv\"\n\tflag \"github.com/spf13/pflag\"\n\t\"golang.org/x/term\"\n)\n\nvar (\n\t// Version contains the application version number. It's set via ldflags\n\t// when building.\n\tVersion = \"\"\n\n\t// CommitSHA contains the SHA of the commit that this application was built\n\t// against. It's set via ldflags when building.\n\tCommitSHA = \"\"\n\n\tenv   = termenv.EnvColorProfile()\n\ttheme Theme\n\n\tgroups        = []string{localDevice, networkDevice, fuseDevice, specialDevice, loopsDevice, bindsMount}\n\tallowedValues = strings.Join(groups, \", \")\n\n\tall         = flag.Bool(\"all\", false, \"include pseudo, duplicate, inaccessible file systems\")\n\thideDevices = flag.String(\"hide\", \"\", \"hide specific devices, separated with commas:\\n\"+allowedValues)\n\thideFs      = flag.String(\"hide-fs\", \"\", \"hide specific filesystems, separated with commas\")\n\thideMp      = flag.String(\"hide-mp\", \"\", \"hide specific mount points, separated with commas (supports wildcards)\")\n\tonlyDevices = flag.String(\"only\", \"\", \"show only specific devices, separated with commas:\\n\"+allowedValues)\n\tonlyFs      = flag.String(\"only-fs\", \"\", \"only specific filesystems, separated with commas\")\n\tonlyMp      = flag.String(\"only-mp\", \"\", \"only specific mount points, separated with commas (supports wildcards)\")\n\n\toutput   = flag.String(\"output\", \"\", \"output fields: \"+strings.Join(columnIDs(), \", \"))\n\tsortBy   = flag.String(\"sort\", \"mountpoint\", \"sort output by: \"+strings.Join(columnIDs(), \", \"))\n\twidth    = flag.Uint(\"width\", 0, \"max output width\")\n\tthemeOpt = flag.String(\"theme\", defaultThemeName(), \"color themes: dark, light, ansi\")\n\tstyleOpt = flag.String(\"style\", defaultStyleName(), \"style: unicode, ascii\")\n\n\tavailThreshold = flag.String(\"avail-threshold\", \"10G,1G\", \"specifies the coloring threshold (yellow, red) of the avail column, must be integer with optional SI prefixes\")\n\tusageThreshold = flag.String(\"usage-threshold\", \"0.5,0.9\", \"specifies the coloring threshold (yellow, red) of the usage bars as a floating point number from 0 to 1\")\n\n\t_          = flag.BoolP(\"human-readable\", \"h\", false, \"ignored, just for df compatibility\")\n\tinodes     = flag.Bool(\"inodes\", false, \"list inode information instead of block usage\")\n\tjsonOutput = flag.Bool(\"json\", false, \"output all devices in JSON format\")\n\twarns      = flag.Bool(\"warnings\", false, \"output all warnings to STDERR\")\n\tversion    = flag.Bool(\"version\", false, \"display version\")\n)\n\n// renderJSON encodes the JSON output and prints it.\nfunc renderJSON(m []Mount) error {\n\toutput, err := json.MarshalIndent(m, \"\", \" \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error formatting the json output: %s\", err)\n\t}\n\n\tfmt.Println(string(output))\n\treturn nil\n}\n\n// parseColumns parses the supplied output flag into a slice of column indices.\nfunc parseColumns(cols string) ([]int, error) {\n\tvar i []int\n\n\ts := strings.Split(cols, \",\")\n\tfor _, v := range s {\n\t\tv = strings.TrimSpace(v)\n\t\tif len(v) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tcol, err := stringToColumn(v)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ti = append(i, col)\n\t}\n\n\treturn i, nil\n}\n\n// parseStyle converts user-provided style option into a table.Style.\nfunc parseStyle(styleOpt string) (table.Style, error) {\n\tswitch styleOpt {\n\tcase \"unicode\":\n\t\treturn table.StyleRounded, nil\n\tcase \"ascii\":\n\t\treturn table.StyleDefault, nil\n\tdefault:\n\t\treturn table.Style{}, fmt.Errorf(\"unknown style option: %s\", styleOpt)\n\t}\n}\n\n// parseCommaSeparatedValues parses comma separated string into a map.\nfunc parseCommaSeparatedValues(values string) map[string]struct{} {\n\tm := make(map[string]struct{})\n\tfor _, v := range strings.Split(values, \",\") {\n\t\tv = strings.TrimSpace(v)\n\t\tif len(v) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tv = strings.ToLower(v)\n\t\tm[v] = struct{}{}\n\t}\n\treturn m\n}\n\n// validateGroups validates the parsed group maps.\nfunc validateGroups(m map[string]struct{}) error {\n\tfor k := range m {\n\t\tfound := false\n\t\tfor _, g := range groups {\n\t\t\tif g == k {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"unknown device group: %s\", k)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// findInKey parse a slice of pattern to match the given key.\nfunc findInKey(str string, km map[string]struct{}) bool {\n\tfor p := range km {\n\t\tif wildcard.Match(p, str) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc printVersion() {\n\tinfo, ok := debug.ReadBuildInfo()\n\tvar buildTime time.Time\n\tvar modified bool\n\tif ok {\n\t\tif len(Version) == 0 {\n\t\t\tvs := strings.Split(info.Main.Version, \"-\")\n\t\t\tif len(vs) >= 1 {\n\t\t\t\tVersion = vs[0]\n\t\t\t}\n\t\t}\n\n\t\tfor _, setting := range info.Settings {\n\t\t\tswitch setting.Key {\n\t\t\tcase \"vcs.revision\":\n\t\t\t\tif len(CommitSHA) == 0 {\n\t\t\t\t\tCommitSHA = setting.Value\n\t\t\t\t\tif len(CommitSHA) > 12 {\n\t\t\t\t\t\tCommitSHA = CommitSHA[:12]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase \"vcs.time\":\n\t\t\t\tbuildTime, _ = time.Parse(time.RFC3339, setting.Value)\n\t\t\tcase \"vcs.modified\":\n\t\t\t\tmodified, _ = strconv.ParseBool(setting.Value)\n\t\t\t}\n\t\t}\n\t}\n\n\tif Version == \"\" || Version == \"(devel)\" {\n\t\tVersion = \"(built from source)\"\n\t}\n\n\tfmt.Printf(\"duf %s\", Version)\n\tif len(CommitSHA) > 0 {\n\t\tif modified {\n\t\t\tCommitSHA += \"+modified\"\n\t\t}\n\t\tfmt.Printf(\" (%s)\", CommitSHA)\n\t}\n\tif !buildTime.IsZero() {\n\t\tfmt.Printf(\" (built on %s)\", buildTime.Format(\"2006-01-02\"))\n\t}\n\n\tfmt.Println()\n}\n\nfunc main() {\n\t// hide -h from help, it's just for df compatibility\n\t_ = flag.CommandLine.MarkHidden(\"human-readable\")\n\tflag.Parse()\n\n\tif *version {\n\t\tprintVersion()\n\t\tos.Exit(0)\n\t}\n\n\t// read mount table\n\tm, warnings, err := mounts()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\t// print JSON\n\tif *jsonOutput {\n\t\tif err = renderJSON(m); err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t}\n\t\treturn\n\t}\n\n\t// validate theme\n\ttheme, err = loadTheme(*themeOpt)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\tif env == termenv.ANSI {\n\t\t// enforce ANSI theme for limited color support\n\t\ttheme, err = loadTheme(\"ansi\")\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\t// validate style\n\tstyle, err := parseStyle(*styleOpt)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\t// validate output columns\n\tcolumns, err := parseColumns(*output)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\tif len(columns) == 0 {\n\t\t// no columns supplied, use defaults\n\t\tif *inodes {\n\t\t\tcolumns = []int{1, 6, 7, 8, 9, 10, 11}\n\t\t} else {\n\t\t\tcolumns = []int{1, 2, 3, 4, 5, 10, 11}\n\t\t}\n\t}\n\n\t// validate sort column\n\tsortCol, err := stringToSortIndex(*sortBy)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\t// validate filters\n\tfilters := FilterOptions{\n\t\tHiddenDevices:     parseCommaSeparatedValues(*hideDevices),\n\t\tOnlyDevices:       parseCommaSeparatedValues(*onlyDevices),\n\t\tHiddenFilesystems: parseCommaSeparatedValues(*hideFs),\n\t\tOnlyFilesystems:   parseCommaSeparatedValues(*onlyFs),\n\t\tHiddenMountPoints: parseCommaSeparatedValues(*hideMp),\n\t\tOnlyMountPoints:   parseCommaSeparatedValues(*onlyMp),\n\t}\n\terr = validateGroups(filters.HiddenDevices)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\terr = validateGroups(filters.OnlyDevices)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\n\t// validate arguments\n\tif len(flag.Args()) > 0 {\n\t\tvar mounts []Mount\n\t\tvis := map[string]struct{}{}\n\n\t\tfor _, v := range flag.Args() {\n\t\t\tvar fm []Mount\n\t\t\tfm, err = findMounts(m, v)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\t// de-duplicate\n\t\t\tfor _, v := range fm {\n\t\t\t\tif _, ok := vis[v.Mountpoint]; !ok {\n\t\t\t\t\tmounts = append(mounts, v)\n\t\t\t\t\tvis[v.Mountpoint] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tm = mounts\n\t}\n\n\t// validate availability thresholds\n\tavailbilityThresholds := strings.Split(*availThreshold, \",\")\n\tif len(availbilityThresholds) != 2 {\n\t\tfmt.Fprintln(os.Stderr, fmt.Errorf(\"error parsing avail-threshold: invalid option '%s'\", *availThreshold))\n\t\tos.Exit(1)\n\t}\n\tfor _, threshold := range availbilityThresholds {\n\t\t_, err = stringToSize(threshold)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, \"error parsing avail-threshold:\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\t// validate usage thresholds\n\tusageThresholds := strings.Split(*usageThreshold, \",\")\n\tif len(usageThresholds) != 2 {\n\t\tfmt.Fprintln(os.Stderr, fmt.Errorf(\"error parsing usage-threshold: invalid option '%s'\", *usageThreshold))\n\t\tos.Exit(1)\n\t}\n\tfor _, threshold := range usageThresholds {\n\t\t_, err = strconv.ParseFloat(threshold, 64)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, \"error parsing usage-threshold:\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\t// print out warnings\n\tif *warns {\n\t\tfor _, warning := range warnings {\n\t\t\tfmt.Fprintln(os.Stderr, warning)\n\t\t}\n\t}\n\n\t// detect terminal width\n\tisTerminal := term.IsTerminal(int(os.Stdout.Fd()))\n\tif isTerminal && *width == 0 {\n\t\tw, _, err := term.GetSize(int(os.Stdout.Fd()))\n\t\tif err == nil {\n\t\t\t*width = uint(w)\n\t\t}\n\t}\n\tif *width == 0 {\n\t\t*width = 80\n\t}\n\n\t// print tables\n\trenderTables(m, filters, TableOptions{\n\t\tColumns:   columns,\n\t\tSortBy:    sortCol,\n\t\tStyle:     style,\n\t\tStyleName: *styleOpt,\n\t})\n}\n"
  },
  {
    "path": "man.go",
    "content": "//go:build mango\n// +build mango\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/muesli/mango\"\n\tmpflag \"github.com/muesli/mango-pflag\"\n\t\"github.com/muesli/roff\"\n\tflag \"github.com/spf13/pflag\"\n)\n\nfunc init() {\n\tusage := `You can simply start duf without any command-line arguments:\n\n  $ duf\n\nIf you supply arguments, duf will only list specific devices & mount points:\n\n  $ duf /home /some/file\n\nIf you want to list everything (including pseudo, duplicate, inaccessible file systems):\n\n  $ duf --all\n\nYou can show and hide specific tables:\n\n  $ duf --only local,network,fuse,special,loops,binds\n  $ duf --hide local,network,fuse,special,loops,binds\n\nYou can also show and hide specific filesystems:\n\n  $ duf --only-fs tmpfs,vfat\n  $ duf --hide-fs tmpfs,vfat\n\n...or specific mount points:\n\n  $ duf --only-mp /,/home,/dev\n  $ duf --hide-mp /,/home,/dev\n\nWildcards inside quotes work:\n\n  $ duf --only-mp '/sys/*,/dev/*'\n\nSort the output:\n\n  $ duf --sort size\n\nValid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem.\n\nShow or hide specific columns:\n\n  $ duf --output mountpoint,size,usage\n\nValid keys are: mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem.\n\nList inode information instead of block usage:\n\n  $ duf --inodes\n\nIf duf doesn't detect your terminal's colors correctly, you can set a theme:\n\n  $ duf --theme light\n\nduf highlights the availability & usage columns in red, green, or yellow, depending on how much space is still available. You can set your own thresholds:\n\n  $ duf --avail-threshold=\"10G,1G\"\n  $ duf --usage-threshold=\"0.5,0.9\"\n\nIf you prefer your output as JSON:\n\n  $ duf --json\n`\n\n\tmanPage := mango.NewManPage(1, \"duf\", \"Disk Usage/Free Utility\").\n\t\tWithLongDescription(\"Simple Disk Usage/Free Utility.\\n\"+\n\t\t\t\"Features:\\n\"+\n\t\t\t\"* User-friendly, colorful output.\\n\"+\n\t\t\t\"* Adjusts to your terminal's theme & width.\\n\"+\n\t\t\t\"* Sort the results according to your needs.\\n\"+\n\t\t\t\"* Groups & filters devices.\\n\"+\n\t\t\t\"* Can conveniently output JSON.\").\n\t\tWithSection(\"Usage\", usage).\n\t\tWithSection(\"Notes\", \"Portions of duf's code are copied and modified from https://github.com/shirou/gopsutil.\\n\"+\n\t\t\t\"gopsutil was written by WAKAYAMA Shirou and is distributed under BSD-3-Clause.\").\n\t\tWithSection(\"Authors\", \"duf was written by Christian Muehlhaeuser <https://github.com/muesli/duf>\").\n\t\tWithSection(\"Copyright\", \"Copyright (C) 2020-2022 Christian Muehlhaeuser <https://github.com/muesli>\\n\"+\n\t\t\t\"Released under MIT license.\")\n\n\tflag.VisitAll(mpflag.PFlagVisitor(manPage))\n\tfmt.Println(manPage.Build(roff.NewDocument()))\n\tos.Exit(0)\n}\n"
  },
  {
    "path": "mounts.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strconv\"\n)\n\n// Mount contains all metadata for a single filesystem mount.\ntype Mount struct {\n\tDevice     string      `json:\"device\"`\n\tDeviceType string      `json:\"device_type\"`\n\tMountpoint string      `json:\"mount_point\"`\n\tFstype     string      `json:\"fs_type\"`\n\tType       string      `json:\"type\"`\n\tOpts       string      `json:\"opts\"`\n\tTotal      uint64      `json:\"total\"`\n\tFree       uint64      `json:\"free\"`\n\tUsed       uint64      `json:\"used\"`\n\tInodes     uint64      `json:\"inodes\"`\n\tInodesFree uint64      `json:\"inodes_free\"`\n\tInodesUsed uint64      `json:\"inodes_used\"`\n\tBlocks     uint64      `json:\"blocks\"`\n\tBlockSize  uint64      `json:\"block_size\"`\n\tMetadata   interface{} `json:\"-\"`\n}\n\nfunc readLines(filename string) ([]string, error) {\n\tfile, err := os.Open(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close() //nolint:errcheck // ignore error\n\n\tscanner := bufio.NewScanner(file)\n\tvar s []string\n\tfor scanner.Scan() {\n\t\ts = append(s, scanner.Text())\n\t}\n\n\treturn s, scanner.Err()\n}\n\nfunc unescapeFstab(path string) string {\n\tescaped, err := strconv.Unquote(`\"` + path + `\"`)\n\tif err != nil {\n\t\treturn path\n\t}\n\treturn escaped\n}\n\n//nolint:unused // used on BSD\nfunc byteToString(orig []byte) string {\n\tn := -1\n\tl := -1\n\tfor i, b := range orig {\n\t\t// skip left side null\n\t\tif l == -1 && b == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif l == -1 {\n\t\t\tl = i\n\t\t}\n\n\t\tif b == 0 {\n\t\t\tbreak\n\t\t}\n\t\tn = i + 1\n\t}\n\tif n == -1 {\n\t\treturn string(orig)\n\t}\n\treturn string(orig[l:n])\n}\n\n//nolint:unused // used on OpenBSD\nfunc intToString(orig []int8) string {\n\tret := make([]byte, len(orig))\n\tsize := -1\n\tfor i, o := range orig {\n\t\tif o == 0 {\n\t\t\tsize = i\n\t\t\tbreak\n\t\t}\n\t\tret[i] = byte(o)\n\t}\n\tif size == -1 {\n\t\tsize = len(orig)\n\t}\n\n\treturn string(ret[0:size])\n}\n"
  },
  {
    "path": "mounts_darwin.go",
    "content": "//go:build darwin\n// +build darwin\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Statfs_t {\n\treturn m.Metadata.(unix.Statfs_t)\n}\n\nfunc mounts() ([]Mount, []string, error) {\n\tvar ret []Mount\n\tvar warnings []string\n\n\tcount, err := unix.Getfsstat(nil, unix.MNT_WAIT)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfs := make([]unix.Statfs_t, count)\n\tif _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfor _, stat := range fs {\n\t\topts := \"rw\"\n\t\tif stat.Flags&unix.MNT_RDONLY != 0 {\n\t\t\topts = \"ro\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_SYNCHRONOUS != 0 {\n\t\t\topts += \",sync\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOEXEC != 0 {\n\t\t\topts += \",noexec\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOSUID != 0 {\n\t\t\topts += \",nosuid\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_UNION != 0 {\n\t\t\topts += \",union\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_ASYNC != 0 {\n\t\t\topts += \",async\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_DONTBROWSE != 0 {\n\t\t\topts += \",nobrowse\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_AUTOMOUNTED != 0 {\n\t\t\topts += \",automounted\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_JOURNALED != 0 {\n\t\t\topts += \",journaled\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_MULTILABEL != 0 {\n\t\t\topts += \",multilabel\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOATIME != 0 {\n\t\t\topts += \",noatime\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NODEV != 0 {\n\t\t\topts += \",nodev\"\n\t\t}\n\n\t\tdevice := byteToString(stat.Mntfromname[:])\n\t\tmountPoint := byteToString(stat.Mntonname[:])\n\t\tfsType := byteToString(stat.Fstypename[:])\n\n\t\tif len(device) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\td := Mount{\n\t\t\tDevice:     device,\n\t\t\tMountpoint: mountPoint,\n\t\t\tFstype:     fsType,\n\t\t\tType:       fsType,\n\t\t\tOpts:       opts,\n\t\t\tMetadata:   stat,\n\t\t\tTotal:      stat.Blocks * uint64(stat.Bsize),\n\t\t\tFree:       stat.Bavail * uint64(stat.Bsize),\n\t\t\tUsed:       (stat.Blocks - stat.Bfree) * uint64(stat.Bsize),\n\t\t\tInodes:     stat.Files,\n\t\t\tInodesFree: stat.Ffree,\n\t\t\tInodesUsed: stat.Files - stat.Ffree,\n\t\t\tBlocks:     stat.Blocks,\n\t\t\tBlockSize:  uint64(stat.Bsize),\n\t\t}\n\t\td.DeviceType = deviceType(d)\n\n\t\tret = append(ret, d)\n\t}\n\n\treturn ret, warnings, nil\n}\n"
  },
  {
    "path": "mounts_freebsd.go",
    "content": "//go:build freebsd\n// +build freebsd\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Statfs_t {\n\treturn m.Metadata.(unix.Statfs_t)\n}\n\nfunc mounts() ([]Mount, []string, error) {\n\tvar ret []Mount\n\tvar warnings []string\n\n\tcount, err := unix.Getfsstat(nil, unix.MNT_WAIT)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfs := make([]unix.Statfs_t, count)\n\tif _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfor _, stat := range fs {\n\t\topts := \"rw\"\n\t\tif stat.Flags&unix.MNT_RDONLY != 0 {\n\t\t\topts = \"ro\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_SYNCHRONOUS != 0 {\n\t\t\topts += \",sync\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOEXEC != 0 {\n\t\t\topts += \",noexec\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOSUID != 0 {\n\t\t\topts += \",nosuid\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_UNION != 0 {\n\t\t\topts += \",union\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_ASYNC != 0 {\n\t\t\topts += \",async\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_SUIDDIR != 0 {\n\t\t\topts += \",suiddir\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_SOFTDEP != 0 {\n\t\t\topts += \",softdep\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOSYMFOLLOW != 0 {\n\t\t\topts += \",nosymfollow\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_GJOURNAL != 0 {\n\t\t\topts += \",gjournal\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_MULTILABEL != 0 {\n\t\t\topts += \",multilabel\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_ACLS != 0 {\n\t\t\topts += \",acls\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOATIME != 0 {\n\t\t\topts += \",noatime\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOCLUSTERR != 0 {\n\t\t\topts += \",noclusterr\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NOCLUSTERW != 0 {\n\t\t\topts += \",noclusterw\"\n\t\t}\n\t\tif stat.Flags&unix.MNT_NFS4ACLS != 0 {\n\t\t\topts += \",nfsv4acls\"\n\t\t}\n\n\t\tdevice := byteToString(stat.Mntfromname[:])\n\t\tmountPoint := byteToString(stat.Mntonname[:])\n\t\tfsType := byteToString(stat.Fstypename[:])\n\n\t\tif len(device) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\td := Mount{\n\t\t\tDevice:     device,\n\t\t\tMountpoint: mountPoint,\n\t\t\tFstype:     fsType,\n\t\t\tType:       fsType,\n\t\t\tOpts:       opts,\n\t\t\tMetadata:   stat,\n\t\t\tTotal:      (uint64(stat.Blocks) * uint64(stat.Bsize)),\n\t\t\tFree:       (uint64(stat.Bavail) * uint64(stat.Bsize)),\n\t\t\tUsed:       (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize),\n\t\t\tInodes:     stat.Files,\n\t\t\tInodesFree: uint64(stat.Ffree),\n\t\t\tInodesUsed: stat.Files - uint64(stat.Ffree),\n\t\t\tBlocks:     uint64(stat.Blocks),\n\t\t\tBlockSize:  uint64(stat.Bsize),\n\t\t}\n\t\td.DeviceType = deviceType(d)\n\n\t\tret = append(ret, d)\n\t}\n\n\treturn ret, warnings, nil\n}\n"
  },
  {
    "path": "mounts_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\t// A line of self/mountinfo has the following structure:\n\t// 36  35  98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue\n\t// (0) (1) (2)   (3)   (4)      (5)      (6)   (7) (8)    (9)           (10)\n\t//\n\t// (0) mount ID: unique identifier of the mount (may be reused after umount).\n\t//mountinfoMountID = 0\n\t// (1) parent ID: ID of parent (or of self for the top of the mount tree).\n\t//mountinfoParentID = 1\n\t// (2) major:minor: value of st_dev for files on filesystem.\n\t//mountinfoMajorMinor = 2\n\t// (3) root: root of the mount within the filesystem.\n\t//mountinfoRoot = 3\n\t// (4) mount point: mount point relative to the process's root.\n\tmountinfoMountPoint = 4\n\t// (5) mount options: per mount options.\n\tmountinfoMountOpts = 5\n\t// (6) optional fields: zero or more fields terminated by \"-\".\n\tmountinfoOptionalFields = 6\n\t// (7) separator between optional fields.\n\t//mountinfoSeparator = 7\n\t// (8) filesystem type: name of filesystem of the form.\n\tmountinfoFsType = 8\n\t// (9) mount source: filesystem specific information or \"none\".\n\tmountinfoMountSource = 9\n\t// (10) super options: per super block options.\n\tmountinfoSuperOptions = 10\n)\n\n// Stat returns the mountpoint's stat information.\nfunc (m *Mount) Stat() unix.Statfs_t {\n\treturn m.Metadata.(unix.Statfs_t)\n}\n\nfunc mounts() ([]Mount, []string, error) {\n\tvar warnings []string\n\n\tfilename := \"/proc/self/mountinfo\"\n\tlines, err := readLines(filename)\n\tif err != nil {\n\t\t// wrapcheck: add context to the error.\n\t\treturn nil, nil, fmt.Errorf(\"reading mountinfo %q: %w\", filename, err)\n\t}\n\n\tret := make([]Mount, 0, len(lines))\n\tfor _, line := range lines {\n\t\tnb, fields := parseMountInfoLine(line)\n\t\tif nb == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// if the number of fields does not match the structure of mountinfo,\n\t\t// emit a warning and ignore the line.\n\t\tif nb < 10 || nb > 11 {\n\t\t\twarnings = append(warnings, fmt.Sprintf(\"found invalid mountinfo line: %s\", line))\n\t\t\tcontinue\n\t\t}\n\n\t\t// blockDeviceID := fields[mountinfoMountID]\n\t\tmountPoint := fields[mountinfoMountPoint]\n\t\tmountOpts := fields[mountinfoMountOpts]\n\t\tfstype := fields[mountinfoFsType]\n\t\tdevice := fields[mountinfoMountSource]\n\n\t\tvar stat unix.Statfs_t\n\t\terr := unix.Statfs(mountPoint, &stat)\n\t\tif err != nil {\n\t\t\tif err != os.ErrPermission {\n\t\t\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", mountPoint, err))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tstat = unix.Statfs_t{}\n\t\t}\n\n\t\td := Mount{\n\t\t\tDevice:     device,\n\t\t\tMountpoint: mountPoint,\n\t\t\tFstype:     fstype,\n\t\t\tType:       fsTypeMap[int64(stat.Type)], //nolint:unconvert\n\t\t\tOpts:       mountOpts,\n\t\t\tMetadata:   stat,\n\t\t\tTotal:      (uint64(stat.Blocks) * uint64(stat.Bsize)),                      //nolint:unconvert\n\t\t\tFree:       (uint64(stat.Bavail) * uint64(stat.Bsize)),                      //nolint:unconvert\n\t\t\tUsed:       (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(stat.Bsize), //nolint:unconvert\n\t\t\tInodes:     stat.Files,\n\t\t\tInodesFree: stat.Ffree,\n\t\t\tInodesUsed: stat.Files - stat.Ffree,\n\t\t\tBlocks:     uint64(stat.Blocks), //nolint:unconvert\n\t\t\tBlockSize:  uint64(stat.Bsize),\n\t\t}\n\t\td.DeviceType = deviceType(d)\n\n\t\t// Resolve /dev/mapper/* device names.\n\t\tif strings.HasPrefix(d.Device, \"/dev/mapper/\") {\n\t\t\tre := regexp.MustCompile(`^/dev/mapper/(.*)-(.*)`)\n\t\t\tmatch := re.FindAllStringSubmatch(d.Device, -1)\n\t\t\tif len(match) > 0 && len(match[0]) == 3 {\n\t\t\t\td.Device = filepath.Join(\"/dev\", match[0][1], match[0][2])\n\t\t\t}\n\t\t}\n\n\t\tret = append(ret, d)\n\t}\n\n\treturn ret, warnings, nil\n}\n\n// splitMountInfoFields splits a mountinfo line into its fields.\n// It treats spaces and tabs as field separators and decodes certain octal escapes.\nfunc splitMountInfoFields(line string) []string {\n\tvar fields []string\n\tvar buf strings.Builder\n\n\tfor i := 0; i < len(line); i++ {\n\t\tc := line[i]\n\n\t\t// Treat both space and tab as separators\n\t\tif c == ' ' || c == '\\t' {\n\t\t\tif buf.Len() > 0 {\n\t\t\t\tfields = append(fields, buf.String())\n\t\t\t\tbuf.Reset()\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif c == '\\\\' && i+3 < len(line) {\n\t\t\toct := line[i+1 : i+4]\n\t\t\tif v, err := strconv.ParseInt(oct, 8, 0); err == nil {\n\t\t\t\tswitch byte(v) {\n\t\t\t\tcase ' ', '\\t', '\\n':\n\t\t\t\t\tbuf.WriteByte(byte(v))\n\t\t\t\t\ti += 3\n\t\t\t\t\tcontinue\n\t\t\t\tdefault:\n\t\t\t\t\t// keep unknown escapes as-is\n\t\t\t\t\tbuf.WriteString(\"\\\\\" + oct)\n\t\t\t\t\ti += 3\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbuf.WriteByte(c)\n\t}\n\n\tif buf.Len() > 0 {\n\t\tfields = append(fields, buf.String())\n\t}\n\n\treturn fields\n}\n\n// parseMountInfoLine parses a line of /proc/self/mountinfo and returns the\n// amount of parsed fields and their values.\nfunc parseMountInfoLine(line string) (int, [11]string) {\n\tvar fields [11]string\n\n\tif len(line) == 0 || line[0] == '#' {\n\t\t// ignore comments and empty lines\n\t\treturn 0, fields\n\t}\n\n\tall := splitMountInfoFields(line)\n\n\tvar i int\n\tsawSep := false\n\tsawSup := false\n\n\tfor _, f := range all {\n\t\tif i >= len(fields) {\n\t\t\tbreak\n\t\t}\n\n\t\tif i == mountinfoOptionalFields {\n\t\t\t// (6)  optional fields: zero or more fields of the form \"tag[:value]\"; see below.\n\t\t\t// (7)  separator: the end of the optional fields is marked by a single hyphen.\n\t\t\tif f != \"-\" {\n\t\t\t\t// Join tokens with spaces for mountinfoOptionalFields.\n\t\t\t\tfields[i] = strings.TrimSpace(fields[i] + \" \" + f)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Found separator.\n\t\t\tsawSep = true\n\t\t\ti++\n\t\t\tfields[i] = f\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\n\t\tif i == mountinfoSuperOptions {\n\t\t\t// join tokens with spaces for WSL2 path=... they are splitted around spaces.\n\t\t\tfields[i] = strings.TrimSpace(fields[i] + \" \" + f)\n\t\t\tsawSup = true\n\t\t\tcontinue\n\t\t}\n\n\t\t// Default case: copy with unescape for certain fields\n\t\tswitch i {\n\t\tcase mountinfoMountPoint, mountinfoMountSource, mountinfoFsType:\n\t\t\tfields[i] = unescapeFstab(f)\n\t\tdefault:\n\t\t\tfields[i] = f\n\t\t}\n\t\ti++\n\t}\n\n\t// Handle malformed line (no \"-\" found).\n\tif !sawSep && len(all) > mountinfoOptionalFields {\n\t\ti = mountinfoOptionalFields\n\t}\n\n\t// When super options are present, the index is one less than 11.\n\tif sawSup {\n\t\ti++\n\t}\n\n\t// clear trailing empties.\n\tfor j := i + 1; j < len(fields); j++ {\n\t\tfields[j] = \"\"\n\t}\n\n\treturn i, fields\n}\n"
  },
  {
    "path": "mounts_linux_test.go",
    "content": "//go:build linux\n// +build linux\n\npackage main\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGetFields(t *testing.T) {\n\tvar tt = []struct {\n\t\tinput    string\n\t\tnumber   int\n\t\texpected [11]string\n\t}{\n\t\t// Empty lines\n\t\t{\n\t\t\tinput:  \"\",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \" \",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \"   \",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \"\t\",\n\t\t\tnumber: 0,\n\t\t},\n\n\t\t// Comments\n\t\t{\n\t\t\tinput:  \"#\",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \"# \",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \"#\t\",\n\t\t\tnumber: 0,\n\t\t},\n\t\t{\n\t\t\tinput:  \"# I'm a lazy dog\",\n\t\t\tnumber: 0,\n\t\t},\n\n\t\t// Bad fields\n\t\t{\n\t\t\tinput:    \"1 2\",\n\t\t\tnumber:   2,\n\t\t\texpected: [11]string{\"1\", \"2\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1\t2\",\n\t\t\tnumber:   2,\n\t\t\texpected: [11]string{\"1\", \"2\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1\t2\t\t3\",\n\t\t\tnumber:   3,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1\t2\t\t3   4\",\n\t\t\tnumber:   4,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3\", \"4\"},\n\t\t},\n\n\t\t// No optional separator or no options\n\t\t{\n\t\t\tinput:    \"1 2 3 4 5 6 7 NotASeparator 9 10 11\",\n\t\t\tnumber:   6,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7 NotASeparator 9 10 11\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1 2 3 4 5 6 7 8 9 10 11\",\n\t\t\tnumber:   6,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7 8 9 10 11\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1 2 3 4 5 6 - 9 10 11\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"\", \"-\", \"9\", \"10\", \"11\"},\n\t\t},\n\n\t\t// Normal mount table line\n\t\t{\n\t\t\tinput:    \"22 27 0:21 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"22\", \"27\", \"0:21\", \"/\", \"/proc\", \"rw,nosuid,nodev,noexec,relatime\", \"shared:5\", \"-\", \"proc\", \"proc\", \"rw\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"31 23 0:27 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"31\", \"23\", \"0:27\", \"/\", \"/sys/fs/cgroup\", \"rw,nosuid,nodev,noexec,relatime\", \"shared:9\", \"-\", \"cgroup2\", \"cgroup2\", \"rw,nsdelegate,memory_recursiveprot\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"40 27 0:33 / /tmp rw,nosuid,nodev shared:18 - tmpfs tmpfs\",\n\t\t\tnumber:   10,\n\t\t\texpected: [11]string{\"40\", \"27\", \"0:33\", \"/\", \"/tmp\", \"rw,nosuid,nodev\", \"shared:18\", \"-\", \"tmpfs\", \"tmpfs\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"40 27 0:33 / /tmp rw,nosuid,nodev shared:18 shared:22 - tmpfs tmpfs\",\n\t\t\tnumber:   10,\n\t\t\texpected: [11]string{\"40\", \"27\", \"0:33\", \"/\", \"/tmp\", \"rw,nosuid,nodev\", \"shared:18 shared:22\", \"-\", \"tmpfs\", \"tmpfs\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"50 27 0:33 / /tmp rw,nosuid,nodev - tmpfs tmpfs\",\n\t\t\tnumber:   10,\n\t\t\texpected: [11]string{\"50\", \"27\", \"0:33\", \"/\", \"/tmp\", \"rw,nosuid,nodev\", \"\", \"-\", \"tmpfs\", \"tmpfs\"},\n\t\t},\n\n\t\t// Exceptional mount table lines\n\t\t{\n\t\t\tinput:    \"328 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs - rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"328\", \"27\", \"0:73\", \"/\", \"/mnt/a\", \"rw,relatime\", \"shared:206\", \"-\", \"tmpfs\", \"-\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"330 27 0:73 / /mnt/a rw,relatime shared:206 - tmpfs 👾 rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"330\", \"27\", \"0:73\", \"/\", \"/mnt/a\", \"rw,relatime\", \"shared:206\", \"-\", \"tmpfs\", \"👾\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"335 27 0:73 / /mnt/👾 rw,relatime shared:206 - tmpfs 👾 rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"335\", \"27\", \"0:73\", \"/\", \"/mnt/👾\", \"rw,relatime\", \"shared:206\", \"-\", \"tmpfs\", \"👾\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"509 27 0:78 / /mnt/- rw,relatime shared:223 - tmpfs 👾 rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"509\", \"27\", \"0:78\", \"/\", \"/mnt/-\", \"rw,relatime\", \"shared:223\", \"-\", \"tmpfs\", \"👾\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"362 27 0:76 / /mnt/a\\\\040b rw,relatime shared:215 - tmpfs 👾 rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"362\", \"27\", \"0:76\", \"/\", \"/mnt/a b\", \"rw,relatime\", \"shared:215\", \"-\", \"tmpfs\", \"👾\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1 2 3:3 / /mnt/\\\\011 rw shared:7 - tmpfs - rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"1\", \"2\", \"3:3\", \"/\", \"/mnt/\\t\", \"rw\", \"shared:7\", \"-\", \"tmpfs\", \"-\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"11 2 3:3 / /mnt/a\\\\012b rw shared:7 - tmpfs - rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"11\", \"2\", \"3:3\", \"/\", \"/mnt/a\\nb\", \"rw\", \"shared:7\", \"-\", \"tmpfs\", \"-\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"111 2 3:3 / /mnt/a\\\\134b rw shared:7 - tmpfs - rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"111\", \"2\", \"3:3\", \"/\", \"/mnt/a\\\\b\", \"rw\", \"shared:7\", \"-\", \"tmpfs\", \"-\", \"rw,inode64\"},\n\t\t},\n\t\t{\n\t\t\tinput:    \"1111 2 3:3 / /mnt/a\\\\042b rw shared:7 - tmpfs - rw,inode64\",\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"1111\", \"2\", \"3:3\", \"/\", \"/mnt/a\\\"b\", \"rw\", \"shared:7\", \"-\", \"tmpfs\", \"-\", \"rw,inode64\"},\n\t\t},\n\t\t// WSL2 9p mount table line.\n\t\t{\n\t\t\tinput:    `380 383 0:33 / /usr/lib/wsl/drivers ro,nosuid,nodev,noatime - 9p drivers ro,dirsync,aname=drivers;fmask=222;dmask=222,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8`,\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"380\", \"383\", \"0:33\", \"/\", \"/usr/lib/wsl/drivers\", \"ro,nosuid,nodev,noatime\", \"\", \"-\", \"9p\", \"drivers\", \"ro,dirsync,aname=drivers;fmask=222;dmask=222,mmap,access=client,msize=65536,trans=fd,rfd=8,wfd=8\"},\n\t\t},\n\t\t{\n\t\t\tinput:    `488 383 0:128 / /mnt/c rw,noatime - 9p C:\\134 rw,dirsync,aname=drvfs;path=C:\\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=5,wfd=5`,\n\t\t\tnumber:   11,\n\t\t\texpected: [11]string{\"488\", \"383\", \"0:128\", \"/\", \"/mnt/c\", \"rw,noatime\", \"\", \"-\", \"9p\", \"C:\\\\\", \"rw,dirsync,aname=drvfs;path=C:\\\\;uid=1000;gid=1000;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=5,wfd=5\"},\n\t\t},\n\t\t{\n\t\t\tinput:  `516 78 0:136 / /Docker/host rw,noatime - 9p C:\\134Program\\040Files\\134Docker\\134Docker\\134resources rw,dirsync,aname=drvfs;path=C:\\Program Files\\Docker\\Docker\\resources;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=3,wfd=3`,\n\t\t\tnumber: 11,\n\t\t\texpected: [11]string{\"516\", \"78\", \"0:136\", \"/\", \"/Docker/host\", \"rw,noatime\", \"\", \"-\", \"9p\", \"C:\\\\Program Files\\\\Docker\\\\Docker\\\\resources\", \"rw,dirsync,aname=drvfs;path=C:\\\\Program Files\\\\Docker\\\\Docker\\\\resources;symlinkroot=/mnt/,mmap,access=client,msize=65536,trans=fd,rfd=3,wfd=3\"},\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tnb, actual := parseMountInfoLine(tc.input)\n\t\tif nb != tc.number || !reflect.DeepEqual(actual, tc.expected) {\n\t\t\tt.Errorf(\"\\nparseMountInfoLine(%q) == \\n(%d) %q, \\nexpected (%d) %q\", tc.input, nb, actual, tc.number, tc.expected)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "mounts_openbsd.go",
    "content": "//go:build openbsd\n// +build openbsd\n\npackage main\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc (m *Mount) Stat() unix.Statfs_t {\n\treturn m.Metadata.(unix.Statfs_t)\n}\n\nfunc mounts() ([]Mount, []string, error) {\n\tvar ret []Mount\n\tvar warnings []string\n\n\tcount, err := unix.Getfsstat(nil, unix.MNT_WAIT)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfs := make([]unix.Statfs_t, count)\n\tif _, err = unix.Getfsstat(fs, unix.MNT_WAIT); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tfor _, stat := range fs {\n\t\topts := \"rw\"\n\t\tif stat.F_flags&unix.MNT_RDONLY != 0 {\n\t\t\topts = \"ro\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_SYNCHRONOUS != 0 {\n\t\t\topts += \",sync\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_NOEXEC != 0 {\n\t\t\topts += \",noexec\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_NOSUID != 0 {\n\t\t\topts += \",nosuid\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_NODEV != 0 {\n\t\t\topts += \",nodev\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_ASYNC != 0 {\n\t\t\topts += \",async\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_SOFTDEP != 0 {\n\t\t\topts += \",softdep\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_NOATIME != 0 {\n\t\t\topts += \",noatime\"\n\t\t}\n\t\tif stat.F_flags&unix.MNT_WXALLOWED != 0 {\n\t\t\topts += \",wxallowed\"\n\t\t}\n\n\t\tdevice := byteToString(stat.F_mntfromname[:])\n\t\tmountPoint := byteToString(stat.F_mntonname[:])\n\t\tfsType := byteToString(stat.F_fstypename[:])\n\n\t\tif len(device) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\td := Mount{\n\t\t\tDevice:     device,\n\t\t\tMountpoint: mountPoint,\n\t\t\tFstype:     fsType,\n\t\t\tType:       fsType,\n\t\t\tOpts:       opts,\n\t\t\tMetadata:   stat,\n\t\t\tTotal:      (uint64(stat.F_blocks) * uint64(stat.F_bsize)),\n\t\t\tFree:       (uint64(stat.F_bavail) * uint64(stat.F_bsize)),\n\t\t\tUsed:       (uint64(stat.F_blocks) - uint64(stat.F_bfree)) * uint64(stat.F_bsize),\n\t\t\tInodes:     stat.F_files,\n\t\t\tInodesFree: uint64(stat.F_ffree),\n\t\t\tInodesUsed: stat.F_files - uint64(stat.F_ffree),\n\t\t\tBlocks:     uint64(stat.F_blocks),\n\t\t\tBlockSize:  uint64(stat.F_bsize),\n\t\t}\n\t\td.DeviceType = deviceType(d)\n\n\t\tret = append(ret, d)\n\t}\n\n\treturn ret, warnings, nil\n}\n"
  },
  {
    "path": "mounts_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"golang.org/x/sys/windows\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// Local devices\nconst (\n\tguidBufLen       = windows.MAX_PATH + 1\n\tvolumeNameBufLen = windows.MAX_PATH + 1\n\trootPathBufLen   = windows.MAX_PATH + 1\n\tfileSystemBufLen = windows.MAX_PATH + 1\n)\n\nfunc getMountPoint(guidBuf []uint16) (mountPoint string, err error) {\n\tvar rootPathLen uint32\n\trootPathBuf := make([]uint16, rootPathBufLen)\n\n\terr = windows.GetVolumePathNamesForVolumeName(&guidBuf[0], &rootPathBuf[0], rootPathBufLen*2, &rootPathLen)\n\tif err != nil && err.(windows.Errno) == windows.ERROR_MORE_DATA {\n\t\t// Retry if buffer size is too small\n\t\trootPathBuf = make([]uint16, (rootPathLen+1)/2)\n\t\terr = windows.GetVolumePathNamesForVolumeName(\n\t\t\t&guidBuf[0], &rootPathBuf[0], rootPathLen, &rootPathLen)\n\t}\n\treturn windows.UTF16ToString(rootPathBuf), err\n}\n\nfunc getVolumeInfo(guidOrMountPointBuf []uint16) (volumeName string, fsType string, err error) {\n\tvolumeNameBuf := make([]uint16, volumeNameBufLen)\n\tfsTypeBuf := make([]uint16, fileSystemBufLen)\n\n\terr = windows.GetVolumeInformation(&guidOrMountPointBuf[0], &volumeNameBuf[0], volumeNameBufLen*2,\n\t\tnil, nil, nil,\n\t\t&fsTypeBuf[0], fileSystemBufLen*2)\n\n\treturn windows.UTF16ToString(volumeNameBuf), windows.UTF16ToString(fsTypeBuf), err\n}\n\nfunc getSpaceInfo(guidOrMountPointBuf []uint16) (totalBytes uint64, freeBytes uint64, err error) {\n\terr = windows.GetDiskFreeSpaceEx(&guidOrMountPointBuf[0], nil, &totalBytes, &freeBytes)\n\treturn\n}\n\nfunc getClusterInfo(guidOrMountPointBuf []uint16) (totalClusters uint32, clusterSize uint32, err error) {\n\tvar sectorsPerCluster uint32\n\tvar bytesPerSector uint32\n\terr = GetDiskFreeSpace(&guidOrMountPointBuf[0], &sectorsPerCluster, &bytesPerSector, nil, &totalClusters)\n\tclusterSize = bytesPerSector * sectorsPerCluster\n\treturn\n}\n\nfunc getMount(guidOrMountPointBuf []uint16, isGUID bool) (m Mount, skip bool, warnings []string) {\n\tvar err error\n\tguidOrMountPoint := windows.UTF16ToString(guidOrMountPointBuf)\n\n\tmountPoint := guidOrMountPoint\n\tif isGUID {\n\t\tmountPoint, err = getMountPoint(guidOrMountPointBuf)\n\t\tif err != nil {\n\t\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", guidOrMountPoint, err))\n\t\t}\n\t\t// Skip unmounted volumes\n\t\tif len(mountPoint) == 0 {\n\t\t\tskip = true\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Get volume name & filesystem type\n\tvolumeName, fsType, err := getVolumeInfo(guidOrMountPointBuf)\n\tif err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", guidOrMountPoint, err))\n\t}\n\n\t// Get space info\n\ttotalBytes, freeBytes, err := getSpaceInfo(guidOrMountPointBuf)\n\tif err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", guidOrMountPoint, err))\n\t}\n\n\t// Get cluster info\n\ttotalClusters, clusterSize, err := getClusterInfo(guidOrMountPointBuf)\n\tif err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", guidOrMountPoint, err))\n\t}\n\n\tm = Mount{\n\t\tDevice:     volumeName,\n\t\tMountpoint: mountPoint,\n\t\tFstype:     fsType,\n\t\tType:       fsType,\n\t\tOpts:       \"\",\n\t\tTotal:      totalBytes,\n\t\tFree:       freeBytes,\n\t\tUsed:       totalBytes - freeBytes,\n\t\tBlocks:     uint64(totalClusters),\n\t\tBlockSize:  uint64(clusterSize),\n\t}\n\tm.DeviceType = deviceType(m)\n\treturn\n}\n\nfunc getMountFromGUID(guidBuf []uint16) (m Mount, skip bool, warnings []string) {\n\tm, skip, warnings = getMount(guidBuf, true)\n\n\t// Use GUID as volume name if no label was set\n\tif len(m.Device) == 0 {\n\t\tm.Device = windows.UTF16ToString(guidBuf)\n\t}\n\n\treturn\n}\n\nfunc getMountFromMountPoint(mountPointBuf []uint16) (m Mount, warnings []string) {\n\tm, _, warnings = getMount(mountPointBuf, false)\n\n\t// Use mount point as volume name if no label was set\n\tif len(m.Device) == 0 {\n\t\tm.Device = windows.UTF16ToString(mountPointBuf)\n\t}\n\n\treturn m, warnings\n}\n\nfunc appendLocalMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {\n\tguidBuf := make([]uint16, guidBufLen)\n\n\thFindVolume, err := windows.FindFirstVolume(&guidBuf[0], guidBufLen*2)\n\tif err != nil {\n\t\treturn mounts, warnings, err\n\t}\n\nVolumeLoop:\n\tfor ; ; err = windows.FindNextVolume(hFindVolume, &guidBuf[0], guidBufLen*2) {\n\t\tif err != nil {\n\t\t\tswitch err.(windows.Errno) {\n\t\t\tcase windows.ERROR_NO_MORE_FILES:\n\t\t\t\tbreak VolumeLoop\n\t\t\tdefault:\n\t\t\t\twarnings = append(warnings, fmt.Sprintf(\"%s: %s\", windows.UTF16ToString(guidBuf), err))\n\t\t\t\tcontinue VolumeLoop\n\t\t\t}\n\t\t}\n\n\t\tif m, skip, w := getMountFromGUID(guidBuf); !skip {\n\t\t\tmounts = append(mounts, m)\n\t\t\twarnings = append(warnings, w...)\n\t\t}\n\t}\n\n\tif err = windows.FindVolumeClose(hFindVolume); err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"%s\", err))\n\t}\n\treturn mounts, warnings, nil\n}\n\n// Network devices\nfunc getMountFromNetResource(netResource NetResource) (m Mount, warnings []string) {\n\tmountPoint := windows.UTF16PtrToString(netResource.LocalName)\n\tif !strings.HasSuffix(mountPoint, string(filepath.Separator)) {\n\t\tmountPoint += string(filepath.Separator)\n\t}\n\tmountPointBuf := windows.StringToUTF16(mountPoint)\n\n\tm, _, warnings = getMount(mountPointBuf, false)\n\n\t// Use remote name as volume name if no label was set\n\tif len(m.Device) == 0 {\n\t\tm.Device = windows.UTF16PtrToString(netResource.RemoteName)\n\t}\n\n\treturn\n}\n\nfunc appendNetworkMounts(mounts []Mount, warnings []string) ([]Mount, []string, error) {\n\thEnumResource, err := WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, RESOURCEUSAGE_CONNECTABLE, nil)\n\tif err != nil {\n\t\treturn mounts, warnings, err\n\t}\n\nEnumLoop:\n\tfor {\n\t\t// Reference: https://docs.microsoft.com/en-us/windows/win32/wnet/enumerating-network-resources\n\t\tvar nrBuf [16384]byte\n\t\tcount := uint32(math.MaxUint32)\n\t\tsize := uint32(len(nrBuf))\n\t\tif err := WNetEnumResource(hEnumResource, &count, &nrBuf[0], &size); err != nil {\n\t\t\tswitch err.(windows.Errno) {\n\t\t\tcase windows.ERROR_NO_MORE_ITEMS:\n\t\t\t\tbreak EnumLoop\n\t\t\tdefault:\n\t\t\t\twarnings = append(warnings, err.Error())\n\t\t\t\tbreak EnumLoop\n\t\t\t}\n\t\t}\n\n\t\tfor i := uint32(0); i < count; i++ {\n\t\t\tnr := (*NetResource)(unsafe.Pointer(&nrBuf[uintptr(i)*NetResourceSize]))\n\t\t\tm, w := getMountFromNetResource(*nr)\n\t\t\tmounts = append(mounts, m)\n\t\t\twarnings = append(warnings, w...)\n\t\t}\n\t}\n\n\tif err = WNetCloseEnum(hEnumResource); err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"%s\", err))\n\t}\n\treturn mounts, warnings, nil\n}\n\nfunc mountPointAlreadyPresent(mounts []Mount, mountPoint string) bool {\n\tfor _, m := range mounts {\n\t\tif m.Mountpoint == mountPoint {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc appendLogicalDrives(mounts []Mount, warnings []string) ([]Mount, []string) {\n\tdriveBitmap, err := windows.GetLogicalDrives()\n\tif err != nil {\n\t\twarnings = append(warnings, fmt.Sprintf(\"GetLogicalDrives(): %s\", err))\n\t\treturn mounts, warnings\n\t}\n\n\tfor drive := 'A'; drive <= 'Z'; drive, driveBitmap = drive+1, driveBitmap>>1 {\n\t\tif driveBitmap&0x1 == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tmountPoint := fmt.Sprintf(\"%c:\\\\\", drive)\n\t\tif mountPointAlreadyPresent(mounts, mountPoint) {\n\t\t\tcontinue\n\t\t}\n\n\t\tmountPointBuf := windows.StringToUTF16(mountPoint)\n\t\tm, w := getMountFromMountPoint(mountPointBuf)\n\t\tmounts = append(mounts, m)\n\t\twarnings = append(warnings, w...)\n\t}\n\n\treturn mounts, warnings\n}\n\nfunc mounts() (ret []Mount, warnings []string, err error) {\n\tret = make([]Mount, 0)\n\n\t// Local devices\n\tif ret, warnings, err = appendLocalMounts(ret, warnings); err != nil {\n\t\treturn\n\t}\n\n\t// Network devices\n\tif ret, warnings, err = appendNetworkMounts(ret, warnings); err != nil {\n\t\treturn\n\t}\n\n\t// Logical devices (from GetLogicalDrives bitflag)\n\t// Check any possible logical drives, in case of some special virtual devices, such as RAM disk\n\tret, warnings = appendLogicalDrives(ret, warnings)\n\n\treturn ret, warnings, nil\n}\n\n// Windows API\nconst (\n\t// Windows Networking const\n\t// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw\n\tRESOURCE_CONNECTED  = 0x00000001\n\tRESOURCE_GLOBALNET  = 0x00000002\n\tRESOURCE_REMEMBERED = 0x00000003\n\tRESOURCE_RECENT     = 0x00000004\n\tRESOURCE_CONTEXT    = 0x00000005\n\n\tRESOURCETYPE_ANY      = 0x00000000\n\tRESOURCETYPE_DISK     = 0x00000001\n\tRESOURCETYPE_PRINT    = 0x00000002\n\tRESOURCETYPE_RESERVED = 0x00000008\n\tRESOURCETYPE_UNKNOWN  = 0xFFFFFFFF\n\n\tRESOURCEUSAGE_CONNECTABLE   = 0x00000001\n\tRESOURCEUSAGE_CONTAINER     = 0x00000002\n\tRESOURCEUSAGE_NOLOCALDEVICE = 0x00000004\n\tRESOURCEUSAGE_SIBLING       = 0x00000008\n\tRESOURCEUSAGE_ATTACHED      = 0x00000010\n\tRESOURCEUSAGE_ALL           = RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED\n\tRESOURCEUSAGE_RESERVED      = 0x80000000\n)\n\nvar (\n\t// Windows syscall\n\tmodmpr      = windows.NewLazySystemDLL(\"mpr.dll\")\n\tmodkernel32 = windows.NewLazySystemDLL(\"kernel32.dll\")\n\n\tprocWNetOpenEnumW     = modmpr.NewProc(\"WNetOpenEnumW\")\n\tprocWNetCloseEnum     = modmpr.NewProc(\"WNetCloseEnum\")\n\tprocWNetEnumResourceW = modmpr.NewProc(\"WNetEnumResourceW\")\n\tprocGetDiskFreeSpaceW = modkernel32.NewProc(\"GetDiskFreeSpaceW\")\n\n\tNetResourceSize = unsafe.Sizeof(NetResource{})\n)\n\n// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/ns-winnetwk-netresourcew\ntype NetResource struct {\n\tScope       uint32\n\tType        uint32\n\tDisplayType uint32\n\tUsage       uint32\n\tLocalName   *uint16\n\tRemoteName  *uint16\n\tComment     *uint16\n\tProvider    *uint16\n}\n\n// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetopenenumw\nfunc WNetOpenEnum(scope uint32, resourceType uint32, usage uint32, resource *NetResource) (handle windows.Handle, err error) {\n\tr1, _, e1 := syscall.Syscall6(procWNetOpenEnumW.Addr(), 5, uintptr(scope), uintptr(resourceType), uintptr(usage), uintptr(unsafe.Pointer(resource)), uintptr(unsafe.Pointer(&handle)), 0)\n\tif r1 != windows.NO_ERROR {\n\t\tif e1 != 0 {\n\t\t\terr = e1\n\t\t} else {\n\t\t\terr = syscall.EINVAL\n\t\t}\n\t}\n\treturn\n}\n\n// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetenumresourcew\nfunc WNetEnumResource(enumResource windows.Handle, count *uint32, buffer *byte, bufferSize *uint32) (err error) {\n\tr1, _, e1 := syscall.Syscall6(procWNetEnumResourceW.Addr(), 4, uintptr(enumResource), uintptr(unsafe.Pointer(count)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(bufferSize)), 0, 0)\n\tif r1 != windows.NO_ERROR {\n\t\tif e1 != 0 {\n\t\t\terr = e1\n\t\t} else {\n\t\t\terr = syscall.EINVAL\n\t\t}\n\t}\n\treturn\n}\n\n// Reference: https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetcloseenum\nfunc WNetCloseEnum(enumResource windows.Handle) (err error) {\n\tr1, _, e1 := syscall.Syscall(procWNetCloseEnum.Addr(), 1, uintptr(enumResource), 0, 0)\n\tif r1 != windows.NO_ERROR {\n\t\tif e1 != 0 {\n\t\t\terr = e1\n\t\t} else {\n\t\t\terr = syscall.EINVAL\n\t\t}\n\t}\n\treturn\n}\n\n// Reference: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespacew\nfunc GetDiskFreeSpace(directoryName *uint16, sectorsPerCluster *uint32, bytesPerSector *uint32, numberOfFreeClusters *uint32, totalNumberOfClusters *uint32) (err error) {\n\tr1, _, e1 := syscall.Syscall6(procGetDiskFreeSpaceW.Addr(), 5, uintptr(unsafe.Pointer(directoryName)), uintptr(unsafe.Pointer(sectorsPerCluster)), uintptr(unsafe.Pointer(bytesPerSector)), uintptr(unsafe.Pointer(numberOfFreeClusters)), uintptr(unsafe.Pointer(totalNumberOfClusters)), 0)\n\tif r1 == 0 {\n\t\tif e1 != 0 {\n\t\t\terr = e1\n\t\t} else {\n\t\t\terr = syscall.EINVAL\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "style.go",
    "content": "package main\n\nimport \"github.com/mattn/go-runewidth\"\n\nfunc defaultStyleName() string {\n\t/*\n\t\tDue to a bug in github.com/mattn/go-runewidth v0.0.9, the width of unicode rune(such as '╭') could not be correctly\n\t\tcalculated.\tDegrade to ascii to prevent broken table structure. Remove this once the bug is fixed.\n\t*/\n\tif runewidth.RuneWidth('╭') > 1 {\n\t\treturn \"ascii\"\n\t}\n\n\treturn \"unicode\"\n}\n"
  },
  {
    "path": "table.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/jedib0t/go-pretty/v6/text\"\n\t\"github.com/mattn/go-runewidth\"\n\t\"github.com/muesli/termenv\"\n)\n\n// TableOptions contains all options for the table.\ntype TableOptions struct {\n\tColumns   []int\n\tSortBy    int\n\tStyle     table.Style\n\tStyleName string\n}\n\n// Column defines a column.\ntype Column struct {\n\tID        string\n\tName      string\n\tSortIndex int\n\tWidth     int\n}\n\n// \"Mounted on\", \"Size\", \"Used\", \"Avail\", \"Use%\", \"Inodes\", \"IUsed\", \"IAvail\", \"IUse%\", \"Type\", \"Filesystem\"\n// mountpoint, size, used, avail, usage, inodes, inodes_used, inodes_avail, inodes_usage, type, filesystem\nvar columns = []Column{\n\t{ID: \"mountpoint\", Name: \"Mounted on\", SortIndex: 1},\n\t{ID: \"size\", Name: \"Size\", SortIndex: 12, Width: 7},\n\t{ID: \"used\", Name: \"Used\", SortIndex: 13, Width: 7},\n\t{ID: \"avail\", Name: \"Avail\", SortIndex: 14, Width: 7},\n\t{ID: \"usage\", Name: \"Use%\", SortIndex: 15, Width: 6},\n\t{ID: \"inodes\", Name: \"Inodes\", SortIndex: 16, Width: 7},\n\t{ID: \"inodes_used\", Name: \"IUsed\", SortIndex: 17, Width: 7},\n\t{ID: \"inodes_avail\", Name: \"IAvail\", SortIndex: 18, Width: 7},\n\t{ID: \"inodes_usage\", Name: \"IUse%\", SortIndex: 19, Width: 6},\n\t{ID: \"type\", Name: \"Type\", SortIndex: 10},\n\t{ID: \"filesystem\", Name: \"Filesystem\", SortIndex: 11},\n}\n\n// initializeTable sets up the table writer with initial configurations.\nfunc initializeTable(tab table.Writer, opts TableOptions) {\n\ttab.SetAllowedRowLength(int(*width))\n\ttab.SetOutputMirror(os.Stdout)\n\ttab.Style().Options.SeparateColumns = true\n\ttab.SetStyle(opts.Style)\n}\n\n// appendHeaders adds the header row to the table.\nfunc appendHeaders(tab table.Writer) {\n\theaders := table.Row{}\n\tfor _, v := range columns {\n\t\theaders = append(headers, v.Name)\n\t}\n\ttab.AppendHeader(headers)\n}\n\n// appendRows adds data rows to the table for each mount.\nfunc appendRows(tab table.Writer, m []Mount) {\n\tfor _, v := range m {\n\t\tvar usage, inodeUsage float64\n\t\tif v.Total > 0 {\n\t\t\tusage = float64(v.Used) / float64(v.Total)\n\t\t\tif usage > 1.0 {\n\t\t\t\tusage = 1.0\n\t\t\t}\n\t\t}\n\t\tif v.Inodes > 0 {\n\t\t\tinodeUsage = float64(v.InodesUsed) / float64(v.Inodes)\n\t\t\tif inodeUsage > 1.0 {\n\t\t\t\tinodeUsage = 1.0\n\t\t\t}\n\t\t}\n\n\t\ttab.AppendRow([]interface{}{\n\t\t\ttermenv.String(v.Mountpoint).Foreground(theme.colorBlue), // mounted on\n\t\t\tv.Total,      // size\n\t\t\tv.Used,       // used\n\t\t\tv.Free,       // avail\n\t\t\tusage,        // use%\n\t\t\tv.Inodes,     // inodes\n\t\t\tv.InodesUsed, // inodes used\n\t\t\tv.InodesFree, // inodes avail\n\t\t\tinodeUsage,   // inodes use%\n\t\t\ttermenv.String(v.Fstype).Foreground(theme.colorGray), // type\n\t\t\ttermenv.String(v.Device).Foreground(theme.colorGray), // filesystem\n\t\t\tv.Total,      // size sorting helper\n\t\t\tv.Used,       // used sorting helper\n\t\t\tv.Free,       // avail sorting helper\n\t\t\tusage,        // use% sorting helper\n\t\t\tv.Inodes,     // inodes sorting helper\n\t\t\tv.InodesUsed, // inodes used sorting helper\n\t\t\tv.InodesFree, // inodes avail sorting helper\n\t\t\tinodeUsage,   // inodes use% sorting helper\n\t\t})\n\t}\n}\n\n// computeMaxContentWidths calculates the maximum content width for each visible column.\nfunc computeMaxContentWidths(m []Mount, opts TableOptions) map[int]int {\n\tvisibleCols := append([]int{}, opts.Columns...)\n\tmaxColContent := map[int]int{}\n\t// Seed with headers\n\tfor _, ci := range visibleCols {\n\t\tmaxColContent[ci] = runewidth.StringWidth(columns[ci-1].Name)\n\t}\n\tfor _, v := range m {\n\t\tif inColumns(opts.Columns, 1) {\n\t\t\tif w := runewidth.StringWidth(v.Mountpoint); w > maxColContent[1] {\n\t\t\t\tmaxColContent[1] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 2) {\n\t\t\tif w := runewidth.StringWidth(sizeToString(v.Total)); w > maxColContent[2] {\n\t\t\t\tmaxColContent[2] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 3) {\n\t\t\tif w := runewidth.StringWidth(sizeToString(v.Used)); w > maxColContent[3] {\n\t\t\t\tmaxColContent[3] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 4) {\n\t\t\tif w := runewidth.StringWidth(sizeToString(v.Free)); w > maxColContent[4] {\n\t\t\t\tmaxColContent[4] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 5) {\n\t\t\tvar usage float64\n\t\t\tif v.Total > 0 {\n\t\t\t\tusage = float64(v.Used) / float64(v.Total)\n\t\t\t\tif usage > 1.0 {\n\t\t\t\t\tusage = 1.0\n\t\t\t\t}\n\t\t\t}\n\t\t\tpercentStr := fmt.Sprintf(\"%.1f%%\", usage*100)\n\t\t\tif w := runewidth.StringWidth(percentStr); w > maxColContent[5] {\n\t\t\t\tmaxColContent[5] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 6) {\n\t\t\tif w := runewidth.StringWidth(strconv.FormatUint(v.Inodes, 10)); w > maxColContent[6] {\n\t\t\t\tmaxColContent[6] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 7) {\n\t\t\tif w := runewidth.StringWidth(strconv.FormatUint(v.InodesUsed, 10)); w > maxColContent[7] {\n\t\t\t\tmaxColContent[7] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 8) {\n\t\t\tif w := runewidth.StringWidth(strconv.FormatUint(v.InodesFree, 10)); w > maxColContent[8] {\n\t\t\t\tmaxColContent[8] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 9) {\n\t\t\tvar usage float64\n\t\t\tif v.Inodes > 0 {\n\t\t\t\tusage = float64(v.InodesUsed) / float64(v.Inodes)\n\t\t\t\tif usage > 1.0 {\n\t\t\t\t\tusage = 1.0\n\t\t\t\t}\n\t\t\t}\n\t\t\tpercentStr := fmt.Sprintf(\"%.1f%%\", usage*100)\n\t\t\tif w := runewidth.StringWidth(percentStr); w > maxColContent[9] {\n\t\t\t\tmaxColContent[9] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 10) {\n\t\t\tif w := runewidth.StringWidth(v.Fstype); w > maxColContent[10] {\n\t\t\t\tmaxColContent[10] = w\n\t\t\t}\n\t\t}\n\t\tif inColumns(opts.Columns, 11) {\n\t\t\tif w := runewidth.StringWidth(v.Device); w > maxColContent[11] {\n\t\t\t\tmaxColContent[11] = w\n\t\t\t}\n\t\t}\n\t}\n\treturn maxColContent\n}\n\n// computeAssignedWidths computes the assigned widths for dynamic columns (1, 10, 11).\nfunc computeAssignedWidths(maxColContent map[int]int, opts TableOptions) (map[int]int, int) {\n\tvisibleCols := append([]int{}, opts.Columns...)\n\tnVis := len(visibleCols)\n\n\t// Non-content overhead\n\tsepWidth := 1\n\tpaddingPerCol := 2\n\toverhead := (nVis+1)*sepWidth + nVis*paddingPerCol\n\ttotalAllowed := int(*width)\n\n\t// Determine targets and their need\n\ttargets := []int{}\n\tweights := map[int]float64{1: 0.4, 10: 0.2, 11: 0.4}\n\tweightSum := 0.0\n\tfor _, t := range []int{1, 10, 11} {\n\t\tif inColumns(opts.Columns, t) {\n\t\t\ttargets = append(targets, t)\n\t\t\tweightSum += weights[t]\n\t\t}\n\t}\n\n\t// Sum fixed widths of non-target visible columns\n\tfixedContentWidth := 0\n\tfor _, ci := range visibleCols {\n\t\tif ci == 1 || ci == 10 || ci == 11 {\n\t\t\tcontinue\n\t\t}\n\t\tfixedContentWidth += maxColContent[ci]\n\t}\n\n\tavailableContent := totalAllowed - overhead - fixedContentWidth\n\tif availableContent < 0 {\n\t\tavailableContent = 0\n\t}\n\n\t// Cap target allocations by their max content need\n\tassigned := map[int]int{}\n\tused := 0\n\tif availableContent > 0 && len(targets) > 0 {\n\t\tfor _, t := range targets {\n\t\t\tshare := int(float64(availableContent) * (weights[t] / weightSum))\n\t\t\tif share > maxColContent[t] {\n\t\t\t\tshare = maxColContent[t]\n\t\t\t}\n\t\t\tassigned[t] = share\n\t\t\tused += share\n\t\t}\n\t\t// remainder distribution\n\t\tremainder := availableContent - used\n\t\tfor remainder > 0 {\n\t\t\tbestCol := 0\n\t\t\tbestNeed := 0\n\t\t\tfor _, t := range targets {\n\t\t\t\tneed := maxColContent[t] - assigned[t]\n\t\t\t\tif need > bestNeed {\n\t\t\t\t\tbestNeed = need\n\t\t\t\t\tbestCol = t\n\t\t\t\t}\n\t\t\t}\n\t\t\tif bestNeed <= 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttake := remainder\n\t\t\tif take > bestNeed {\n\t\t\t\ttake = bestNeed\n\t\t\t}\n\t\t\tassigned[bestCol] += take\n\t\t\tremainder -= take\n\t\t}\n\t}\n\n\t// Calculate final slack\n\tpredictedTotal := overhead + fixedContentWidth\n\tfor _, t := range targets {\n\t\tpredictedTotal += assigned[t]\n\t}\n\tslack := totalAllowed - predictedTotal\n\treturn assigned, slack\n}\n\n// setColumnConfigs configures the columns for the table.\nfunc setColumnConfigs(tab table.Writer, maxColContent map[int]int, assigned map[int]int, opts TableOptions, barTransformerFunc func(interface{}) string) {\n\tcfgs := []table.ColumnConfig{\n\t\t{Number: 1, Hidden: !inColumns(opts.Columns, 1), WidthMax: assigned[1]},\n\t\t{Number: 2, Hidden: !inColumns(opts.Columns, 2), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[2]},\n\t\t{Number: 3, Hidden: !inColumns(opts.Columns, 3), Transformer: sizeTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[3]},\n\t\t{Number: 4, Hidden: !inColumns(opts.Columns, 4), Transformer: spaceTransformer, Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[4]},\n\t\t{Number: 5, Hidden: !inColumns(opts.Columns, 5), Transformer: barTransformerFunc, AlignHeader: text.AlignCenter, WidthMax: maxColContent[5]},\n\t\t{Number: 6, Hidden: !inColumns(opts.Columns, 6), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[6]},\n\t\t{Number: 7, Hidden: !inColumns(opts.Columns, 7), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[7]},\n\t\t{Number: 8, Hidden: !inColumns(opts.Columns, 8), Align: text.AlignRight, AlignHeader: text.AlignRight, WidthMax: maxColContent[8]},\n\t\t{Number: 9, Hidden: !inColumns(opts.Columns, 9), Transformer: barTransformerFunc, AlignHeader: text.AlignCenter, WidthMax: maxColContent[9]},\n\t\t{Number: 10, Hidden: !inColumns(opts.Columns, 10), WidthMax: assigned[10]},\n\t\t{Number: 11, Hidden: !inColumns(opts.Columns, 11), WidthMax: assigned[11]},\n\t\t{Number: 12, Hidden: true}, // sortBy helper for size\n\t\t{Number: 13, Hidden: true}, // sortBy helper for used\n\t\t{Number: 14, Hidden: true}, // sortBy helper for avail\n\t\t{Number: 15, Hidden: true}, // sortBy helper for usage\n\t\t{Number: 16, Hidden: true}, // sortBy helper for inodes size\n\t\t{Number: 17, Hidden: true}, // sortBy helper for inodes used\n\t\t{Number: 18, Hidden: true}, // sortBy helper for inodes avail\n\t\t{Number: 19, Hidden: true}, // sortBy helper for inodes usage\n\t}\n\ttab.SetColumnConfigs(cfgs)\n}\n\n// printTable prints an individual table of mounts.\nfunc printTable(title string, m []Mount, opts TableOptions) {\n\ttab := table.NewWriter()\n\tinitializeTable(tab, opts)\n\tappendHeaders(tab)\n\tappendRows(tab, m)\n\n\tif tab.Length() == 0 {\n\t\treturn\n\t}\n\n\tmaxColContent := computeMaxContentWidths(m, opts)\n\tassigned, slack := computeAssignedWidths(maxColContent, opts)\n\n\torigPercentWidth5 := maxColContent[5]\n\torigPercentWidth9 := maxColContent[9]\n\tpercentWidth := origPercentWidth5\n\tif origPercentWidth9 > percentWidth {\n\t\tpercentWidth = origPercentWidth9\n\t}\n\n\tbarWidth := 0\n\tnumBars := 0\n\tif inColumns(opts.Columns, 5) {\n\t\tnumBars++\n\t}\n\tif inColumns(opts.Columns, 9) {\n\t\tnumBars++\n\t}\n\tif numBars > 0 && slack >= 6 {\n\t\t// Each bar consumes: barWidth + 1 (for space)\n\t\t// So for numBars, total consumption is: numBars * (barWidth + 1)\n\t\tmaxBarWidth := min((slack/numBars)-1, 20)\n\n\t\tif maxBarWidth > 0 {\n\t\t\tbarWidth = maxBarWidth\n\t\t\tif inColumns(opts.Columns, 5) {\n\t\t\t\tmaxColContent[5] = barWidth + 1 + percentWidth\n\t\t\t}\n\t\t\tif inColumns(opts.Columns, 9) {\n\t\t\t\tmaxColContent[9] = barWidth + 1 + percentWidth\n\t\t\t}\n\t\t}\n\t}\n\n\t// Define barTransformerFunc\n\tbarTransformerFunc := func(val interface{}) string {\n\t\tusage := val.(float64)\n\t\tif barWidth <= 0 {\n\t\t\ts := fmt.Sprintf(\"%*s\", percentWidth, fmt.Sprintf(\"%.1f%%\", usage*100))\n\t\t\treturn termenv.String(s).String()\n\t\t}\n\n\t\tbw := barWidth\n\t\tvar filledChar, halfChar, emptyChar string\n\t\tif opts.StyleName == \"unicode\" {\n\t\t\tfilledChar = \"█\"\n\t\t\thalfChar = \"▌\"\n\t\t\temptyChar = \" \"\n\t\t} else {\n\t\t\tbw -= 2\n\t\t\tfilledChar = \"#\"\n\t\t\thalfChar = \"#\"\n\t\t\temptyChar = \".\"\n\t\t}\n\n\t\tfilled := int(usage * float64(bw))\n\t\tpartial := usage*float64(bw) - float64(filled)\n\t\tempty := bw - filled\n\n\t\tvar filledStr, emptyStr string\n\t\tfilledStr = strings.Repeat(filledChar, filled)\n\n\t\t// If we have a sufficiently large partial, render a half block.\n\t\tif partial >= 0.5 {\n\t\t\tfilledStr += halfChar\n\t\t\tempty--\n\t\t}\n\n\t\tif empty < 0 {\n\t\t\tempty = 0\n\t\t}\n\t\temptyStr = strings.Repeat(emptyChar, empty)\n\n\t\tvar format string\n\t\tif opts.StyleName == \"unicode\" {\n\t\t\tformat = \"%s%s %*s\"\n\t\t} else {\n\t\t\tformat = \"[%s%s] %*s\"\n\t\t}\n\n\t\t// Apply colors\n\t\tredUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, \",\")[1], 64)\n\t\tyellowUsage, _ := strconv.ParseFloat(strings.Split(*usageThreshold, \",\")[0], 64)\n\n\t\tvar fgColor termenv.Color\n\t\tswitch {\n\t\tcase usage >= redUsage:\n\t\t\tfgColor = theme.colorRed\n\t\tcase usage >= yellowUsage:\n\t\t\tfgColor = theme.colorYellow\n\t\tdefault:\n\t\t\tfgColor = theme.colorGreen\n\t\t}\n\n\t\tfilledPart := termenv.String(filledStr).Foreground(fgColor)\n\t\temptyPart := termenv.String(emptyStr)\n\t\tif opts.StyleName == \"unicode\" {\n\t\t\t// Add background to filled part to prevent black spaces in half blocks\n\t\t\t// Use a background color that complements the foreground\n\t\t\tvar bgColor termenv.Color\n\t\t\tswitch {\n\t\t\tcase usage >= redUsage:\n\t\t\t\tbgColor = theme.colorBgRed\n\t\t\tcase usage >= yellowUsage:\n\t\t\t\tbgColor = theme.colorBgYellow\n\t\t\tdefault:\n\t\t\t\tbgColor = theme.colorBgGreen\n\t\t\t}\n\t\t\tfilledPart = filledPart.Background(bgColor).Foreground(fgColor)\n\t\t\t// Use a neutral background for empty areas\n\t\t\temptyPart = emptyPart.Background(bgColor)\n\t\t}\n\n\t\ts := fmt.Sprintf(format, filledPart, emptyPart, percentWidth, fmt.Sprintf(\"%.1f%%\", usage*100))\n\t\treturn termenv.String(s).String()\n\t}\n\n\tsetColumnConfigs(tab, maxColContent, assigned, opts, barTransformerFunc)\n\n\tsuffix := \"device\"\n\tif tab.Length() > 1 {\n\t\tsuffix = \"devices\"\n\t}\n\ttab.SetTitle(\"%d %s %s\", tab.Length(), title, suffix)\n\n\t// tab.AppendFooter(table.Row{fmt.Sprintf(\"%d %s\", tab.Length(), title)})\n\tsortMode := table.Asc\n\tif opts.SortBy >= 12 {\n\t\tsortMode = table.AscNumeric\n\t}\n\n\ttab.SortBy([]table.SortBy{{Number: opts.SortBy, Mode: sortMode}})\n\ttab.Render()\n}\n\n// sizeTransformer makes a size human-readable.\nfunc sizeTransformer(val interface{}) string {\n\treturn sizeToString(val.(uint64))\n}\n\n// spaceTransformer makes a size human-readable and applies a color coding.\nfunc spaceTransformer(val interface{}) string {\n\tfree := val.(uint64)\n\n\ts := termenv.String(sizeToString(free))\n\tredAvail, _ := stringToSize(strings.Split(*availThreshold, \",\")[1])\n\tyellowAvail, _ := stringToSize(strings.Split(*availThreshold, \",\")[0])\n\tswitch {\n\tcase free < redAvail:\n\t\ts = s.Foreground(theme.colorRed)\n\tcase free < yellowAvail:\n\t\ts = s.Foreground(theme.colorYellow)\n\tdefault:\n\t\ts = s.Foreground(theme.colorGreen)\n\t}\n\n\treturn s.String()\n}\n\n// inColumns return true if the column with index i is in the slice of visible\n// columns cols.\nfunc inColumns(cols []int, i int) bool {\n\tfor _, v := range cols {\n\t\tif v == i {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// sizeToString prettifies sizes.\nfunc sizeToString(size uint64) (str string) {\n\tb := float64(size)\n\n\tswitch {\n\tcase size >= 1<<60:\n\t\tstr = fmt.Sprintf(\"%.1fE\", b/(1<<60))\n\tcase size >= 1<<50:\n\t\tstr = fmt.Sprintf(\"%.1fP\", b/(1<<50))\n\tcase size >= 1<<40:\n\t\tstr = fmt.Sprintf(\"%.1fT\", b/(1<<40))\n\tcase size >= 1<<30:\n\t\tstr = fmt.Sprintf(\"%.1fG\", b/(1<<30))\n\tcase size >= 1<<20:\n\t\tstr = fmt.Sprintf(\"%.1fM\", b/(1<<20))\n\tcase size >= 1<<10:\n\t\tstr = fmt.Sprintf(\"%.1fK\", b/(1<<10))\n\tdefault:\n\t\tstr = fmt.Sprintf(\"%dB\", size)\n\t}\n\n\treturn\n}\n\n// stringToSize transforms an SI size into a number.\nfunc stringToSize(s string) (size uint64, err error) {\n\tregex := regexp.MustCompile(`^(\\d+)([KMGTPE]?)$`)\n\tmatches := regex.FindStringSubmatch(s)\n\tif len(matches) == 0 {\n\t\treturn 0, fmt.Errorf(\"'%s' is not valid, must have integer with optional SI prefix\", s)\n\t}\n\n\tnum, err := strconv.ParseUint(matches[1], 10, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif matches[2] != \"\" {\n\t\tprefix := matches[2]\n\t\tswitch prefix {\n\t\tcase \"K\":\n\t\t\tsize = num << 10\n\t\tcase \"M\":\n\t\t\tsize = num << 20\n\t\tcase \"G\":\n\t\t\tsize = num << 30\n\t\tcase \"T\":\n\t\t\tsize = num << 40\n\t\tcase \"P\":\n\t\t\tsize = num << 50\n\t\tcase \"E\":\n\t\t\tsize = num << 60\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"prefix '%s' not allowed, valid prefixes are K, M, G, T, P, E\", prefix)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tsize = num\n\t}\n\treturn\n}\n\n// stringToColumn converts a column name to its index.\nfunc stringToColumn(s string) (int, error) {\n\ts = strings.ToLower(s)\n\n\tfor i, v := range columns {\n\t\tif v.ID == s {\n\t\t\treturn i + 1, nil\n\t\t}\n\t}\n\n\treturn 0, fmt.Errorf(\"unknown column: %s (valid: %s)\", s, strings.Join(columnIDs(), \", \"))\n}\n\n// stringToSortIndex converts a column name to its sort index.\nfunc stringToSortIndex(s string) (int, error) {\n\ts = strings.ToLower(s)\n\n\tfor _, v := range columns {\n\t\tif v.ID == s {\n\t\t\treturn v.SortIndex, nil\n\t\t}\n\t}\n\n\treturn 0, fmt.Errorf(\"unknown column: %s (valid: %s)\", s, strings.Join(columnIDs(), \", \"))\n}\n\n// columnsIDs returns a slice of all column IDs.\nfunc columnIDs() []string {\n\ts := make([]string, len(columns))\n\tfor i, v := range columns {\n\t\ts[i] = v.ID\n\t}\n\n\treturn s\n}\n"
  },
  {
    "path": "themes.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/muesli/termenv\"\n)\n\n// Theme defines a color theme used for printing tables.\ntype Theme struct {\n\tcolorRed     termenv.Color\n\tcolorYellow  termenv.Color\n\tcolorGreen   termenv.Color\n\tcolorBlue    termenv.Color\n\tcolorGray    termenv.Color\n\tcolorMagenta termenv.Color\n\tcolorCyan    termenv.Color\n\n\tcolorBgRed    termenv.Color\n\tcolorBgYellow termenv.Color\n\tcolorBgGreen  termenv.Color\n}\n\nfunc defaultThemeName() string {\n\tif !termenv.HasDarkBackground() {\n\t\treturn \"light\"\n\t}\n\treturn \"dark\"\n}\n\nfunc loadTheme(theme string) (Theme, error) {\n\tthemes := make(map[string]Theme)\n\n\tthemes[\"dark\"] = Theme{\n\t\tcolorRed:      env.Color(\"#E88388\"),\n\t\tcolorYellow:   env.Color(\"#DBAB79\"),\n\t\tcolorGreen:    env.Color(\"#A8CC8C\"),\n\t\tcolorBlue:     env.Color(\"#71BEF2\"),\n\t\tcolorGray:     env.Color(\"#B9BFCA\"),\n\t\tcolorMagenta:  env.Color(\"#D290E4\"),\n\t\tcolorCyan:     env.Color(\"#66C2CD\"),\n\t\tcolorBgRed:    env.Color(\"#2d1b1b\"),\n\t\tcolorBgYellow: env.Color(\"#2d2d1b\"),\n\t\tcolorBgGreen:  env.Color(\"#1b2d1b\"),\n\t}\n\n\tthemes[\"light\"] = Theme{\n\t\tcolorRed:      env.Color(\"#D70000\"),\n\t\tcolorYellow:   env.Color(\"#FFAF00\"),\n\t\tcolorGreen:    env.Color(\"#005F00\"),\n\t\tcolorBlue:     env.Color(\"#000087\"),\n\t\tcolorGray:     env.Color(\"#303030\"),\n\t\tcolorMagenta:  env.Color(\"#AF00FF\"),\n\t\tcolorCyan:     env.Color(\"#0087FF\"),\n\t\tcolorBgRed:    env.Color(\"#ffdede\"),\n\t\tcolorBgYellow: env.Color(\"#fff4d0\"),\n\t\tcolorBgGreen:  env.Color(\"#e6ffe6\"),\n\t}\n\n\tthemes[\"ansi\"] = Theme{\n\t\tcolorRed:      env.Color(\"9\"),\n\t\tcolorYellow:   env.Color(\"11\"),\n\t\tcolorGreen:    env.Color(\"10\"),\n\t\tcolorBlue:     env.Color(\"12\"),\n\t\tcolorGray:     env.Color(\"7\"),\n\t\tcolorMagenta:  env.Color(\"13\"),\n\t\tcolorCyan:     env.Color(\"8\"),\n\t\tcolorBgRed:    env.Color(\"1\"),\n\t\tcolorBgYellow: env.Color(\"3\"),\n\t\tcolorBgGreen:  env.Color(\"2\"),\n\t}\n\n\tif _, ok := themes[theme]; !ok {\n\t\treturn Theme{}, fmt.Errorf(\"unknown theme: %s\", theme)\n\t}\n\n\treturn themes[theme], nil\n}\n"
  }
]