├── .github ├── FUNDING.yml └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── go-cli.txtar ├── go.mod ├── go.sum └── springerle_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: carlmjohnson 4 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Set up Go 1.x 10 | uses: actions/setup-go@v2 11 | with: 12 | go-version: ^1.14 13 | id: go 14 | 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v2 17 | 18 | - name: Get dependencies 19 | run: go mod download 20 | 21 | - name: Test 22 | run: | 23 | # Setup a git user, so test of finalize.sh will pass 24 | git config --global user.name "Jane Doe" 25 | git config --global user.email "user@example.com" 26 | go test -v ./... 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | context.json 2 | finalize.sh 3 | project_x/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Carl Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-CLI: A txtar template for creating a Go CLI application 2 | 3 | See https://blog.carlmjohnson.net/post/2020/go-cli-how-to-and-advice/ for background on this project. 4 | 5 | To use this template to create a Go CLI: 6 | 7 | 1. Install [Go](https://golang.org) and [springerle](https://github.com/carlmjohnson/springerle) 8 | 2. Run `springerle https://github.com/carlmjohnson/go-cli/raw/master/go-cli.txtar` and answer prompts for information. 9 | 3. Verify the contents of the files written to disk, then run `./finalize.sh` to download Go dependencies and initialize the git repo. 10 | -------------------------------------------------------------------------------- /go-cli.txtar: -------------------------------------------------------------------------------- 1 | author: Author full name? John Doe 2 | proj_full: Project full name? Project X 3 | proj_short: Project short name? {{xstringstosnakecase .proj_full}} 4 | repo: Repo URL? github.com/{{ xstringstosnakecase .author }}/{{.proj_short}} 5 | pkg: Application package name? app 6 | gversion: Go version? 1.17 7 | description: Project description? 8 | -- finalize.sh -- 9 | #!/bin/bash 10 | 11 | set -eu -o pipefail 12 | 13 | # Get the directory that this script file is in 14 | THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 15 | 16 | cd "$THIS_DIR/{{.proj_short}}" 17 | go mod tidy 18 | go mod download 19 | go fmt . 20 | go test ./... 21 | git init 22 | git add . 23 | git commit -m 'Init commit' 24 | cd "$THIS_DIR" 25 | rm ./finalize.sh 26 | 27 | -- {{.proj_short}}/.github/workflows/go.yml -- 28 | name: Go 29 | 30 | on: [ push, pull_request ] 31 | jobs: 32 | 33 | build: 34 | name: Build 35 | runs-on: ubuntu-latest 36 | steps: 37 | 38 | - name: Set up Go 1.x 39 | uses: actions/setup-go@v2 40 | with: 41 | go-version: ^{{.gversion}} 42 | id: go 43 | 44 | - name: Check out code into the Go module directory 45 | uses: actions/checkout@v2 46 | 47 | - name: Get dependencies 48 | run: go mod download 49 | 50 | - name: Test 51 | run: go test -v ./... 52 | -- {{.proj_short}}/.gitignore -- 53 | -- {{.proj_short}}/LICENSE -- 54 | MIT License 55 | 56 | Copyright (c) {{ timenow.Format "2006" }} {{ .author }} 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy 59 | of this software and associated documentation files (the "Software"), to deal 60 | in the Software without restriction, including without limitation the rights 61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | copies of the Software, and to permit persons to whom the Software is 63 | furnished to do so, subject to the following conditions: 64 | 65 | The above copyright notice and this permission notice shall be included in all 66 | copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 69 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 70 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 71 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 72 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 73 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 74 | SOFTWARE. 75 | -- {{.proj_short}}/README.md -- 76 | # {{ .proj_full }} [![GoDoc](https://godoc.org/{{.repo}}?status.svg)](https://godoc.org/{{.repo}}) [![Go Report Card](https://goreportcard.com/badge/{{.repo}})](https://goreportcard.com/report/{{.repo}}) 77 | 78 | {{ .description }} 79 | 80 | ## Installation 81 | 82 | First install [Go](http://golang.org). 83 | 84 | If you just want to install the binary to your current directory and don't care about the source code, run 85 | 86 | ```bash 87 | GOBIN="$(pwd)" go install {{.repo}}@latest 88 | ``` 89 | 90 | ## Screenshots 91 | 92 | ``` 93 | ``` 94 | -- {{.proj_short}}/{{.pkg}}/{{.pkg}}.go -- 95 | package {{.pkg}} 96 | 97 | import ( 98 | "flag" 99 | "fmt" 100 | "io" 101 | "log" 102 | "os" 103 | 104 | "github.com/carlmjohnson/flagx" 105 | "github.com/carlmjohnson/flagx/lazyio" 106 | "github.com/carlmjohnson/versioninfo" 107 | ) 108 | 109 | const AppName = "{{.proj_full}}" 110 | 111 | func CLI(args []string) error { 112 | var app appEnv 113 | err := app.ParseArgs(args) 114 | if err != nil { 115 | return err 116 | } 117 | if err = app.Exec(); err != nil { 118 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 119 | } 120 | return err 121 | } 122 | 123 | func (app *appEnv) ParseArgs(args []string) error { 124 | fl := flag.NewFlagSet(AppName, flag.ContinueOnError) 125 | src := lazyio.FileOrURL(lazyio.StdIO, nil) 126 | app.src = src 127 | fl.Var(src, "src", "source file or URL") 128 | app.Logger = log.New(io.Discard, AppName+" ", log.LstdFlags) 129 | flagx.BoolFunc(fl, "verbose", "log debug output", func() error { 130 | app.Logger.SetOutput(os.Stderr) 131 | return nil 132 | }) 133 | fl.Usage = func() { 134 | fmt.Fprintf(fl.Output(), `{{.proj_short}} - %s 135 | 136 | {{.description}} 137 | 138 | Usage: 139 | 140 | {{.proj_short}} [options] 141 | 142 | Options: 143 | `, versioninfo.Version) 144 | fl.PrintDefaults() 145 | } 146 | if err := fl.Parse(args); err != nil { 147 | return err 148 | } 149 | if err := flagx.ParseEnv(fl, AppName); err != nil { 150 | return err 151 | } 152 | return nil 153 | } 154 | 155 | type appEnv struct { 156 | src io.ReadCloser 157 | *log.Logger 158 | } 159 | 160 | func (app *appEnv) Exec() (err error) { 161 | app.Println("starting") 162 | defer func() { app.Println("done") }() 163 | 164 | n, err := io.Copy(os.Stdout, app.src) 165 | defer func() { 166 | e2 := app.src.Close() 167 | if err == nil { 168 | err = e2 169 | } 170 | }() 171 | app.Printf("copied %d bytes\n", n) 172 | 173 | return err 174 | } 175 | -- {{.proj_short}}/go.mod -- 176 | module {{.repo}} 177 | 178 | go {{.gversion}} 179 | -- {{.proj_short}}/main.go -- 180 | package main 181 | 182 | import ( 183 | "os" 184 | 185 | "github.com/carlmjohnson/exitcode" 186 | "{{.repo}}/{{.pkg}}" 187 | ) 188 | 189 | func main() { 190 | exitcode.Exit({{.pkg}}.CLI(os.Args[1:])) 191 | } 192 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/carlmjohnson/go-cli 2 | 3 | go 1.17 4 | 5 | require github.com/carlmjohnson/springerle v0.21.8 6 | 7 | require ( 8 | github.com/Songmu/prompter v0.5.0 // indirect 9 | github.com/carlmjohnson/flagext v0.21.0 // indirect 10 | github.com/huandu/xstrings v1.3.2 // indirect 11 | github.com/mattn/go-isatty v0.0.14 // indirect 12 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 13 | golang.org/x/sys v0.1.0 // indirect 14 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect 15 | golang.org/x/tools v0.1.7 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Songmu/prompter v0.5.0 h1:uf60xlFItY5nW+rlLJ2XIUfaUReo4gUEeftuUeHpio8= 2 | github.com/Songmu/prompter v0.5.0/go.mod h1:S4Eg25l60kPlnfB2ttFVpvBKYw7RKJexzB3gzpAansY= 3 | github.com/carlmjohnson/exitcode v0.20.2 h1:vE6rmkCGNA4kO4m1qwWIa77PKlUBVg46cNjs22eAOXE= 4 | github.com/carlmjohnson/exitcode v0.20.2/go.mod h1:MZ6ThCDx517DQcrpYnnns1pLh8onjFl+B/AsrOrdmpc= 5 | github.com/carlmjohnson/flagext v0.20.2/go.mod h1:Eenv0epIUAr4NuedNmkzI8WmBmjIxZC239XcKxYS2ac= 6 | github.com/carlmjohnson/flagext v0.21.0 h1:/c4uK3ie786Z7caXLcIMvePNSSiH3bQVGDvmGLMme60= 7 | github.com/carlmjohnson/flagext v0.21.0/go.mod h1:Eenv0epIUAr4NuedNmkzI8WmBmjIxZC239XcKxYS2ac= 8 | github.com/carlmjohnson/springerle v0.21.8 h1:8qQEzk9J8/HUzpdZf1dDRFiM0ye6SqZkv7mmc2LCw00= 9 | github.com/carlmjohnson/springerle v0.21.8/go.mod h1:gv/snZJpQ76/rdnGsTGaKJhPOiVVcE9Ux165XanZ83E= 10 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 11 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 12 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 13 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 14 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 15 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 16 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 17 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 18 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 19 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 20 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 23 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 24 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 25 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 26 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 27 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 28 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 29 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 30 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 45 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 47 | golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 48 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= 49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 52 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 53 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 54 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 55 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 56 | golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= 57 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 58 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 59 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 60 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | -------------------------------------------------------------------------------- /springerle_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "testing" 7 | 8 | "github.com/carlmjohnson/springerle/txtartmpl" 9 | ) 10 | 11 | func TestProj(t *testing.T) { 12 | dst := t.TempDir() 13 | const context = ` 14 | { 15 | "author": "John Doe", 16 | "proj_full": "Project X", 17 | "proj_short": "project_x", 18 | "repo": "github.com/john_doe/project_x", 19 | "pkg": "app", 20 | "gversion": "1.18", 21 | "description": "tktk" 22 | } 23 | ` 24 | err := txtartmpl.CLI([]string{"-context", context, "-dest", dst, "go-cli.txtar"}) 25 | if err != nil { 26 | t.Fatalf("err: %v", err) 27 | } 28 | if err := os.Chdir(dst); err != nil { 29 | t.Fatalf("err: %v", err) 30 | } 31 | cmd := exec.Command("./finalize.sh") 32 | output, err := cmd.CombinedOutput() 33 | if err != nil { 34 | t.Fatal(string(output)) 35 | } 36 | if err := os.Chdir("./project_x"); err != nil { 37 | t.Fatalf("err: %v", err) 38 | } 39 | cmd = exec.Command("go", "test", "./...") 40 | output, err = cmd.CombinedOutput() 41 | if err != nil { 42 | t.Fatal(string(output)) 43 | } 44 | } 45 | --------------------------------------------------------------------------------