diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e8cbc2..e4c5da4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - SRC_DIR: src/github.com/${{ github.repository }} - jobs: Go: name: Go @@ -34,25 +31,21 @@ jobs: strategy: matrix: - go: [ '1.19.x', '1.20.x' ] + go: [ '1.21.x', '1.22.x' ] steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - - name: Checkout - uses: actions/checkout@v3 - with: - path: ${{env.SRC_DIR}} - - name: Download dependencies - working-directory: ${{env.SRC_DIR}} run: make deps - name: Build binary - working-directory: ${{env.SRC_DIR}} run: make all Aligo: @@ -62,24 +55,20 @@ jobs: needs: Go steps: - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.19.x' - - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 with: - path: ${{env.SRC_DIR}} + go-version: '1.21.x' - name: Download dependencies - working-directory: ${{env.SRC_DIR}} run: make deps - name: Check Golang sources with Aligo - uses: essentialkaos/aligo-action@v1 + uses: essentialkaos/aligo-action@v2 with: - path: ${{env.SRC_DIR}} files: ./... Shellcheck: @@ -90,7 +79,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check scripts with Shellcheck uses: essentialkaos/shellcheck-action@v1 @@ -105,13 +94,27 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check dockerfiles with Hadolint uses: essentialkaos/hadolint-action@v1 with: files: .docker/ol7.docker .docker/ol8.docker .docker/ol9.docker + Typos: + name: Typos + runs-on: ubuntu-latest + + needs: Go + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check spelling + continue-on-error: true + uses: crate-ci/typos@master + DockerBuild: name: Docker Build Check runs-on: ubuntu-latest @@ -136,11 +139,11 @@ jobs: # More info about issue: https://github.com/actions/runner/issues/491 - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 if: ${{ github.event_name == 'pull_request' }} - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} if: ${{ github.event_name == 'pull_request' && env.DOCKERHUB_USERNAME != '' }} @@ -149,7 +152,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 if: ${{ github.event_name == 'pull_request' }} with: registry: ghcr.io diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf5ecc9..bfc4df5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,14 +20,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: go - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 263682f..02316c5 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -32,18 +32,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -149,7 +149,7 @@ jobs: - name: Build and push Docker images (Docker) if: ${{ steps.build_check.outputs.build == 'true' }} - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: push: true context: . @@ -160,7 +160,7 @@ jobs: - name: Build and push Docker images (GHCR) if: ${{ steps.build_check.outputs.build == 'true' }} - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: push: true context: . diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..cc5caea --- /dev/null +++ b/.typos.toml @@ -0,0 +1,5 @@ +[files] +extend-exclude = ["go.sum"] + +[default.extend-identifiers] +O_WRONLY = "O_WRONLY" diff --git a/README.md b/README.md index 75a7ef9..6c17857 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ #### From source -To build the `bop` from scratch, make sure you have a working Go 1.18+ workspace (_[instructions](https://golang.org/doc/install)_), then: +To build the `bop` from scratch, make sure you have a working Go 1.18+ workspace (_[instructions](https://go.dev/doc/install)_), then: ```bash go install github.com/essentialkaos/bop@latest @@ -34,7 +34,7 @@ bash <(curl -fsSL https://apps.kaos.st/get) bop #### Container image -Official `bop` images available on [GitHub Container Registry](https://kaos.sh/p/bop) and [Docker Hub](https://kaos.sh/d/bop). Install the latest version of Podman or Docker, then: +Official `bop` images available on [GitHub Container Registry](https://kaos.sh/p/bop) and [Docker Hub](https://kaos.sh/d/bop). Install the latest version of [Podman](https://podman.io/getting-started/installation.html) or [Docker](https://docs.docker.com/engine/install/), then: ```bash curl -#L -o bop-container https://kaos.sh/bop/bop-container @@ -80,7 +80,7 @@ Usage: bop {options} name package… Options --output, -o file Output file - --service, -s service List of services for checking (mergable) + --service, -s service List of services for checking (mergeable) --no-color, -nc Disable colors in output --help, -h Show this help message --version, -v Show version diff --git a/bop-container b/bop-container index 3793df1..fab662d 100755 --- a/bop-container +++ b/bop-container @@ -57,7 +57,7 @@ main() { engine=$(getContainerEngine) if [[ -z "$engine" ]] ; then - error "You must install Docker first" + error "You must install Podman or Docker first" exit 1 fi diff --git a/bop.go b/bop.go index 3820681..8ee5ea7 100644 --- a/bop.go +++ b/bop.go @@ -5,7 +5,7 @@ package main // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/cli/cli.go b/cli/cli.go index 9f34247..e4ac080 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -2,7 +2,7 @@ package cli // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -19,6 +19,7 @@ import ( "github.com/essentialkaos/ek/v12/fsutil" "github.com/essentialkaos/ek/v12/options" "github.com/essentialkaos/ek/v12/pluralize" + "github.com/essentialkaos/ek/v12/terminal/tty" "github.com/essentialkaos/ek/v12/timeutil" "github.com/essentialkaos/ek/v12/usage" "github.com/essentialkaos/ek/v12/usage/completion/bash" @@ -38,7 +39,7 @@ import ( // App info const ( APP = "bop" - VER = "1.2.2" + VER = "1.3.0" DESC = "Utility for generating formal bibop tests for RPM packages" ) @@ -71,6 +72,8 @@ var optMap = options.Map{ OPT_GENERATE_MAN: {Type: options.BOOL}, } +var colorTagApp, colorTagVer string + // ////////////////////////////////////////////////////////////////////////////////// // // Run is main utility function @@ -113,25 +116,17 @@ func Run(gitRev string, gomod []byte) { // preConfigureUI preconfigures UI based on information about user terminal func preConfigureUI() { - term := os.Getenv("TERM") - - fmtc.DisableColors = true - - if term != "" { - switch { - case strings.Contains(term, "xterm"), - strings.Contains(term, "color"), - term == "screen": - fmtc.DisableColors = false - } - } - - if !fsutil.IsCharacterDevice("/dev/stdout") && os.Getenv("FAKETTY") == "" { + if !tty.IsTTY() { fmtc.DisableColors = true } - if os.Getenv("NO_COLOR") != "" { - fmtc.DisableColors = true + switch { + case fmtc.IsTrueColorSupported(): + colorTagApp, colorTagVer = "{*}{#9966CC}", "{#9966CC}" + case fmtc.Is256ColorsSupported(): + colorTagApp, colorTagVer = "{*}{#140}", "{#140}" + default: + colorTagApp, colorTagVer = "{*}{m}", "{m}" } } @@ -229,7 +224,7 @@ func printWarn(f string, a ...interface{}) { fmtc.Fprintf(os.Stderr, "{y}"+f+"{!}\n", a...) } -// printErrorAndExit print error mesage and exit with exit code 1 +// printErrorAndExit print error message and exit with exit code 1 func printErrorAndExit(f string, a ...interface{}) { printError(f, a...) os.Exit(1) @@ -269,8 +264,10 @@ func printMan() { func genUsage() *usage.Info { info := usage.NewInfo("", "name", "package…") + info.AppNameColorTag = colorTagApp + info.AddOption(OPT_OUTPUT, "Output file", "file") - info.AddOption(OPT_SERVICE, "List of services for checking {c}(mergable){!}", "service") + info.AddOption(OPT_SERVICE, "List of services for checking {c}(mergeable){!}", "service") info.AddOption(OPT_NO_COLOR, "Disable colors in output") info.AddOption(OPT_HELP, "Show this help message") info.AddOption(OPT_VER, "Show version") @@ -285,11 +282,16 @@ func genUsage() *usage.Info { // genAbout generates info about version func genAbout(gitRev string) *usage.About { about := &usage.About{ - App: APP, - Version: VER, - Desc: DESC, - Year: 2006, - Owner: "ESSENTIAL KAOS", + App: APP, + Version: VER, + Desc: DESC, + Year: 2006, + Owner: "ESSENTIAL KAOS", + + AppNameColorTag: colorTagApp, + VersionColorTag: colorTagVer, + DescSeparator: "{s}—{!}", + License: "Apache License, Version 2.0 ", UpdateChecker: usage.UpdateChecker{"essentialkaos/bop", update.GitHubChecker}, } diff --git a/cli/support/support.go b/cli/support/support.go index 9374af0..83982d1 100644 --- a/cli/support/support.go +++ b/cli/support/support.go @@ -2,7 +2,7 @@ package support // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -11,6 +11,7 @@ import ( "fmt" "os" "runtime" + "runtime/debug" "strings" "github.com/essentialkaos/ek/v12/fmtc" @@ -107,6 +108,23 @@ func showDepsInfo(gomod []byte) { } } +// extractGitRevFromBuildInfo extracts git SHA from embedded build info +func extractGitRevFromBuildInfo() string { + info, ok := debug.ReadBuildInfo() + + if !ok { + return "" + } + + for _, s := range info.Settings { + if s.Key == "vcs.revision" && len(s.Value) > 7 { + return s.Value[:7] + } + } + + return "" +} + // getHashColorBullet return bullet with color from hash func getHashColorBullet(v string) string { if len(v) > 6 { @@ -118,7 +136,7 @@ func getHashColorBullet(v string) string { // printInfo formats and prints info record func printInfo(size int, name, value string) { - name = name + ":" + name += ":" size++ if value == "" { diff --git a/cli/support/support_linux.go b/cli/support/support_linux.go index eb1354c..bb893d7 100644 --- a/cli/support/support_linux.go +++ b/cli/support/support_linux.go @@ -2,7 +2,7 @@ package support // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/data/data.go b/data/data.go index 9fd99ad..32e6f0a 100644 --- a/data/data.go +++ b/data/data.go @@ -2,7 +2,7 @@ package data // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -15,17 +15,18 @@ import ( // Info contains info about all packages type Info struct { - Dist string - Pkgs []string - Apps []string - Configs []*rpm.Object - SharedLibs []string - StaticLibs []*rpm.Object - Headers []string - PkgConfigs []string - Users UserMap - Groups GroupMap - Services []string + Dist string + Pkgs []string + Apps []string + Configs []*rpm.Object + SharedLibs []string + StaticLibs []*rpm.Object + Headers []string + PkgConfigs []string + Completions []string + Users UserMap + Groups GroupMap + Services []string Python2Dirs []*rpm.Object Python2Files []*rpm.Object diff --git a/extractor/extractor.go b/extractor/extractor.go index d642fac..4674bfc 100644 --- a/extractor/extractor.go +++ b/extractor/extractor.go @@ -2,7 +2,7 @@ package extractor // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -111,6 +111,7 @@ func addPackageInfo(info *data.Info, pkg *rpm.Package) { addAppsInfo(info, pkg) addConfigsInfo(info, pkg) + addCompletions(info, pkg) addLibsInfo(info, pkg) addHeadersInfo(info, pkg) addPkgConfigsInfo(info, pkg) @@ -166,6 +167,20 @@ func addConfigsInfo(info *data.Info, pkg *rpm.Package) { } } +// addCompletions extracts info about shell completions +func addCompletions(info *data.Info, pkg *rpm.Package) { + for _, obj := range pkg.Payload { + if strutil.HasPrefixAny( + obj.Path, + "/usr/share/bash-completion/completions", + "/usr/share/fish/vendor_completions.d", + "/usr/share/zsh/site-functions", + ) { + info.Completions = append(info.Completions, obj.Path) + } + } +} + // addPkgConfigsInfo extracts info about package configuration files // from package info func addPkgConfigsInfo(info *data.Info, pkg *rpm.Package) { @@ -317,7 +332,7 @@ func isPackagesWithMixedDist(pkgs []*rpm.Package) bool { return false } -// formatLibName fromats lib name to glob +// formatLibName formats lib name to glob func formatLibName(file string) string { basename := path.Base(file) soIndex := strings.Index(basename, ".so.") @@ -408,7 +423,7 @@ func parseUserAddCommand(command string) *data.User { var isComment bool for i := 0; i < 20; i++ { - option := strutil.ReadField(command, i, true, " ") + option := strutil.ReadField(command, i, true, ' ') if strings.Contains(option, "\"") || strings.Contains(option, "'") { isComment = !isComment @@ -430,19 +445,19 @@ func parseUserAddCommand(command string) *data.User { i++ continue // ignore option and value case "-d", "--home-dir": - result.Home = strutil.ReadField(command, i+1, true, " ") + result.Home = strutil.ReadField(command, i+1, true, ' ') i++ case "-g", "--gid": - result.GID = strutil.ReadField(command, i+1, true, " ") + result.GID = strutil.ReadField(command, i+1, true, ' ') i++ case "-u", "--uid": - result.UID = strutil.ReadField(command, i+1, true, " ") + result.UID = strutil.ReadField(command, i+1, true, ' ') i++ case "-s", "--shell": - result.Shell = strutil.ReadField(command, i+1, true, " ") + result.Shell = strutil.ReadField(command, i+1, true, ' ') i++ case "-G", "--groups": - result.Group = strutil.ReadField(command, i+1, true, " ") + result.Group = strutil.ReadField(command, i+1, true, ' ') i++ default: result.Name = option @@ -465,7 +480,7 @@ func parseGroupAddCommand(command string) *data.Group { command = command[gi+9:] for i := 0; i < 10; i++ { - option := strutil.ReadField(command, i, true, " ") + option := strutil.ReadField(command, i, true, ' ') switch option { case "-f", "--force", "-o", "-non-unique", "-r", "--system": @@ -474,7 +489,7 @@ func parseGroupAddCommand(command string) *data.Group { i++ continue // ignore option and value case "-g", "--gid": - result.GID = strutil.ReadField(command, i+1, true, " ") + result.GID = strutil.ReadField(command, i+1, true, ' ') i++ default: result.Name = option diff --git a/generator/generator.go b/generator/generator.go index a977cb3..885db2f 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -2,7 +2,7 @@ package generator // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -293,7 +293,7 @@ func genPythonWheelsCheck(info *data.Info) string { for _, wheel := range info.PythonWheels { data += fmt.Sprintf(" exist %s\n", wheel.Path) - data += fmt.Sprintf(" perms %s %o\n\n", wheel.Path, wheel.Mode) + data += fmt.Sprintf(" mode %s %o\n\n", wheel.Path, wheel.Mode) } return data @@ -301,7 +301,7 @@ func genPythonWheelsCheck(info *data.Info) string { // genBasicEnvCheck generates env checks for very simple package func genBasicEnvCheck(info *data.Info) string { - if len(info.Apps) == 0 && len(info.Services) == 0 && len(info.Configs) == 0 { + if len(info.Apps)+len(info.Completions)+len(info.Services)+len(info.Configs) == 0 { return "" } @@ -332,6 +332,15 @@ func genBasicEnvCheck(info *data.Info) string { data += "\n" } + if len(info.Completions) > 0 { + for _, compl := range info.Completions { + data += fmt.Sprintf(" exist %s\n", compl) + data += fmt.Sprintf(" mode %s 644\n", compl) + } + + data += "\n" + } + return data } diff --git a/go.mod b/go.mod index 0174c97..16bb447 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/essentialkaos/bop go 1.18 require ( - github.com/essentialkaos/depsy v1.0.0 - github.com/essentialkaos/ek/v12 v12.63.0 + github.com/essentialkaos/depsy v1.1.0 + github.com/essentialkaos/ek/v12 v12.100.0 ) -require golang.org/x/sys v0.6.0 // indirect +require golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index a0cb15a..550426d 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk= -github.com/essentialkaos/depsy v1.0.0 h1:FikBtTnNhk+xFO/hFr+CfiKs6QnA3wMD6tGL0XTEUkc= -github.com/essentialkaos/depsy v1.0.0/go.mod h1:XVsB2eVUonEzmLKQP3ig2P6v2+WcHVgJ10zm0JLqFMM= -github.com/essentialkaos/ek/v12 v12.63.0 h1:9yaEu5W3bx//9y52ShqYCoFDKOcwEdrnvgSkUYyatgI= -github.com/essentialkaos/ek/v12 v12.63.0/go.mod h1:9MlSuHpewu7OZ9tM9dLFHvoA8dflBIUPCA0Ctt97wRs= +github.com/essentialkaos/depsy v1.1.0 h1:U6dp687UkQwXlZU17Hg2KMxbp3nfZAoZ8duaeUFYvJI= +github.com/essentialkaos/depsy v1.1.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8= +github.com/essentialkaos/ek/v12 v12.100.0 h1:bup8cqsUUXJtKHAgdt2eHjYKBFU/0rPbg/us2H1E46I= +github.com/essentialkaos/ek/v12 v12.100.0/go.mod h1:VjMWDJ1r4HsfBYJuCNDUo4R1lhCgYkFZOMOH0S3W6iM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/rpm/rpm.go b/rpm/rpm.go index f92119a..ca6e4fb 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -2,7 +2,7 @@ package rpm // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2023 ESSENTIAL KAOS // +// Copyright (c) 2024 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -145,9 +145,9 @@ func extractPackageInfo(file string) (string, string, bool, error) { return "", "", false, err } - name := strutil.ReadField(data, 0, false, " ") - dist := extractDist(strutil.ReadField(data, 1, false, " ")) - isSrc := strutil.ReadField(data, 2, false, " ") == "1" + name := strutil.ReadField(data, 0, false, ' ') + dist := extractDist(strutil.ReadField(data, 1, false, ' ')) + isSrc := strutil.ReadField(data, 2, false, ' ') == "1" return name, dist, isSrc, nil } @@ -168,17 +168,17 @@ func parseDumpData(data string) ([]*Object, error) { // parsePayloadInfo parses payload object info func parsePayloadInfo(data string) *Object { - modeStr := strutil.Tail(strutil.ReadField(data, 4, false, " "), 4) + modeStr := strutil.Tail(strutil.ReadField(data, 4, false, ' '), 4) modeUint, _ := strconv.ParseUint(modeStr, 8, 32) - emptyHash := strings.Trim(strutil.ReadField(data, 3, false, " "), "0") == "" - link := strutil.ReadField(data, 10, false, " ") + emptyHash := strings.Trim(strutil.ReadField(data, 3, false, ' '), "0") == "" + link := strutil.ReadField(data, 10, false, ' ') return &Object{ - Path: strutil.ReadField(data, 0, false, " "), - User: strutil.ReadField(data, 5, false, " "), - Group: strutil.ReadField(data, 6, false, " "), + Path: strutil.ReadField(data, 0, false, ' '), + User: strutil.ReadField(data, 5, false, ' '), + Group: strutil.ReadField(data, 6, false, ' '), Mode: os.FileMode(modeUint), - IsConfig: strutil.ReadField(data, 7, false, " ") == "1", + IsConfig: strutil.ReadField(data, 7, false, ' ') == "1", IsDir: link == "X" && emptyHash, IsLink: link != "X" && emptyHash, }