[
  {
    "path": ".gitignore",
    "content": "\n########## GOLAND ##########\n\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n.idea/**/aws.xml\n.idea/**/contentModel.xml\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n.idea/**/gradle.xml\n.idea/**/libraries\n.idea/**/mongoSettings.xml\n*.iws\natlassian-ide-plugin.xml\n.idea/httpRequests\n.idea/caches/build_file_checksums.ser\n.idea/$CACHE_FILE$\n\n########## Linux ##########\n\n*~\n.fuse_hidden*\n.directory\n.Trash-*\n.nfs*\n\n########## Custom ##########\n\n\n_out/*\n\ninput.test\ninput1.test\ninput2.test\ninput3.test\ninput.json\ninput1.json\ninput2.json\ninput_small.json\ninput1_small.json\ninput2_small.json"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local storage ignored files\n/dataSources/\n/dataSources.local.xml\n"
  },
  {
    "path": ".idea/better-docker-ps.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"Go\" enabled=\"true\" />\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\" />\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"LanguageDetectionInspection\" enabled=\"false\" level=\"WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"SpellCheckingInspection\" enabled=\"false\" level=\"TYPO\" enabled_by_default=\"false\">\n      <option name=\"processCode\" value=\"true\" />\n      <option name=\"processLiterals\" value=\"true\" />\n      <option name=\"processComments\" value=\"true\" />\n    </inspection_tool>\n  </profile>\n</component>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/better-docker-ps.iml\" filepath=\"$PROJECT_DIR$/.idea/better-docker-ps.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Mike Schwörer\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"
  },
  {
    "path": "Makefile",
    "content": "build:\n\tgo generate ./...\n\tCGO_ENABLED=0 go build -o _out/dops ./cmd/dops\n\nrun: build\n\t./_out/dops\n\nclean:\n\tgo clean\n\trm ./_out/*\n\npackage:\n\t@echo \"Make sure you have updated file://$(shell pwd)/consts/version.go\"\n\t@echo \"Make sure youhave created+pished a matching tag\"\n\t@read -p \"Continue?\"\n\n\tgo clean\n\trm -rf ./_out/*\n\n\t_data/package-data/sanitycheck.sh\n\n\tGOARCH=386   GOOS=linux   CGO_ENABLED=0 go build -o _out/dops_linux-386-static                     ./cmd/dops  # Linux - 32 bit\n\tGOARCH=amd64 GOOS=linux   CGO_ENABLED=0 go build -o _out/dops_linux-amd64-static                   ./cmd/dops  # Linux - 64 bit\n\tGOARCH=arm64 GOOS=linux   CGO_ENABLED=0 go build -o _out/dops_linux-arm64-static                   ./cmd/dops  # Linux - ARM\n\tGOARCH=386   GOOS=linux                 go build -o _out/dops_linux-386                            ./cmd/dops  # Linux - 32 bit\n\tGOARCH=amd64 GOOS=linux                 go build -o _out/dops_linux-amd64                          ./cmd/dops  # Linux - 64 bit\n\tGOARCH=arm64 GOOS=linux                 go build -o _out/dops_linux-arm64                          ./cmd/dops  # Linux - ARM\n\tGOARCH=arm   GOOS=linux   GOARM=5       go build -o _out/dops_linux-arm32v5                        ./cmd/dops  # Linux - ARM32 v5 (e.g. Raspberry 3)\n\tGOARCH=arm   GOOS=linux   GOARM=6       go build -o _out/dops_linux-arm32v6                        ./cmd/dops  # Linux - ARM32 v6\n\tGOARCH=arm   GOOS=linux   GOARM=7       go build -o _out/dops_linux-arm32v7                        ./cmd/dops  # Linux - ARM32 v7\n\tGOARCH=amd64 GOOS=darwin                go build -o _out/dops_macos-amd64                          ./cmd/dops  # macOS - 64 bit\n\tGOARCH=arm64 GOOS=darwin                go build -o _out/dops_macos-arm64                          ./cmd/dops  # macOS (Apple Silicon)\n\tGOARCH=amd64 GOOS=openbsd               go build -o _out/dops_openbsd-amd64                        ./cmd/dops  # OpenBSD - 64 bit\n\tGOARCH=arm64 GOOS=openbsd               go build -o _out/dops_openbsd-arm64                        ./cmd/dops  # OpenBSD - ARM\n\tGOARCH=amd64 GOOS=freebsd               go build -o _out/dops_freebsd-amd64                        ./cmd/dops  # FreeBSD - 64 bit\n\tGOARCH=arm64 GOOS=freebsd               go build -o _out/dops_freebsd-arm64                        ./cmd/dops  # FreeBSD - ARM\n\n\t_data/package-data/aur-git.sh\n\t_data/package-data/aur-bin.sh\n\t_data/package-data/homebrew.sh\n\n\techo \"\"\n\techo \"[TODO]: call 'make package-push-aur-git'  \"\n\techo \"[TODO]: call 'make package-push-aur-bin'  \"\n\techo \"[TODO]: call 'make package-push-homebrew' \"\n\techo \"[TODO]: create github release\"\n\techo \"\"\n\npackage-push-aur-git:\n\tcd _out/dops-git && git push\n\npackage-push-aur-bin:\n\tcd _out/dops-bin && git push\n\npackage-push-homebrew:\n\tcd _out/homebrew-tap && git push\n"
  },
  {
    "path": "README.md",
    "content": "# ./dops - better `docker ps` \nA replacement for the default docker-ps that tries really hard to fit within your terminal width.\n\n![](readme.d/main.png)\n\n## Rationale\n\nBy default, my `docker ps` output is really wide and every line wraps around into three.\nThis (obviously) breaks the tabular display and makes everything chaotic.  \n*(This gets especially bad if one container has multiple port mappings, and they are all displayed in a single row)*\nIt doesn't look like we'll get improved output in the foreseeable future (see [moby#7477](https://github.com/moby/moby/issues/7477)), so I decided to make my own drop-in replacement.  \n\n## Features\n\n - All normal commandline flags/options from docker-ps work *(almost)* the same.\n - Write multi-value data (like multiple port mappings, multiple networks, etc.) into multiple lines instead of concatenating them.\n - Add color to the STATE and STATUS column (green / yellow / red).\n - Automatically remove columns in the output until it fits in the current terminal width.\n - sort the output with the `--sort` argument\n - Enter watch mode with the `--watch` argument\n\nMore Changes from default docker-ps:\n - Show (by default) the container-cmd without arguments.\n - Show the ImageName (by default) without the registry prefix, and split ImageName and ImageTag into two columns.\n - Added the columns IP and NETWORK to the default column set (if they fit)\n - Added support for a few new columns (via --format):  \n   `{{.ImageName}`, `{{.ImageTag}`, `{{.Tag}`, `{{.ImageRegistry}`, `{{.Registry}`, `{{.ShortCommand}`, `{{.LabelKeys}`, `{{.IP}`                         \n - Added options to control the color-output, the used socket, the time-zone and time-format, etc (see `./dops --help`) \n\n## Getting started\n\n### Generic Linux (e.g. Debian/Fedora/...)\n - Download the latest binary from the [releases page](https://github.com/Mikescher/better-docker-ps/releases) and put it into your PATH (eg /usr/local/bin)\n - You can also use the following one-liner (afterwards you can use the `dops` command everywhere):\n```\nsudo wget \"https://github.com/Mikescher/better-docker-ps/releases/latest/download/dops_linux-amd64-static\" -O \"/usr/local/bin/dops\" && sudo chmod +x \"/usr/local/bin/dops\"\n```\n\n### ArchLinux\n - Alternatively you can use one of the AUR packages (under Arch Linux):\n    * https://aur.archlinux.org/packages/dops-bin (installs `dops` into your PATH)\n    * https://aur.archlinux.org/packages/dops-git (installs `dops` into your PATH)\n - or the homebrew package: \n    * `brew tap mikescher/tap && brew install dops`\n\n### Optional steps\n - Alias the docker ps command to `dops` (see [section below](#usage-as-drop-in-replacement))\n\n### Building from source\n\nIf you want to build `dops` from source, you need to have Go installed.\n\n```sh\ngit clone https://github.com/Mikescher/better-docker-ps.git\ncd better-docker-ps\nmake build\nmv _out/dops \"$HOME/.local/bin/\"\n```\n\n## Screenshots\n\n![](readme.d/fullsize.png)  \nAll (default) columns visible\n\n&nbsp;\n\n![](readme.d/default.png)  \nOutput on a medium sized terminal\n\n&nbsp;\n\n![](readme.d/small.png)  \nOutput on a small terminal\n\n&nbsp;\n\n## Usage as drop-in replacement\n\nYou can fully replace docker ps by creating a shell function in your `.bashrc` / `.zshrc`...\n\n~~~sh\ndocker() {\n  case $1 in\n    ps)\n      shift\n      command dops \"$@\"\n      ;;\n    *)\n      command docker \"$@\";;\n  esac\n}\n~~~\n\nThis will alias every call to `docker ps ...` with `dops ...` (be sure to have the dops binary in your PATH).\n\nIf you are using the fish-shell you have to create a (similar) function:\n\n~~~fish\nfunction docker\n    if test -n \"$argv[1]\"\n        switch $argv[1]\n            case ps\n                dops $argv[2..-1]\n            case '*'\n                command docker $argv[1..-1]\n        end\n    end\nend\n~~~\n\n## Changing the output format\n\nBy default dops tries to be \"intelligent\" and find the best output format for your terminal width.\nThe current output formats (= table columns) are defined in the [options.go](https://github.com/Mikescher/better-docker-ps/blob/master/cli/options.go).\nThe first format that fits in your terminal width is used.\n\nBut you can also override it by supplying a `--format` parameter. If you supply more than one `--format` parameter the first one that fits your terminal is used (same logic as with the default ones...)\n\nNormally only simple columns aka `{{.Status}}` are supported.  \nBut you can also use the full golang template syntax (e.g. `{{ printf \"%.15s\" .Command }}`).\nIn this case it can be useful to specify the column header by prefixing it with a colon (`SHORTENED NAME:{{ printf \"%.10s\" (join .Names \";\") }}`)\n\nThe following functions are defined in these templates (plus the [default go functions](https://pkg.go.dev/text/template)):\n - `join`: strings.Join\n - `array_last`: v\\[-1\\]\n - `array_slice`: v\\[a..b\\] \n - `in_array`: v1.contains(v2)\n - `json`: json.Marshal(v)\n - `json_indent`: json.MarshalIndent(v, \"\", \"  \")\n - `json_pretty`:  json.Indent(v, \"\", \"  \")\n - `coalesce`: v1 ?? v2\n - `to_string`: fmt.Sprintf(\"%v\", v)\n - `deref`: *v\n - `now`: time.Now()\n - `uniqid`: UUID\n\nExamples:\n~~~~\n$ ./dops --format \"table {{.ID}}\"\n$ ./dops --format \"table {{.ID}}\\\\t{{.Names}}\\\\t{{.State}}\"\n\n$ ./dops --format \"idlist\"\n\n$ ./dops --format \"table {{.ID}}\\\\t{{.Names}}\\\\t{{.State}}\"  --format \"table {{.ID}}\\\\t{{.Names}}\" --format \"table {{.ID}}\"\n\n$ ./dops --format \"ID: {{.ID}}; Name: {{.Names}}\"\n\n$ ./dops -aq\n\n$ ./dops --sort \"IP\" --sort-direction \"ASC\"\n\n$ ./dops --format \"table {{.ID}}\\\\tCMD:{{ printf \\\"%.15s\\\" .Command }}\"\n$ ./dops --format \"table {{.ID}}\\\\tNAME:{{ printf \\\"%.10s\\\" (join .Names \\\";\\\") }}\"\n\n~~~~\n\n## Persistant configuration\n\nYou can also configure some/most of the options via a configuration file.  \nPlace a TOML formatted file in `$HOME/.config/dops.conf` / `$XDG_CONFIG_HOME/dops.conf`.  \n( `~/Library/Application Support/dops.conf` under macOS )\n\nThe following keys are supported:\n - verbose\n - silent\n - timezone\n - timeformat\n - timeformat-header\n - color\n - socket\n - all\n - size\n - filter (= string array)\n - search\n - format (= string array)\n - last\n - latest\n - truncate\n - header (= true / false / simple)\n - sort (= string array)\n - sort-direction (= string array)\n\nExample:\n```toml\nverbose = 0\n\ntimezone = \"Europe/Berlin\"\n\nformat = [\n   \"table {{.ID}}\\t{{.Names}}\\t{{.State}}\\t{{.Status}}\",\n   \"table {{.ID}}\\t{{.Names}}\\t{{.State}}\",\n   \"table {{.ID}}\\t{{.Names}}\",\n   \"table {{.ID}}\",\n]\n\nheader = \"simple\"\n```\n\n## Manual\n\nOutput of `./dops --help`:\n\n~~~~~~\nbetter-docker-ps\n\nUsage:\n  dops [OPTIONS]                     List docker container\n\nOptions (default):\n  -h, --help                         Show this screen.\n  --version                          Show version.\n  --all , -a                         Show all containers (default shows just running)\n  --filter <ftr>, -f <ftr>           Filter output based on conditions provided\n  --search <str>, -g <str>           Filter output by substring match across all visible columns (case-insensitive)\n  --format <fmt>                     Pretty-print containers using a Go template\n  --last , -n                        Show n last created containers (includes all states)\n  --latest , -l                      Show the latest created container (includes all states)\n  --no-trunc                         Don't truncate output (eg ContainerIDs, Sha256 Image references, commandline)\n  --quiet , -q                       Only display container IDs\n  --size , -s                        Display total file sizes\n\nOptions (extra | do not exist in `docker ps`):\n  --silent                           Do not print any output\n  --timezone                         Specify the timezone for date outputs\n  --color <true|false>               Enable/Disable terminal color output\n  --no-color                         Disable terminal color output\n  --socket <filepath>                Specify the docker socket location (Default: `auto` - which calls the docker cli to determine the socket)\n  --timeformat <go-time-fmt>         Specify the datetime output format (golang syntax)\n  --no-header                        Do not print the table header\n  --simple-header                    Do not print the lines under the header\n  --format <fmt>                     You can specify multiple formats and the first one that fits your terminal widt will be used\n  --sort <col>                       Sort output by a specific column, use the same identifier as in --format, only useful together with table formats \n  --sort-direction <ASC|DESC>        The sort direction, only useful in combination with --sort\n  --watch <interval>, -w <interval>  Automatically refresh output periodically (interval is optional, default: 2s) \n\nAvailable --format keys (default):\n  {{.ID}}                            Container ID\n  {{.Image}}                         Image ID\n  {{.Command}}                       Quoted command\n  {{.CreatedAt}}                     Time when the container was created.\n  {{.RunningFor}}                    Elapsed time since the container was started.\n  {{.Ports}}                         Published ports. ([!] differs from docker CLI, these are only the published ports)\n  {{.State}}                         Container status\n  {{.Status}}                        Container status with details\n  {{.Size}}                          Container disk size.\n  {{.Names}}                         Container names.\n  {{.Labels}}                        All labels assigned to the container.\n  {{.Label}}                         [!] Unsupported\n  {{.Mounts}}                        Names of the volumes mounted in this container.\n  {{.Networks}}                      Names of the networks attached to this container.\n\nAvailable --format keys (extra | do not exist in `docker ps`):\n  {{.ImageName}}                     Image ID (without tag and registry)\n  {{.ImageTag}}, {{.Tag}}            Image Tag\n  {{.ImageRegistry}}, {{.Registry}}  Image Registry\n  {{.ShortCommand}}                  Command without arguments\n  {{.LabelKeys}}                     All labels assigned to the container (keys only)\n  {{.ShortPublishedPorts}}           Published ports, shorter output than {{.Ports}}\n  {{.LongPublishedPorts}}            Published ports, full output with IP\n  {{.ExposedPorts}}                  Exposed ports\n  {{.PublishedPorts}}                Published ports\n  {{.NotPublishedPorts}}             Exposed but not published ports\n  {{.PublicPorts}}                   Only the public part of published ports\n  {{.IP}}                            Internal IP Address\n~~~~~~\n"
  },
  {
    "path": "_data/package-data/aur-bin/.gitignore",
    "content": "pkg/\nsrc/\ndops/\ndops-bin/\ndops-git/\n.SRCINFO\n*.tar.zst"
  },
  {
    "path": "_data/package-data/aur-bin/PKGBUILD",
    "content": "# Maintainer: Mikescher <aur@mikescher.com>\n# Repo:       https://github.com/Mikescher/better-docker-ps\n\npkgname=dops-bin\npkgver=1.13\npkgrel=1\n\npkgdesc=\"A replacement for the default docker-ps that tries really hard to fit into the width of your terminal.\"\n\nurl=\"https://github.com/Mikescher/better-docker-ps\"\nlicense=('Apache')\n\narch=('x86_64')\n\n_binary=\"dops_linux-amd64\"\n\nsource=(\n  \"https://github.com/Mikescher/better-docker-ps/releases/download/v${pkgver}/${_binary}\"\n)\n\n_bin_sha='48addb268291151b0dd6752675829dc3ba81523d1515d6094ccf18269e26dfd6'\n\nsha256sums=(\n  \"$_bin_sha\"\n)\n\npackage()\n{\n  install -D -m755 \"$srcdir/${_binary}\" \"${pkgdir}/usr/bin/dops\"\n}\n\n"
  },
  {
    "path": "_data/package-data/aur-bin.sh",
    "content": "#!/bin/bash\n\nset -o nounset   # disallow usage of unset vars  ( set -u )\nset -o errexit   # Exit immediately if a pipeline returns non-zero.  ( set -e )\nset -o errtrace  # Allow the above trap be inherited by all functions in the script.  ( set -E )\nset -o pipefail  # Return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status\nIFS=$'\\n\\t'      # Set $IFS to only newline and tab.\n\ncd \"$(dirname \"$0\")/aur-bin\"\ngit clean -ffdX\n\nversion=\"$(cd ../../../ && git tag --sort=-v:refname | grep -P 'v[0-9\\.]' | head -1 | cut -c2-)\"\ncs0=\"$(cd ../../../ && sha256sum _out/dops_linux-amd64 | cut -d ' ' -f 1)\"\n\necho \"Version: ${version} (${cs0})\"\n\nsed --regexp-extended  -i \"s/pkgver=[0-9\\.]+/pkgver=${version}/g\"         PKGBUILD\nsed --regexp-extended  -i \"s/_bin_sha='[A-Za-z0-9]+'/_bin_sha='${cs0}'/g\" PKGBUILD\n\nnamcap PKGBUILD\nmakepkg --printsrcinfo > .SRCINFO\n# makepkg #(do not makepkg, release is probably not live)\n\n\ncd ../../../\ngit clone ssh://aur@aur.archlinux.org/dops-bin.git _out/dops-bin\ncp -v _data/package-data/aur-bin/PKGBUILD _out/dops-bin/PKGBUILD\ncp -v _data/package-data/aur-bin/.SRCINFO _out/dops-bin/.SRCINFO\n\n\n\ncd _out/dops-bin\n\ngit add PKGBUILD\ngit add .SRCINFO\n\nif [ -z \"$(git status --porcelain)\" ]; then \n  echo \"(!) Nothing changed -- nothing to commit\"\nelse \n  git commit -m \"v${version}\"\nfi\n\n\ncd \"../../_data/package-data/aur-bin\"\ngit clean -ffdX\n\n# git push manually (!)\n"
  },
  {
    "path": "_data/package-data/aur-git/.gitignore",
    "content": "pkg/\nsrc/\ndops/\ndops-bin/\ndops-git/\n.SRCINFO\n*.tar.zst"
  },
  {
    "path": "_data/package-data/aur-git/PKGBUILD",
    "content": "# Maintainer: Mikescher <aur@mikescher.com>\n# Repo:       https://github.com/Mikescher/better-docker-ps\n\npkgname=dops-git\npkgver=1.13\npkgrel=1\n\npkgdesc=\"A replacement for the default docker-ps that tries really hard to fit into the width of your terminal.\"\n\nurl=\"https://github.com/Mikescher/better-docker-ps\"\nlicense=('Apache')\n\nmakedepends=('go' 'git')\n\narch=('any')\n\nsource=(\"$pkgname::git+https://github.com/Mikescher/better-docker-ps\")\n\nsha256sums=('SKIP')\n\nbuild() {\n  cd \"$pkgname\"\n  go build -o dops  cmd/dops/main.go\n}\n\npackage()\n{\n  install -D -m755 \"$pkgname/dops\" \"${pkgdir}/usr/bin/dops\"\n}\n\n"
  },
  {
    "path": "_data/package-data/aur-git.sh",
    "content": "#!/bin/bash\n\nset -o nounset   # disallow usage of unset vars  ( set -u )\nset -o errexit   # Exit immediately if a pipeline returns non-zero.  ( set -e )\nset -o errtrace  # Allow the above trap be inherited by all functions in the script.  ( set -E )\nset -o pipefail  # Return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status\nIFS=$'\\n\\t'      # Set $IFS to only newline and tab.\n\ncd \"$(dirname \"$0\")/aur-git\"\ngit clean -ffdX\n\nversion=$(cd ../../../ && git tag --sort=-v:refname | grep -P 'v[0-9\\.]' | head -1 | cut -c2-)\n\necho \"Version: ${version}\"\n\nsed --regexp-extended  -i \"s/pkgver=[0-9\\.]+/pkgver=${version}/g\" PKGBUILD\n\n\n\nnamcap PKGBUILD\nmakepkg --printsrcinfo > .SRCINFO\nmakepkg\n\n\ncd ../../../\npwd\ngit clone ssh://aur@aur.archlinux.org/dops-git.git _out/dops-git\ncp _data/package-data/aur-git/PKGBUILD _out/dops-git/PKGBUILD\ncp _data/package-data/aur-git/.SRCINFO _out/dops-git/.SRCINFO\n\n\ncd _out/dops-git\n\ngit add PKGBUILD\ngit add .SRCINFO\n\nif [ -z \"$(git status --porcelain)\" ]; then \n  echo \"(!) Nothing changed -- nothing to commit\"\nelse \n  git commit -m \"v${version}\"\nfi\n\n\ncd \"../../_data/package-data/aur-git\"\ngit clean -ffdX\n\n# git push manually (!)\n"
  },
  {
    "path": "_data/package-data/homebrew/dops.rb",
    "content": "class Dops < Formula\n\n  desc     \" A replacement for the default docker-ps that tries really hard to fit into the width of your terminal. \"\n  homepage \"https://github.com/Mikescher/better-docker-ps\"\n  url      \"https://github.com/Mikescher/better-docker-ps/releases/download/v<<version>>/dops_macos-arm64\"\n  sha256   \"<<shahash>>\"\n\n  def install\n    bin.install \"dops_macos-arm64\" => \"dops\"\n  end\n\n  test do\n    assert true\n  end\n\nend\n"
  },
  {
    "path": "_data/package-data/homebrew.sh",
    "content": "#!/bin/bash\n\nset -o nounset   # disallow usage of unset vars  ( set -u )\nset -o errexit   # Exit immediately if a pipeline returns non-zero.  ( set -e )\nset -o errtrace  # Allow the above trap be inherited by all functions in the script.  ( set -E )\nset -o pipefail  # Return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status\nIFS=$'\\n\\t'      # Set $IFS to only newline and tab.\n\nset -o functrace\n\ncd \"$(dirname \"$0\")/homebrew\"\n\ncp dops.rb dops_patch.rb\n\n\nversion=\"$(cd ../../../ && git tag --sort=-v:refname | grep -P 'v[0-9\\.]' | head -1 | cut -c2-)\"\ncs0=\"$(cd ../../../ && sha256sum _out/dops_macos-arm64 | cut -d ' ' -f 1)\"\n\necho \"Version: ${version} (${cs0})\"\n\nsed --regexp-extended  -i \"s/<<version>>/${version}/g\"  dops_patch.rb\nsed --regexp-extended  -i \"s/<<shahash>>/${cs0}/g\"      dops_patch.rb\n\ncd ../../../\ngit clone https://github.com/Mikescher/homebrew-tap.git _out/homebrew-tap\n\ncp \"_data/package-data/homebrew/dops_patch.rb\" _out/homebrew-tap/dops.rb\nrm \"_data/package-data/homebrew/dops_patch.rb\"\n\n\ncd _out/homebrew-tap/\n\ngit add dops.rb\n\nif [ -z \"$(git status --porcelain)\" ]; then \n  echo \"(!) Nothing changed -- nothing to commit\"\nelse \n  git commit -m \"dops v${version}\"\nfi\n\n\n\n# git push manually (!)\n"
  },
  {
    "path": "_data/package-data/sanitycheck.sh",
    "content": "#!/bin/bash\n\ncd \"$(dirname \"$0\")\"\n\nversion_tag=\"$(cd ../../ && git tag --sort=-v:refname | grep -P 'v[0-9\\.]' | head -1 | cut -c2-)\"\n\nversion_code=\"$(cd ../../ && cat consts/version.go | grep -oP 'BETTER_DOCKER_PS_VERSION = .*' | grep -oP '\"[0-9\\.]+\"' | grep -oP '[0-9\\.]+' )\"\n\n\nif [ \"$version_tag\" != \"$version_code\" ]; then\n\n  echo \"Git-Tag version and Code-const version do not match!\"\n  echo \"[GIT-TAG] := $version_tag\"\n  echo \"[GO-CODE] := $version_code\"\n\n  exit 1\n\nelse\n\n  echo \"Version ('$version_tag') ok\"\n\nfi"
  },
  {
    "path": "cli/argTuple.go",
    "content": "package cli\n\ntype ArgumentTuple struct {\n\tKey   string\n\tValue *string\n}\n"
  },
  {
    "path": "cli/context.go",
    "content": "package cli\n\nimport (\n\t\"better-docker-ps/pserr\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/termext\"\n)\n\ntype PSContext struct {\n\tcontext.Context\n\tOpt   Options\n\tCache map[string]any\n}\n\nfunc (c PSContext) PrintPrimaryOutput(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tc.printPrimaryRaw(msg + \"\\n\")\n}\n\nfunc (c PSContext) PrintFatalMessage(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tc.printErrorRaw(msg + \"\\n\")\n}\n\nfunc (c PSContext) PrintFatalError(e error) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tc.printErrorRaw(pserr.FormatError(e, c.Opt.Verbose) + \"\\n\")\n}\n\nfunc (c PSContext) PrintErrorMessage(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tc.printErrorRaw(msg + \"\\n\")\n}\n\nfunc (c PSContext) PrintVerbose(msg string) {\n\tif c.Opt.Quiet || !c.Opt.Verbose {\n\t\treturn\n\t}\n\n\tc.printVerboseRaw(msg + \"\\n\")\n}\n\nfunc (c PSContext) PrintVerboseHeader(msg string) {\n\tif c.Opt.Quiet || !c.Opt.Verbose {\n\t\treturn\n\t}\n\n\tc.printVerboseRaw(\"\\n\")\n\tc.printVerboseRaw(\"========================================\" + \"\\n\")\n\tc.printVerboseRaw(msg + \"\\n\")\n\tc.printVerboseRaw(\"========================================\" + \"\\n\")\n\tc.printVerboseRaw(\"\\n\")\n}\n\nfunc (c PSContext) PrintVerboseKV(key string, vval any) {\n\tif c.Opt.Quiet || !c.Opt.Verbose {\n\t\treturn\n\t}\n\n\ttermlen := 236\n\tkeylen := 28\n\n\tvar val = \"\"\n\tswitch v := vval.(type) {\n\tcase []byte:\n\t\tval = hex.EncodeToString(v)\n\tcase string:\n\t\tval = v\n\tcase time.Time:\n\t\tval = v.In(c.Opt.TimeZone).Format(time.RFC3339Nano)\n\tdefault:\n\t\tval = fmt.Sprintf(\"%v\", v)\n\t}\n\n\tif len(val) > (termlen-keylen-4) || strings.Contains(val, \"\\n\") {\n\n\t\tc.printVerboseRaw(key + \" :=\\n\" + val + \"\\n\")\n\n\t} else {\n\n\t\tpadkey := langext.StrPadRight(key, \" \", keylen)\n\t\tc.printVerboseRaw(padkey + \" := \" + val + \"\\n\")\n\n\t}\n}\n\nfunc (c PSContext) ClearTerminal() {\n\tfmt.Print(\"\\033[H\\033[2J\")\n}\n\nfunc (c PSContext) printPrimaryRaw(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\twriteStdout(msg)\n}\n\nfunc (c PSContext) printErrorRaw(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tif c.Opt.OutputColor {\n\t\twriteStderr(termext.Red(msg))\n\t} else {\n\t\twriteStderr(msg)\n\t}\n}\n\nfunc (c PSContext) printVerboseRaw(msg string) {\n\tif c.Opt.Quiet {\n\t\treturn\n\t}\n\n\tif c.Opt.OutputColor {\n\t\twriteStdout(termext.Gray(msg))\n\t} else {\n\t\twriteStdout(msg)\n\t}\n}\n\nfunc writeStdout(msg string) {\n\t_, err := os.Stdout.WriteString(msg)\n\tif err != nil {\n\t\tpanic(\"failed to write to stdout: \" + err.Error())\n\t}\n}\n\nfunc writeStderr(msg string) {\n\t_, err := os.Stderr.WriteString(msg)\n\tif err != nil {\n\t\tpanic(\"failed to write to stdout: \" + err.Error())\n\t}\n}\n\nfunc NewContext(opt Options) (*PSContext, error) {\n\treturn &PSContext{\n\t\tContext: context.Background(),\n\t\tOpt:     opt,\n\t\tCache:   make(map[string]any),\n\t}, nil\n}\n\nfunc NewEarlyContext() *PSContext {\n\treturn &PSContext{\n\t\tContext: context.Background(),\n\t\tOpt:     DefaultCLIOptions(),\n\t\tCache:   make(map[string]any),\n\t}\n}\n\nfunc (c PSContext) Finish() {\n\t// ...\n}\n\nfunc (c *PSContext) GetIntFromCache(key string, calc func() int) int {\n\tif v1, ok := c.Cache[key]; ok {\n\t\tif v2, ok := v1.(int); ok {\n\t\t\treturn v2\n\t\t}\n\t\tpanic(fmt.Sprintf(\"Wrong type in cache type(%s) = %T  (expected: int)\", key, v1))\n\t}\n\n\tval := calc()\n\tc.Cache[key] = val\n\treturn val\n}\n"
  },
  {
    "path": "cli/options.go",
    "content": "package cli\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/cmdext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/termext\"\n)\n\ntype SortDirection string\n\nconst (\n\tSortASC  SortDirection = \"ASC\"\n\tSortDESC SortDirection = \"DESC\"\n)\n\ntype Options struct {\n\tVersion          bool\n\tHelp             bool\n\tSocket           string\n\tQuiet            bool\n\tVerbose          bool\n\tOutputColor      bool\n\tTimeZone         *time.Location\n\tTimeFormat       string\n\tTimeFormatHeader string\n\tInput            *string\n\tAll              bool\n\tWithSize         bool\n\tFilter           *map[string][]string\n\tSearch           *string\n\tLimit            int\n\tDefaultFormat    bool\n\tFormat           []string // if more than 1 value, we use the later values as fallback for too-small terminal\n\tPrintHeader      bool\n\tPrintHeaderLines bool\n\tTruncate         bool\n\tSortColumns      []string\n\tSortDirection    []SortDirection\n\tWatchInterval    *time.Duration\n}\n\nfunc DefaultCLIOptions() Options {\n\treturn Options{\n\t\tVersion:          false,\n\t\tHelp:             false,\n\t\tQuiet:            false,\n\t\tVerbose:          false,\n\t\tOutputColor:      termext.SupportsColors(),\n\t\tTimeZone:         time.Local,\n\t\tTimeFormatHeader: \"Z07:00 MST\",\n\t\tTimeFormat:       \"2006-01-02 15:04:05\",\n\t\tSocket:           \"auto\",\n\t\tInput:            nil,\n\t\tAll:              false,\n\t\tWithSize:         false,\n\t\tLimit:            -1,\n\t\tDefaultFormat:    true,\n\t\tFormat: []string{\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.ShortCommand}}\\\\t{{.CreatedAt}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.LongPublishedPorts}}\\\\t{{.Networks}}\\\\t{{.IP}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.ShortCommand}}\\\\t{{.CreatedAt}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.LongPublishedPorts}}\\\\t{{.IP}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.CreatedAt}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.LongPublishedPorts}}\\\\t{{.IP}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.CreatedAt}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.PublishedPorts}}\\\\t{{.IP}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.CreatedAt}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.PublishedPorts}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.ImageName}}\\\\t{{.Tag}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.PublishedPorts}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.Tag}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.PublishedPorts}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.Tag}}\\\\t{{.State}}\\\\t{{.Status}}\\\\t{{.ShortPublishedPorts}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.Tag}}\\\\t{{.State}}\\\\t{{.Status}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.State}}\\\\t{{.Status}}\",\n\t\t\t\"table {{.ID}}\\\\t{{.Names}}\\\\t{{.State}}\",\n\t\t\t\"table {{.Names}}\\\\t{{.State}}\",\n\t\t\t\"table {{.Names}}\",\n\t\t\t\"table {{.ID}}\",\n\t\t},\n\t\tPrintHeader:      true,\n\t\tPrintHeaderLines: true,\n\t\tTruncate:         true,\n\t\tSortColumns:      make([]string, 0),\n\t\tSortDirection:    make([]SortDirection, 0),\n\t\tWatchInterval:    nil,\n\t}\n}\n\nfunc getDefaultSocket() string {\n\tif runtime.GOOS == \"darwin\" {\n\t\thome, err := os.UserHomeDir()\n\t\tif err != nil {\n\t\t\treturn \"/var/run/docker.sock\"\n\t\t}\n\t\treturn filepath.Join(home, \".docker/run/docker.sock\")\n\t}\n\treturn \"/var/run/docker.sock\"\n}\n\nfunc (o Options) GetSocket() string {\n\n\t// [1] Manually specified socket\n\n\tif o.Socket != \"auto\" {\n\t\treturn o.Socket\n\t}\n\n\t// [2] Auto-detect from current docker context\n\n\tres, err := cmdext.Runner(\"docker\").Arg(\"context\").Arg(\"list\").Arg(\"--format\").Arg(\"json\").Timeout(10 * time.Second).FailOnTimeout().FailOnExitCode().Run()\n\tif err == nil {\n\t\tfor _, line := range strings.Split(res.StdOut, \"\\n\") {\n\t\t\tvar context dockerContext\n\t\t\terr = json.Unmarshal([]byte(line), &context)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif context.Current {\n\t\t\t\treturn context.socket()\n\t\t\t}\n\t\t}\n\t}\n\n\t// [3] MacOS homedir\n\n\tif runtime.GOOS == \"darwin\" {\n\t\tif home, err := os.UserHomeDir(); err == nil {\n\t\t\tfp := filepath.Join(home, \".docker/run/docker.sock\")\n\t\t\tif _, err = os.Stat(fp); err == nil {\n\t\t\t\treturn fp\n\t\t\t}\n\t\t}\n\t}\n\n\t// [4] Default\n\n\treturn \"/var/run/docker.sock\"\n}\n\ntype dockerContext struct {\n\tName           string\n\tDescription    string\n\tDockerEndpoint string\n\tCurrent        bool\n\tError          string\n\tContextType    string\n}\n\nvar unixSocketPrefixPat = regexp.MustCompile(\"^unix://\")\n\nfunc (ctx dockerContext) socket() string {\n\treturn unixSocketPrefixPat.ReplaceAllString(ctx.DockerEndpoint, \"\")\n}\n\nfunc p(v bool) *bool {\n\treturn &v\n}\n"
  },
  {
    "path": "cli/parser.go",
    "content": "package cli\n\nimport (\n\t\"better-docker-ps/pserr\"\n\t\"fmt\"\n\t\"github.com/BurntSushi/toml\"\n\t\"github.com/kirsle/configdir\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/timeext\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n)\n\nfunc ParseCommandline(columnKeys []string) (Options, error) {\n\to, err := parseCommandlineInternal(columnKeys)\n\tif err != nil {\n\t\treturn Options{}, errorx.Decorate(err, \"failed to parse commandline\")\n\t}\n\treturn o, nil\n}\n\nfunc parseCommandlineInternal(columnKeys []string) (Options, error) {\n\tunprocessedArgs := os.Args[1:]\n\n\tallOptionArguments := make([]ArgumentTuple, 0)\n\n\t// Parse Commandline KeyValue pairs\n\n\tfor len(unprocessedArgs) > 0 {\n\t\targ := unprocessedArgs[0]\n\t\tunprocessedArgs = unprocessedArgs[1:]\n\n\t\tif strings.HasPrefix(arg, \"--\") {\n\n\t\t\targ = arg[2:]\n\n\t\t\tif strings.Contains(arg, \"=\") {\n\t\t\t\tkey := arg[0:strings.Index(arg, \"=\")]\n\t\t\t\tval := arg[strings.Index(arg, \"=\")+1:]\n\n\t\t\t\tif len(key) <= 1 {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown/Misplaced argument: \" + arg)\n\t\t\t\t}\n\n\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: key, Value: langext.Ptr(val)})\n\t\t\t\tcontinue\n\t\t\t} else {\n\n\t\t\t\tkey := arg\n\n\t\t\t\tif len(key) <= 1 {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown/Misplaced argument: \" + arg)\n\t\t\t\t}\n\n\t\t\t\tif len(unprocessedArgs) == 0 || strings.HasPrefix(unprocessedArgs[0], \"-\") {\n\t\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: key, Value: nil})\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\tval := unprocessedArgs[0]\n\t\t\t\t\tunprocessedArgs = unprocessedArgs[1:]\n\t\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: key, Value: langext.Ptr(val)})\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t} else if strings.HasPrefix(arg, \"-\") {\n\n\t\t\targ = arg[1:]\n\n\t\t\tif len(arg) > 1 {\n\t\t\t\tfor i := 0; i < len(arg); i++ {\n\t\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: arg[i : i+1], Value: nil})\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tkey := arg\n\n\t\t\tif key == \"\" {\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown/Misplaced argument: \" + arg)\n\t\t\t}\n\n\t\t\tif len(unprocessedArgs) == 0 || strings.HasPrefix(unprocessedArgs[0], \"-\") {\n\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: key, Value: nil})\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\tval := unprocessedArgs[0]\n\t\t\t\tunprocessedArgs = unprocessedArgs[1:]\n\t\t\t\tallOptionArguments = append(allOptionArguments, ArgumentTuple{Key: key, Value: langext.Ptr(val)})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t} else {\n\t\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown/Misplaced argument: \" + arg)\n\t\t}\n\t}\n\n\t// Process common options\n\n\topt := DefaultCLIOptions()\n\n\tconfPath := filepath.Join(configdir.LocalConfig(), \"dops.conf\")\n\n\tif v, err := os.ReadFile(confPath); err == nil {\n\n\t\ttomldata := make(map[string]any)\n\t\t_, err = toml.Decode(string(v), &tomldata)\n\t\tif err != nil {\n\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"': \" + err.Error())\n\t\t}\n\n\t\tfor tk, tvany := range tomldata {\n\n\t\t\ttv := fmt.Sprintf(\"%v\", tvany)\n\t\t\tvar tvarr []string = nil\n\t\t\tif varr1, ok := tvany.([]string); ok {\n\t\t\t\ttvarr = varr1\n\t\t\t} else if varr2, ok := tvany.([]any); ok && langext.ArrAll(varr2, func(v any) bool { _, ok := v.(string); return ok }) {\n\t\t\t\ttvarr = langext.ArrCastSafe[any, string](varr2)\n\t\t\t} else {\n\t\t\t\ttvarr = []string{tv}\n\t\t\t}\n\n\t\t\tif tk == \"verbose\" {\n\t\t\t\topt.Verbose, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'verbose'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"silent\" {\n\t\t\t\topt.Quiet, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'silent'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"timezone\" {\n\t\t\t\tloc, err := time.LoadLocation(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \": Unknown timezone: \" + tv)\n\t\t\t\t}\n\t\t\t\topt.TimeZone = loc\n\t\t\t} else if tk == \"timeformat\" {\n\t\t\t\topt.TimeFormat = tv\n\t\t\t} else if tk == \"timeformat-header\" {\n\t\t\t\topt.TimeFormatHeader = tv\n\t\t\t} else if tk == \"color\" {\n\t\t\t\topt.OutputColor, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'color'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"socket\" {\n\t\t\t\topt.Socket = tv\n\t\t\t} else if tk == \"all\" {\n\t\t\t\topt.All, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'all'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"size\" {\n\t\t\t\topt.WithSize, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'size'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"filter\" {\n\t\t\t\tfor _, elem := range tvarr {\n\t\t\t\t\tspl := strings.SplitN(elem, \"=\", 2)\n\t\t\t\t\tif len(spl) != 2 {\n\t\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"': Filter value must have a key and a value (a=b): \" + elem)\n\t\t\t\t\t}\n\t\t\t\t\tif opt.Filter == nil {\n\t\t\t\t\t\t_v := make(map[string][]string)\n\t\t\t\t\t\topt.Filter = &_v\n\t\t\t\t\t}\n\t\t\t\t\tfilter := *opt.Filter\n\t\t\t\t\tfilter[spl[0]] = []string{spl[1]}\n\n\t\t\t\t\topt.Filter = &filter\n\t\t\t\t}\n\t\t\t} else if tk == \"search\" {\n\t\t\t\topt.Search = langext.Ptr(tv)\n\t\t\t} else if tk == \"format\" {\n\t\t\t\tfor _, elem := range tvarr {\n\t\t\t\t\tif opt.DefaultFormat {\n\t\t\t\t\t\topt.Format = make([]string, 0)\n\t\t\t\t\t}\n\t\t\t\t\topt.Format = append(opt.Format, elem)\n\t\t\t\t\topt.DefaultFormat = false\n\t\t\t\t}\n\t\t\t} else if tk == \"last\" {\n\t\t\t\tif vint, err := strconv.ParseInt(tv, 10, 32); err == nil {\n\t\t\t\t\topt.Limit = int(vint)\n\t\t\t\t\topt.All = true\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"': Failed to parse number of field 'last': '\" + tv + \"'\")\n\t\t\t\t}\n\n\t\t\t} else if tk == \"latest\" {\n\t\t\t\tvbool, err := strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"': Failed to parse boolean value of 'latest': '\" + tv + \"'\")\n\t\t\t\t}\n\t\t\t\tif vbool {\n\t\t\t\t\topt.Limit = -1\n\t\t\t\t\topt.All = true\n\t\t\t\t} else {\n\t\t\t\t\topt.Limit = 1\n\t\t\t\t\topt.All = false\n\t\t\t\t}\n\t\t\t} else if tk == \"truncate\" {\n\t\t\t\topt.Truncate, err = strconv.ParseBool(tv)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (invalid value for 'truncate'): \" + err.Error())\n\t\t\t\t}\n\t\t\t} else if tk == \"header\" {\n\t\t\t\tif strings.EqualFold(tv, \"no\") {\n\t\t\t\t\topt.PrintHeader = false\n\t\t\t\t} else if strings.EqualFold(tv, \"simple\") {\n\t\t\t\t\topt.PrintHeader = true\n\t\t\t\t\topt.PrintHeaderLines = false\n\t\t\t\t} else {\n\t\t\t\t\tvbool, err := strconv.ParseBool(tv)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"': Failed to parse boolean value of 'latest': '\" + tv + \"'\")\n\t\t\t\t\t}\n\t\t\t\t\tif vbool {\n\t\t\t\t\t\topt.PrintHeader = true\n\t\t\t\t\t\topt.PrintHeaderLines = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\topt.PrintHeader = false\n\t\t\t\t\t\topt.PrintHeaderLines = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if tk == \"sort\" {\n\t\t\t\topt.SortColumns = tvarr\n\t\t\t} else if tk == \"sort-direction\" {\n\t\t\t\topt.SortDirection = make([]SortDirection, 0)\n\t\t\t\tfor _, sdv := range tvarr {\n\t\t\t\t\tif strings.ToUpper(sdv) == \"ASC\" {\n\t\t\t\t\t\topt.SortDirection = append(opt.SortDirection, SortASC)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif strings.ToUpper(sdv) == \"DESC\" {\n\t\t\t\t\t\topt.SortDirection = append(opt.SortDirection, SortDESC)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treturn Options{}, pserr.DirectOutput.New(fmt.Sprintf(\"Failed to parse config file '\"+confPath+\"': Failed to parse sort-direction argument '%s'\", sdv))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse config file '\" + confPath + \"' (unknown key '\" + tk + \"')\")\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, arg := range allOptionArguments {\n\n\t\tif (arg.Key == \"h\" || arg.Key == \"help\") && arg.Value == nil {\n\t\t\treturn Options{Help: true}, nil\n\t\t}\n\n\t\tif arg.Key == \"version\" && arg.Value == nil {\n\t\t\treturn Options{Version: true}, nil\n\t\t}\n\n\t\tif (arg.Key == \"v\" || arg.Key == \"verbose\") && arg.Value == nil {\n\t\t\topt.Verbose = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"silent\") && arg.Value == nil {\n\t\t\topt.Quiet = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"q\" || arg.Key == \"quiet\") && arg.Value == nil {\n\t\t\topt.Format = []string{\"idlist\"}\n\t\t\topt.DefaultFormat = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"timezone\" && arg.Value != nil {\n\t\t\tloc, err := time.LoadLocation(*arg.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown timezone: \" + *arg.Value)\n\t\t\t}\n\t\t\topt.TimeZone = loc\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"timeformat\" && arg.Value != nil {\n\t\t\topt.TimeFormat = *arg.Value\n\t\t\topt.TimeFormatHeader = \"\"\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"timeformat-header\" && arg.Value != nil {\n\t\t\topt.TimeFormatHeader = *arg.Value\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"color\" && arg.Value != nil && (strings.ToLower(*arg.Value) == \"true\" || *arg.Value == \"1\") {\n\t\t\topt.OutputColor = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"color\" && arg.Value != nil && (strings.ToLower(*arg.Value) == \"false\" || *arg.Value == \"0\") {\n\t\t\topt.OutputColor = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"no-color\" && arg.Value == nil {\n\t\t\topt.OutputColor = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"socket\") && arg.Value != nil {\n\t\t\topt.Socket = *arg.Value\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"input\") && arg.Value != nil {\n\t\t\t// used for testing\n\t\t\topt.Input = langext.Ptr(*arg.Value)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"all\" || arg.Key == \"a\") && arg.Value == nil {\n\t\t\topt.All = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"size\") && arg.Value == nil {\n\t\t\topt.WithSize = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"filter\" || arg.Key == \"f\") && arg.Value != nil {\n\t\t\tspl := strings.SplitN(*arg.Value, \"=\", 2)\n\t\t\tif len(spl) != 2 {\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Filter argument must have a key and a value (a=b): \" + arg.Key)\n\t\t\t}\n\t\t\tif opt.Filter == nil {\n\t\t\t\t_v := make(map[string][]string)\n\t\t\t\topt.Filter = &_v\n\t\t\t}\n\t\t\tfilter := *opt.Filter\n\t\t\tif spl[0] == \"project\" {\n\t\t\t\tspl[0] = \"label\"\n\t\t\t\tspl[1] = \"com.docker.compose.project=\" + spl[1]\n\t\t\t}\n\t\t\tfilter[spl[0]] = []string{spl[1]}\n\t\t\topt.Filter = &filter\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"search\" || arg.Key == \"g\") && arg.Value != nil {\n\t\t\topt.Search = langext.Ptr(*arg.Value)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"format\") && arg.Value != nil {\n\t\t\tif opt.DefaultFormat {\n\t\t\t\topt.Format = make([]string, 0)\n\t\t\t}\n\t\t\topt.Format = append(opt.Format, *arg.Value)\n\t\t\topt.DefaultFormat = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"last\" || arg.Key == \"n\") && arg.Value != nil {\n\t\t\tif v, err := strconv.ParseInt(*arg.Value, 10, 32); err == nil {\n\t\t\t\topt.Limit = int(v)\n\t\t\t\topt.All = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse number argument '--last': '\" + *arg.Value + \"'\")\n\t\t}\n\n\t\tif (arg.Key == \"latest\" || arg.Key == \"l\") && arg.Value != nil {\n\t\t\topt.Limit = 1\n\t\t\topt.All = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"no-trunc\" || arg.Key == \"no-truncate\") && arg.Value == nil {\n\t\t\topt.Truncate = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"no-header\") && arg.Value == nil {\n\t\t\topt.PrintHeader = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.Key == \"simple-header\") && arg.Value == nil {\n\t\t\topt.PrintHeaderLines = false\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"sort\" && arg.Value != nil {\n\t\t\topt.SortColumns = strings.Split(*arg.Value, \",\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"sort-direction\" && arg.Value != nil {\n\t\t\topt.SortDirection = make([]SortDirection, 0)\n\t\t\tfor _, sdv := range strings.Split(*arg.Value, \",\") {\n\t\t\t\tif strings.ToUpper(sdv) == \"ASC\" {\n\t\t\t\t\topt.SortDirection = append(opt.SortDirection, SortASC)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif strings.ToUpper(sdv) == \"DESC\" {\n\t\t\t\t\topt.SortDirection = append(opt.SortDirection, SortDESC)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(fmt.Sprintf(\"Failed to parse sort-direction argument '%s'\", sdv))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif arg.Key == \"watch\" || arg.Key == \"w\" {\n\t\t\td, err := timeext.ParseDurationShortString(langext.Coalesce(arg.Value, \"2s\"))\n\t\t\tif err != nil {\n\t\t\t\treturn Options{}, pserr.DirectOutput.New(\"Failed to parse duration argument of '--watch': '\" + *arg.Value + \"'\")\n\t\t\t}\n\t\t\topt.WatchInterval = &d\n\t\t\tcontinue\n\t\t}\n\n\t\treturn Options{}, pserr.DirectOutput.New(\"Unknown argument: \" + arg.Key)\n\t}\n\n\t// Post Processing\n\n\tif len(opt.SortDirection) == 0 && len(opt.SortColumns) > 0 {\n\t\tfor i := 0; i < len(opt.SortColumns); i++ {\n\t\t\topt.SortDirection = append(opt.SortDirection, SortASC) // default sort (if not specified) is ASC on all sort columns\n\t\t}\n\t}\n\n\tif len(opt.SortDirection) != len(opt.SortColumns) {\n\t\treturn Options{}, pserr.DirectOutput.New(fmt.Sprintf(\"Must specify the same number of values in --sort and --sort-direction ( %d <> %d )\", len(opt.SortDirection), len(opt.SortColumns)))\n\t}\n\n\tfor _, colkey := range opt.SortColumns {\n\t\tif !langext.InArray(colkey, columnKeys) {\n\t\t\treturn Options{}, pserr.DirectOutput.New(fmt.Sprintf(\"Unknown column : '%s' in --sort\", colkey))\n\t\t}\n\t}\n\n\treturn opt, nil\n}\n"
  },
  {
    "path": "cmd/dops/main.go",
    "content": "package main\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"better-docker-ps/consts\"\n\t\"better-docker-ps/impl\"\n\t\"better-docker-ps/pserr\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n)\n\n// Inspiration: https://github.com/moby/moby/issues/7477\n\nfunc main() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\t_, _ = os.Stderr.WriteString(fmt.Sprintf(\"%v\\n\\n%s\", err, string(debug.Stack())))\n\t\t\tos.Exit(consts.ExitcodePanic)\n\t\t}\n\t}()\n\n\topt, err := cli.ParseCommandline(langext.MapKeyArr(impl.ColumnMap))\n\tif err != nil {\n\t\tctx := cli.NewEarlyContext()\n\t\tctx.PrintFatalError(err)\n\t\tos.Exit(pserr.GetExitCode(err, consts.ExitcodeCLIParse))\n\t\treturn\n\t}\n\n\tctx, err := cli.NewContext(opt)\n\tif err != nil {\n\t\tctx.PrintFatalError(err)\n\t\tos.Exit(pserr.GetExitCode(err, consts.ExitcodeError))\n\t\treturn\n\t}\n\n\tdefer ctx.Finish()\n\n\tif opt.Version {\n\t\tctx.PrintPrimaryOutput(\"better-docker-ps v\" + consts.BETTER_DOCKER_PS_VERSION)\n\t\tos.Exit(0)\n\t\treturn\n\t}\n\n\tif opt.Help {\n\t\tprintHelp(ctx)\n\t\tos.Exit(0)\n\t\treturn\n\t}\n\n\tif opt.WatchInterval == nil {\n\n\t\terr = impl.Execute(ctx)\n\t\tif err != nil {\n\t\t\tctx.PrintFatalError(err)\n\t\t\tos.Exit(pserr.GetExitCode(err, consts.ExitcodeError))\n\t\t\treturn\n\t\t}\n\n\t\tos.Exit(0)\n\n\t} else {\n\n\t\terr = impl.Watch(ctx, *opt.WatchInterval)\n\t\tif err != nil {\n\t\t\tctx.PrintFatalError(err)\n\t\t\tos.Exit(pserr.GetExitCode(err, consts.ExitcodeError))\n\t\t\treturn\n\t\t}\n\n\t\tos.Exit(0)\n\n\t}\n\n}\n\nfunc printHelp(ctx *cli.PSContext) {\n\tctx.PrintPrimaryOutput(\"better-docker-ps\")\n\tctx.PrintPrimaryOutput(\"\")\n\tctx.PrintPrimaryOutput(\"Usage:\")\n\tctx.PrintPrimaryOutput(\"  dops [OPTIONS]                     List docker container\")\n\tctx.PrintPrimaryOutput(\"\")\n\tctx.PrintPrimaryOutput(\"Options (default):\")\n\tctx.PrintPrimaryOutput(\"  -h, --help                         Show this screen.\")\n\tctx.PrintPrimaryOutput(\"  --version                          Show version.\")\n\tctx.PrintPrimaryOutput(\"  --all , -a                         Show all containers (default shows just running)\")\n\tctx.PrintPrimaryOutput(\"  --filter <ftr>, -f <ftr>           Filter output based on conditions provided\")\n\tctx.PrintPrimaryOutput(\"  --search <str>, -g <str>           Filter output by substring match across all visible columns (case-insensitive)\")\n\tctx.PrintPrimaryOutput(\"  --format <fmt>                     Pretty-print containers using a Go template\")\n\tctx.PrintPrimaryOutput(\"  --last , -n                        Show n last created containers (includes all states)\")\n\tctx.PrintPrimaryOutput(\"  --latest , -l                      Show the latest created container (includes all states)\")\n\tctx.PrintPrimaryOutput(\"  --no-trunc                         Don't truncate output (eg ContainerIDs, Sha256 Image references, commandline)\")\n\tctx.PrintPrimaryOutput(\"  --quiet , -q                       Only display container IDs\")\n\tctx.PrintPrimaryOutput(\"  --size , -s                        Display total file sizes\")\n\tctx.PrintPrimaryOutput(\"\")\n\tctx.PrintPrimaryOutput(\"Options (extra | do not exist in `docker ps`):\")\n\tctx.PrintPrimaryOutput(\"  --silent                           Do not print any output\")\n\tctx.PrintPrimaryOutput(\"  --timezone                         Specify the timezone for date outputs\")\n\tctx.PrintPrimaryOutput(\"  --color <true|false>               Enable/Disable terminal color output\")\n\tctx.PrintPrimaryOutput(\"  --no-color                         Disable terminal color output\")\n\tctx.PrintPrimaryOutput(\"  --socket <filepath>                Specify the docker socket location (Default: `auto` - which calls the docker cli to determine the socket)\")\n\tctx.PrintPrimaryOutput(\"  --timeformat <go-time-fmt>         Specify the datetime output format (golang syntax)\")\n\tctx.PrintPrimaryOutput(\"  --no-header                        Do not print the table header\")\n\tctx.PrintPrimaryOutput(\"  --simple-header                    Do not print the lines under the header\")\n\tctx.PrintPrimaryOutput(\"  --format <fmt>                     You can specify multiple formats and the first one that fits your terminal widt will be used\")\n\tctx.PrintPrimaryOutput(\"  --sort <col>                       Sort output by a specific column, use the same identifier as in --format, only useful together with table formats \")\n\tctx.PrintPrimaryOutput(\"  --sort-direction <ASC|DESC>        The sort direction, only useful in combination with --sort\")\n\tctx.PrintPrimaryOutput(\"  --watch <interval>                 Automatically refresh output periodically (interval is optional, default: 2s)\")\n\tctx.PrintPrimaryOutput(\"\")\n\tctx.PrintPrimaryOutput(\"Available --format keys (default):\")\n\tctx.PrintPrimaryOutput(\"  {{.ID}}                            Container ID\")\n\tctx.PrintPrimaryOutput(\"  {{.Image}}                         Image ID\")\n\tctx.PrintPrimaryOutput(\"  {{.Command}}                       Quoted command\")\n\tctx.PrintPrimaryOutput(\"  {{.CreatedAt}}                     Time when the container was created.\")\n\tctx.PrintPrimaryOutput(\"  {{.RunningFor}}                    Elapsed time since the container was started.\")\n\tctx.PrintPrimaryOutput(\"  {{.Ports}}                         Published ports. ([!] differs from docker CLI, these are only the published ports)\")\n\tctx.PrintPrimaryOutput(\"  {{.State}}                         Container status\")\n\tctx.PrintPrimaryOutput(\"  {{.Status}}                        Container status with details\")\n\tctx.PrintPrimaryOutput(\"  {{.Size}}                          Container disk size.\")\n\tctx.PrintPrimaryOutput(\"  {{.Names}}                         Container names.\")\n\tctx.PrintPrimaryOutput(\"  {{.Labels}}                        All labels assigned to the container.\")\n\tctx.PrintPrimaryOutput(\"  {{.Label}}                         [!] Unsupported\")\n\tctx.PrintPrimaryOutput(\"  {{.Mounts}}                        Names of the volumes mounted in this container.\")\n\tctx.PrintPrimaryOutput(\"  {{.Networks}}                      Names of the networks attached to this container.\")\n\tctx.PrintPrimaryOutput(\"\")\n\tctx.PrintPrimaryOutput(\"Available --format keys (extra | do not exist in `docker ps`):\")\n\tctx.PrintPrimaryOutput(\"  {{.ImageName}                      Image ID (without tag and registry)\")\n\tctx.PrintPrimaryOutput(\"  {{.ImageTag}, {{.Tag}              Image Tag\")\n\tctx.PrintPrimaryOutput(\"  {{.ImageRegistry}, {{.Registry}    Image Registry\")\n\tctx.PrintPrimaryOutput(\"  {{.ShortCommand}                   Command without arguments\")\n\tctx.PrintPrimaryOutput(\"  {{.LabelKeys}                      All labels assigned to the container (keys only)\")\n\tctx.PrintPrimaryOutput(\"  {{.ShortPublishedPorts}}           Published ports, shorter output than {{.Ports}}\")\n\tctx.PrintPrimaryOutput(\"  {{.LongPublishedPorts}}            Published ports, full output with IP\")\n\tctx.PrintPrimaryOutput(\"  {{.ExposedPorts}}                  Exposed ports\")\n\tctx.PrintPrimaryOutput(\"  {{.NotPublishedPorts}}             Exposed but not published ports\")\n\tctx.PrintPrimaryOutput(\"  {{.PublishedPorts}}                Published ports\")\n\tctx.PrintPrimaryOutput(\"  {{.PublicPorts}}                   Only the public part of published ports\")\n\tctx.PrintPrimaryOutput(\"  {{.IP}                             Internal IP Address\")\n\tctx.PrintPrimaryOutput(\"\")\n}\n"
  },
  {
    "path": "consts/api.go",
    "content": "package consts\n\n// DockerAPIContainerList -> see https://docs.docker.com/engine/api/v1.41/#tag/Container/operation/ContainerList\nconst DockerAPIContainerList = \"http://localhost/v1.44/containers/json\"\n"
  },
  {
    "path": "consts/exitcode.go",
    "content": "package consts\n\nconst (\n\tExitcodeError                   = 60\n\tExitcodePanic                   = 61\n\tExitcodeNoArguments             = 62\n\tExitcodeCLIParse                = 63\n\tExitcodeNoLogin                 = 64\n\tExitcodeUnsupportedOutputFormat = 65\n\tExitcodeRecordNotFound          = 66\n)\n\nconst (\n\tExitcodeInvalidSession            = 81\n\tExitcodePasswordNotFound          = 82\n\tExitcodeParentNotAFolder          = 83\n\tExitcodeInvalidPosition           = 84\n\tExitcodeBookmarkFieldNotSupported = 85\n)\n"
  },
  {
    "path": "consts/version.go",
    "content": "package consts\n\n//go:generate /bin/bash version.sh\n\nconst BETTER_DOCKER_PS_VERSION = \"1.17\"\n"
  },
  {
    "path": "consts/version.sh",
    "content": "#!/bin/bash\n\nsed -i 's/const BETTER_DOCKER_PS_VERSION = \".*\"/const BETTER_DOCKER_PS_VERSION = \"'$(git describe --tags --abbrev=0 | sed \"s/v//\")'\"/' \"version.go\"\n"
  },
  {
    "path": "docker/api.go",
    "content": "package docker\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"better-docker-ps/consts\"\n\t\"better-docker-ps/pserr\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/joomcode/errorx\"\n)\n\nfunc ListContainer(ctx *cli.PSContext) ([]byte, error) {\n\tif ctx.Opt.Input != nil {\n\t\tdata, err := os.ReadFile(*ctx.Opt.Input)\n\t\tif err != nil {\n\t\t\treturn nil, pserr.DirectOutput.Wrap(err, \"Failed to read --input file\")\n\t\t}\n\t\treturn data, nil\n\t}\n\n\tsocket := ctx.Opt.GetSocket()\n\n\tclient := http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext: func(_ context.Context, _, _ string) (net.Conn, error) {\n\t\t\t\treturn net.Dial(\"unix\", socket)\n\t\t\t},\n\t\t},\n\t}\n\n\turi := fmt.Sprintf(\"%s?1=1\", consts.DockerAPIContainerList)\n\n\tif ctx.Opt.All {\n\t\turi += \"&all=true\"\n\t}\n\n\tif ctx.Opt.WithSize {\n\t\turi += \"&size=true\"\n\t}\n\n\tif ctx.Opt.Limit != -1 {\n\t\turi += \"&limit=\" + strconv.Itoa(ctx.Opt.Limit)\n\t}\n\n\tif ctx.Opt.Filter != nil {\n\t\tbin, err := json.Marshal(*ctx.Opt.Filter)\n\t\tif err != nil {\n\t\t\treturn nil, errorx.InternalError.Wrap(err, \"Failed to marshal filter\")\n\t\t}\n\n\t\turi += \"&filters=\" + url.PathEscape(string(bin))\n\t}\n\n\tresponse, err := client.Get(uri)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"connect: permission denied\") {\n\t\t\treturn nil, pserr.DirectOutput.Wrap(err, \"Call to unix socket failed (permission denied)\")\n\t\t} else {\n\t\t\treturn nil, pserr.DirectOutput.Wrap(err, \"Call to unix socket failed\")\n\t\t}\n\t}\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn nil, errorx.InternalError.Wrap(err, \"Failed to read unix socket response\")\n\t}\n\n\treturn body, nil\n}\n"
  },
  {
    "path": "docker/schema.go",
    "content": "package docker\n\nimport (\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n\t\"net\"\n)\n\ntype ContainerSchema struct {\n\tID              string                   `json:\"Id\"`\n\tNames           []string                 `json:\"Names\"`\n\tImage           string                   `json:\"Image\"`\n\tImageID         string                   `json:\"ImageID\"`\n\tCommand         string                   `json:\"Command\"`\n\tCreated         int64                    `json:\"Created\"`\n\tPorts           []PortSchema             `json:\"Ports\"`\n\tLabels          map[string]string        `json:\"Labels\"`\n\tState           ContainerState           `json:\"State\"`\n\tStatus          string                   `json:\"Status\"`\n\tHostConfig      ContainerHostConfig      `json:\"HostConfig\"`\n\tNetworkSettings ContainerNetworkSettings `json:\"NetworkSettings\"`\n\tMounts          []ContainerMount         `json:\"Mounts\"`\n\tSizeRw          int64                    `json:\"SizeRw\"`\n\tSizeRootFs      int64                    `json:\"SizeRootFs\"`\n}\n\nfunc (s ContainerSchema) PortsSorted() []PortSchema {\n\tports := langext.ArrCopy(s.Ports)\n\n\tlangext.SortSliceStable(ports, func(p1, p2 PortSchema) bool {\n\t\tif p1.PublicPort != p2.PublicPort {\n\t\t\treturn p1.PublicPort < p2.PublicPort\n\t\t}\n\t\tif p1.PrivatePort != p2.PrivatePort {\n\t\t\treturn p1.PrivatePort < p2.PrivatePort\n\t\t}\n\t\treturn false\n\t})\n\n\treturn ports\n}\n\ntype ContainerHostConfig struct {\n\tNetworkMode string `json:\"NetworkMode\"`\n}\n\ntype ContainerNetworkSettings struct {\n\tNetworks map[string]ContainerSingleNetworkSettings `json:\"Networks\"`\n}\ntype ContainerSingleNetworkSettings struct {\n\tNetworkMode         string `json:\"NetworkID\"`\n\tEndpointID          string `json:\"EndpointID\"`\n\tGateway             string `json:\"Gateway\"`\n\tIPAddress           string `json:\"IPAddress\"`\n\tIPPrefixLen         int    `json:\"IPPrefixLen\"`\n\tIPv6Gateway         string `json:\"IPv6Gateway\"`\n\tGlobalIPv6Address   string `json:\"GlobalIPv6Address\"`\n\tGlobalIPv6PrefixLen int    `json:\"GlobalIPv6PrefixLen\"`\n\tMacAddress          string `json:\"MacAddress\"`\n}\n\ntype PortSchema struct {\n\tIP          string `json:\"IP\"`\n\tPrivatePort int    `json:\"PrivatePort\"`\n\tPublicPort  int    `json:\"PublicPort\"`\n\tType        string `json:\"Type\"`\n}\n\nfunc (s PortSchema) IsLoopback() bool {\n\tip := net.ParseIP(s.IP)\n\treturn ip != nil && ip.IsLoopback()\n}\n\ntype ContainerMount struct {\n\tName        string `json:\"Name\"`\n\tSource      string `json:\"Source\"`\n\tDestination string `json:\"Destination\"`\n\tDriver      string `json:\"Driver\"`\n\tMode        string `json:\"Mode\"`\n\tRW          bool   `json:\"RW\"`\n\tPropagation string `json:\"Propagation\"`\n}\n\ntype ContainerState string\n\nconst (\n\tStateCreated    ContainerState = \"created\"\n\tStateRunning    ContainerState = \"running\"\n\tStateRestarting ContainerState = \"restarting\"\n\tStateExited     ContainerState = \"exited\"\n\tStatePaused     ContainerState = \"paused\"\n\tStateDead       ContainerState = \"dead\"\n)\n\nfunc (ct ContainerState) Num() int {\n\tswitch ct {\n\tcase StateCreated:\n\t\treturn 0\n\tcase StateRunning:\n\t\treturn 1\n\tcase StateRestarting:\n\t\treturn 2\n\tcase StateExited:\n\t\treturn 3\n\tcase StatePaused:\n\t\treturn 4\n\tcase StateDead:\n\t\treturn 5\n\tdefault:\n\t\treturn 999\n\t}\n}\n"
  },
  {
    "path": "docker/util.go",
    "content": "package docker\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"strings\"\n)\n\nvar registryPrefixList = []string{\n\t\".com\",\n\t\".de\",\n\t\".net\",\n\t\".io\",\n\t\".org\",\n}\n\nfunc SplitDockerImage(ctx *cli.PSContext, img string) (string, string, string) {\n\n\tresultRegistry := \"\"\n\tresultImage := \"\"\n\tresultTag := \"\"\n\n\tif v := strings.Split(img, \":\"); len(v) > 1 {\n\t\tlast := v[len(v)-1]\n\t\tif !strings.Contains(last, \"/\") {\n\t\t\tresultTag = last\n\t\t\timg = img[0 : len(img)-len(last)-1]\n\t\t}\n\t}\n\n\tif v := strings.Split(img, \"/\"); len(v) > 1 {\n\t\tfirst := v[0]\n\t\tif len(v) == 3 {\n\t\t\tresultRegistry = first\n\t\t\timg = img[len(resultRegistry)+1:]\n\t\t} else {\n\t\t\tfor _, rpl := range registryPrefixList {\n\t\t\t\tif strings.HasSuffix(first, rpl) {\n\t\t\t\t\tresultRegistry = first\n\t\t\t\t\timg = img[len(resultRegistry)+1:]\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tresultImage = img\n\n\tif resultImage == \"sha256\" && len(resultTag) == 64 && ctx.Opt.Truncate {\n\t\tresultImage = \"(sha256)\"\n\t\tresultTag = resultTag[0:12] + \"...\"\n\t}\n\n\treturn resultRegistry, resultImage, resultTag\n\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module better-docker-ps\n\ngo 1.24.2\n\nrequire (\n\tgit.blackforestbytes.com/BlackForestBytes/goext v0.0.572\n\tgithub.com/BurntSushi/toml v1.4.0\n\tgithub.com/joomcode/errorx v1.1.1\n\tgithub.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f\n\tgolang.org/x/term v0.31.0\n)\n\nrequire (\n\tgithub.com/bytedance/sonic v1.13.2 // indirect\n\tgithub.com/bytedance/sonic/loader v0.2.4 // indirect\n\tgithub.com/cloudwego/base64x v0.1.5 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.9 // indirect\n\tgithub.com/gin-contrib/sse v1.1.0 // indirect\n\tgithub.com/gin-gonic/gin v1.10.0 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-playground/validator/v10 v10.26.0 // indirect\n\tgithub.com/goccy/go-json v0.10.5 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/rs/xid v1.6.0 // indirect\n\tgithub.com/rs/zerolog v1.34.0 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/ugorji/go/codec v1.2.12 // indirect\n\tgo.mongodb.org/mongo-driver v1.17.3 // indirect\n\tgolang.org/x/arch v0.16.0 // indirect\n\tgolang.org/x/crypto v0.37.0 // indirect\n\tgolang.org/x/net v0.39.0 // indirect\n\tgolang.org/x/sync v0.13.0 // indirect\n\tgolang.org/x/sys v0.32.0 // indirect\n\tgolang.org/x/text v0.24.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "git.blackforestbytes.com/BlackForestBytes/goext v0.0.572 h1:NALJ4KKkrRZcNJNsmGrUsjFdOclHSA/KyB6f94QV43k=\ngit.blackforestbytes.com/BlackForestBytes/goext v0.0.572/go.mod h1:C4mXq6MwC919jRHjN5IM++qGy6wmvzJZyz30nf285MU=\ngithub.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=\ngithub.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=\ngithub.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=\ngithub.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=\ngithub.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=\ngithub.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=\ngithub.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=\ngithub.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=\ngithub.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\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/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=\ngithub.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=\ngithub.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=\ngithub.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=\ngithub.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=\ngithub.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=\ngithub.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=\ngithub.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=\ngithub.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=\ngithub.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/joomcode/errorx v1.1.1 h1:/LFG/qSk1gUTuZjs+qlyOJEpcVjD9DXgBNFhdZkQrjY=\ngithub.com/joomcode/errorx v1.1.1/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU=\ngithub.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\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/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\ngithub.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=\ngithub.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=\ngithub.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngo.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=\ngo.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=\ngolang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=\ngolang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=\ngolang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=\ngolang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=\ngolang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=\ngolang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=\ngolang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=\ngolang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=\ngolang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=\ngolang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=\ngolang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=\ngolang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nnullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=\n"
  },
  {
    "path": "impl/columns.go",
    "content": "package impl\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"better-docker-ps/docker\"\n\t\"better-docker-ps/printer\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/mathext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/rext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/termext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/timeext\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n)\n\nvar rexIP = rext.W(regexp.MustCompile(\"^(?P<b0>[0-9]{1,3})\\\\.(?P<b1>[0-9]{1,3})\\\\.(?P<b2>[0-9]{1,3})\\\\.(?P<b3>[0-9]{1,3})$\"))\n\ntype ColSortFun = func(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int\n\ntype ColumnDef struct {\n\tReader printer.ColFun\n\tSorter ColSortFun\n}\n\nvar ColumnMap = map[string]ColumnDef{\n\t\"ID\":                  {ColContainerID, SortContainerID},\n\t\"Image\":               {ColFullImage, SortFullImage},\n\t\"ImageName\":           {ColImage, SortImage},\n\t\"ImageTag\":            {ColImageTag, SortImageTag},\n\t\"Registry\":            {ColRegistry, SortRegistry},\n\t\"ImageRegistry\":       {ColRegistry, SortRegistry},\n\t\"Tag\":                 {ColImageTag, SortImageTag},\n\t\"Command\":             {ColCommand, SortCommand},\n\t\"ShortCommand\":        {ColShortCommand, SortShortCommand},\n\t\"CreatedAt\":           {ColCreatedAt, SortCreatedAt},\n\t\"RunningFor\":          {ColRunningFor, SortRunningFor},\n\t\"Ports\":               {ColPortsPublished, SortPortsPublished},\n\t\"PublishedPorts\":      {ColPortsPublished, SortPortsPublished},\n\t\"ShortPublishedPorts\": {ColPortsPublishedShort, SortPortsPublishedShort},\n\t\"LongPublishedPorts\":  {ColPortsPublishedLong, SortPortsPublishedLong},\n\t\"ExposedPorts\":        {ColPortsExposed, SortPortsExposed},\n\t\"NotPublishedPorts\":   {ColPortsNotPublished, SortPortsNotPublished},\n\t\"PublicPorts\":         {ColPortsPublicPart, SortPortsPublicPart},\n\t\"State\":               {ColState, SortState},\n\t\"Status\":              {ColStatus, SortStatus},\n\t\"Size\":                {ColSize, SortSize},\n\t\"Names\":               {ColName, SortName},\n\t\"Labels\":              {ColLabels, SortLabels},\n\t\"LabelKeys\":           {ColLabelKeys, SortLabelKeys},\n\t\"Mounts\":              {ColMounts, SortMounts},\n\t\"Networks\":            {ColNetworks, SortNetworks},\n\t\"IP\":                  {ColIP, SortIP},\n}\n\nfunc ColContainerID(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"CONTAINER ID\"}\n\t}\n\n\tif ctx.Opt.Truncate {\n\t\treturn []string{cont.ID[0:12]}\n\t} else {\n\t\treturn []string{cont.ID}\n\t}\n}\n\nfunc ColFullImage(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"IMAGE\"}\n\t}\n\n\treturn []string{cont.Image}\n}\n\nfunc ColRegistry(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"REGISTRY\"}\n\t}\n\n\tv, _, _ := docker.SplitDockerImage(ctx, cont.Image)\n\n\treturn []string{v}\n}\n\nfunc ColImage(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"IMAGE\"}\n\t}\n\n\t_, v, _ := docker.SplitDockerImage(ctx, cont.Image)\n\n\treturn []string{v}\n}\n\nfunc ColImageTag(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"TAG\"}\n\t}\n\n\t_, _, v := docker.SplitDockerImage(ctx, cont.Image)\n\n\treturn []string{v}\n}\n\nfunc ColCommand(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"COMMAND\"}\n\t}\n\n\tcmd := cont.Command\n\tif ctx.Opt.Truncate && len(cmd) > 20 {\n\t\tcmd = cmd[:19] + \"…\"\n\t}\n\n\tcmd = \"\\\"\" + cmd + \"\\\"\"\n\n\treturn []string{cmd}\n}\n\nfunc ColShortCommand(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"COMMAND\"}\n\t}\n\n\tspl := strings.Split(cont.Command, \" \")\n\tif len(spl) == 0 {\n\t\treturn []string{\"\"}\n\t} else {\n\t\treturn []string{spl[0]}\n\t}\n\n}\n\nfunc ColRunningFor(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"CREATED\"}\n\t}\n\n\tts := time.Unix(cont.Created, 0)\n\tdiff := time.Now().Sub(ts)\n\n\treturn []string{timeext.FormatNaturalDurationEnglish(diff)}\n}\n\nfunc ColCreatedAt(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\tif ctx.Opt.TimeFormatHeader != \"\" {\n\t\t\thdr := time.Now().In(ctx.Opt.TimeZone).Format(ctx.Opt.TimeFormatHeader)\n\t\t\tif hdr == \"Z UTC\" {\n\t\t\t\thdr = \"UTC\"\n\t\t\t}\n\t\t\treturn []string{\"CREATED AT (\" + hdr + \")\"}\n\t\t} else {\n\t\t\treturn []string{\"CREATED AT\"}\n\t\t}\n\t}\n\n\tts := time.Unix(cont.Created, 0)\n\n\treturn []string{ts.In(ctx.Opt.TimeZone).Format(ctx.Opt.TimeFormat)}\n}\n\nfunc ColState(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"STATE\"}\n\t}\n\n\tstrstate := \"[\" + strings.ToUpper(string(cont.State)) + \"]\"\n\n\tif !ctx.Opt.OutputColor {\n\t\treturn []string{strstate}\n\t}\n\n\treturn []string{stateColor(cont.State, strstate)}\n}\n\nfunc ColStatus(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"STATUS\"}\n\t}\n\n\tif !ctx.Opt.OutputColor {\n\t\treturn []string{cont.Status}\n\t}\n\n\treturn []string{statusColor(cont.Status, cont.Status)}\n}\n\nfunc ColPortsExposed(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"EXPOSED PORTS\"}\n\t}\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tp1 := langext.StrPadLeft(strconv.Itoa(port.PublicPort), \" \", 5)\n\t\tp2 := langext.StrPadLeft(strconv.Itoa(port.PrivatePort), \" \", 5)\n\n\t\tif port.PublicPort == 0 {\n\t\t\tstr := fmt.Sprintf(\"         %s / %s\", p2, port.Type)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t} else {\n\t\t\tstr := fmt.Sprintf(\"%s -> %s / %s\", p1, p2, port.Type)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColPortsPublicPart(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"EXPOSED PORTS\"}\n\t}\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tif port.PublicPort != 0 {\n\t\t\tstr := fmt.Sprintf(\"%d\", port.PublicPort)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, strconv.Itoa(port.PublicPort))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColPortsPublished(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"PUBLISHED PORTS\"}\n\t}\n\n\tpubPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PublicPort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tprivPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PrivatePort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tp1 := langext.StrPadLeft(strconv.Itoa(port.PublicPort), \" \", pubPortLenMax)\n\t\tp2 := langext.StrPadLeft(strconv.Itoa(port.PrivatePort), \" \", privPortLenMax)\n\n\t\tif port.PublicPort != 0 {\n\t\t\tstr := fmt.Sprintf(\"%s -> %s / %s\", p1, p2, port.Type)\n\t\t\tif port.IsLoopback() {\n\t\t\t\tstr += \" (loc)\"\n\t\t\t}\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColPortsPublishedShort(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"PUBLISHED PORTS\"}\n\t}\n\n\tpubPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PublicPort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tprivPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PrivatePort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tp1 := langext.StrPadLeft(strconv.Itoa(port.PublicPort), \" \", pubPortLenMax)\n\t\tp2 := langext.StrPadLeft(strconv.Itoa(port.PrivatePort), \" \", privPortLenMax)\n\n\t\tif port.PublicPort != 0 {\n\t\t\tstr := fmt.Sprintf(\"%s -> %s\", p1, p2)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColPortsPublishedLong(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"PUBLISHED PORTS\"}\n\t}\n\n\tiplenMax := ctx.GetIntFromCache(\"Printer::Ports::ip_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(v2.IP))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tpubPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PublicPort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tprivPortLenMax := ctx.GetIntFromCache(\"Printer::Ports::port_pub_length\", func() int {\n\t\tml := 0\n\t\tfor _, v1 := range allData {\n\t\t\tfor _, v2 := range v1.Ports {\n\t\t\t\tml = mathext.Max(ml, len(strconv.Itoa(v2.PrivatePort)))\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t})\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tp0 := langext.StrPadLeft(\"[\"+port.IP+\"]\", \" \", iplenMax+2)\n\t\tp1 := langext.StrPadLeft(strconv.Itoa(port.PublicPort), \" \", pubPortLenMax)\n\t\tp2 := langext.StrPadLeft(strconv.Itoa(port.PrivatePort), \" \", privPortLenMax)\n\n\t\tif port.PublicPort != 0 {\n\t\t\tstr := fmt.Sprintf(\"%s:%s -> %s\", p0, p1, p2)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColPortsNotPublished(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"NOT PUBLISHED PORTS\"}\n\t}\n\n\tm := make(map[string]bool)\n\tr := make([]string, 0)\n\tfor _, port := range cont.PortsSorted() {\n\t\tp2 := langext.StrPadLeft(strconv.Itoa(port.PrivatePort), \" \", 5)\n\n\t\tif port.PublicPort == 0 {\n\t\t\tstr := fmt.Sprintf(\"%s / %s\", p2, port.Type)\n\t\t\tif _, ok := m[str]; !ok {\n\t\t\t\tm[str] = true\n\t\t\t\tr = append(r, str)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColName(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"NAME\"}\n\t}\n\n\tr := make([]string, 0, len(cont.Names))\n\tfor _, n := range cont.Names {\n\t\tif len(n) > 0 && n[0] == '/' {\n\t\t\tn = n[1:]\n\t\t}\n\t\tr = append(r, n)\n\t}\n\n\treturn r\n}\n\nfunc ColSize(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"SIZE\"}\n\t}\n\n\tif cont.SizeRw == 0 && cont.SizeRootFs == 0 {\n\t\treturn []string{}\n\t}\n\n\treturn []string{fmt.Sprintf(\"%v (virt %v)\", langext.StrPadRight(langext.FormatBytes(cont.SizeRw), \" \", 11), langext.FormatBytes(cont.SizeRootFs))}\n}\n\nfunc ColMounts(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"MOUNTS\"}\n\t}\n\n\tr := make([]string, 0, len(cont.Mounts))\n\tfor _, mnt := range cont.Mounts {\n\t\tval := fmt.Sprintf(\"%s -> %s\", mnt.Source, mnt.Destination)\n\t\tif !mnt.RW {\n\t\t\tval += \" [ro]\"\n\t\t}\n\t\tr = append(r, val)\n\t}\n\n\treturn r\n}\n\nfunc ColIP(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"IP\"}\n\t}\n\n\tr := make([]string, 0, len(cont.NetworkSettings.Networks))\n\tfor _, nw := range cont.NetworkSettings.Networks {\n\t\tif nw.IPAddress != \"\" {\n\t\t\tr = append(r, nw.IPAddress)\n\t\t}\n\t}\n\n\treturn r\n}\n\nfunc ColLabels(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"LABELS\"}\n\t}\n\n\tr := make([]string, 0, len(cont.Mounts))\n\tfor k, v := range cont.Labels {\n\t\tr = append(r, fmt.Sprintf(\"%s := %s\", k, v))\n\t}\n\n\treturn r\n}\n\nfunc ColLabelKeys(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"LABELS\"}\n\t}\n\n\tr := make([]string, 0, len(cont.Mounts))\n\tfor k, _ := range cont.Labels {\n\t\tr = append(r, k)\n\t}\n\n\treturn r\n}\n\nfunc ColNetworks(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\tif cont == nil {\n\t\treturn []string{\"NETWORKS\"}\n\t}\n\n\tr := make([]string, 0, len(cont.Mounts))\n\tfor k := range cont.NetworkSettings.Networks {\n\t\tr = append(r, k)\n\t}\n\n\treturn r\n}\n\nfunc ColPlaintext(str string) printer.ColFun {\n\treturn func(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string {\n\t\treturn []string{str}\n\t}\n}\n\n// #####################################################################################################################\n\nfunc SortContainerID(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tif ctx.Opt.Truncate {\n\t\treturn langext.Compare(v1.ID[0:12], v2.ID[0:12])\n\t} else {\n\t\treturn langext.Compare(v1.ID, v2.ID)\n\t}\n}\n\nfunc SortFullImage(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.Image, v2.Image)\n}\n\nfunc SortRegistry(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treg1, _, _ := docker.SplitDockerImage(ctx, v1.Image)\n\treg2, _, _ := docker.SplitDockerImage(ctx, v2.Image)\n\n\treturn langext.Compare(reg1, reg2)\n}\n\nfunc SortImage(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\t_, img1, _ := docker.SplitDockerImage(ctx, v1.Image)\n\t_, img2, _ := docker.SplitDockerImage(ctx, v2.Image)\n\n\treturn langext.Compare(img1, img2)\n}\n\nfunc SortImageTag(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\t_, _, tag1 := docker.SplitDockerImage(ctx, v1.Image)\n\t_, _, tag2 := docker.SplitDockerImage(ctx, v2.Image)\n\n\treturn langext.Compare(tag1, tag2)\n}\n\nfunc SortCommand(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.Command, v2.Command)\n}\n\nfunc SortShortCommand(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tspl1 := strings.Split(v1.Command, \" \")\n\tsc1 := \"\"\n\tif len(spl1) > 0 {\n\t\tsc1 = spl1[0]\n\t}\n\n\tspl2 := strings.Split(v2.Command, \" \")\n\tsc2 := \"\"\n\tif len(spl2) > 0 {\n\t\tsc2 = spl2[0]\n\t}\n\n\treturn langext.Compare(sc1, sc2)\n}\n\nfunc SortRunningFor(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.Created, v2.Created) * -1 // runnign for is 'now - created', so we need to invert the sort order\n}\n\nfunc SortCreatedAt(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.Created, v2.Created)\n}\n\nfunc SortState(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.State.Num(), v2.State.Num())\n}\n\nfunc SortStatus(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.Compare(v1.Status, v2.Status)\n}\n\nfunc SortPortsExposed(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \"\\n\")\n\tpstr2 := strings.Join(pl2, \"\\n\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortPortsPublished(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tparr1 = langext.ArrFilter(parr1, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\tparr2 = langext.ArrFilter(parr2, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \"\\n\")\n\tpstr2 := strings.Join(pl2, \"\\n\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortPortsPublishedShort(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tparr1 = langext.ArrFilter(parr1, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\tparr2 = langext.ArrFilter(parr2, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \"\\n\")\n\tpstr2 := strings.Join(pl2, \"\\n\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortPortsPublishedLong(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tparr1 = langext.ArrFilter(parr1, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\tparr2 = langext.ArrFilter(parr2, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \"\\n\")\n\tpstr2 := strings.Join(pl2, \"\\n\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortPortsNotPublished(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tparr1 = langext.ArrFilter(parr1, func(v docker.PortSchema) bool { return v.PublicPort == 0 })\n\tparr2 = langext.ArrFilter(parr2, func(v docker.PortSchema) bool { return v.PublicPort == 0 })\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%s;%08d;%08d;%s\", v.IP, v.PrivatePort, v.PublicPort, v.Type)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \"\\n\")\n\tpstr2 := strings.Join(pl2, \"\\n\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortPortsPublicPart(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tparr1 := langext.ArrCopy(v1.Ports)\n\tparr2 := langext.ArrCopy(v2.Ports)\n\n\tparr1 = langext.ArrFilter(parr1, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\tparr2 = langext.ArrFilter(parr2, func(v docker.PortSchema) bool { return v.PublicPort != 0 })\n\n\tpl1 := langext.ArrMap(parr1, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%08d\", v.PublicPort)\n\t})\n\tpl2 := langext.ArrMap(parr2, func(v docker.PortSchema) string {\n\t\treturn fmt.Sprintf(\"%08d\", v.PublicPort)\n\t})\n\n\tlangext.SortStable(pl1)\n\tlangext.SortStable(pl2)\n\n\tpstr1 := strings.Join(pl1, \":\")\n\tpstr2 := strings.Join(pl2, \":\")\n\n\treturn langext.Compare(pstr1, pstr2)\n}\n\nfunc SortName(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tnames1 := langext.ArrCopy(v1.Names)\n\tnames2 := langext.ArrCopy(v2.Names)\n\n\tlangext.SortStable(names1)\n\tlangext.SortStable(names2)\n\n\treturn langext.CompareArr(names1, names2)\n}\n\nfunc SortSize(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\treturn langext.CompareArr([]int64{v1.SizeRw, v1.SizeRootFs}, []int64{v2.SizeRw, v2.SizeRootFs})\n}\n\nfunc SortMounts(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tmounts1 := langext.ArrMap(v1.Mounts, func(v docker.ContainerMount) string {\n\t\treturn fmt.Sprintf(\"%s\\n%s\", v.Source, v.Destination)\n\t})\n\tmounts2 := langext.ArrMap(v2.Mounts, func(v docker.ContainerMount) string {\n\t\treturn fmt.Sprintf(\"%s\\n%s\", v.Source, v.Destination)\n\t})\n\n\tlangext.SortStable(mounts1)\n\tlangext.SortStable(mounts2)\n\n\treturn langext.CompareArr(mounts1, mounts2)\n}\n\nfunc SortIP(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tips1 := langext.ArrMap(langext.MapToArr(v1.NetworkSettings.Networks), func(v langext.MapEntry[string, docker.ContainerSingleNetworkSettings]) string {\n\t\treturn v.Value.IPAddress\n\t})\n\tips2 := langext.ArrMap(langext.MapToArr(v2.NetworkSettings.Networks), func(v langext.MapEntry[string, docker.ContainerSingleNetworkSettings]) string {\n\t\treturn v.Value.IPAddress\n\t})\n\n\tips1 = langext.ArrFilter(ips1, func(v string) bool {\n\t\treturn v != \"\"\n\t})\n\tips2 = langext.ArrFilter(ips2, func(v string) bool {\n\t\treturn v != \"\"\n\t})\n\n\tips1 = langext.ArrMap(ips1, func(v string) string {\n\t\treturn ipExpand(v)\n\t})\n\tips2 = langext.ArrMap(ips2, func(v string) string {\n\t\treturn ipExpand(v)\n\t})\n\n\tlangext.SortStable(ips1)\n\tlangext.SortStable(ips2)\n\n\treturn langext.CompareArr(ips1, ips2)\n}\n\nfunc SortLabels(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tlbls1 := langext.ArrMap(langext.MapToArr(v1.Labels), func(v langext.MapEntry[string, string]) string {\n\t\treturn fmt.Sprintf(\"%s\\n%s\", v.Key, v.Value)\n\t})\n\tlbls2 := langext.ArrMap(langext.MapToArr(v2.Labels), func(v langext.MapEntry[string, string]) string {\n\t\treturn fmt.Sprintf(\"%s\\t%s\", v.Key, v.Value)\n\t})\n\n\tlangext.SortStable(lbls1)\n\tlangext.SortStable(lbls2)\n\n\treturn langext.CompareArr(lbls1, lbls2)\n}\n\nfunc SortLabelKeys(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tlbls1 := langext.ArrMap(langext.MapToArr(v1.Labels), func(v langext.MapEntry[string, string]) string {\n\t\treturn v.Key\n\t})\n\tlbls2 := langext.ArrMap(langext.MapToArr(v2.Labels), func(v langext.MapEntry[string, string]) string {\n\t\treturn v.Key\n\t})\n\n\tlangext.SortStable(lbls1)\n\tlangext.SortStable(lbls2)\n\n\treturn langext.CompareArr(lbls1, lbls2)\n}\n\nfunc SortNetworks(ctx *cli.PSContext, v1 *docker.ContainerSchema, v2 *docker.ContainerSchema) int {\n\tntwrk1 := langext.ArrMap(langext.MapToArr(v1.NetworkSettings.Networks), func(v langext.MapEntry[string, docker.ContainerSingleNetworkSettings]) string {\n\t\treturn v.Key\n\t})\n\tntwrk2 := langext.ArrMap(langext.MapToArr(v2.NetworkSettings.Networks), func(v langext.MapEntry[string, docker.ContainerSingleNetworkSettings]) string {\n\t\treturn v.Key\n\t})\n\n\tlangext.SortStable(ntwrk1)\n\tlangext.SortStable(ntwrk2)\n\n\treturn langext.CompareArr(ntwrk1, ntwrk2)\n}\n\n// #####################################################################################################################\n\nfunc getColFun(colkey string) (printer.ColFun, bool) {\n\n\t// Fast branch, simple references to columns\n\n\tfor k, v := range ColumnMap {\n\t\tif \"{{.\"+k+\"}}\" == colkey {\n\t\t\treturn v.Reader, true\n\t\t}\n\t}\n\n\t// Slow branch, fully-featured go templates\n\n\tif strings.HasPrefix(colkey, \"{{\") && strings.HasSuffix(colkey, \"}}\") {\n\t\treturn templateColFun(colkey, \"\"), true\n\t}\n\n\tif splt := strings.SplitN(colkey, \":\", 2); len(splt) == 2 && strings.HasPrefix(splt[1], \"{{\") && strings.HasSuffix(splt[1], \"}}\") {\n\t\treturn templateColFun(splt[1], splt[0]), true\n\t}\n\n\t// Fallback, nothing\n\n\treturn nil, false\n}\n\nfunc templateColFun(fmtstr string, header string) printer.ColFun {\n\treturn func(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) (res []string) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tctx.PrintErrorMessage(fmt.Sprintf(\"Panic in template evaluation of '%s':\\n%v\", fmtstr, r))\n\t\t\t\tres = []string{\"@ERROR\"}\n\t\t\t}\n\t\t}()\n\n\t\tif cont == nil {\n\t\t\treturn []string{header}\n\t\t}\n\n\t\tfuncs := template.FuncMap{\n\t\t\t\"join\": strings.Join,\n\t\t\t\"array_last\": func(v any) any {\n\t\t\t\trval := reflect.ValueOf(v)\n\t\t\t\talen := rval.Len()\n\t\t\t\tif alen == 0 {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn rval.Index(alen - 1).Interface()\n\t\t\t},\n\t\t\t\"array_slice\": func(v any, start int, end int) any {\n\t\t\t\trval := reflect.ValueOf(v)\n\t\t\t\talen := rval.Len()\n\n\t\t\t\tstart = max(0, min(alen, start))\n\t\t\t\tend = max(0, min(alen, end))\n\n\t\t\t\treturn rval.Slice(start, end).Interface()\n\t\t\t},\n\t\t\t\"in_array\": func(compval any, arrval any) (resp bool) {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif rec := recover(); rec != nil {\n\t\t\t\t\t\tresp = false\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\tv := reflect.ValueOf(arrval)\n\t\t\t\tfor i := 0; i < v.Len(); i++ {\n\t\t\t\t\tif v.Index(i).Equal(reflect.ValueOf(compval)) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t},\n\t\t\t\"json\": func(obj any) string {\n\t\t\t\tv, err := json.Marshal(obj)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\treturn string(v)\n\t\t\t},\n\t\t\t\"json_indent\": func(obj any) string {\n\t\t\t\tv, err := json.MarshalIndent(obj, \"\", \"  \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\treturn string(v)\n\t\t\t},\n\t\t\t\"json_pretty\": func(v string) string {\n\t\t\t\tbuffer := &bytes.Buffer{}\n\t\t\t\terr := json.Indent(buffer, []byte(v), \"\", \"  \")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn v\n\t\t\t\t} else {\n\t\t\t\t\treturn buffer.String()\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"coalesce\": func(val any, def any) any {\n\t\t\t\tif langext.IsNil(val) {\n\t\t\t\t\treturn def\n\t\t\t\t} else {\n\t\t\t\t\treturn val\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"to_string\": func(v any) string {\n\t\t\t\treturn fmt.Sprintf(\"%v\", v)\n\t\t\t},\n\t\t\t\"deref\": func(vInput any) any {\n\t\t\t\tval := reflect.ValueOf(vInput)\n\t\t\t\tif val.Kind() == reflect.Ptr {\n\t\t\t\t\treturn val.Elem().Interface()\n\t\t\t\t}\n\t\t\t\treturn \"\"\n\t\t\t},\n\t\t\t\"now\": func() time.Time {\n\t\t\t\treturn time.Now()\n\t\t\t},\n\t\t\t\"uniqid\": func() string {\n\t\t\t\treturn langext.MustRawHexUUID()\n\t\t\t},\n\t\t}\n\n\t\ttempl, err := template.New(\"col\").Funcs(funcs).Parse(fmtstr)\n\t\tif err != nil {\n\t\t\tctx.PrintErrorMessage(fmt.Sprintf(\"Error in template parsing of '%s':\\n%v\", fmtstr, err.Error()))\n\t\t\tres = []string{\"@ERROR\"}\n\t\t}\n\n\t\tbfr := &bytes.Buffer{}\n\t\terr = templ.Execute(bfr, *cont)\n\t\tif err != nil {\n\t\t\tctx.PrintErrorMessage(fmt.Sprintf(\"Error in template evaluation of '%s':\\n%v\", fmtstr, err.Error()))\n\t\t\tres = []string{\"@ERROR\"}\n\t\t}\n\n\t\treturn strings.Split(bfr.String(), \"\\n\")\n\t}\n}\n\nfunc getSortFun(colkey string) (ColSortFun, bool) {\n\tif cdef, ok := ColumnMap[colkey]; ok {\n\t\treturn cdef.Sorter, true\n\t}\n\treturn nil, false\n}\n\nfunc replaceSingleLineColumnData(ctx *cli.PSContext, allData []docker.ContainerSchema, data docker.ContainerSchema, format string) string {\n\n\tr := format\n\n\tfor k, v := range ColumnMap {\n\t\tr = strings.ReplaceAll(r, \"{{.\"+k+\"}}\", strings.Join(v.Reader(ctx, allData, &data), \" \"))\n\t}\n\n\treturn r\n\n}\n\nfunc parseTableDef(fmt string) []printer.ColFun {\n\tsplit := regexp.MustCompile(\"(\\\\\\\\t|\\\\t)\").Split(fmt[6:], -1)\n\tcolumns1 := make([]printer.ColFun, 0)\n\tfor _, v := range split {\n\t\tif cf, ok := getColFun(v); ok {\n\t\t\tcolumns1 = append(columns1, cf)\n\t\t} else {\n\t\t\tcolumns1 = append(columns1, ColPlaintext(v))\n\t\t}\n\t}\n\treturn columns1\n}\n\nfunc stateColor(state docker.ContainerState, value string) string {\n\tswitch state {\n\tcase docker.StateCreated:\n\t\treturn termext.Yellow(value)\n\tcase docker.StateRunning:\n\t\treturn termext.Green(value)\n\tcase docker.StateRestarting:\n\t\treturn termext.Yellow(value)\n\tcase docker.StateExited:\n\t\treturn termext.Red(value)\n\tcase docker.StatePaused:\n\t\treturn termext.Yellow(value)\n\tcase docker.StateDead:\n\t\treturn termext.Red(value)\n\t}\n\treturn value\n}\n\nfunc statusColor(status string, value string) string {\n\tif status == \"Created\" {\n\t\treturn termext.Yellow(value)\n\t}\n\n\tif strings.HasPrefix(status, \"Exited\") {\n\t\treturn termext.Red(value)\n\t}\n\n\tif strings.HasPrefix(status, \"Up\") {\n\t\tif strings.HasSuffix(status, \"(unhealthy)\") {\n\t\t\treturn termext.Red(value)\n\t\t}\n\t\tif strings.HasSuffix(status, \"(health: starting)\") {\n\t\t\treturn termext.Yellow(value)\n\t\t}\n\n\t\treturn termext.Green(value)\n\t}\n\n\treturn value\n}\n\nfunc ipExpand(ip string) string {\n\tif match, ok := rexIP.MatchFirst(ip); ok {\n\t\treturn fmt.Sprintf(\"%03s.%03s.%03s.%03s\",\n\t\t\tmatch.GroupByName(\"b0\").Value(),\n\t\t\tmatch.GroupByName(\"b1\").Value(),\n\t\t\tmatch.GroupByName(\"b2\").Value(),\n\t\t\tmatch.GroupByName(\"b3\").Value())\n\t}\n\treturn ip\n}\n"
  },
  {
    "path": "impl/impl.go",
    "content": "package impl\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"better-docker-ps/docker\"\n\t\"better-docker-ps/printer\"\n\t\"better-docker-ps/pserr\"\n\t\"encoding/json\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/langext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/mathext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/syncext\"\n\t\"golang.org/x/term\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n)\n\nfunc Execute(ctx *cli.PSContext) error {\n\treturn executeSingle(ctx, false)\n}\n\nfunc Watch(ctx *cli.PSContext, d time.Duration) error {\n\n\tsigTermChannel := make(chan os.Signal, 8)\n\tsignal.Notify(sigTermChannel, os.Interrupt, syscall.SIGTERM)\n\n\tfor {\n\n\t\terr := executeSingle(ctx, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, isSig := syncext.ReadChannelWithTimeout(sigTermChannel, d)\n\t\tif isSig {\n\t\t\tctx.PrintPrimaryOutput(\"\")\n\t\t\tctx.PrintPrimaryOutput(\"Watch canceled with Ctrl+C\")\n\t\t\treturn nil\n\t\t}\n\n\t}\n}\n\nfunc executeSingle(ctx *cli.PSContext, clear bool) error {\n\tfor _, fmt := range ctx.Opt.Format {\n\t\tif strings.Contains(fmt, \"{{.Size}}\") {\n\t\t\tctx.Opt.WithSize = true\n\t\t}\n\t}\n\n\tjsonraw, err := docker.ListContainer(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx.PrintVerboseKV(\"API response\", langext.TryPrettyPrintJson(string(jsonraw)))\n\n\tvar data []docker.ContainerSchema\n\terr = json.Unmarshal(jsonraw, &data)\n\tif err != nil {\n\t\treturn pserr.DirectOutput.Wrap(err, \"Failed to decode Docker API response\")\n\t}\n\n\tif len(ctx.Opt.SortColumns) > 0 {\n\t\tdata = doSort(ctx, data, ctx.Opt.SortColumns, ctx.Opt.SortDirection)\n\t}\n\n\tif ctx.Opt.Search != nil {\n\t\tdata = doSearch(ctx, data, *ctx.Opt.Search)\n\t}\n\n\tfor i, v := range ctx.Opt.Format {\n\n\t\tif clear {\n\t\t\tctx.ClearTerminal()\n\t\t}\n\n\t\tok, err := doOutput(ctx, data, v, i == len(ctx.Opt.Format)-1)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif ok {\n\t\t\treturn nil\n\t\t}\n\n\t}\n\n\treturn pserr.DirectOutput.New(\"Missing format specification for output\")\n}\n\nfunc doSearch(ctx *cli.PSContext, data []docker.ContainerSchema, needle string) []docker.ContainerSchema {\n\tneedle = strings.ToLower(needle)\n\n\thaystackFormat := \"\"\n\tfor _, f := range ctx.Opt.Format {\n\t\tif strings.HasPrefix(f, \"table \") {\n\t\t\thaystackFormat = f\n\t\t\tbreak\n\t\t}\n\t}\n\n\tresult := make([]docker.ContainerSchema, 0, len(data))\n\tfor _, cont := range data {\n\t\thay := cont.ID + \" \" + strings.Join(cont.Names, \" \") + \" \" + cont.Image + \" \" + cont.Command\n\t\tif haystackFormat != \"\" {\n\t\t\tfor _, fn := range parseTableDef(haystackFormat) {\n\t\t\t\thay += \" \" + strings.Join(fn(ctx, data, &cont), \" \")\n\t\t\t}\n\t\t} else if len(ctx.Opt.Format) > 0 {\n\t\t\thay += \" \" + replaceSingleLineColumnData(ctx, data, cont, ctx.Opt.Format[0])\n\t\t}\n\t\tif strings.Contains(strings.ToLower(hay), needle) {\n\t\t\tresult = append(result, cont)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc doSort(ctx *cli.PSContext, data []docker.ContainerSchema, skeys []string, sdirs []cli.SortDirection) []docker.ContainerSchema {\n\n\tlangext.SortSliceStable(data, func(v1, v2 docker.ContainerSchema) bool {\n\n\t\t// return true if v1 < v2\n\n\t\tfor i := 0; i < len(skeys); i++ {\n\n\t\t\tsfn, ok := getSortFun(skeys[i])\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcmp := sfn(ctx, &v1, &v2)\n\t\t\tif sdirs[i] == \"DESC\" {\n\t\t\t\tcmp = cmp * -1\n\t\t\t}\n\n\t\t\tif cmp < 0 {\n\t\t\t\treturn true\n\t\t\t} else if cmp > 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\treturn false // equals\n\t})\n\n\treturn data\n}\n\nfunc doOutput(ctx *cli.PSContext, data []docker.ContainerSchema, format string, force bool) (bool, error) {\n\tif format == \"idlist\" {\n\n\t\tfor _, v := range data {\n\t\t\tif ctx.Opt.Truncate {\n\t\t\t\tctx.PrintPrimaryOutput(v.ID[0:12])\n\t\t\t} else {\n\t\t\t\tctx.PrintPrimaryOutput(v.ID)\n\t\t\t}\n\t\t}\n\t\treturn true, nil\n\n\t} else if strings.HasPrefix(format, \"table \") {\n\n\t\tcolumns := parseTableDef(format)\n\t\toutWidth := printer.Width(ctx, data, columns)\n\n\t\tif !force {\n\t\t\ttermWidth, _, err := term.GetSize(int(os.Stdout.Fd()))\n\t\t\tif err == nil && 0 < termWidth && termWidth < outWidth {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\n\t\tprinter.Print(ctx, data, columns)\n\t\treturn true, nil\n\n\t} else {\n\n\t\tlines := make([]string, 0)\n\t\toutWidth := 0\n\n\t\tfor _, v := range data {\n\t\t\tstr := replaceSingleLineColumnData(ctx, data, v, format)\n\t\t\tlines = append(lines, str)\n\t\t\toutWidth = mathext.Max(outWidth, printer.RealStrLen(str))\n\t\t}\n\n\t\tif !force {\n\t\t\ttermWidth, _, err := term.GetSize(int(os.Stdout.Fd()))\n\t\t\tif err == nil && 0 < termWidth && termWidth < outWidth {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\n\t\tfor _, v := range lines {\n\t\t\tctx.PrintPrimaryOutput(v)\n\t\t}\n\t\treturn true, nil\n\n\t}\n}\n"
  },
  {
    "path": "install.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This script handles the installation of 'dops' by detecting the OS\n# and architecture, downloading the appropriate binary, and configuring the shell PATH.\n\n# Reset\nColor_Off=''\n\n# Regular Colors\nRed=''\nGreen=''\nDim='' # White\n\n# Bold\nBold_Green=''\nBold_White=''\n\nif [[ -t 1 ]]; then\n    # Reset\n    Color_Off='\\033[0m' # Text Reset\n\n    # Regular Colors\n    Red='\\033[0;31m'   # Red\n    Green='\\033[0;32m' # Green\n    Dim='\\033[0;2m'    # White\n\n    # Bold\n    Bold_Green='\\033[1;32m' # Bold Green\n    Bold_White='\\033[1m'    # Bold White\nfi\n\nerror() {\n    echo -e \"${Red}error${Color_Off}:\" \"$@\" >&2\n    exit 1\n}\n\ninfo() {\n    echo -e \"${Dim}$@ ${Color_Off}\"\n}\n\nsuccess() {\n    echo -e \"${Green}$@ ${Color_Off}\"\n}\n\ninfo_bold() {\n    echo -e \"${Bold_White}$@ ${Color_Off}\"\n}\n\n# Check if we're on Arch Linux\nis_arch_linux() {\n    [[ -f /etc/arch-release ]] || command -v pacman >/dev/null 2>&1\n}\n\n# Detect available AUR helpers in order of preference\ndetect_aur_helper() {\n    local aur_helpers=(\"yay\" \"paru\" \"pikaur\" \"pamac\" \"trizen\" \"yaourt\")\n    \n    for helper in \"${aur_helpers[@]}\"; do\n        if command -v \"$helper\" >/dev/null 2>&1; then\n            echo \"$helper\"\n            return 0\n        fi\n    done\n    return 1\n}\n\n# Install via AUR\ninstall_via_aur() {\n    local aur_helper=\"$1\"\n    \n    info \"Installing 'dops' via AUR using ${aur_helper}...\"\n    \n    case \"$aur_helper\" in\n        yay|paru|pikaur)\n            \"$aur_helper\" -S --noconfirm dops-bin ||\n                error \"Failed to install dops-bin via $aur_helper\"\n            ;;\n        pamac)\n            pamac install --no-confirm dops-bin ||\n                error \"Failed to install dops-bin via pamac\"\n            ;;\n        trizen|yaourt)\n            \"$aur_helper\" -S --noconfirm dops-bin ||\n                error \"Failed to install dops-bin via $aur_helper\"\n            ;;\n        *)\n            error \"Unsupported AUR helper: $aur_helper\"\n            ;;\n    esac\n    \n    success \"dops was installed successfully via AUR using $aur_helper\"\n    echo \"Run 'dops --help' to get started\"\n    \n    echo\n    info \"To use 'dops' as a drop-in replacement for 'docker ps',\"\n    info \"add the following function to your shell configuration file (e.g., ~/.zshrc, ~/.bashrc):\"\n    echo\n    info_bold 'docker() {'\n    info_bold '  case $1 in'\n    info_bold '    ps)'\n    info_bold '      shift'\n    info_bold '      command dops \"$@\"'\n    info_bold '      ;;'\n    info_bold '    *)'\n    info_bold '      command docker \"$@\";;'\n    info_bold '  esac'\n    info_bold '}'\n    \n    exit 0\n}\n\n# Try AUR installation first on Arch Linux\nif is_arch_linux; then\n    info \"Detected Arch Linux, checking for AUR helpers...\"\n    \n    if aur_helper=$(detect_aur_helper); then\n        install_via_aur \"$aur_helper\"\n    else\n        info \"No AUR helper found (yay, paru, pikaur, pamac, trizen, yaourt)\"\n        info \"Falling back to binary installation...\"\n        echo\n    fi\nfi\n\n# Check for curl\ncommand -v curl >/dev/null ||\n    error 'curl is required to install dops'\n\nREPO=\"Mikescher/better-docker-ps\"\nBINARY_NAME=\"\"\n\n# Platform detection\nOS=$(uname -s)\nARCH=$(uname -m)\n\ninfo \"Detecting platform: ${OS}/${ARCH}...\"\n\ncase \"$OS\" in\n    Linux)\n        if [ \"$ARCH\" = \"x86_64\" ]; then\n            BINARY_NAME=\"dops_linux-amd64-static\"\n        elif [ \"$ARCH\" = \"aarch64\" ] || [ \"$ARCH\" = \"arm64\" ]; then\n            BINARY_NAME=\"dops_linux-arm64-static\"\n        fi\n        ;;\n    Darwin)\n        if [ \"$ARCH\" = \"arm64\" ]; then\n            BINARY_NAME=\"dops_macos-arm64\"\n        elif [ \"$ARCH\" = \"x86_64\" ]; then\n            error \"Intel-based Macs are not supported.\"\n        fi\n        ;;\nesac\n\nif [ -z \"$BINARY_NAME\" ]; then\n    error \"Unsupported OS or Architecture: ${OS}/${ARCH}\"\nfi\n\nDOWNLOAD_URL=\"https://github.com/${REPO}/releases/latest/download/${BINARY_NAME}\"\n\ninstall_env=DOPS_INSTALL\nbin_env=\\$$install_env/bin\n\ninstall_dir=${!install_env:-$HOME/.dops}\nbin_dir=$install_dir/bin\nexe=$bin_dir/dops\n\nif [[ ! -d $bin_dir ]]; then\n    mkdir -p \"$bin_dir\" ||\n        error \"Failed to create install directory \\\"$bin_dir\\\"\"\nfi\n\ninfo \"Downloading 'dops' from ${DOWNLOAD_URL}...\"\n\ncurl --fail --location --progress-bar --output \"$exe\" \"$DOWNLOAD_URL\" ||\n    error \"Failed to download dops from \\\"$DOWNLOAD_URL\\\"\"\n\nchmod +x \"$exe\" ||\n    error 'Failed to set permissions on dops executable'\n\ntildify() {\n    if [[ $1 = $HOME/* ]]; then\n        local replacement=\\~/\n        echo \"${1/$HOME\\//$replacement}\"\n    else\n        echo \"$1\"\n    fi\n}\n\nsuccess \"dops was installed successfully to $Bold_Green$(tildify \"$exe\")\"\n\nif command -v dops >/dev/null; then\n    echo \"Run 'dops --help' to get started\"\n    exit\nfi\n\nrefresh_command=''\n\ntilde_bin_dir=$(tildify \"$bin_dir\")\nquoted_install_dir=\\\"${install_dir//\\\"/\\\\\\\"}\\\"\n\nif [[ $quoted_install_dir = \\\"$HOME/* ]]; then\n    quoted_install_dir=${quoted_install_dir/$HOME\\//\\$HOME/}\nfi\n\necho\n\ncase $(basename \"$SHELL\") in\nfish)\n    commands=(\n        \"set --export $install_env $quoted_install_dir\"\n        \"set --export PATH $bin_env \\$PATH\"\n    )\n\n    fish_config=$HOME/.config/fish/config.fish\n    tilde_fish_config=$(tildify \"$fish_config\")\n\n    if [[ -w $fish_config ]]; then\n        {\n            echo -e '\\n# dops'\n            for command in \"${commands[@]}\"; do\n                echo \"$command\"\n            done\n        } >>\"$fish_config\"\n        info \"Added \\\"$tilde_bin_dir\\\" to \\$PATH in \\\"$tilde_fish_config\\\"\"\n        refresh_command=\"source $tilde_fish_config\"\n    else\n        echo \"Manually add the directory to $tilde_fish_config (or similar):\"\n        for command in \"${commands[@]}\"; do\n            info_bold \"  $command\"\n        done\n    fi\n    ;;\nzsh)\n    commands=(\n        \"export $install_env=$quoted_install_dir\"\n        \"export PATH=\\\"$bin_env:\\$PATH\\\"\"\n    )\n\n    zsh_config=$HOME/.zshrc\n    tilde_zsh_config=$(tildify \"$zsh_config\")\n\n    if [[ -w $zsh_config ]]; then\n        {\n            echo -e '\\n# dops'\n            for command in \"${commands[@]}\"; do\n                echo \"$command\"\n            done\n        } >>\"$zsh_config\"\n        info \"Added \\\"$tilde_bin_dir\\\" to \\$PATH in \\\"$tilde_zsh_config\\\"\"\n        refresh_command=\"exec $SHELL\"\n    else\n        echo \"Manually add the directory to $tilde_zsh_config (or similar):\"\n        for command in \"${commands[@]}\"; do\n            info_bold \"  $command\"\n        done\n    fi\n    ;;\nbash)\n    commands=(\n        \"export $install_env=$quoted_install_dir\"\n        \"export PATH=\\\"$bin_env:\\$PATH\\\"\"\n    )\n\n    bash_configs=(\n        \"$HOME/.bashrc\"\n        \"$HOME/.bash_profile\"\n    )\n\n    if [[ ${XDG_CONFIG_HOME:-} ]]; then\n        bash_configs+=(\n            \"$XDG_CONFIG_HOME/.bash_profile\"\n            \"$XDG_CONFIG_HOME/.bashrc\"\n            \"$XDG_CONFIG_HOME/bash_profile\"\n            \"$XDG_CONFIG_HOME/bashrc\"\n        )\n    fi\n\n    set_manually=true\n    for bash_config in \"${bash_configs[@]}\"; do\n        tilde_bash_config=$(tildify \"$bash_config\")\n\n        if [[ -w $bash_config ]]; then\n            {\n                echo -e '\\n# dops'\n                for command in \"${commands[@]}\"; do\n                    echo \"$command\"\n                done\n            } >>\"$bash_config\"\n            info \"Added \\\"$tilde_bin_dir\\\" to \\$PATH in \\\"$tilde_bash_config\\\"\"\n            refresh_command=\"source $bash_config\"\n            set_manually=false\n            break\n        fi\n    done\n\n    if [[ $set_manually = true ]]; then\n        echo \"Manually add the directory to your shell configuration file (or similar):\"\n        for command in \"${commands[@]}\"; do\n            info_bold \"  $command\"\n        done\n    fi\n    ;;\n*)\n    echo 'Manually add the directory to your shell configuration file (or similar):'\n    info_bold \"  export $install_env=$quoted_install_dir\"\n    info_bold \"  export PATH=\\\"$bin_env:\\$PATH\\\"\"\n    ;;\nesac\n\necho\ninfo \"To get started, run:\"\necho\n\nif [[ $refresh_command ]]; then\n    info_bold \"  $refresh_command\"\nfi\n\ninfo_bold \"  dops --help\"\n\necho\ninfo \"To use 'dops' as a drop-in replacement for 'docker ps',\"\ninfo \"add the following function to your shell configuration file (e.g., ~/.zshrc, ~/.bashrc):\"\necho\ninfo_bold 'docker() {'\ninfo_bold '  case $1 in'\ninfo_bold '    ps)'\ninfo_bold '      shift'\ninfo_bold '      command dops \"$@\"'\ninfo_bold '      ;;'\ninfo_bold '    *)'\ninfo_bold '      command docker \"$@\";;'\ninfo_bold '  esac'\ninfo_bold '}'\n"
  },
  {
    "path": "printer/printer.go",
    "content": "package printer\n\nimport (\n\t\"better-docker-ps/cli\"\n\t\"better-docker-ps/docker\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/mathext\"\n\t\"git.blackforestbytes.com/BlackForestBytes/goext/termext\"\n\t\"strings\"\n)\n\ntype ColFun = func(ctx *cli.PSContext, allData []docker.ContainerSchema, cont *docker.ContainerSchema) []string\n\nfunc Width(ctx *cli.PSContext, data []docker.ContainerSchema, cols []ColFun) int {\n\tvar cells = make([][]string, 0)\n\n\tif ctx.Opt.PrintHeader {\n\t\trow := make([]string, 0)\n\n\t\tfor _, fn := range cols {\n\t\t\th := fn(ctx, data, nil)\n\t\t\trow = append(row, h[0])\n\t\t}\n\n\t\tcells = append(cells, row)\n\t}\n\n\tfor _, dat := range data {\n\t\textrow := make([][]string, 0)\n\n\t\tmaxheight := 1\n\t\tfor _, fn := range cols {\n\t\t\th := fn(ctx, data, &dat)\n\t\t\textrow = append(extrow, h)\n\t\t\tmaxheight = mathext.Max(maxheight, len(h))\n\t\t}\n\n\t\tfor yy := 0; yy < maxheight; yy++ {\n\t\t\trow := make([]string, len(cols))\n\t\t\tfor xx := 0; xx < len(cols); xx++ {\n\t\t\t\tif yy < len(extrow[xx]) {\n\t\t\t\t\trow[xx] = extrow[xx][yy]\n\t\t\t\t}\n\t\t\t}\n\t\t\tcells = append(cells, row)\n\t\t}\n\n\t}\n\n\tif len(cells) == 0 {\n\t\treturn 0\n\t}\n\n\tlens := make([]int, len(cells[0]))\n\tfor _, row := range cells {\n\t\tfor i, cell := range row {\n\t\t\tlens[i] = mathext.Max(lens[i], RealStrLen(cell))\n\t\t}\n\t}\n\n\tw := 0\n\tfor _, v := range lens {\n\t\tw += v\n\t}\n\n\treturn w + 4*(len(cols)-1)\n}\n\nfunc Print(ctx *cli.PSContext, data []docker.ContainerSchema, cols []ColFun) {\n\n\tvar cells = make([][]string, 0)\n\n\tif ctx.Opt.PrintHeader {\n\t\trow := make([]string, 0)\n\n\t\tfor _, fn := range cols {\n\t\t\th := fn(ctx, data, nil)\n\t\t\trow = append(row, h[0])\n\t\t}\n\n\t\tcells = append(cells, row)\n\t}\n\n\tfor _, dat := range data {\n\t\textrow := make([][]string, 0)\n\n\t\tmaxheight := 1\n\t\tfor _, fn := range cols {\n\t\t\th := fn(ctx, data, &dat)\n\t\t\textrow = append(extrow, h)\n\t\t\tmaxheight = mathext.Max(maxheight, len(h))\n\t\t}\n\n\t\tfor yy := 0; yy < maxheight; yy++ {\n\t\t\trow := make([]string, len(cols))\n\t\t\tfor xx := 0; xx < len(cols); xx++ {\n\t\t\t\tif yy < len(extrow[xx]) {\n\t\t\t\t\trow[xx] = extrow[xx][yy]\n\t\t\t\t}\n\t\t\t}\n\t\t\tcells = append(cells, row)\n\t\t}\n\n\t}\n\n\tif len(cells) == 0 {\n\t\treturn\n\t}\n\n\tlens := make([]int, len(cells[0]))\n\tfor _, row := range cells {\n\t\tfor i, cell := range row {\n\t\t\tlens[i] = mathext.Max(lens[i], RealStrLen(cell))\n\t\t}\n\t}\n\n\tfor rowidx, row := range cells {\n\n\t\t{\n\t\t\trowstr := \"\"\n\t\t\tfor colidx, cell := range row {\n\t\t\t\tif colidx > 0 {\n\t\t\t\t\trowstr += \"    \"\n\t\t\t\t}\n\t\t\t\tif colidx == len(row)-1 {\n\t\t\t\t\trowstr += cell // do not pad last\n\t\t\t\t} else {\n\t\t\t\t\trowstr += TermStrPadRight(cell, \" \", lens[colidx])\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.PrintPrimaryOutput(rowstr)\n\t\t}\n\n\t\tif ctx.Opt.PrintHeader && ctx.Opt.PrintHeaderLines && rowidx == 0 {\n\t\t\trowstr := \"\"\n\t\t\tfor colidx := range row {\n\t\t\t\tif colidx > 0 {\n\t\t\t\t\trowstr += \"    \"\n\t\t\t\t}\n\t\t\t\trowstr += TermStrPadRight(\"\", \"-\", lens[colidx])\n\t\t\t}\n\t\t\tctx.PrintPrimaryOutput(rowstr)\n\t\t}\n\t}\n\n}\n\nfunc RealStrLen(cell string) int {\n\treturn len([]rune(termext.CleanString(cell)))\n}\n\nfunc TermStrPadRight(str string, pad string, padlen int) string {\n\tif pad == \"\" {\n\t\tpad = \" \"\n\t}\n\n\tif RealStrLen(str) >= padlen {\n\t\treturn str\n\t}\n\n\treturn str + strings.Repeat(pad, padlen-RealStrLen(str))[0:(padlen-RealStrLen(str))]\n}\n"
  },
  {
    "path": "pserr/err.go",
    "content": "package pserr\n\nimport \"github.com/joomcode/errorx\"\n\nvar (\n\tDopsErrors = errorx.NewNamespace(\"dops\")\n)\n\nvar (\n\tDirectOutput = DopsErrors.NewType(\"direct_out\")\n)\n\nvar (\n\tExitcode = errorx.RegisterProperty(\"dops.exitcode\")\n)\n"
  },
  {
    "path": "pserr/util.go",
    "content": "package pserr\n\nimport (\n\t\"fmt\"\n\t\"github.com/joomcode/errorx\"\n)\n\nfunc GetDirectOutput(err error) *errorx.Error {\n\n\tsub := err\n\tfor sub != nil {\n\n\t\terrx := errorx.Cast(sub)\n\t\tif errx == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif uw := errx.Unwrap(); uw != nil {\n\t\t\tsub = uw\n\t\t\tcontinue\n\t\t}\n\n\t\tif errx.Type() == DirectOutput {\n\t\t\treturn errx\n\t\t}\n\n\t\tsub = errx.Cause()\n\t}\n\n\treturn nil\n}\n\nfunc FormatError(err error, verbose bool) string {\n\tif errx := GetDirectOutput(err); errx != nil {\n\t\tif verbose {\n\t\t\treturn fmt.Sprintf(\"%s\\n\\n%+v\", errx.Message(), err)\n\t\t} else {\n\t\t\treturn errx.Message()\n\t\t}\n\t}\n\n\tif verbose {\n\t\treturn fmt.Sprintf(\"%+v\", err)\n\t} else {\n\t\treturn err.Error()\n\t}\n}\n\nfunc GetExitCode(err error, fallback int) int {\n\tif errx := GetDirectOutput(err); errx != nil {\n\t\tif ec, ok := errx.Property(Exitcode); ok {\n\t\t\tif eci, ok := ec.(int); ok {\n\t\t\t\treturn eci\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fallback\n}\n"
  }
]