pax_global_header00006660000000000000000000000064143253256460014524gustar00rootroot0000000000000052 comment=718a4caea996e6cce55d8291480aa7e49815c4a8 golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/000077500000000000000000000000001432532564600233375ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/.github/000077500000000000000000000000001432532564600246775ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/.github/workflows/000077500000000000000000000000001432532564600267345ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/.github/workflows/ci.yml000066400000000000000000000031621432532564600300540ustar00rootroot00000000000000name: Continuous Integration env: GO_VERSION: 1.18 on: push: branches: - master pull_request: jobs: unit-tests: strategy: fail-fast: false matrix: os: - ubuntu-latest - windows-latest include: - os: ubuntu-latest cache_path: ~/.cache/go-build - os: windows-latest cache_path: ~\AppData\Local\go-build name: ci - ${{matrix.os}} runs-on: ${{matrix.os}} steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v1 with: go-version: 1.18.x - name: Cache build uses: actions/cache@v3 with: path: | ${{matrix.cache_path}} ~/go/pkg/mod key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test restore-keys: | ${{runner.os}}-go- - name: Test code run: | go test ./... lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v1 with: go-version: 1.18.x - name: Cache build uses: actions/cache@v1 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test restore-keys: | ${{runner.os}}-go- - name: Lint uses: golangci/golangci-lint-action@v3.1.0 with: version: latest - name: errors run: golangci-lint run if: ${{ failure() }} golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/.gitignore000066400000000000000000000004151432532564600253270ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/.golangci.yml000066400000000000000000000010071432532564600257210ustar00rootroot00000000000000linters: disable: - structcheck # gives false positives enable: - gofumpt - thelper - goimports - tparallel - wastedassign - exportloopref - unparam - prealloc - unconvert - exhaustive - makezero - nakedret # - goconst # TODO: enable and fix issues fast: false linters-settings: exhaustive: default-signifies-exhaustive: true nakedret: # the gods will judge me but I just don't like naked returns at all max-func-lines: 0 run: go: 1.18 golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/LICENSE000066400000000000000000000020571432532564600243500ustar00rootroot00000000000000MIT License Copyright (c) 2022 Jesse Duffield Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/README.md000066400000000000000000000000751432532564600246200ustar00rootroot00000000000000# lazycore Shared functionality for lazygit, lazydocker, etc golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/go.mod000066400000000000000000000005251432532564600244470ustar00rootroot00000000000000module github.com/jesseduffield/lazycore go 1.18 require ( github.com/samber/lo v1.31.0 github.com/stretchr/testify v1.8.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20220317015231-48e79f11773a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/go.sum000066400000000000000000000036651432532564600245040ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM= github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= golang.org/x/exp v0.0.0-20220317015231-48e79f11773a h1:DAzrdbxsb5tXNOhMCSwF7ZdfMbW46hE9fSVO6BsmUZM= golang.org/x/exp v0.0.0-20220317015231-48e79f11773a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/000077500000000000000000000000001432532564600241205ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/boxlayout/000077500000000000000000000000001432532564600261465ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/boxlayout/boxlayout.go000066400000000000000000000135741432532564600305350ustar00rootroot00000000000000package boxlayout import ( "github.com/jesseduffield/lazycore/pkg/utils" "github.com/samber/lo" ) type Dimensions struct { X0 int X1 int Y0 int Y1 int } type Direction int const ( ROW Direction = iota COLUMN ) // to give a high-level explanation of what's going on here. We layout our windows by arranging a bunch of boxes in the available space. // If a box has children, it needs to specify how it wants to arrange those children: ROW or COLUMN. // If a box represents a window, you can put the window name in the Window field. // When determining how to divvy-up the available height (for row children) or width (for column children), we first // give the boxes with a static `size` the space that they want. Then we apportion // the remaining space based on the weights of the dynamic boxes (you can't define // both size and weight at the same time: you gotta pick one). If there are two // boxes, one with weight 1 and the other with weight 2, the first one gets 33% // of the available space and the second one gets the remaining 66% type Box struct { // Direction decides how the children boxes are laid out. ROW means the children will each form a row i.e. that they will be stacked on top of eachother. Direction Direction // function which takes the width and height assigned to the box and decides which orientation it will have ConditionalDirection func(width int, height int) Direction Children []*Box // function which takes the width and height assigned to the box and decides the layout of the children. ConditionalChildren func(width int, height int) []*Box // Window refers to the name of the window this box represents, if there is one Window string // static Size. If parent box's direction is ROW this refers to height, otherwise width Size int // dynamic size. Once all statically sized children have been considered, Weight decides how much of the remaining space will be taken up by the box // TODO: consider making there be one int and a type enum so we can't have size and Weight simultaneously defined Weight int } func ArrangeWindows(root *Box, x0, y0, width, height int) map[string]Dimensions { children := root.getChildren(width, height) if len(children) == 0 { // leaf node if root.Window != "" { dimensionsForWindow := Dimensions{X0: x0, Y0: y0, X1: x0 + width - 1, Y1: y0 + height - 1} return map[string]Dimensions{root.Window: dimensionsForWindow} } return map[string]Dimensions{} } direction := root.getDirection(width, height) var availableSize int if direction == COLUMN { availableSize = width } else { availableSize = height } sizes := calcSizes(children, availableSize) result := map[string]Dimensions{} offset := 0 for i, child := range children { boxSize := sizes[i] var resultForChild map[string]Dimensions if direction == COLUMN { resultForChild = ArrangeWindows(child, x0+offset, y0, boxSize, height) } else { resultForChild = ArrangeWindows(child, x0, y0+offset, width, boxSize) } result = mergeDimensionMaps(result, resultForChild) offset += boxSize } return result } func calcSizes(boxes []*Box, availableSpace int) []int { normalizedWeights := normalizeWeights(lo.Map(boxes, func(box *Box, _ int) int { return box.Weight })) totalWeight := 0 reservedSpace := 0 for i, box := range boxes { if box.isStatic() { reservedSpace += box.Size } else { totalWeight += normalizedWeights[i] } } dynamicSpace := utils.Max(0, availableSpace-reservedSpace) unitSize := 0 extraSpace := 0 if totalWeight > 0 { unitSize = dynamicSpace / totalWeight extraSpace = dynamicSpace % totalWeight } result := make([]int, len(boxes)) for i, box := range boxes { if box.isStatic() { // assuming that only one static child can have a size greater than the // available space. In that case we just crop the size to what's available result[i] = utils.Min(availableSpace, box.Size) } else { result[i] = unitSize * normalizedWeights[i] } } // distribute the remainder across dynamic boxes. for extraSpace > 0 { for i, weight := range normalizedWeights { if weight > 0 { result[i]++ extraSpace-- normalizedWeights[i]-- if extraSpace == 0 { break } } } } return result } // removes common multiple from weights e.g. if we get 2, 4, 4 we return 1, 2, 2. func normalizeWeights(weights []int) []int { if len(weights) == 0 { return []int{} } // to spare us some computation we'll exit early if any of our weights is 1 if lo.SomeBy(weights, func(weight int) bool { return weight == 1 }) { return weights } // map weights to factorSlices and find the lowest common factor positiveWeights := lo.Filter(weights, func(weight int, _ int) bool { return weight > 0 }) factorSlices := lo.Map(positiveWeights, func(weight int, _ int) []int { return calcFactors(weight) }) commonFactors := factorSlices[0] for _, factors := range factorSlices { commonFactors = lo.Intersect(commonFactors, factors) } if len(commonFactors) == 0 { return weights } newWeights := lo.Map(weights, func(weight int, _ int) int { return weight / commonFactors[0] }) return normalizeWeights(newWeights) } func calcFactors(n int) []int { factors := []int{} for i := 2; i <= n; i++ { if n%i == 0 { factors = append(factors, i) } } return factors } func (b *Box) isStatic() bool { return b.Size > 0 } func (b *Box) getDirection(width int, height int) Direction { if b.ConditionalDirection != nil { return b.ConditionalDirection(width, height) } return b.Direction } func (b *Box) getChildren(width int, height int) []*Box { if b.ConditionalChildren != nil { return b.ConditionalChildren(width, height) } return b.Children } func mergeDimensionMaps(a map[string]Dimensions, b map[string]Dimensions) map[string]Dimensions { result := map[string]Dimensions{} for _, dimensionMap := range []map[string]Dimensions{a, b} { for k, v := range dimensionMap { result[k] = v } } return result } golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/boxlayout/boxlayout_test.go000066400000000000000000000234141432532564600315660ustar00rootroot00000000000000package boxlayout import ( "testing" "github.com/stretchr/testify/assert" ) func TestArrangeWindows(t *testing.T) { type scenario struct { testName string root *Box x0 int y0 int width int height int test func(result map[string]Dimensions) } scenarios := []scenario{ { testName: "Empty box", root: &Box{}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues(t, result, map[string]Dimensions{}) }, }, { testName: "Box with static and dynamic panel", root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic"}}}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic": {X0: 0, X1: 9, Y0: 1, Y1: 9}, "static": {X0: 0, X1: 9, Y0: 0, Y1: 0}, }, ) }, }, { testName: "Box with static and two dynamic panels", root: &Box{Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "static": {X0: 0, X1: 9, Y0: 0, Y1: 0}, "dynamic1": {X0: 0, X1: 9, Y0: 1, Y1: 3}, "dynamic2": {X0: 0, X1: 9, Y0: 4, Y1: 9}, }, ) }, }, { testName: "Box with COLUMN direction", root: &Box{Direction: COLUMN, Children: []*Box{{Size: 1, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "static": {X0: 0, X1: 0, Y0: 0, Y1: 9}, "dynamic1": {X0: 1, X1: 3, Y0: 0, Y1: 9}, "dynamic2": {X0: 4, X1: 9, Y0: 0, Y1: 9}, }, ) }, }, { testName: "Box with COLUMN direction only on wide boxes with narrow box", root: &Box{ConditionalDirection: func(width int, height int) Direction { if width > 4 { return COLUMN } else { return ROW } }, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}}, x0: 0, y0: 0, width: 4, height: 4, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 1}, "dynamic2": {X0: 0, X1: 3, Y0: 2, Y1: 3}, }, ) }, }, { testName: "Box with COLUMN direction only on wide boxes with wide box", root: &Box{ConditionalDirection: func(width int, height int) Direction { if width > 4 { return COLUMN } else { return ROW } }, Children: []*Box{{Weight: 1, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}}}, // 5 / 2 = 2 remainder 1. That remainder goes to the first box. x0: 0, y0: 0, width: 5, height: 5, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 2, Y0: 0, Y1: 4}, "dynamic2": {X0: 3, X1: 4, Y0: 0, Y1: 4}, }, ) }, }, { testName: "Box with conditional children where box is wide", root: &Box{ConditionalChildren: func(width int, height int) []*Box { if width > 4 { return []*Box{{Window: "wide", Weight: 1}} } else { return []*Box{{Window: "narrow", Weight: 1}} } }}, x0: 0, y0: 0, width: 5, height: 5, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "wide": {X0: 0, X1: 4, Y0: 0, Y1: 4}, }, ) }, }, { testName: "Box with conditional children where box is narrow", root: &Box{ConditionalChildren: func(width int, height int) []*Box { if width > 4 { return []*Box{{Window: "wide", Weight: 1}} } else { return []*Box{{Window: "narrow", Weight: 1}} } }}, x0: 0, y0: 0, width: 4, height: 4, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "narrow": {X0: 0, X1: 3, Y0: 0, Y1: 3}, }, ) }, }, { testName: "Box with static child with size too large", root: &Box{Direction: COLUMN, Children: []*Box{{Size: 11, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "static": {X0: 0, X1: 9, Y0: 0, Y1: 9}, // not sure if X0: 10, X1: 9 makes any sense, but testing this in the // actual GUI it seems harmless "dynamic1": {X0: 10, X1: 9, Y0: 0, Y1: 9}, "dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9}, }, ) }, }, { // 10 total space minus 2 from the status box leaves us with 8. // Total weight is 3, 8 / 3 = 2 with 2 remainder. // We want to end up with 2, 3, 5 (one unit from remainder to each dynamic box) testName: "Distributing remainder across weighted boxes", root: &Box{Direction: COLUMN, Children: []*Box{{Size: 2, Window: "static"}, {Weight: 1, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}}}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "static": {X0: 0, X1: 1, Y0: 0, Y1: 9}, // 2 "dynamic1": {X0: 2, X1: 4, Y0: 0, Y1: 9}, // 3 "dynamic2": {X0: 5, X1: 9, Y0: 0, Y1: 9}, // 5 }, ) }, }, { // 9 total space. // total weight is 5, 9 / 5 = 1 with 4 remainder // we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last. // Reason being that we just give units to each box evenly and consider weight in subsequent passes. testName: "Distributing remainder across weighted boxes 2", root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 2, Window: "dynamic1"}, {Weight: 2, Window: "dynamic2"}, {Weight: 1, Window: "dynamic3"}}}, x0: 0, y0: 0, width: 9, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4 "dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3 "dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2 }, ) }, }, { // 9 total space. // total weight is 5, 9 / 5 = 1 with 4 remainder // we want to give 2 of that remainder to the first, 1 to the second, and 1 to the last. // Reason being that we just give units to each box evenly and consider weight in subsequent passes. testName: "Distributing remainder across weighted boxes with unnormalized weights", root: &Box{Direction: COLUMN, Children: []*Box{{Weight: 4, Window: "dynamic1"}, {Weight: 4, Window: "dynamic2"}, {Weight: 2, Window: "dynamic3"}}}, x0: 0, y0: 0, width: 9, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 3, Y0: 0, Y1: 9}, // 4 "dynamic2": {X0: 4, X1: 6, Y0: 0, Y1: 9}, // 3 "dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2 }, ) }, }, { testName: "Another distribution test", root: &Box{Direction: COLUMN, Children: []*Box{ {Weight: 3, Window: "dynamic1"}, {Weight: 1, Window: "dynamic2"}, {Weight: 1, Window: "dynamic3"}, }}, x0: 0, y0: 0, width: 9, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 4, Y0: 0, Y1: 9}, // 5 "dynamic2": {X0: 5, X1: 6, Y0: 0, Y1: 9}, // 2 "dynamic3": {X0: 7, X1: 8, Y0: 0, Y1: 9}, // 2 }, ) }, }, { testName: "Box with zero weight", root: &Box{Direction: COLUMN, Children: []*Box{ {Weight: 1, Window: "dynamic1"}, {Weight: 0, Window: "dynamic2"}, }}, x0: 0, y0: 0, width: 10, height: 10, test: func(result map[string]Dimensions) { assert.EqualValues( t, result, map[string]Dimensions{ "dynamic1": {X0: 0, X1: 9, Y0: 0, Y1: 9}, "dynamic2": {X0: 10, X1: 9, Y0: 0, Y1: 9}, // when X0 > X1, we will hide the window }, ) }, }, } for _, s := range scenarios { s := s t.Run(s.testName, func(t *testing.T) { s.test(ArrangeWindows(s.root, s.x0, s.y0, s.width, s.height)) }) } } func TestNormalizeWeights(t *testing.T) { scenarios := []struct { testName string input []int expected []int }{ { testName: "empty", input: []int{}, expected: []int{}, }, { testName: "one item of value 1", input: []int{1}, expected: []int{1}, }, { testName: "one item of value greater than 1", input: []int{2}, expected: []int{1}, }, { testName: "slice contains 1", input: []int{2, 1}, expected: []int{2, 1}, }, { testName: "slice contains 2 and 2", input: []int{2, 2}, expected: []int{1, 1}, }, { testName: "no common multiple", input: []int{2, 3}, expected: []int{2, 3}, }, { testName: "complex case", input: []int{10, 10, 20}, expected: []int{1, 1, 2}, }, { testName: "when a zero weight is included it is ignored", input: []int{10, 10, 20, 0}, expected: []int{1, 1, 2, 0}, }, } for _, s := range scenarios { s := s t.Run(s.testName, func(t *testing.T) { assert.EqualValues(t, s.expected, normalizeWeights(s.input)) }) } } golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/utils/000077500000000000000000000000001432532564600252605ustar00rootroot00000000000000golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/utils/once_writer.go000066400000000000000000000007751432532564600301400ustar00rootroot00000000000000package utils import ( "io" "sync" ) // This wraps a writer and ensures that before we actually write anything we call a given function first type OnceWriter struct { writer io.Writer once sync.Once f func() } var _ io.Writer = &OnceWriter{} func NewOnceWriter(writer io.Writer, f func()) *OnceWriter { return &OnceWriter{ writer: writer, f: f, } } func (self *OnceWriter) Write(p []byte) (n int, err error) { self.once.Do(func() { self.f() }) return self.writer.Write(p) } golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/utils/once_writer_test.go000066400000000000000000000005601432532564600311670ustar00rootroot00000000000000package utils import ( "bytes" "testing" ) func TestOnceWriter(t *testing.T) { innerWriter := bytes.NewBuffer(nil) counter := 0 onceWriter := NewOnceWriter(innerWriter, func() { counter += 1 }) _, _ = onceWriter.Write([]byte("hello")) _, _ = onceWriter.Write([]byte("hello")) if counter != 1 { t.Errorf("expected counter to be 1, got %d", counter) } } golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/utils/utils.go000066400000000000000000000017541432532564600267560ustar00rootroot00000000000000package utils import ( "log" "os" "path/filepath" ) // Min returns the minimum of two integers func Min(x, y int) int { if x < y { return x } return y } // Max returns the maximum of two integers func Max(x, y int) int { if x > y { return x } return y } // Clamp returns a value x restricted between min and max func Clamp(x int, min int, max int) int { if x < min { return min } else if x > max { return max } return x } // GetLazyRootDirectory finds a lazy project root directory. // // It's used for cheatsheet scripts and integration tests. Not to be confused with finding the // root directory of _any_ random repo. func GetLazyRootDirectory() string { path, err := os.Getwd() if err != nil { panic(err) } for { _, err := os.Stat(filepath.Join(path, ".git")) if err == nil { return path } if !os.IsNotExist(err) { panic(err) } path = filepath.Dir(path) if path == "/" { log.Fatal("must run in lazy project folder or child folder") } } } golang-github-jesseduffield-lazycore-0.0~git20221023.718a4ca/pkg/utils/utils_test.go000066400000000000000000000016251432532564600300120ustar00rootroot00000000000000package utils import ( "testing" "github.com/stretchr/testify/assert" ) // TestMin is a function. func TestMin(t *testing.T) { type scenario struct { a int b int expected int } scenarios := []scenario{ { 1, 1, 1, }, { 1, 2, 1, }, { 2, 1, 1, }, } for _, s := range scenarios { assert.EqualValues(t, s.expected, Min(s.a, s.b)) } } func TestClamp(t *testing.T) { tests := []struct { name string x int min int max int want int }{ { "successX", 5, 1, 10, 5, }, { "successMin", -5, 1, 10, 1, }, { "successMax", 15, 1, 10, 10, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.want, Clamp(tt.x, tt.min, tt.max)) }) } } func TestGetLazyRootDirectory(t *testing.T) { assert.NotPanics(t, func() { GetLazyRootDirectory() }) }