├── README.md ├── assets └── dictionary.png ├── contrib ├── docker │ └── Dockerfile ├── completion │ ├── bash_autocomplete │ └── zsh_autocomplete └── homebrew │ └── converter.rb ├── .releaserc.js ├── internal └── tools │ └── tools.go ├── AUTHORS ├── go.mod ├── .dockerignore ├── cmd ├── json2toml │ ├── main.go │ └── depaware.txt └── converter │ ├── main.go │ └── depaware.txt ├── LICENSE ├── .golangci.yml ├── Dockerfile ├── .goreleaser.yml ├── doc.go ├── Makefile ├── converter.go ├── converter_test.go ├── go.sum ├── converters.go └── rules.mk /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/converter/master/README.md -------------------------------------------------------------------------------- /assets/dictionary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/converter/master/assets/dictionary.png -------------------------------------------------------------------------------- /contrib/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ADD ./converter /converter 3 | ENTRYPOINT ["/converter"] 4 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: 'master', 3 | plugins: [ 4 | '@semantic-release/commit-analyzer', 5 | '@semantic-release/release-notes-generator', 6 | '@semantic-release/github', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package tools 4 | 5 | import ( 6 | // required by depaware 7 | _ "github.com/tailscale/depaware/depaware" 8 | 9 | // required by goimports 10 | _ "golang.org/x/tools/cover" 11 | ) 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This file lists all individuals having contributed content to the repository. 2 | # For how it is generated, see 'https://github.com/moul/rules.mk' 3 | 4 | ImgBotApp 5 | Manfred Touron <94029+moul@users.noreply.github.com> 6 | Manfred Touron 7 | moul-bot 8 | Renovate Bot 9 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module moul.io/converter 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/davecgh/go-spew v1.1.1 // indirect 8 | github.com/mr-tron/base58 v1.2.0 9 | github.com/stretchr/testify v1.7.0 10 | github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 11 | github.com/urfave/cli v1.22.5 12 | golang.org/x/tools v0.1.5 13 | ) 14 | -------------------------------------------------------------------------------- /contrib/completion/bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | _cli_bash_autocomplete() { 4 | local cur opts base 5 | COMPREPLY=() 6 | cur="${COMP_WORDS[COMP_CWORD]}" 7 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) 8 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 9 | return 0 10 | } 11 | 12 | complete -F _cli_bash_autocomplete converter 13 | -------------------------------------------------------------------------------- /contrib/completion/zsh_autocomplete: -------------------------------------------------------------------------------- 1 | autoload -U compinit && compinit 2 | autoload -U bashcompinit && bashcompinit 3 | 4 | _cli_bash_autocomplete() { 5 | local cur opts base 6 | COMPREPLY=() 7 | cur="${COMP_WORDS[COMP_CWORD]}" 8 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) 9 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 10 | return 0 11 | } 12 | 13 | complete -F _cli_bash_autocomplete converter 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ## 2 | ## Specific to .dockerignore 3 | ## 4 | 5 | .git/ 6 | Dockerfile 7 | contrib/ 8 | 9 | ## 10 | ## Common with .gitignore 11 | ## 12 | 13 | # Temporary files 14 | *~ 15 | *# 16 | .#* 17 | 18 | # Vendors 19 | node_modules/ 20 | vendor/ 21 | 22 | # Binaries for programs and plugins 23 | dist/ 24 | gin-bin 25 | *.exe 26 | *.exe~ 27 | *.dll 28 | *.so 29 | *.dylib 30 | 31 | # Test binary, build with `go test -c` 32 | *.test 33 | 34 | # Output of the go coverage tool, specifically when used with LiteIDE 35 | *.out 36 | -------------------------------------------------------------------------------- /cmd/json2toml/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "moul.io/converter" 10 | ) 11 | 12 | func main() { 13 | err := run() 14 | if err != nil { 15 | log.Fatalf("Error: %v", err) 16 | } 17 | } 18 | 19 | func run() error { 20 | fn, err := converter.ChainFunc([]string{"json-decode", "toml", "_bytes-to-string"}) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | input, err := ioutil.ReadAll(os.Stdin) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | ret, err := fn(input) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | fmt.Println(ret) 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Manfred Touron 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 | 23 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 1m 3 | tests: false 4 | skip-files: 5 | - "testing.go" 6 | - ".*\\.pb\\.go" 7 | - ".*\\.gen\\.go" 8 | 9 | linters-settings: 10 | golint: 11 | min-confidence: 0 12 | maligned: 13 | suggest-new: true 14 | goconst: 15 | min-len: 5 16 | min-occurrences: 4 17 | misspell: 18 | locale: US 19 | 20 | linters: 21 | disable-all: true 22 | enable: 23 | - asciicheck 24 | - bodyclose 25 | - deadcode 26 | - depguard 27 | - dogsled 28 | - dupl 29 | - errcheck 30 | - exhaustive 31 | - exportloopref 32 | - gochecknoinits 33 | - gocognit 34 | - goconst 35 | - gocritic 36 | - gocyclo 37 | - godot 38 | - gofmt 39 | - gofumpt 40 | - goimports 41 | - golint 42 | - gomnd 43 | - gomodguard 44 | - gosec 45 | - gosimple 46 | - govet 47 | - ineffassign 48 | - interfacer 49 | - maligned 50 | - misspell 51 | - nakedret 52 | - nestif 53 | - noctx 54 | - nolintlint 55 | - prealloc 56 | - scopelint 57 | - sqlclosecheck 58 | - staticcheck 59 | - structcheck 60 | - stylecheck 61 | - typecheck 62 | - unconvert 63 | - unparam 64 | - unused 65 | - varcheck 66 | - whitespace 67 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # dynamic config 2 | ARG BUILD_DATE 3 | ARG VCS_REF 4 | ARG VERSION 5 | 6 | # build 7 | FROM golang:1.19.0-alpine as builder 8 | RUN apk add --no-cache git gcc musl-dev make 9 | ENV GO111MODULE=on 10 | WORKDIR /go/src/moul.io/converter 11 | COPY go.* ./ 12 | RUN go mod download 13 | COPY . ./ 14 | RUN make install 15 | 16 | # minimalist runtime 17 | FROM alpine:3.16.0 18 | LABEL org.label-schema.build-date=$BUILD_DATE \ 19 | org.label-schema.name="converter" \ 20 | org.label-schema.description="" \ 21 | org.label-schema.url="https://moul.io/converter/" \ 22 | org.label-schema.vcs-ref=$VCS_REF \ 23 | org.label-schema.vcs-url="https://github.com/moul/converter" \ 24 | org.label-schema.vendor="Manfred Touron" \ 25 | org.label-schema.version=$VERSION \ 26 | org.label-schema.schema-version="1.0" \ 27 | org.label-schema.cmd="docker run -i -t --rm moul/converter" \ 28 | org.label-schema.help="docker exec -it $CONTAINER converter --help" 29 | COPY --from=builder /go/bin/converter /bin/ 30 | ENTRYPOINT ["/bin/converter"] 31 | #CMD [] 32 | -------------------------------------------------------------------------------- /contrib/homebrew/converter.rb: -------------------------------------------------------------------------------- 1 | require "language/go" 2 | 3 | class Converter < Formula 4 | desc "converter: Advanced SSH config - A transparent wrapper that adds regex, aliases, gateways, includes, dynamic hostnames to SSH" 5 | homepage "https://github.com/moul/converter" 6 | url "https://github.com/moul/converter/archive/v1.0.0.tar.gz" 7 | sha256 "c7d53c61c7ca20827979c1d20ba7fcefcb315ac0645821f69ddbe41c432e160b" 8 | 9 | head "https://github.com/moul/converter.git" 10 | 11 | depends_on "go" => :build 12 | 13 | def install 14 | ENV["GOPATH"] = buildpath 15 | ENV["CGO_ENABLED"] = "0" 16 | ENV.prepend_create_path "PATH", buildpath/"bin" 17 | 18 | mkdir_p buildpath/"src/github.com/moul" 19 | ln_s buildpath, buildpath/"src/github.com/moul/converter" 20 | Language::Go.stage_deps resources, buildpath/"src" 21 | 22 | # FIXME: update version 23 | system "go", "get", "github.com/BurntSushi/toml" 24 | system "go", "get", "github.com/Sirupsen/logrus" 25 | system "go", "build", "-o", "converter", "./cmd/converter" 26 | bin.install "converter" 27 | 28 | bash_completion.install "contrib/completion/bash_autocomplete" 29 | zsh_completion.install "contrib/completion/zsh_autocomplete" 30 | end 31 | 32 | test do 33 | output = shell_output(bin/"converter --version") 34 | assert output.include? "converter version 2" 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | env: 2 | - GO111MODULE=on 3 | - GOPROXY=https://proxy.golang.org 4 | before: 5 | hooks: 6 | - go mod download 7 | builds: 8 | - 9 | main: ./cmd/converter 10 | env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - darwin 15 | - windows 16 | goarch: 17 | - 386 18 | - amd64 19 | - arm 20 | - arm64 21 | ignore: 22 | - 23 | goos: darwin 24 | goarch: 386 25 | flags: 26 | - "-a" 27 | ldflags: 28 | - '-extldflags "-static"' 29 | checksum: 30 | name_template: '{{.ProjectName}}_checksums.txt' 31 | changelog: 32 | sort: asc 33 | filters: 34 | exclude: 35 | - '^docs:' 36 | - '^test:' 37 | - Merge pull request 38 | - Merge branch 39 | archives: 40 | - 41 | name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 42 | replacements: 43 | darwin: Darwin 44 | linux: Linux 45 | windows: Windows 46 | 386: i386 47 | amd64: x86_64 48 | format_overrides: 49 | - 50 | goos: windows 51 | format: zip 52 | wrap_in_directory: true 53 | brews: 54 | - 55 | name: converter 56 | # github: 57 | # owner: moul 58 | # name: homebrew-moul 59 | commit_author: 60 | name: moul-bot 61 | email: "bot@moul.io" 62 | homepage: https://github.com/moul/converter 63 | description: "converter" 64 | nfpms: 65 | - 66 | file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 67 | homepage: https://github.com/moul/converter 68 | description: "converter" 69 | maintainer: "Manfred Touron " 70 | license: "Apache-2.0 OR MIT" 71 | vendor: moul 72 | formats: 73 | - deb 74 | - rpm 75 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2020 Manfred Touron 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT 3 | 4 | // message from the author: 5 | // +--------------------------------------------------------------+ 6 | // | * * * ░░░░░░░░░░░░░░░░░░░░ Hello ░░░░░░░░░░░░░░░░░░░░░░░░░░| 7 | // +--------------------------------------------------------------+ 8 | // | | 9 | // | ++ ______________________________________ | 10 | // | ++++ / \ | 11 | // | ++++ | | | 12 | // | ++++++++++ | Feel free to contribute to this | | 13 | // | +++ | | project or contact me on | | 14 | // | ++ | | manfred.life if you like this | | 15 | // | + -== ==| | project! | | 16 | // | ( <*> <*> | | | 17 | // | | | /| :) | | 18 | // | | _) / | | | 19 | // | | +++ / \______________________________________/ | 20 | // | \ =+ / | 21 | // | \ + | 22 | // | |\++++++ | 23 | // | | ++++ ||// | 24 | // | ___| |___ _||/__ __| 25 | // | / --- \ \| ||| __ _ ___ __ __/ /| 26 | // |/ | | \ \ / / ' \/ _ \/ // / / | 27 | // || | | | | | /_/_/_/\___/\_,_/_/ | 28 | // +--------------------------------------------------------------+ 29 | package converter // import "moul.io/converter" 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOPKG ?= moul.io/converter 2 | DOCKER_IMAGE ?= moul/converter 3 | GOBINS ?= ./cmd/json2toml ./cmd/converter 4 | 5 | include rules.mk 6 | 7 | generate: install 8 | GO111MODULE=off go get github.com/campoy/embedmd 9 | mkdir -p .tmp 10 | echo 'foo@bar:~$$ converter -h' > .tmp/usage.txt 11 | converter -h 2>&1 >> .tmp/usage.txt 12 | echo 'foo@bar:~$$ echo -n "Hello World!" | converter _bytes-to-string rev' > .tmp/examples.txt 13 | echo -n 'Hello World!' | converter _bytes-to-string rev >> .tmp/examples.txt 14 | echo 'foo@bar:~$$ echo "Hello World!" | converter md5' >> .tmp/examples.txt 15 | echo 'Hello World!' | converter md5 >> .tmp/examples.txt 16 | echo 'foo@bar:~$$ echo "Hello World!" | converter md5 md5' >> .tmp/examples.txt 17 | echo 'Hello World!' | converter md5 md5 >> .tmp/examples.txt 18 | echo 'foo@bar:~$$ echo "Hello World!" | converter md5 md5 md5' >> .tmp/examples.txt 19 | echo 'Hello World!' | converter md5 md5 md5 >> .tmp/examples.txt 20 | echo 'foo@bar:~$$ echo "Hello World!" | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev' >> .tmp/examples.txt 21 | echo 'Hello World!' | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev >> .tmp/examples.txt 22 | echo 'foo@bar:~$$ echo "Hello World!" | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev base64-decode' >> .tmp/examples.txt 23 | echo 'Hello World!' | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev base64-decode >> .tmp/examples.txt 24 | echo 'foo@bar:~$$ echo "Hello World!" | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev base64-decode bytes-to-string' >> .tmp/examples.txt 25 | echo 'Hello World!' | converter _bytes-to-string rev _string-to-bytes md5 hex upper rev base64-decode _bytes-to-string >> .tmp/examples.txt 26 | embedmd -w README.md 27 | sed -i 's/[[:blank:]]*$$//' README.md 28 | rm -rf .tmp 29 | .PHONY: generate 30 | 31 | lint: 32 | cd tool/lint; make 33 | .PHONY: lint 34 | -------------------------------------------------------------------------------- /cmd/converter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "sort" 9 | 10 | "github.com/urfave/cli" 11 | "moul.io/converter" 12 | ) 13 | 14 | var ( 15 | VERSION string 16 | GITCOMMIT string 17 | ) 18 | 19 | func main() { 20 | app := cli.NewApp() 21 | app.Name = "converter" 22 | app.Author = "Manfred Touron" 23 | app.Email = "https://github.com/moul/converter" 24 | if VERSION != "" || GITCOMMIT != "" { 25 | app.Version = VERSION + " (" + GITCOMMIT + ")" 26 | } 27 | app.EnableBashCompletion = true 28 | 29 | app.Commands = []cli.Command{} 30 | filters := []string{} 31 | for filter := range converter.Converters { 32 | filters = append(filters, filter) 33 | } 34 | sort.Strings(filters) 35 | for _, filter := range filters { 36 | command := cli.Command{ 37 | Name: filter, 38 | Action: Action, 39 | BashComplete: BashComplete, 40 | Hidden: filter[0] == '_', 41 | // Usage: fmt.Sprintf("%s -> %s", filter.InputType, filter.OutputType), 42 | } 43 | app.Commands = append(app.Commands, command) 44 | } 45 | 46 | if err := app.Run(os.Args); err != nil { 47 | log.Fatalf("Error: %v", err) 48 | } 49 | } 50 | 51 | func BashComplete(c *cli.Context) { 52 | for filter := range converter.Converters { 53 | fmt.Println(filter) 54 | } 55 | } 56 | 57 | func Action(c *cli.Context) error { 58 | args := append([]string{c.Command.Name}, c.Args()...) 59 | if len(args) == 0 { 60 | return fmt.Errorf("you need to use at least one filter") // nolint:goerr113 61 | } 62 | 63 | fn, err := converter.ChainFunc(args) 64 | if err != nil { 65 | return fmt.Errorf("failed to create a converter: %w", err) 66 | } 67 | 68 | input, err := ioutil.ReadAll(os.Stdin) 69 | if err != nil { 70 | return fmt.Errorf("failed to read from stdin: %w", err) 71 | } 72 | 73 | output, err := fn(input) 74 | if err != nil { 75 | return fmt.Errorf("failed to convert: %w", err) 76 | } 77 | 78 | fmt.Printf("%v\n", output) 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /converter.go: -------------------------------------------------------------------------------- 1 | package converter 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type Func func(interface{}) (interface{}, error) 9 | 10 | func Chain(input interface{}, converters []string) (interface{}, error) { 11 | fn, err := ChainFunc(converters) 12 | if err != nil { 13 | return nil, fmt.Errorf("failed to build chain func: %w", err) 14 | } 15 | 16 | return fn(input) 17 | } 18 | 19 | func ChainFunc(converters []string) (Func, error) { 20 | if len(converters) == 0 { 21 | return func(input interface{}) (interface{}, error) { 22 | return input, nil 23 | }, nil 24 | } 25 | 26 | var ret Func 27 | 28 | for _, name := range converters { 29 | converter, found := Converters[name] 30 | if !found { 31 | return nil, fmt.Errorf("no such converter with name %q", name) 32 | } 33 | 34 | fn, err := converterToFunc(converter) 35 | if err != nil { 36 | return nil, fmt.Errorf( 37 | "invalid converter signature %q: %w", 38 | reflect.TypeOf(converter).String(), 39 | err, 40 | ) 41 | } 42 | 43 | ret = bridgeFuncs(ret, fn) 44 | } 45 | 46 | return ret, nil 47 | } 48 | 49 | func bridgeFuncs(left, right Func) Func { 50 | if left == nil { 51 | return right 52 | } 53 | return func(input interface{}) (interface{}, error) { 54 | ret, err := left(input) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return right(ret) 59 | } 60 | } 61 | 62 | func converterToFunc(converter interface{}) (Func, error) { 63 | x := reflect.TypeOf(converter) 64 | // 1 input parameter, 1 or 2 output parameters 65 | if x.NumIn() != 1 || x.NumOut() < 1 || x.NumOut() > 2 || x.IsVariadic() { 66 | return nil, fmt.Errorf("invalid amount of parameters") 67 | } 68 | var retErrT reflect.Type 69 | if x.NumOut() == 2 { // nolint:gomnd 70 | retErrT = x.Out(1) 71 | errorInterface := reflect.TypeOf((*error)(nil)).Elem() 72 | if !retErrT.Implements(errorInterface) { 73 | return nil, fmt.Errorf("second return argument should be an error") 74 | } 75 | } 76 | 77 | v := reflect.ValueOf(converter) 78 | fn := func(input interface{}) (interface{}, error) { 79 | args := []reflect.Value{reflect.ValueOf(input)} 80 | ret := v.Call(args) 81 | 82 | retV := ret[0].Interface() 83 | var err error 84 | if len(ret) == 2 { // nolint:gomnd 85 | if v := ret[1].Interface(); v != nil { 86 | err = v.(error) 87 | } 88 | } 89 | return retV, err 90 | } 91 | return fn, nil 92 | } 93 | -------------------------------------------------------------------------------- /cmd/json2toml/depaware.txt: -------------------------------------------------------------------------------- 1 | moul.io/converter/cmd/json2toml dependencies: (generated by github.com/tailscale/depaware) 2 | 3 | github.com/BurntSushi/toml from moul.io/converter 4 | github.com/mr-tron/base58 from moul.io/converter 5 | moul.io/converter from moul.io/converter/cmd/json2toml 6 | bufio from encoding/csv+ 7 | bytes from bufio+ 8 | crypto from crypto/md5+ 9 | crypto/md5 from moul.io/converter 10 | crypto/sha1 from moul.io/converter 11 | encoding from encoding/json+ 12 | encoding/base32 from moul.io/converter 13 | encoding/base64 from encoding/json+ 14 | encoding/binary from crypto/md5+ 15 | encoding/csv from moul.io/converter 16 | encoding/hex from moul.io/converter 17 | encoding/json from moul.io/converter 18 | encoding/xml from moul.io/converter 19 | errors from bufio+ 20 | fmt from encoding/csv+ 21 | hash from crypto+ 22 | io from bufio+ 23 | io/fs from io/ioutil+ 24 | io/ioutil from github.com/BurntSushi/toml+ 25 | log from moul.io/converter/cmd/json2toml 26 | math from encoding/binary+ 27 | math/big from github.com/mr-tron/base58 28 | math/bits from crypto/md5+ 29 | math/rand from math/big 30 | os from fmt+ 31 | path from io/fs 32 | path/filepath from io/ioutil 33 | reflect from encoding/binary+ 34 | sort from encoding/json+ 35 | strconv from crypto+ 36 | strings from bufio+ 37 | sync from encoding/binary+ 38 | sync/atomic from internal/poll+ 39 | syscall from internal/poll+ 40 | time from github.com/BurntSushi/toml+ 41 | unicode from bytes+ 42 | unicode/utf16 from encoding/json+ 43 | unicode/utf8 from bufio+ 44 | -------------------------------------------------------------------------------- /cmd/converter/depaware.txt: -------------------------------------------------------------------------------- 1 | moul.io/converter/cmd/converter dependencies: (generated by github.com/tailscale/depaware) 2 | 3 | github.com/BurntSushi/toml from moul.io/converter 4 | github.com/cpuguy83/go-md2man/v2/md2man from github.com/urfave/cli 5 | github.com/mr-tron/base58 from moul.io/converter 6 | github.com/russross/blackfriday/v2 from github.com/cpuguy83/go-md2man/v2/md2man 7 | github.com/shurcooL/sanitized_anchor_name from github.com/russross/blackfriday/v2 8 | github.com/urfave/cli from moul.io/converter/cmd/converter 9 | moul.io/converter from moul.io/converter/cmd/converter 10 | bufio from encoding/csv+ 11 | bytes from bufio+ 12 | crypto from crypto/md5+ 13 | crypto/md5 from moul.io/converter 14 | crypto/sha1 from moul.io/converter 15 | encoding from encoding/json+ 16 | encoding/base32 from moul.io/converter 17 | encoding/base64 from encoding/json+ 18 | encoding/binary from crypto/md5+ 19 | encoding/csv from moul.io/converter 20 | encoding/hex from moul.io/converter 21 | encoding/json from moul.io/converter 22 | encoding/xml from moul.io/converter 23 | errors from bufio+ 24 | flag from github.com/urfave/cli 25 | fmt from encoding/csv+ 26 | hash from crypto+ 27 | html from github.com/russross/blackfriday/v2 28 | io from bufio+ 29 | io/fs from io/ioutil+ 30 | io/ioutil from github.com/BurntSushi/toml+ 31 | log from moul.io/converter/cmd/converter 32 | math from encoding/binary+ 33 | math/big from github.com/mr-tron/base58 34 | math/bits from crypto/md5+ 35 | math/rand from math/big 36 | net/url from text/template 37 | os from flag+ 38 | path from io/fs+ 39 | path/filepath from github.com/urfave/cli+ 40 | reflect from encoding/binary+ 41 | regexp from github.com/russross/blackfriday/v2 42 | regexp/syntax from regexp 43 | sort from encoding/json+ 44 | strconv from crypto+ 45 | strings from bufio+ 46 | sync from encoding/binary+ 47 | sync/atomic from internal/poll+ 48 | syscall from github.com/urfave/cli+ 49 | text/tabwriter from github.com/urfave/cli 50 | text/template from github.com/urfave/cli 51 | text/template/parse from text/template 52 | time from flag+ 53 | unicode from bytes+ 54 | unicode/utf16 from encoding/json+ 55 | unicode/utf8 from bufio+ 56 | -------------------------------------------------------------------------------- /converter_test.go: -------------------------------------------------------------------------------- 1 | package converter_test 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | "moul.io/converter" 10 | ) 11 | 12 | func TestConverter(t *testing.T) { 13 | cases := []struct { 14 | input interface{} 15 | converters []string 16 | expectedOutput interface{} 17 | expectedRunError bool 18 | expectedInitError bool 19 | }{ 20 | {"42", []string{"_string-to-int64"}, int64(42), false, false}, 21 | {"HELLO WORLD", []string{"lower"}, "hello world", false, false}, 22 | {"hello world", []string{"no-exists"}, "", false, true}, 23 | {"hello world", []string{"rev"}, "dlrow olleh", false, false}, 24 | {"hello world", []string{"title"}, "Hello World", false, false}, 25 | {"hello world", []string{"upper"}, "HELLO WORLD", false, false}, 26 | {"hello world", []string{}, "hello world", false, false}, 27 | {"hello world", nil, "hello world", false, false}, 28 | {"42", []string{"_string-to-float64"}, float64(42), false, false}, 29 | {"42.42", []string{"_string-to-int64"}, nil, true, false}, 30 | {"42.42", []string{"_string-to-float64"}, float64(42.42), false, false}, 31 | {int64(42), []string{"_int64-to-string"}, "42", false, false}, 32 | {float64(42), []string{"_float64-to-string"}, "42", false, false}, 33 | {float64(42.5), []string{"_float64-to-string"}, "42.5", false, false}, 34 | {[]byte("hello world"), []string{"_bytes-to-string"}, "hello world", false, false}, 35 | {"hello world", []string{"_parse-string"}, "hello world", false, false}, 36 | {"42", []string{"_parse-string"}, int64(42), false, false}, 37 | {"42.42", []string{"_parse-string"}, float64(42.42), false, false}, 38 | {"hello world", []string{"rev", "rev"}, "hello world", false, false}, 39 | {"hello world", []string{"rev", "upper"}, "DLROW OLLEH", false, false}, 40 | {"hello world", []string{"upper", "rev"}, "DLROW OLLEH", false, false}, 41 | {"hello world", []string{"_string-to-bytes", "hex"}, "68656c6c6f20776f726c64", false, false}, 42 | {"hello world", []string{"_string-to-bytes", "hex", "hex-decode", "_bytes-to-string"}, "hello world", false, false}, 43 | {"hello world", []string{"_string-to-bytes", "md5", "hex"}, "5eb63bbbe01eeed093cb22bb8f5acdc3", false, false}, 44 | {"hello world", []string{"_string-to-bytes", "md5", "md5", "hex"}, "241d8a27c836427bd7f04461b60e7359", false, false}, 45 | {"hello world", []string{"_string-to-bytes", "sha1", "hex"}, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", false, false}, 46 | {"hello world", []string{"_string-to-bytes", "base32"}, "NBSWY3DPEB3W64TMMQ======", false, false}, 47 | {"hello world", []string{"_string-to-bytes", "hexbase32"}, "D1IMOR3F41RMUSJCCG======", false, false}, 48 | {"hello world", []string{"_string-to-bytes", "base58"}, "StV1DL6CwTryKyV", false, false}, 49 | {"hello world", []string{"_string-to-bytes", "base64"}, "aGVsbG8gd29ybGQ=", false, false}, 50 | {"hello world", []string{"_string-to-bytes", "urlbase64"}, "aGVsbG8gd29ybGQ=", false, false}, 51 | {"hello world", []string{"_string-to-bytes", "rawurlbase64"}, "aGVsbG8gd29ybGQ", false, false}, 52 | {"hello world", []string{"_string-to-bytes", "base32", "base32-decode", "_bytes-to-string"}, "hello world", false, false}, 53 | {"hello world", []string{"_string-to-bytes", "hexbase32", "hexbase32-decode", "_bytes-to-string"}, "hello world", false, false}, 54 | {"hello world", []string{"_string-to-bytes", "base58", "base58-decode", "_bytes-to-string"}, "hello world", false, false}, 55 | {"hello world", []string{"_string-to-bytes", "base64", "base64-decode", "_bytes-to-string"}, "hello world", false, false}, 56 | {"hello world", []string{"_string-to-bytes", "urlbase64", "urlbase64-decode", "_bytes-to-string"}, "hello world", false, false}, 57 | {"hello world", []string{"_string-to-bytes", "rawurlbase64", "rawurlbase64-decode", "_bytes-to-string"}, "hello world", false, false}, 58 | {[]string{"hello", "world"}, []string{"json", "_bytes-to-string"}, `["hello","world"]`, false, false}, 59 | {map[string]string{"a": "hello", "b": "world"}, []string{"toml", "_bytes-to-string"}, "a = \"hello\"\nb = \"world\"\n", false, false}, 60 | {`["hello","world"]`, []string{"_string-to-bytes", "json-decode", "json", "_bytes-to-string"}, `["hello","world"]`, false, false}, 61 | {`{"a": ["hello", "world"]}`, []string{"_string-to-bytes", "json-decode", "toml", "_bytes-to-string"}, "a = [\"hello\", \"world\"]\n", false, false}, 62 | {"a,b,c\n1,2,3\n", []string{"csv-decode"}, [][]string{[]string{"a", "b", "c"}, []string{"1", "2", "3"}}, false, false}, 63 | {"a,b,c\n1,2,3\n", []string{"csv-decode", "json", "_bytes-to-string"}, `[["a","b","c"],["1","2","3"]]`, false, false}, 64 | // {map[string]string{"a": "hello", "b": "world"}, []string{"xml", "_bytes-to-string"}, "a = \"hello\"\nb = \"world\"\n", false, false}, 65 | // {"Hello423.1415", []string{"_string-to-bytes", "xml-decode"}, "salut", false, false}, 66 | } 67 | 68 | for _, tc := range cases { 69 | name := fmt.Sprintf("%v_%s", tc.input, strings.Join(tc.converters, ",")) 70 | t.Run(name, func(t *testing.T) { 71 | fn, err := converter.ChainFunc(tc.converters) 72 | if tc.expectedInitError { 73 | require.Error(t, err) 74 | require.Nil(t, fn) 75 | return 76 | } 77 | require.NoError(t, err) 78 | require.NotNil(t, fn) 79 | 80 | ret, err := fn(tc.input) 81 | if tc.expectedRunError { 82 | require.Error(t, err) 83 | } else { 84 | require.NoError(t, err) 85 | require.Equal(t, tc.expectedOutput, ret) 86 | } 87 | }) 88 | } 89 | } 90 | 91 | func Example() { 92 | ret, _ := converter.Chain("hello world", []string{"rev", "upper"}) 93 | fmt.Println(ret) 94 | // Output: DLROW OLLEH 95 | } 96 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 9 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 10 | github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8= 11 | github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 15 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 16 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 17 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 18 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 19 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 22 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE= 25 | github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= 26 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 27 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 28 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 29 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 32 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 33 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 34 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 35 | golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= 36 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 37 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 38 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 39 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 40 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 41 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 44 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 45 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 49 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= 50 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 52 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 53 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 56 | golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 57 | golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= 58 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 59 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 60 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 61 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 62 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 64 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 66 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 67 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 68 | -------------------------------------------------------------------------------- /converters.go: -------------------------------------------------------------------------------- 1 | package converter 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" // nolint:gosec 6 | "crypto/sha1" // nolint:gosec 7 | "encoding/base32" 8 | "encoding/base64" 9 | "encoding/csv" 10 | "encoding/hex" 11 | "encoding/json" 12 | "encoding/xml" 13 | "fmt" 14 | "strconv" 15 | "strings" 16 | 17 | "github.com/BurntSushi/toml" 18 | "github.com/mr-tron/base58" 19 | ) 20 | 21 | // Converters is a map containing converters that can be called using a name. 22 | var Converters = map[string]interface{}{ 23 | "base32": base32Encode, 24 | "base32-decode": base32Decode, 25 | "base58": base58Encode, 26 | "base58-decode": base58Decode, 27 | "base64": base64Encode, 28 | "base64-decode": base64Decode, 29 | "hex": hexEncode, 30 | "hex-decode": hexDecode, 31 | "hexbase32": hexbase32Encode, 32 | "hexbase32-decode": hexbase32Decode, 33 | "lower": lower, 34 | "md5": md5Sum, 35 | "rawurlbase64": rawurlbase64Encode, 36 | "rawurlbase64-decode": rawurlbase64Decode, 37 | "rev": rev, 38 | "sha1": sha1Sum, 39 | "title": title, 40 | "upper": upper, 41 | "urlbase64": urlbase64Encode, 42 | "urlbase64-decode": urlbase64Decode, 43 | "json": jsonMarshal, 44 | "toml": tomlEncode, 45 | "json-decode": jsonDecode, 46 | "csv-decode": csvDecode, 47 | "xml": xmlEncode, 48 | "xml-decode": xmlDecode, 49 | 50 | // internal 51 | "_parse-string": parseString, 52 | "_bytes-to-string": bytesToString, 53 | "_string-to-bytes": stringToBytes, 54 | "_int64-to-string": int64ToString, 55 | "_string-to-int64": stringToInt64, 56 | "_string-to-float64": stringToFloat64, 57 | "_float64-to-string": float64ToString, 58 | /* 59 | fetch string -> []byte 60 | sleep-100ms interface{} -> interface{} 61 | sleep-1s interface{} -> interface{} 62 | sleep-2s interface{} -> interface{} 63 | sleep-5s interface{} -> interface{} 64 | sleep-10s interface{} -> interface{} 65 | sleep-1m interface{} -> interface{} 66 | reverse string -> string 67 | upper string -> string 68 | lower string -> string 69 | split-lines []byte -> []byte 70 | to-unix time.Time -> int64 71 | parse-ansi-date string -> time.Time 72 | parse-rfc339-date string -> time.Time 73 | parse-rfc822-date string -> time.Time 74 | parse-rfc850-date string -> time.Time 75 | parse-rfc1123-date string -> time.Time 76 | parse-unix-date string -> time.Time 77 | parse-date string -> time.Time 78 | time-to-string time.Time -> string 79 | parse-unix-timestamp int64 -> time.Time 80 | */ 81 | } 82 | 83 | // parseString takes a string in input and tries to cast it in a more specific type (date, int, etc). 84 | // This function should be the first one to be called in a chain when using a CLI. 85 | func parseString(in string) interface{} { 86 | if n, err := strconv.ParseInt(in, 10, 0); err == nil { 87 | return n 88 | } 89 | if n, err := strconv.ParseFloat(in, 64); err == nil { 90 | return n 91 | } 92 | 93 | // FIXME: try to parse other formats first 94 | return in 95 | } 96 | 97 | func rev(in string) (string, error) { 98 | runes := []rune(in) 99 | for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { 100 | runes[i], runes[j] = runes[j], runes[i] 101 | } 102 | return string(runes), nil 103 | } 104 | 105 | func tomlEncode(in interface{}) ([]byte, error) { 106 | buf := new(bytes.Buffer) 107 | err := toml.NewEncoder(buf).Encode(in) 108 | return buf.Bytes(), err 109 | } 110 | 111 | func jsonDecode(in []byte) (interface{}, error) { 112 | var out interface{} 113 | err := json.Unmarshal(in, &out) 114 | return out, err 115 | } 116 | 117 | func csvDecode(in string) (interface{}, error) { 118 | r := csv.NewReader(strings.NewReader(in)) 119 | return r.ReadAll() 120 | } 121 | 122 | func xmlEncode(in interface{}) ([]byte, error) { 123 | return xml.Marshal(in) 124 | } 125 | 126 | func xmlDecode(in []byte) (interface{}, error) { 127 | var out interface{} 128 | err := xml.Unmarshal(in, &out) 129 | return out, err 130 | } 131 | 132 | func base32Encode(in []byte) string { 133 | return base32.StdEncoding.EncodeToString(in) 134 | } 135 | 136 | func base32Decode(in string) ([]byte, error) { 137 | return base32.StdEncoding.DecodeString(in) 138 | } 139 | 140 | func base58Encode(in []byte) string { 141 | return base58.Encode(in) 142 | } 143 | 144 | func base58Decode(in string) ([]byte, error) { 145 | return base58.Decode(in) 146 | } 147 | 148 | func base64Encode(in []byte) string { 149 | return base64.StdEncoding.EncodeToString(in) 150 | } 151 | 152 | func base64Decode(in string) ([]byte, error) { 153 | return base64.StdEncoding.DecodeString(in) 154 | } 155 | 156 | func urlbase64Encode(in []byte) string { 157 | return base64.URLEncoding.EncodeToString(in) 158 | } 159 | 160 | func urlbase64Decode(in string) ([]byte, error) { 161 | return base64.URLEncoding.DecodeString(in) 162 | } 163 | 164 | func rawurlbase64Encode(in []byte) string { 165 | return base64.RawURLEncoding.EncodeToString(in) 166 | } 167 | 168 | func rawurlbase64Decode(in string) ([]byte, error) { 169 | return base64.RawURLEncoding.DecodeString(in) 170 | } 171 | 172 | func hexEncode(in []byte) string { 173 | return fmt.Sprintf("%x", in) 174 | } 175 | 176 | func hexDecode(in string) ([]byte, error) { 177 | return hex.DecodeString(in) 178 | } 179 | 180 | func hexbase32Encode(in []byte) string { 181 | return base32.HexEncoding.EncodeToString(in) 182 | } 183 | 184 | func hexbase32Decode(in string) ([]byte, error) { 185 | return base32.HexEncoding.DecodeString(in) 186 | } 187 | 188 | func md5Sum(in []byte) []byte { 189 | ret := md5.Sum(in) // nolint:gosec 190 | return ret[:] 191 | } 192 | 193 | func sha1Sum(in []byte) []byte { 194 | ret := sha1.Sum(in) // nolint:gosec 195 | return ret[:] 196 | } 197 | 198 | func lower(in string) string { 199 | return strings.ToLower(in) 200 | } 201 | 202 | func upper(in string) string { 203 | return strings.ToUpper(in) 204 | } 205 | 206 | func title(in string) string { 207 | return strings.Title(in) 208 | } 209 | 210 | func jsonMarshal(in interface{}) ([]byte, error) { 211 | return json.Marshal(in) 212 | } 213 | 214 | func bytesToString(in []byte) string { 215 | return string(in) 216 | } 217 | 218 | func stringToBytes(in string) []byte { 219 | return []byte(in) 220 | } 221 | 222 | func int64ToString(in int64) string { 223 | return strconv.FormatInt(in, 10) 224 | } 225 | 226 | func stringToInt64(in string) (int64, error) { 227 | return strconv.ParseInt(strings.TrimSpace(in), 10, 0) 228 | } 229 | 230 | func stringToFloat64(in string) (float64, error) { 231 | return strconv.ParseFloat(strings.TrimSpace(in), 64) 232 | } 233 | 234 | func float64ToString(in float64) string { 235 | return strconv.FormatFloat(in, 'f', -1, 64) 236 | } 237 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | # +--------------------------------------------------------------+ 2 | # | * * * moul.io/rules.mk | 3 | # +--------------------------------------------------------------+ 4 | # | | 5 | # | ++ ______________________________________ | 6 | # | ++++ / \ | 7 | # | ++++ | | | 8 | # | ++++++++++ | https://moul.io/rules.mk is a set | | 9 | # | +++ | | of common Makefile rules that can | | 10 | # | ++ | | be configured from the Makefile | | 11 | # | + -== ==| | or with environment variables. | | 12 | # | ( <*> <*> | | | 13 | # | | | /| Manfred Touron | | 14 | # | | _) / | manfred.life | | 15 | # | | +++ / \______________________________________/ | 16 | # | \ =+ / | 17 | # | \ + | 18 | # | |\++++++ | 19 | # | | ++++ ||// | 20 | # | ___| |___ _||/__ __| 21 | # | / --- \ \| ||| __ _ ___ __ __/ /| 22 | # |/ | | \ \ / / ' \/ _ \/ // / / | 23 | # || | | | | | /_/_/_/\___/\_,_/_/ | 24 | # +--------------------------------------------------------------+ 25 | 26 | .PHONY: _default_entrypoint 27 | _default_entrypoint: help 28 | 29 | ## 30 | ## Common helpers 31 | ## 32 | 33 | rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) 34 | check-program = $(foreach exec,$(1),$(if $(shell PATH="$(PATH)" which $(exec)),,$(error "No $(exec) in PATH"))) 35 | my-filter-out = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v))) 36 | novendor = $(call my-filter-out,vendor/,$(1)) 37 | 38 | ## 39 | ## rules.mk 40 | ## 41 | ifneq ($(wildcard rules.mk),) 42 | .PHONY: rulesmk.bumpdeps 43 | rulesmk.bumpdeps: 44 | wget -O rules.mk https://raw.githubusercontent.com/moul/rules.mk/master/rules.mk 45 | BUMPDEPS_STEPS += rulesmk.bumpdeps 46 | endif 47 | 48 | ## 49 | ## Maintainer 50 | ## 51 | 52 | ifneq ($(wildcard .git/HEAD),) 53 | .PHONY: generate.authors 54 | generate.authors: AUTHORS 55 | AUTHORS: .git/ 56 | echo "# This file lists all individuals having contributed content to the repository." > AUTHORS 57 | echo "# For how it is generated, see 'https://github.com/moul/rules.mk'" >> AUTHORS 58 | echo >> AUTHORS 59 | git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf >> AUTHORS 60 | GENERATE_STEPS += generate.authors 61 | endif 62 | 63 | ## 64 | ## Golang 65 | ## 66 | 67 | ifndef GOPKG 68 | ifneq ($(wildcard go.mod),) 69 | GOPKG = $(shell sed '/module/!d;s/^omdule\ //' go.mod) 70 | endif 71 | endif 72 | ifdef GOPKG 73 | GO ?= go 74 | GOPATH ?= $(HOME)/go 75 | GO_INSTALL_OPTS ?= 76 | GO_TEST_OPTS ?= -test.timeout=30s 77 | GOMOD_DIRS ?= $(sort $(call novendor,$(dir $(call rwildcard,*,*/go.mod go.mod)))) 78 | GOCOVERAGE_FILE ?= ./coverage.txt 79 | GOTESTJSON_FILE ?= ./go-test.json 80 | GOBUILDLOG_FILE ?= ./go-build.log 81 | GOINSTALLLOG_FILE ?= ./go-install.log 82 | 83 | ifdef GOBINS 84 | .PHONY: go.install 85 | go.install: 86 | ifeq ($(CI),true) 87 | @rm -f /tmp/goinstall.log 88 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 89 | cd $$dir; \ 90 | $(GO) install -v $(GO_INSTALL_OPTS) .; \ 91 | ); done 2>&1 | tee $(GOINSTALLLOG_FILE) 92 | 93 | else 94 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 95 | cd $$dir; \ 96 | $(GO) install $(GO_INSTALL_OPTS) .; \ 97 | ); done 98 | endif 99 | INSTALL_STEPS += go.install 100 | 101 | .PHONY: go.release 102 | go.release: 103 | $(call check-program, goreleaser) 104 | goreleaser --snapshot --skip-publish --rm-dist 105 | @echo -n "Do you want to release? [y/N] " && read ans && \ 106 | if [ $${ans:-N} = y ]; then set -xe; goreleaser --rm-dist; fi 107 | RELEASE_STEPS += go.release 108 | endif 109 | 110 | .PHONY: go.unittest 111 | go.unittest: 112 | ifeq ($(CI),true) 113 | @echo "mode: atomic" > /tmp/gocoverage 114 | @rm -f $(GOTESTJSON_FILE) 115 | @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -euf pipefail; \ 116 | cd $$dir; \ 117 | (($(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race -json && touch $@.ok) | tee -a $(GOTESTJSON_FILE) 3>&1 1>&2 2>&3 | tee -a $(GOBUILDLOG_FILE); \ 118 | ); \ 119 | rm $@.ok 2>/dev/null || exit 1; \ 120 | if [ -f /tmp/profile.out ]; then \ 121 | cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \ 122 | rm -f /tmp/profile.out; \ 123 | fi)); done 124 | @mv /tmp/gocoverage $(GOCOVERAGE_FILE) 125 | else 126 | @echo "mode: atomic" > /tmp/gocoverage 127 | @set -e; for dir in $(GOMOD_DIRS); do (set -e; (set -xe; \ 128 | cd $$dir; \ 129 | $(GO) test ./... $(GO_TEST_OPTS) -cover -coverprofile=/tmp/profile.out -covermode=atomic -race); \ 130 | if [ -f /tmp/profile.out ]; then \ 131 | cat /tmp/profile.out | sed "/mode: atomic/d" >> /tmp/gocoverage; \ 132 | rm -f /tmp/profile.out; \ 133 | fi); done 134 | @mv /tmp/gocoverage $(GOCOVERAGE_FILE) 135 | endif 136 | 137 | .PHONY: go.checkdoc 138 | go.checkdoc: 139 | go doc $(first $(GOMOD_DIRS)) 140 | 141 | .PHONY: go.coverfunc 142 | go.coverfunc: go.unittest 143 | go tool cover -func=$(GOCOVERAGE_FILE) | grep -v .pb.go: | grep -v .pb.gw.go: 144 | 145 | .PHONY: go.lint 146 | go.lint: 147 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 148 | cd $$dir; \ 149 | golangci-lint run --verbose ./...; \ 150 | ); done 151 | 152 | .PHONY: go.tidy 153 | go.tidy: 154 | @# tidy dirs with go.mod files 155 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 156 | cd $$dir; \ 157 | $(GO) mod tidy; \ 158 | ); done 159 | 160 | .PHONY: go.depaware-update 161 | go.depaware-update: go.tidy 162 | @# gen depaware for bins 163 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 164 | cd $$dir; \ 165 | $(GO) run github.com/tailscale/depaware --update .; \ 166 | ); done 167 | @# tidy unused depaware deps if not in a tools_test.go file 168 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 169 | cd $$dir; \ 170 | $(GO) mod tidy; \ 171 | ); done 172 | 173 | .PHONY: go.depaware-check 174 | go.depaware-check: go.tidy 175 | @# gen depaware for bins 176 | @set -e; for dir in $(GOBINS); do ( set -xe; \ 177 | cd $$dir; \ 178 | $(GO) run github.com/tailscale/depaware --check .; \ 179 | ); done 180 | 181 | 182 | .PHONY: go.build 183 | go.build: 184 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 185 | cd $$dir; \ 186 | $(GO) build ./...; \ 187 | ); done 188 | 189 | .PHONY: go.bump-deps 190 | go.bumpdeps: 191 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 192 | cd $$dir; \ 193 | $(GO) get -u ./...; \ 194 | ); done 195 | 196 | .PHONY: go.bump-deps 197 | go.fmt: 198 | @set -e; for dir in $(GOMOD_DIRS); do ( set -xe; \ 199 | cd $$dir; \ 200 | $(GO) run golang.org/x/tools/cmd/goimports -w `go list -f '{{.Dir}}' ./...` \ 201 | ); done 202 | 203 | VERIFY_STEPS += go.depaware-check 204 | BUILD_STEPS += go.build 205 | BUMPDEPS_STEPS += go.bumpdeps go.depaware-update 206 | TIDY_STEPS += go.tidy 207 | LINT_STEPS += go.lint 208 | UNITTEST_STEPS += go.unittest 209 | FMT_STEPS += go.fmt 210 | 211 | # FIXME: disabled, because currently slow 212 | # new rule that is manually run sometimes, i.e. `make pre-release` or `make maintenance`. 213 | # alternative: run it each time the go.mod is changed 214 | #GENERATE_STEPS += go.depaware-update 215 | endif 216 | 217 | ## 218 | ## Gitattributes 219 | ## 220 | 221 | ifneq ($(wildcard .gitattributes),) 222 | .PHONY: _linguist-ignored 223 | _linguist-kept: 224 | @git check-attr linguist-vendored $(shell git check-attr linguist-generated $(shell find . -type f | grep -v .git/) | grep unspecified | cut -d: -f1) | grep unspecified | cut -d: -f1 | sort 225 | 226 | .PHONY: _linguist-kept 227 | _linguist-ignored: 228 | @git check-attr linguist-vendored linguist-ignored `find . -not -path './.git/*' -type f` | grep '\ set$$' | cut -d: -f1 | sort -u 229 | endif 230 | 231 | ## 232 | ## Node 233 | ## 234 | 235 | ifndef NPM_PACKAGES 236 | ifneq ($(wildcard package.json),) 237 | NPM_PACKAGES = . 238 | endif 239 | endif 240 | ifdef NPM_PACKAGES 241 | .PHONY: npm.publish 242 | npm.publish: 243 | @echo -n "Do you want to npm publish? [y/N] " && read ans && \ 244 | @if [ $${ans:-N} = y ]; then \ 245 | set -e; for dir in $(NPM_PACKAGES); do ( set -xe; \ 246 | cd $$dir; \ 247 | npm publish --access=public; \ 248 | ); done; \ 249 | fi 250 | RELEASE_STEPS += npm.publish 251 | endif 252 | 253 | ## 254 | ## Docker 255 | ## 256 | 257 | docker_build = docker build \ 258 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 259 | --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 260 | --build-arg VERSION=`git describe --tags --always` \ 261 | -t "$2" -f "$1" "$(dir $1)" 262 | 263 | ifndef DOCKERFILE_PATH 264 | DOCKERFILE_PATH = ./Dockerfile 265 | endif 266 | ifndef DOCKER_IMAGE 267 | ifneq ($(wildcard Dockerfile),) 268 | DOCKER_IMAGE = $(notdir $(PWD)) 269 | endif 270 | endif 271 | ifdef DOCKER_IMAGE 272 | ifneq ($(DOCKER_IMAGE),none) 273 | .PHONY: docker.build 274 | docker.build: 275 | $(call check-program, docker) 276 | $(call docker_build,$(DOCKERFILE_PATH),$(DOCKER_IMAGE)) 277 | 278 | BUILD_STEPS += docker.build 279 | endif 280 | endif 281 | 282 | ## 283 | ## Common 284 | ## 285 | 286 | TEST_STEPS += $(UNITTEST_STEPS) 287 | TEST_STEPS += $(LINT_STEPS) 288 | TEST_STEPS += $(TIDY_STEPS) 289 | 290 | ifneq ($(strip $(TEST_STEPS)),) 291 | .PHONY: test 292 | test: $(PRE_TEST_STEPS) $(TEST_STEPS) 293 | endif 294 | 295 | ifdef INSTALL_STEPS 296 | .PHONY: install 297 | install: $(PRE_INSTALL_STEPS) $(INSTALL_STEPS) 298 | endif 299 | 300 | ifdef UNITTEST_STEPS 301 | .PHONY: unittest 302 | unittest: $(PRE_UNITTEST_STEPS) $(UNITTEST_STEPS) 303 | endif 304 | 305 | ifdef LINT_STEPS 306 | .PHONY: lint 307 | lint: $(PRE_LINT_STEPS) $(FMT_STEPS) $(LINT_STEPS) 308 | endif 309 | 310 | ifdef TIDY_STEPS 311 | .PHONY: tidy 312 | tidy: $(PRE_TIDY_STEPS) $(TIDY_STEPS) 313 | endif 314 | 315 | ifdef BUILD_STEPS 316 | .PHONY: build 317 | build: $(PRE_BUILD_STEPS) $(BUILD_STEPS) 318 | endif 319 | 320 | ifdef VERIFY_STEPS 321 | .PHONY: verify 322 | verify: $(PRE_VERIFY_STEPS) $(VERIFY_STEPS) 323 | endif 324 | 325 | ifdef RELEASE_STEPS 326 | .PHONY: release 327 | release: $(PRE_RELEASE_STEPS) $(RELEASE_STEPS) 328 | endif 329 | 330 | ifdef BUMPDEPS_STEPS 331 | .PHONY: bumpdeps 332 | bumpdeps: $(PRE_BUMDEPS_STEPS) $(BUMPDEPS_STEPS) 333 | endif 334 | 335 | ifdef FMT_STEPS 336 | .PHONY: fmt 337 | fmt: $(PRE_FMT_STEPS) $(FMT_STEPS) 338 | endif 339 | 340 | ifdef GENERATE_STEPS 341 | .PHONY: generate 342 | generate: $(PRE_GENERATE_STEPS) $(GENERATE_STEPS) 343 | endif 344 | 345 | .PHONY: help 346 | help:: 347 | @echo "General commands:" 348 | @[ "$(BUILD_STEPS)" != "" ] && echo " build" || true 349 | @[ "$(BUMPDEPS_STEPS)" != "" ] && echo " bumpdeps" || true 350 | @[ "$(FMT_STEPS)" != "" ] && echo " fmt" || true 351 | @[ "$(GENERATE_STEPS)" != "" ] && echo " generate" || true 352 | @[ "$(INSTALL_STEPS)" != "" ] && echo " install" || true 353 | @[ "$(LINT_STEPS)" != "" ] && echo " lint" || true 354 | @[ "$(RELEASE_STEPS)" != "" ] && echo " release" || true 355 | @[ "$(TEST_STEPS)" != "" ] && echo " test" || true 356 | @[ "$(TIDY_STEPS)" != "" ] && echo " tidy" || true 357 | @[ "$(UNITTEST_STEPS)" != "" ] && echo " unittest" || true 358 | @[ "$(VERIFY_STEPS)" != "" ] && echo " verify" || true 359 | @# FIXME: list other commands 360 | 361 | print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true 362 | --------------------------------------------------------------------------------