diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..7f19ed9b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Community Support + url: https://github.com/knqyf263/pet/discussions + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/privileged.yml b/.github/ISSUE_TEMPLATE/privileged.yml new file mode 100644 index 00000000..a72214bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/privileged.yml @@ -0,0 +1,27 @@ +name: Privileged +description: You were directed from Github discussions to create an issue in Pet 👇 +body: + - type: markdown + attributes: + value: | + ## Thanks! + Thanks for your interest in Pet! 🚀 + If you are were not directed to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/knqyf263/pet/discussions/categories/questions) instead! + + ## Got a PR? + If you want to create a PR, share the idea first in [Github discussions](https://github.com/knqyf263/pet/discussions)! đŸ’Ŧ + This will give you ⚡ī¸ quick feedback ⚡ī¸ before you've spent time on it, instead of waiting for a much longer review process AFTER you've done the work. + - type: checkboxes + id: privileged + attributes: + label: Privileged issue + description: Confirm that you were directed to create an issue here. + options: + - label: I've been directed through Discussions to create an issue here. + required: true + - type: textarea + id: content + attributes: + label: Issue Content + description: Add the content of the issue here. + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..a83ef385 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: / + schedule: + interval: daily diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..88483f35 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,82 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..629644b6 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,29 @@ +name: Release +on: + push: + tags: + - "v*" +jobs: + release: + name: Release + runs-on: ubuntu-20.04 + steps: + - name: Install dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install rpm + - name: Checkout code + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Release + uses: goreleaser/goreleaser-action@v5 + with: + version: v1.18.2 + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.PET_PAT }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..87d49230 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +name: Test +on: + push: + branches: + - main + paths-ignore: + - '*.md' + - 'doc/**' + - 'LICENSE' + pull_request: +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.1 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Run unit tests + run: make test diff --git a/.goreleaser.yml b/.goreleaser.yml index 740f125a..f61c3ee7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,22 +3,23 @@ release: owner: knqyf263 name: pet name_template: '{{.Tag}}' -brew: - github: - owner: knqyf263 - name: homebrew-pet - commit_author: - name: goreleaserbot - email: goreleaser@carlosbecker.com - description: "Simple command-line snippet manager" - homepage: "https://github.com/knqyf263/pet" - dependencies: - - fzf - install: | - bin.install Dir['pet'] - zsh_completion.install "misc/completions/zsh/_pet" - test: | - system "#{bin}/pet" +brews: + - + tap: + owner: knqyf263 + name: homebrew-pet + commit_author: + name: goreleaserbot + email: goreleaser@carlosbecker.com + description: "Simple command-line snippet manager" + homepage: "https://github.com/knqyf263/pet" + dependencies: + - fzf + install: | + bin.install Dir['pet'] + zsh_completion.install "misc/completions/zsh/_pet" + test: | + system "#{bin}/pet" builds: - goos: - linux @@ -26,28 +27,32 @@ builds: - windows goarch: - amd64 + - arm + - arm64 - "386" goarm: - "6" main: . ldflags: -s -w -X github.com/knqyf263/pet/cmd.version={{.Version}} -archive: - format: tar.gz - name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ - .Arm }}{{ end }}' - files: - - LICENSE* - - README* - - CHANGELOG* - - misc/completions/zsh/_pet -nfpm: - homepage: https://github.com/knqyf263/pet - maintainer: Teppei Fukuda - description: "Simple command-line snippet manager" - bindir: /usr/local/bin - license: MIT - formats: - - deb - - rpm +archives: + - + format: tar.gz + name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ + .Arm }}{{ end }}' + files: + - LICENSE* + - README* + - CHANGELOG* + - misc/completions/zsh/_pet +nfpms: + - + homepage: https://github.com/knqyf263/pet + maintainer: Teppei Fukuda + description: "Simple command-line snippet manager" + bindir: /usr/local/bin + license: MIT + formats: + - deb + - rpm checksum: name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 382a383d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: go - -sudo: false -addons: - apt: - packages: - - ruby - - ruby-dev - - build-essential - - rpm - -go: - - "1.12.x" - -script: - - env GO111MODULE=on go mod vendor - - env GO111MODULE=on go build -install: true -deploy: -- provider: script - skip_cleanup: true - script: curl -sL https://git.io/goreleaser | bash - on: - tags: true - condition: $TRAVIS_OS_NAME = linux diff --git a/Makefile b/Makefile index 863f4e6f..a59f6c1f 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,21 @@ .PHONY: \ - all \ dep \ - depup \ - update \ - build \ install \ - lint \ + build \ vet \ - fmt \ - fmtcheck \ - clean \ - pretest \ test -VERSION := $(shell git describe --tags --abbrev=0) -SRCS = $(shell git ls-files '*.go') -PKGS = $(shell go list ./... | grep -v /vendor/) - -all: dep build test - dep: - go get -u github.com/golang/dep/... - dep ensure -vendor-only - -depup: - go get -u github.com/golang/dep/... - dep ensure -u + go mod download -build: main.go dep +build: main.go go build -o pet $< -install: main.go dep +install: main.go go install -lint: - @ go get -v github.com/golang/lint/golint - $(foreach file,$(SRCS),golint $(file) || exit;) +test: + go test ./... vet: - go vet $(PKGS) || exit; - -fmt: - gofmt -w $(SRCS) - -fmtcheck: - @ $(foreach file,$(SRCS),gofmt -s -l $(file);) - -clean: - go clean $(shell glide nv) - -pretest: vet fmtcheck - -test: pretest - go install - @ $(foreach pkg,$(PKGS), go test $(pkg) || exit;) + go vet diff --git a/README.md b/README.md index 943f36a9..3661d015 100644 --- a/README.md +++ b/README.md @@ -35,34 +35,35 @@ So I made it possible to register snippets with description and search them easi # TOC - [Main features](#main-features) +- [Parameters] (#parameters) - [Examples](#examples) - - [Register the previous command easily](#register-the-previous-command-easily) - - [bash](#bash-prev-function) - - [zsh](#zsh-prev-function) - - [fish](#fish) - - [Select snippets at the current line (like C-r)](#select-snippets-at-the-current-line-like-c-r) - - [bash](#bash) - - [zsh](#zsh) - - [fish](#fish-1) - - [Copy snippets to clipboard](#copy-snippets-to-clipboard) + - [Register the previous command easily](#register-the-previous-command-easily) + - [bash](#bash-prev-function) + - [zsh](#zsh-prev-function) + - [fish](#fish) + - [Select snippets at the current line (like C-r) (RECOMMENDED)](#select-snippets-at-the-current-line-like-c-r-recommended) + - [bash](#bash) + - [zsh](#zsh) + - [fish](#fish-1) + - [Copy snippets to clipboard](#copy-snippets-to-clipboard) - [Features](#features) - - [Edit snippets](#edit-snippets) - - [Sync snippets](#sync-snippets) + - [Edit snippets](#edit-snippets) + - [Sync snippets](#sync-snippets) - [Hands-on Tutorial](#hands-on-tutorial) - [Usage](#usage) - [Snippet](#snippet) - [Configuration](#configuration) - - [Selector option](#selector-option) - - [Tag](#tag) - - [Sync](#sync) - - [Auto Sync](#auto-sync) + - [Selector option](#selector-option) + - [Tag](#tag) + - [Sync](#sync) + - [Auto Sync](#auto-sync) - [Installation](#installation) - - [Binary](#binary) - - [Mac OS X / Homebrew](#mac-os-x--homebrew) - - [RedHat, CentOS](#redhat-centos) - - [Debian, Ubuntu](#debian-ubuntu) - - [Archlinux](#archlinux) - - [Build](#build) + - [Binary](#binary) + - [Mac OS X / Homebrew](#mac-os-x--homebrew) + - [RedHat, CentOS](#redhat-centos) + - [Debian, Ubuntu](#debian-ubuntu) + - [Archlinux](#archlinux) + - [Build](#build) - [Migration](#migration) - [Contribute](#contribute) @@ -70,12 +71,27 @@ So I made it possible to register snippets with description and search them easi `pet` has the following features. - Register your command snippets easily. -- Use variables in snippets. +- Use variables (with one or several default values) in snippets. - Search snippets interactively. - Run snippets directly. - Edit snippets easily (config is just a TOML file). - Sync snippets via Gist or GitLab Snippets automatically. +# Parameters +There are `` ways of entering parameters. + +They can contain default values: Hello `` +defined by the equal sign. + +They can even contain `` where the default value would be \spaces & = signs\>. + +Default values just can't \. + +They can also contain multiple default values: +Hello `` + +The values in this case would be :Hello \John\_\|\|\_Sam\_\|\|\_Jane Doe = special #chars\_\|\> + # Examples Some examples are shown below. @@ -107,10 +123,12 @@ https://github.com/otms61/fish-pet -## Select snippets at the current line (like C-r) +## Select snippets at the current line (like C-r) (RECOMMENDED) ### bash By adding the following config to `.bashrc`, you can search snippets and output on the shell. +This will also allow you to execute the commands yourself, which will add them to your shell history! This is basically the only way we can manipulate shell history. +This also allows you to *chain* commands! [Example here](https://github.com/knqyf263/pet/discussions/266) ``` $ cat .bashrc @@ -227,6 +245,7 @@ Run `pet configure` selectcmd = "fzf" # selector command for edit command (fzf or peco) backend = "gist" # specify backend service to sync snippets (gist or gitlab, default: gist) sortby = "description" # specify how snippets get sorted (recency (default), -recency, description, -description, command, -command, output, -output) + cmd = ["sh", "-c"] # specify the command to execute the snippet with [Gist] file_name = "pet-snippet.toml" # specify gist file name @@ -290,6 +309,14 @@ $ pet search [ping]: ping 8.8.8.8 #network #google ``` +You can exec snipet with filtering the tag + +``` +$ pet exec -t google + +[ping]: ping 8.8.8.8 #network #google +``` + ## Sync ### Gist You must obtain access token. @@ -323,8 +350,12 @@ Upload success ### GitLab Snippets You must obtain access token. -Go https://gitlab.com/profile/personal_access_tokens and create access token. -Set that to `access_token` in `[GitLab]` or use an environment variable with the name `$PET_GITLAB_ACCESS_TOKEN`.. +Go https://gitlab.com/-/profile/personal_access_tokens and create access token. +Set that to `access_token` in `[GitLab]` or use an environment variable with the name `$PET_GITLAB_ACCESS_TOKEN`. + +You also have to configure the `url` under `[GitLab]`, so pet knows which endpoint to access. You would use `url = "https://gitlab.com"`unless you have another instance of Gitlab. + +At last, switch the `backend` under `[General]` to `backend = "gitlab"`. After setting, you can upload snippets to GitLab Snippets. If `id` is not set, new snippet will be created. @@ -391,8 +422,8 @@ $ sudo rpm -ivh https://github.com/knqyf263/pet/releases/download/v0.3.0/pet_0.3 ## Debian, Ubuntu Download deb package from [the releases page](https://github.com/knqyf263/pet/releases) ``` -$ wget https://github.com/knqyf263/pet/releases/download/v0.3.0/pet_0.3.0_linux_amd64.deb -dpkg -i pet_0.3.0_linux_amd64.deb +$ wget https://github.com/knqyf263/pet/releases/download/v0.3.6/pet_0.3.6_linux_amd64.deb +dpkg -i pet_0.3.6_linux_amd64.deb ``` ## Archlinux diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..124f8e0c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +## Reporting a Vulnerability + +If you discover a potential security issue in this project we ask that you notify our maintainers via email to rami.awar.ra {at} gmail.com or knqyf263 {at} gmail.com + +Please do **not** create a public github issue. \ No newline at end of file diff --git a/cmd/clip.go b/cmd/clip.go new file mode 100644 index 00000000..6f448406 --- /dev/null +++ b/cmd/clip.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/atotto/clipboard" + "github.com/fatih/color" + "github.com/knqyf263/pet/config" + "github.com/spf13/cobra" +) + +// clipCmd represents the clip command +var clipCmd = &cobra.Command{ + Use: "clip", + Short: "Copy the selected commands", + Long: `Copy the selected commands to clipboard`, + RunE: clip, +} + +func clip(cmd *cobra.Command, args []string) (err error) { + flag := config.Flag + + var options []string + if flag.Query != "" { + options = append(options, fmt.Sprintf("--query %s", flag.Query)) + } + + commands, err := filter(options, flag.FilterTag) + if err != nil { + return err + } + command := strings.Join(commands, flag.Delimiter) + if flag.Command && command != "" { + fmt.Printf("%s: %s\n", color.YellowString("Command"), command) + } + return clipboard.WriteAll(command) +} + +func init() { + RootCmd.AddCommand(clipCmd) + clipCmd.Flags().StringVarP(&config.Flag.Query, "query", "q", "", + `Initial value for query`) + clipCmd.Flags().BoolVarP(&config.Flag.Command, "command", "", false, + `Display snippets in one line`) + clipCmd.Flags().StringVarP(&config.Flag.Delimiter, "delimiter", "d", "; ", + `Use delim as the command delimiter character`) + clipCmd.Flags().StringVarP(&config.Flag.FilterTag, "tag", "t", "", + `Filter tag`) +} diff --git a/cmd/edit.go b/cmd/edit.go index 6148c8a1..0929a955 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -1,7 +1,7 @@ package cmd import ( - "io/ioutil" + "os" "github.com/knqyf263/pet/config" petSync "github.com/knqyf263/pet/sync" @@ -44,7 +44,7 @@ func edit(cmd *cobra.Command, args []string) (err error) { } func fileContent(fname string) string { - data, _ := ioutil.ReadFile(fname) + data, _ := os.ReadFile(fname) return string(data) } diff --git a/cmd/exec.go b/cmd/exec.go index 4189a6df..022bf188 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -7,9 +7,9 @@ import ( "strings" "syscall" - "github.com/fatih/color" "github.com/knqyf263/pet/config" "github.com/spf13/cobra" + "gopkg.in/alessio/shellescape.v1" ) // execCmd represents the exec command @@ -25,22 +25,19 @@ func execute(cmd *cobra.Command, args []string) (err error) { var options []string if flag.Query != "" { - options = append(options, fmt.Sprintf("--query %s", flag.Query)) + options = append(options, fmt.Sprintf("--query %s", shellescape.Quote(flag.Query))) } - commands, err := filter(options) + commands, err := filter(options, flag.FilterTag) if err != nil { return err } command := strings.Join(commands, "; ") - if config.Flag.Debug { - fmt.Printf("Command: %s\n", command) - } - if config.Flag.Command { - fmt.Printf("%s: %s\n", color.YellowString("Command"), command) - } + // Show final command before executing it + fmt.Printf("> %s\n", command) signal.Ignore(syscall.SIGINT) + return run(command, os.Stdin, os.Stdout) } @@ -50,6 +47,6 @@ func init() { `Enable colorized output (only fzf)`) execCmd.Flags().StringVarP(&config.Flag.Query, "query", "q", "", `Initial value for query`) - execCmd.Flags().BoolVarP(&config.Flag.Command, "command", "c", false, - `Show the command with the plain text before executing`) + execCmd.Flags().StringVarP(&config.Flag.FilterTag, "tag", "t", "", + `Filter tag`) } diff --git a/cmd/list.go b/cmd/list.go index f749a791..75ed3e02 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -41,32 +41,32 @@ func list(cmd *cobra.Command, args []string) error { // make sure multiline command printed as oneline command = strings.Replace(command, "\n", "\\n", -1) fmt.Fprintf(color.Output, "%s : %s\n", - color.GreenString(description), color.YellowString(command)) + color.HiGreenString(description), color.HiYellowString(command)) } else { fmt.Fprintf(color.Output, "%12s %s\n", - color.GreenString("Description:"), snippet.Description) + color.HiGreenString("Description:"), snippet.Description) if strings.Contains(snippet.Command, "\n") { lines := strings.Split(snippet.Command, "\n") firstLine, restLines := lines[0], lines[1:] fmt.Fprintf(color.Output, "%12s %s\n", - color.YellowString(" Command:"), firstLine) + color.HiYellowString(" Command:"), firstLine) for _, line := range restLines { fmt.Fprintf(color.Output, "%12s %s\n", " ", line) } } else { fmt.Fprintf(color.Output, "%12s %s\n", - color.YellowString(" Command:"), snippet.Command) + color.HiYellowString(" Command:"), snippet.Command) } if snippet.Tag != nil { tag := strings.Join(snippet.Tag, " ") fmt.Fprintf(color.Output, "%12s %s\n", - color.CyanString(" Tag:"), tag) + color.HiCyanString(" Tag:"), tag) } if snippet.Output != "" { output := strings.Replace(snippet.Output, "\n", "\n ", -1) fmt.Fprintf(color.Output, "%12s %s\n", - color.RedString(" Output:"), output) + color.HiRedString(" Output:"), output) } fmt.Println(strings.Repeat("-", 30)) } diff --git a/cmd/new.go b/cmd/new.go index 2369d44e..5e15e3d6 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -5,8 +5,6 @@ import ( "fmt" "io" "os" - "path/filepath" - "runtime" "strings" "github.com/chzyer/readline" @@ -25,13 +23,21 @@ var newCmd = &cobra.Command{ RunE: new, } -func scan(message string) (string, error) { - tempFile := "/tmp/pet.tmp" - if runtime.GOOS == "windows" { - tempDir := os.Getenv("TEMP") - tempFile = filepath.Join(tempDir, "pet.tmp") +func CanceledError() error { + return errors.New("canceled") +} + +func scan(message string, out io.Writer, in io.ReadCloser, allowEmpty bool) (string, error) { + f, err := os.CreateTemp("", "pet-") + if err != nil { + return "", err } + defer os.Remove(f.Name()) // clean up temp file + tempFile := f.Name() + l, err := readline.NewEx(&readline.Config{ + Stdout: out, + Stdin: in, Prompt: message, HistoryFile: tempFile, InterruptPrompt: "^C", @@ -39,6 +45,7 @@ func scan(message string) (string, error) { HistorySearchFold: true, }) + if err != nil { return "", err } @@ -56,13 +63,16 @@ func scan(message string) (string, error) { break } + // If empty string, just ignore tags line = strings.TrimSpace(line) - if line == "" { + if line == "" && !allowEmpty { continue + } else if line == "" { + return "", nil } return line, nil } - return "", errors.New("canceled") + return "", CanceledError() } func new(cmd *cobra.Command, args []string) (err error) { @@ -77,29 +87,32 @@ func new(cmd *cobra.Command, args []string) (err error) { if len(args) > 0 { command = strings.Join(args, " ") - fmt.Fprintf(color.Output, "%s %s\n", color.YellowString("Command>"), command) + fmt.Fprintf(color.Output, "%s %s\n", color.HiYellowString("Command>"), command) } else { - command, err = scan(color.YellowString("Command> ")) + command, err = scan(color.HiYellowString("Command> "), os.Stdout, os.Stdin, false) if err != nil { return err } } - description, err = scan(color.GreenString("Description> ")) + description, err = scan(color.HiGreenString("Description> "), os.Stdout, os.Stdin, false) if err != nil { return err } if config.Flag.Tag { var t string - if t, err = scan(color.CyanString("Tag> ")); err != nil { + if t, err = scan(color.HiCyanString("Tag> "), os.Stdout, os.Stdin, true); err != nil { return err } - tags = strings.Fields(t) + + if t != "" { + tags = strings.Fields(t) + } } for _, s := range snippets.Snippets { if s.Description == description { - return fmt.Errorf("Snippet [%s] already exists", description) + return fmt.Errorf("snippet [%s] already exists", description) } } diff --git a/cmd/new_test.go b/cmd/new_test.go new file mode 100644 index 00000000..3a760564 --- /dev/null +++ b/cmd/new_test.go @@ -0,0 +1,101 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +// MockReadCloser is a mock implementation of io.ReadCloser +type MockReadCloser struct { + *strings.Reader +} + +// Close does nothing for this mock implementation +func (m *MockReadCloser) Close() error { + return nil +} + +func TestScan(t *testing.T) { + message := "Enter something: " + + input := "test\n" // Simulated user input + want := "test" // Expected output + expectedError := error(nil) + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input + inputReader := &MockReadCloser{strings.NewReader(input)} + + result, err := scan(message, &outputBuffer, inputReader, false) + + // Check if the input was printed + got := result + + // Check if the result matches the expected result + if want != got { + t.Errorf("Expected result %q, but got %q", want, got) + } + + // Check if the error matches the expected error + if err != expectedError { + t.Errorf("Expected error %v, but got %v", expectedError, err) + } +} + +func TestScan_EmptyStringWithAllowEmpty(t *testing.T) { + message := "Enter something: " + + input := "\n" // Simulated user input + want := "" // Expected output + expectedError := error(nil) + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input + inputReader := &MockReadCloser{strings.NewReader(input)} + + result, err := scan(message, &outputBuffer, inputReader, true) + + // Check if the input was printed + got := result + + // Check if the result matches the expected result + if want != got { + t.Errorf("Expected result %q, but got %q", want, got) + } + + // Check if the error matches the expected error + if err != expectedError { + t.Errorf("Expected error %v, but got %v", expectedError, err) + } +} + +func TestScan_EmptyStringWithoutAllowEmpty(t *testing.T) { + message := "Enter something: " + + input := "\n" // Simulated user input + want := "" // Expected output + expectedError := CanceledError() + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input + inputReader := &MockReadCloser{strings.NewReader(input)} + + result, err := scan(message, &outputBuffer, inputReader, false) + + // Check if the input was printed + got := result + + // Check if the result matches the expected result + if want != got { + t.Errorf("Expected result %q, but got %q", want, got) + } + + // Check if the error matches the expected error + if err.Error() != expectedError.Error() { + t.Errorf("Expected error %v, but got %v", expectedError, err) + } +} diff --git a/cmd/search.go b/cmd/search.go index 04f9ae4d..bc637385 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -7,6 +7,7 @@ import ( "github.com/knqyf263/pet/config" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" + "gopkg.in/alessio/shellescape.v1" ) var delimiter string @@ -24,9 +25,9 @@ func search(cmd *cobra.Command, args []string) (err error) { var options []string if flag.Query != "" { - options = append(options, fmt.Sprintf("--query %s", flag.Query)) + options = append(options, fmt.Sprintf("--query %s", shellescape.Quote(flag.Query))) } - commands, err := filter(options) + commands, err := filter(options, flag.FilterTag) if err != nil { return err } @@ -44,6 +45,8 @@ func init() { `Enable colorized output (only fzf)`) searchCmd.Flags().StringVarP(&config.Flag.Query, "query", "q", "", `Initial value for query`) + searchCmd.Flags().StringVarP(&config.Flag.FilterTag, "tag", "t", "", + `Filter tag`) searchCmd.Flags().StringVarP(&config.Flag.Delimiter, "delimiter", "d", "; ", `Use delim as the command delimiter character`) } diff --git a/cmd/util.go b/cmd/util.go index 1ae676f1..2e25fb72 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -3,10 +3,7 @@ package cmd import ( "bytes" "fmt" - "io" "os" - "os/exec" - "runtime" "strings" "github.com/fatih/color" @@ -20,25 +17,24 @@ func editFile(command, file string) error { return run(command, os.Stdin, os.Stdout) } -func run(command string, r io.Reader, w io.Writer) error { - var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("cmd", "/c", command) - } else { - cmd = exec.Command("sh", "-c", command) - } - cmd.Stderr = os.Stderr - cmd.Stdout = w - cmd.Stdin = r - return cmd.Run() -} - -func filter(options []string) (commands []string, err error) { +func filter(options []string, tag string) (commands []string, err error) { var snippets snippet.Snippets if err := snippets.Load(); err != nil { return commands, fmt.Errorf("Load snippet failed: %v", err) } + if 0 < len(tag) { + var filteredSnippets snippet.Snippets + for _, snippet := range snippets.Snippets { + for _, t := range snippet.Tag { + if tag == t { + filteredSnippets.Snippets = append(filteredSnippets.Snippets, snippet) + } + } + } + snippets = filteredSnippets + } + snippetTexts := map[string]snippet.SnippetInfo{} var text string for _, s := range snippets.Snippets { @@ -57,7 +53,7 @@ func filter(options []string) (commands []string, err error) { snippetTexts[t] = s if config.Flag.Color { t = fmt.Sprintf("[%s]: %s%s", - color.RedString(s.Description), command, color.BlueString(tags)) + color.HiRedString(s.Description), command, color.HiCyanString(tags)) } text += t + "\n" } @@ -71,8 +67,16 @@ func filter(options []string) (commands []string, err error) { } lines := strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n") + var params [][2]string + + // If only one line is selected, search for params in the command + if len(lines) == 1 { + snippetInfo := snippetTexts[lines[0]] + params = dialog.SearchForParams(snippetInfo.Command) + } else { + params = nil + } - params := dialog.SearchForParams(lines) if params != nil { snippetInfo := snippetTexts[lines[0]] dialog.CurrentCommand = snippetInfo.Command diff --git a/cmd/util_unix.go b/cmd/util_unix.go new file mode 100644 index 00000000..12ac3c0f --- /dev/null +++ b/cmd/util_unix.go @@ -0,0 +1,25 @@ +//go:build !windows + +package cmd + +import ( + "io" + "os" + "os/exec" + + "github.com/knqyf263/pet/config" +) + +func run(command string, r io.Reader, w io.Writer) error { + var cmd *exec.Cmd + if len(config.Conf.General.Cmd) > 0 { + line := append(config.Conf.General.Cmd, command) + cmd = exec.Command(line[0], line[1:]...) + } else { + cmd = exec.Command("sh", "-c", command) + } + cmd.Stderr = os.Stderr + cmd.Stdout = w + cmd.Stdin = r + return cmd.Run() +} diff --git a/cmd/util_windows.go b/cmd/util_windows.go new file mode 100644 index 00000000..2827fbb2 --- /dev/null +++ b/cmd/util_windows.go @@ -0,0 +1,28 @@ +//go:build windows + +package cmd + +import ( + "fmt" + "io" + "os" + "os/exec" + "syscall" + + "github.com/knqyf263/pet/config" +) + +func run(command string, r io.Reader, w io.Writer) error { + var cmd *exec.Cmd + if len(config.Conf.General.Cmd) > 0 { + line := append(config.Conf.General.Cmd, command) + cmd = exec.Command(line[0], line[1:]...) + } else { + cmd = exec.Command("cmd.exe") + cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: fmt.Sprintf("/c \"%s\"", command)} + } + cmd.Stderr = os.Stderr + cmd.Stdout = w + cmd.Stdin = r + return cmd.Run() +} diff --git a/config/config.go b/config/config.go index a0b8ea32..705f8f99 100644 --- a/config/config.go +++ b/config/config.go @@ -16,19 +16,20 @@ var Conf Config // Config is a struct of config type Config struct { - General GeneralConfig - Gist GistConfig - GitLab GitLabConfig + General GeneralConfig `toml:"General"` + Gist GistConfig `toml:"Gist"` + GitLab GitLabConfig `toml:"GitLab"` } // GeneralConfig is a struct of general config type GeneralConfig struct { - SnippetFile string `toml:"snippetfile"` - Editor string `toml:"editor"` - Column int `toml:"column"` - SelectCmd string `toml:"selectcmd"` - Backend string `toml:"backend"` - SortBy string `toml:"sortby"` + SnippetFile string `toml:"snippetfile"` + Editor string `toml:"editor"` + Column int `toml:"column"` + SelectCmd string `toml:"selectcmd"` + Backend string `toml:"backend"` + SortBy string `toml:"sortby"` + Cmd []string `toml:"cmd"` } // GistConfig is a struct of config for Gist @@ -48,6 +49,7 @@ type GitLabConfig struct { ID string `toml:"id"` Visibility string `toml:"visibility"` AutoSync bool `toml:"auto_sync"` + Insecure bool `toml:"skip_ssl"` } // Flag is global flag variable @@ -57,6 +59,7 @@ var Flag FlagConfig type FlagConfig struct { Debug bool Query string + FilterTag string Command bool Delimiter string OneLine bool @@ -103,7 +106,7 @@ func (cfg *Config) Load(file string) error { } } cfg.General.Column = 40 - cfg.General.SelectCmd = "fzf" + cfg.General.SelectCmd = "fzf --ansi --layout=reverse --border --height=90% --pointer=* --cycle --prompt=Snippets:" cfg.General.Backend = "gist" cfg.Gist.FileName = "pet-snippet.toml" @@ -116,7 +119,9 @@ func (cfg *Config) Load(file string) error { // GetDefaultConfigDir returns the default config directory func GetDefaultConfigDir() (dir string, err error) { - if runtime.GOOS == "windows" { + if env, ok := os.LookupEnv("PET_CONFIG_DIR"); ok { + dir = env + } else if runtime.GOOS == "windows" { dir = os.Getenv("APPDATA") if dir == "" { dir = filepath.Join(os.Getenv("USERPROFILE"), "Application Data", "pet") @@ -125,7 +130,7 @@ func GetDefaultConfigDir() (dir string, err error) { } else { dir = filepath.Join(os.Getenv("HOME"), ".config", "pet") } - if err := os.MkdirAll(dir, 0700); err != nil { + if err := os.MkdirAll(dir, 0o700); err != nil { return "", fmt.Errorf("cannot create directory: %v", err) } return dir, nil diff --git a/dialog/params.go b/dialog/params.go index 8f7e5074..b89ae318 100644 --- a/dialog/params.go +++ b/dialog/params.go @@ -4,52 +4,84 @@ import ( "regexp" "strings" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) var ( - views = []string{} - layoutStep = 3 - curView = -1 - idxView = 0 + views = []string{} //CurrentCommand is the command before assigning to variables CurrentCommand string //FinalCommand is the command after assigning to variables FinalCommand string + + // This matches most encountered patterns + // Skips match if there is a whitespace at the end ex. + // Ignores <, > characters since they're used to match the pattern + parameterStringRegex = `<([^<>]*[^\s])>` ) -func insertParams(command string, params map[string]string) string { +func insertParams(command string, filledInParams map[string]string) string { + r := regexp.MustCompile(parameterStringRegex) + + matches := r.FindAllStringSubmatch(command, -1) + if len(matches) == 0 { + return command + } + resultCommand := command - for k, v := range params { - resultCommand = strings.Replace(resultCommand, k, v, -1) + + // First match is the whole match (with brackets), second is the first group + // Ex. echo + // -> matches[0][0]: + // -> matches[0][1]: param='my param' + for _, p := range matches { + whole, matchedGroup := p[0], p[1] + param, _, _ := strings.Cut(matchedGroup, "=") + + // Replace the whole match with the filled-in value of the param + resultCommand = strings.Replace(resultCommand, whole, filledInParams[param], -1) } + return resultCommand } // SearchForParams returns variables from a command -func SearchForParams(lines []string) map[string]string { - re := `<([\S].+?[\S])>` - if len(lines) == 1 { - r, _ := regexp.Compile(re) - - params := r.FindAllStringSubmatch(lines[0], -1) - if len(params) == 0 { - return nil +func SearchForParams(command string) [][2]string { + r := regexp.MustCompile(parameterStringRegex) + + params := r.FindAllStringSubmatch(command, -1) + if len(params) == 0 { + return nil + } + + extracted := map[string]string{} + ordered_params := [][2]string{} + for _, p := range params { + _, matchedGroup := p[0], p[1] + paramKey, defaultValue, separatorFound := strings.Cut(matchedGroup, "=") + _, param_exists := extracted[paramKey] + + // Set to empty if no value is provided and param is not already set + if !separatorFound && !param_exists { + extracted[paramKey] = "" + } else if separatorFound { + // Set to default value instead if it is provided + extracted[paramKey] = defaultValue } - extracted := map[string]string{} - for _, p := range params { - splitted := strings.Split(p[1], "=") - if len(splitted) == 1 { - extracted[p[0]] = "" - } else { - extracted[p[0]] = splitted[1] - } + // Fill in the keys only if seen for the first time to track order + if !param_exists { + ordered_params = append(ordered_params, [2]string{paramKey, ""}) } - return extracted } - return nil + + // Fill in the values + for i, param := range ordered_params { + pair := [2]string{param[0], extracted[param[0]]} + ordered_params[i] = pair + } + return ordered_params } func evaluateParams(g *gocui.Gui, _ *gocui.View) error { diff --git a/dialog/params_test.go b/dialog/params_test.go new file mode 100644 index 00000000..e99544b9 --- /dev/null +++ b/dialog/params_test.go @@ -0,0 +1,286 @@ +package dialog + +import ( + "testing" + + "github.com/go-test/deep" +) + +func TestSearchForParams(t *testing.T) { + command := " hello" + + want := [][2]string{ + {"a", "1"}, + {"b", ""}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_WithNoParams(t *testing.T) { + command := "no params" + + got := SearchForParams(command) + + if got != nil { + t.Fatalf("wanted nil, got '%v'", got) + } +} + +func TestSearchForParams_WithMultipleParams(t *testing.T) { + command := " " + + want := [][2]string{ + {"a", "1"}, + {"b", ""}, + {"c", "3"}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_WithEmptyCommand(t *testing.T) { + command := "" + + got := SearchForParams(command) + + if got != nil { + t.Fatalf("wanted nil, got '%v'", got) + } +} + +func TestSearchForParams_WithNewline(t *testing.T) { + command := " hello\n" + + want := [][2]string{ + {"a", "1"}, + {"b", ""}, + {"c", "3"}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_ValueWithSpaces(t *testing.T) { + command := "example_function --flag=" + + want := [][2]string{ + {"param", "Lots of Bananas"}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_InvalidParamFormat(t *testing.T) { + command := " hello" + want := [][2]string{ + {"b", ""}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_InvalidParamFormatWithoutSpaces(t *testing.T) { + command := "hello" + want := [][2]string{ + {"b", ""}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_ConfusingBrackets(t *testing.T) { + command := "cat < \nEOF" + want := [][2]string{ + {"file", "path/to/file"}, + } + got := SearchForParams(command) + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKey(t *testing.T) { + command := " " + want := [][2]string{ + {"a", "3"}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKeyDifferentValues(t *testing.T) { + command := " " + want := [][2]string{ + {"a", "3"}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKeyDifferentValues_MultipleLines(t *testing.T) { + command := " \n" + want := [][2]string{ + {"a", "3"}, + {"b", "4"}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKeyDifferentValues_InvalidFormat(t *testing.T) { + command := " " + want := [][2]string{ + {"a", "3"}, + } + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKeyDifferentValues_InvalidFormat_MultipleLines(t *testing.T) { + command := " " + want := [][2]string{ + {"a", "2"}, + {"b", "4"}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleParamsSameKeyDifferentValues_InvalidFormat_MultipleLines2(t *testing.T) { + command := " \n\"" + want := [][2]string{ + {"param", "Hello == World!==="}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestSearchForParams_MultipleDefaultValuesDoNotBreakFunction(t *testing.T) { + command := "echo \" , \"" + want := [][2]string{ + {"param", "|_Hello_||_Hello world_||_How are you?_|"}, + {"second", "Hello"}, + {"third", ""}, + } + + got := SearchForParams(command) + + if diff := deep.Equal(want, got); diff != nil { + t.Fatal(diff) + } +} + +func TestInsertParams(t *testing.T) { + command := " hello" + + params := map[string]string{ + "a": "test", + "b": "case", + } + + got := insertParams(command, params) + want := "test test case hello" + if want != got { + t.Fatalf("wanted '%s', got '%s'", want, got) + } +} + +func TestInsertParams_unique_parameters(t *testing.T) { + command := "curl -X POST \"/\" -H 'Content-Type: application/json'" + + params := map[string]string{ + "host": "localhost:9200", + "index": "test", + } + + got := insertParams(command, params) + want := "curl -X POST \"localhost:9200/test\" -H 'Content-Type: application/json'" + if got != want { + t.Fatalf("got %s, want %s", got, want) + } +} + +func TestInsertParams_complex(t *testing.T) { + command := "something //_delete_by_query/" + + params := map[string]string{ + "host": "localhost:9200", + "test": "case", + } + + got := insertParams(command, params) + want := "something localhost:9200/case/_delete_by_query/localhost:9200" + if got != want { + t.Fatalf("got %s, want %s", got, want) + } +} + +func TestInsertParams_EqualsInDefaultValueIgnored(t *testing.T) { + command := "echo \"\"" + + params := map[string]string{ + "param": "something == something", + } + + got := insertParams(command, params) + want := "echo \"something == something\"" + if got != want { + t.Fatalf("got %s, want %s", got, want) + } +} diff --git a/dialog/view.go b/dialog/view.go index be7d9c74..ff75f115 100644 --- a/dialog/view.go +++ b/dialog/view.go @@ -3,35 +3,109 @@ package dialog import ( "fmt" "log" + "regexp" - "github.com/jroimartin/gocui" + "github.com/awesome-gocui/gocui" ) -func generateView(g *gocui.Gui, desc string, fill string, coords []int, editable bool) error { - if StringInSlice(desc, views) { +var ( + layoutStep = 3 + curView = -1 + + // This is for matching multiple default values in parameters + parameterMultipleValueRegex = `(\|_.*?_\|)` +) + +// createView sets up a new view with the given parameters. +func createView(g *gocui.Gui, name string, coords []int, editable bool) (*gocui.View, error) { + if StringInSlice(name, views) { + return nil, nil + } + + v, err := g.SetView(name, coords[0], coords[1], coords[2], coords[3], 0) + if err != nil && err != gocui.ErrUnknownView { + return nil, err + } + + v.Title = name + v.Wrap = true + v.Autoscroll = true + v.Editable = editable + + views = append(views, name) + + return v, nil +} + +func generateSingleParameterView(g *gocui.Gui, name string, defaultParam string, coords []int, editable bool) error { + view, err := createView(g, name, coords, editable) + if err != nil { + return err + } + + g.SetKeybinding(view.Name(), gocui.KeyCtrlK, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + v.Clear() return nil + }) + + fmt.Fprint(view, defaultParam) + return nil +} + +func generateMultipleParameterView(g *gocui.Gui, name string, defaultParams []string, coords []int, editable bool) error { + view, err := createView(g, name, coords, editable) + if err != nil { + return err + } + + currentOpt := 0 + maxOpt := len(defaultParams) + + fmt.Fprint(view, defaultParams[currentOpt]) + + viewTitle := name + // Adjust view title to hint the user about the available + // options if there are more than one + if maxOpt > 1 { + viewTitle = name + " (UP/DOWN => Select default value)" } - if v, err := g.SetView(desc, coords[0], coords[1], coords[2], coords[3]); err != nil { - if err != gocui.ErrUnknownView { - return err + + view.Title = viewTitle + + g.SetKeybinding(view.Name(), gocui.KeyArrowDown, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + if maxOpt == 0 { + return nil } - fmt.Fprint(v, fill) - } - view, _ := g.View(desc) - view.Title = desc - view.Wrap = false - view.Autoscroll = true - view.Editable = editable + next := currentOpt + 1 + if next >= maxOpt { + next = 0 + } + v.Clear() + fmt.Fprint(v, defaultParams[next]) + currentOpt = next + return nil + }) - views = append(views, desc) - idxView++ + g.SetKeybinding(view.Name(), gocui.KeyArrowUp, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + if maxOpt == 0 { + return nil + } + prev := currentOpt - 1 + if prev < 0 { + prev = maxOpt - 1 + } + v.Clear() + fmt.Fprint(v, defaultParams[prev]) + currentOpt = prev + return nil + }) return nil } // GenerateParamsLayout generates CUI to receive params -func GenerateParamsLayout(params map[string]string, command string) { - g, err := gocui.NewGui(gocui.OutputNormal) +func GenerateParamsLayout(params [][2]string, command string) { + g, err := gocui.NewGui(gocui.OutputNormal, false) if err != nil { log.Panicln(err) } @@ -44,12 +118,48 @@ func GenerateParamsLayout(params map[string]string, command string) { g.SetManagerFunc(layout) maxX, maxY := g.Size() - generateView(g, "Command(TAB => Select next, ENTER => Execute command):", - command, []int{maxX / 10, maxY / 10, (maxX / 2) + (maxX / 3), maxY/10 + 5}, false) + leftX := (maxX / 2) - (maxX / 3) + rightX := (maxX / 2) + (maxX / 3) + + generateSingleParameterView(g, "Command(TAB => Select next, ENTER => Execute command):", + command, []int{leftX, maxY / 10, rightX, maxY/10 + 5}, false) idx := 0 - for k, v := range params { - generateView(g, k, v, []int{maxX / 10, (maxY / 4) + (idx+1)*layoutStep, - maxX/10 + 20, (maxY / 4) + 2 + (idx+1)*layoutStep}, true) + + // Create a view for each param + for _, pair := range params { + // Unpack parameter key and value + parameterKey, parameterValue := pair[0], pair[1] + + // Check value for multiple defaults + r := regexp.MustCompile(parameterMultipleValueRegex) + matches := r.FindAllStringSubmatch(parameterValue, -1) + + if len(matches) > 0 { + // Extract the default values and generate multiple params view + parameters := []string{} + for _, p := range matches { + _, matchedGroup := p[0], p[1] + // Remove the separators + matchedGroup = matchedGroup[2 : len(matchedGroup)-2] + parameters = append(parameters, matchedGroup) + } + generateMultipleParameterView( + g, parameterKey, parameters, []int{ + leftX, + (maxY / 4) + (idx+1)*layoutStep, + rightX, + (maxY / 4) + 2 + (idx+1)*layoutStep}, + true) + } else { + // Generate single param view using the single value + generateSingleParameterView(g, parameterKey, parameterValue, + []int{ + leftX, + (maxY / 4) + (idx+1)*layoutStep, + rightX, + (maxY / 4) + 2 + (idx+1)*layoutStep}, + true) + } idx++ } diff --git a/go.mod b/go.mod index ef8a6583..1c999144 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,50 @@ module github.com/knqyf263/pet -go 1.16 +go 1.20 require ( github.com/BurntSushi/toml v0.3.0 + github.com/atotto/clipboard v0.1.4 github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5 + github.com/chzyer/logex v1.1.10 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20210722231415-061457976a23 // indirect github.com/fatih/color v1.7.0 - github.com/golang/protobuf v1.1.0 // indirect github.com/google/go-github v15.0.0+incompatible - github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jroimartin/gocui v0.4.0 - github.com/mattn/go-colorable v0.0.9 // indirect - github.com/mattn/go-isatty v0.0.3 // indirect - github.com/mattn/go-runewidth v0.0.2 - github.com/nsf/termbox-go v0.0.0-20180509163535-21a4d435a862 // indirect + github.com/mattn/go-runewidth v0.0.10 github.com/pkg/errors v0.8.0 github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.1 // indirect - github.com/xanzy/go-gitlab v0.10.5 - golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 - golang.org/x/net v0.0.0-20180530234432-1e491301e022 // indirect - golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a - golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect - google.golang.org/appengine v1.0.0 // indirect + github.com/xanzy/go-gitlab v0.50.3 + //github.com/xanzy/go-gitlab v0.10.5 + golang.org/x/crypto v0.17.0 + golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 +) + +require ( + github.com/awesome-gocui/gocui v1.1.0 + github.com/go-test/deep v1.1.0 +) + +require ( + github.com/alessio/shellescape v1.4.1 // indirect + github.com/gdamore/encoding v1.0.0 // indirect + github.com/gdamore/tcell/v2 v2.4.0 // indirect + github.com/golang/protobuf v1.2.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect + github.com/hashicorp/go-retryablehttp v0.6.8 // indirect + github.com/lucasb-eyer/go-colorful v1.0.3 // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.3 // indirect + github.com/rivo/uniseg v0.1.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect + google.golang.org/appengine v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 5fb81225..9a82fced 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,102 @@ github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII= +github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg= github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5 h1:osZyZB7J4kE1tKLeaUjV6+uZVBfS835T0I/RxmwWw1w= github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5/go.mod h1:hw/JEQBIE+c/BLI4aKM8UU8v+ZqrD3h7HC27kKt8JQU= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= +github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-github v15.0.0+incompatible h1:jlPg2Cpsxb/FyEV/MFiIE9tW/2RAevQNZDPeHbf5a94= github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= -github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/nsf/termbox-go v0.0.0-20180509163535-21a4d435a862 h1:LspjgiiJb/DZ7UVtzoAdjGX29eb6VQwFWGgdC6fguB4= -github.com/nsf/termbox-go v0.0.0-20180509163535-21a4d435a862/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/xanzy/go-gitlab v0.10.5 h1:/Bk9oLs2cHJNJixzLvldXqS1zsfJAnnyHY3e6b28Ba4= -github.com/xanzy/go-gitlab v0.10.5/go.mod h1:CRKHkvFWNU6C3AEfqLWjnCNnAs4nj8Zk95rX2S3X6Mw= -golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 h1:wviDUSmtheHRBfoY8B9U8ELl2USoXi2YFwdGdpIIkzI= -golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180530234432-1e491301e022 h1:MVYFTUmVD3/+ERcvRRI+P/C2+WOUimXh+Pd8LVsklZ4= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a h1:1Fy38jwe/QZhQfFQBy6dMj9F/WU1C+jo3/zLNr/WhW4= -golang.org/x/oauth2 v0.0.0-20180603041954-1e0a3fa8ba9a/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sys v0.0.0-20180606202747-9527bec2660b h1:5rOiLYVqtE+JehJPVJTXQJaP8aT3cpJC1Iy22+5WLFU= -golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -google.golang.org/appengine v1.0.0 h1:dN4LljjBKVChsv0XCSI+zbyzdqrkEwX5LQFUMRSGqOc= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/xanzy/go-gitlab v0.50.3 h1:M7ncgNhCN4jaFNyXxarJhCLa9Qi6fdmCxFFhMTQPZiY= +github.com/xanzy/go-gitlab v0.50.3/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/sync/gist.go b/sync/gist.go index 3e751e95..8528a3f4 100644 --- a/sync/gist.go +++ b/sync/gist.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "golang.org/x/oauth2" ) + const ( githubTokenEnvVariable = "PET_GITHUB_ACCESS_TOKEN" ) @@ -61,7 +62,7 @@ func (g GistClient) GetSnippet() (*Snippet, error) { gist, res, err := g.Client.Gists.Get(context.Background(), g.ID) if err != nil { - if res.StatusCode == 404 { + if res != nil && res.StatusCode == 404 { return nil, errors.Wrapf(err, "No gist ID (%s)", g.ID) } return nil, errors.Wrapf(err, "Failed to get gist") diff --git a/sync/gitlab.go b/sync/gitlab.go index 2c64c647..613ee51f 100644 --- a/sync/gitlab.go +++ b/sync/gitlab.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "net/http" + "crypto/tls" "strconv" "time" @@ -33,24 +35,47 @@ Write access_token in config file (pet configure) or export $%v. `, gitlabTokenEnvVariable) } - client := GitLabClient{ - Client: gitlab.NewClient(nil, accessToken), - ID: 0, - } + u := "https://git.mydomain.com/api/v4" + id := 0 + + h := &http.Client{} if config.Conf.GitLab.Url != "" { - client.Client.SetBaseURL(config.Conf.GitLab.Url) + fmt.Println(config.Conf.GitLab.Url) + u = config.Conf.GitLab.Url + } + + if config.Conf.GitLab.Insecure == true { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + h = &http.Client{Transport: tr} + } + + c, err := gitlab.NewClient(accessToken, gitlab.WithBaseURL(u), gitlab.WithHTTPClient(h)) + if err != nil { + return nil, errors.Wrapf(err, "Failed to create GitLab client: %d", id) } if config.Conf.GitLab.ID == "" { + client := GitLabClient{ + Client: c, + ID: id, + } + return client, nil } - id, err := strconv.Atoi(config.Conf.GitLab.ID) + id, err = strconv.Atoi(config.Conf.GitLab.ID) if err != nil { return nil, errors.Wrapf(err, "Invalid GitLab Snippet ID: %d", id) } - client.ID = id + + client := GitLabClient{ + Client: c, + ID: id, + } + return client, nil } diff --git a/sync/sync.go b/sync/sync.go index 905c37a3..a35c9ccb 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -2,7 +2,6 @@ package sync import ( "fmt" - "io/ioutil" "os" "time" @@ -108,5 +107,5 @@ func download(content string) error { } fmt.Println("Download success") - return ioutil.WriteFile(snippetFile, []byte(content), os.ModePerm) + return os.WriteFile(snippetFile, []byte(content), os.ModePerm) }