diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..84324f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.bitrise* +_tmp \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..c8258e6 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,43 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/bitrise-io/go-utils" + packages = [ + "colorstring", + "command", + "errorutil", + "log", + "parseutil", + "pathutil", + "pointers", + "sliceutil", + "ziputil" + ] + revision = "b33f6bcef9b50045d7e62364b354584afc3ee329" + +[[projects]] + branch = "master" + name = "github.com/bitrise-tools/go-android" + packages = ["gradle"] + revision = "926dbb2d85c3378c84c282884d1310c4287a8da8" + +[[projects]] + branch = "master" + name = "github.com/bitrise-tools/go-steputils" + packages = ["stepconf"] + revision = "d4d9e08cc4347e8784bb18419fcdceb932e17019" + +[[projects]] + name = "github.com/ryanuber/go-glob" + packages = ["."] + revision = "572520ed46dbddaed19ea3d9541bdd0494163693" + version = "v0.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "0bf6ee014103ddba75ac2819e4fbfb2dbaa48e1b9fa05cdd643d11cdd34901e6" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100755 index 0000000..872ce11 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,11 @@ +[[constraint]] + name = "github.com/bitrise-io/go-utils" + branch = "master" + +[[constraint]] + name = "github.com/bitrise-tools/go-steputils" + branch = "master" + +[prune] + go-tests = true + unused-packages = true diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..021df7b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 bitrise-steplib + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ce3cde7..cb7887e --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# bitrise-step-android-unit-test \ No newline at end of file +# Android Unit Test + +Runs your Android project's unit tests. + +## How to use this Step + +Can be run directly with the [bitrise CLI](https://github.com/bitrise-io/bitrise), +just `git clone` this repository, `cd` into it's folder in your Terminal/Command Line +and call `bitrise run test`. + +*Check the `bitrise.yml` file for required inputs which have to be +added to your `.bitrise.secrets.yml` file!* + +Step by step: + +1. Open up your Terminal / Command Line +2. `git clone` the repository +3. `cd` into the directory of the step (the one you just `git clone`d) +5. Create a `.bitrise.secrets.yml` file in the same directory of `bitrise.yml` - the `.bitrise.secrets.yml` is a git ignored file, you can store your secrets in +6. Check the `bitrise.yml` file for any secret you should set in `.bitrise.secrets.yml` + * Best practice is to mark these options with something like `# define these in your .bitrise.secrets.yml`, in the `app:envs` section. +7. Once you have all the required secret parameters in your `.bitrise.secrets.yml` you can just run this step with the [bitrise CLI](https://github.com/bitrise-io/bitrise): `bitrise run test` + +An example `.bitrise.secrets.yml` file: + +``` +envs: +- A_SECRET_PARAM_ONE: the value for secret one +- A_SECRET_PARAM_TWO: the value for secret two +``` + +## How to create your own step + +1. Create a new git repository for your step (**don't fork** the *step template*, create a *new* repository) +2. Copy the [step template](https://github.com/bitrise-steplib/step-template) files into your repository +3. Fill the `step.sh` with your functionality +4. Wire out your inputs to `step.yml` (`inputs` section) +5. Fill out the other parts of the `step.yml` too +6. Provide test values for the inputs in the `bitrise.yml` +7. Run your step with `bitrise run test` - if it works, you're ready + +__For Step development guidelines & best practices__ check this documentation: [https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md](https://github.com/bitrise-io/bitrise/blob/master/_docs/step-development-guideline.md). + +**NOTE:** + +If you want to use your step in your project's `bitrise.yml`: + +1. git push the step into it's repository +2. reference it in your `bitrise.yml` with the `git::PUBLIC-GIT-CLONE-URL@BRANCH` step reference style: + +``` +- git::https://github.com/user/my-step.git@branch: + title: My step + inputs: + - my_input_1: "my value 1" + - my_input_2: "my value 2" +``` + +You can find more examples of step reference styles +in the [bitrise CLI repository](https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml#L65). + +## How to contribute to this Step + +1. Fork this repository +2. `git clone` it +3. Create a branch you'll work on +4. To use/test the step just follow the **How to use this Step** section +5. Do the changes you want to +6. Run/test the step before sending your contribution + * You can also test the step in your `bitrise` project, either on your Mac or on [bitrise.io](https://www.bitrise.io) + * You just have to replace the step ID in your project's `bitrise.yml` with either a relative path, or with a git URL format + * (relative) path format: instead of `- original-step-id:` use `- path::./relative/path/of/script/on/your/Mac:` + * direct git URL format: instead of `- original-step-id:` use `- git::https://github.com/user/step.git@branch:` + * You can find more example of alternative step referencing at: https://github.com/bitrise-io/bitrise/blob/master/_examples/tutorials/steps-and-workflows/bitrise.yml +7. Once you're done just commit your changes & create a Pull Request + + +## Share your own Step + +You can share your Step or step version with the [bitrise CLI](https://github.com/bitrise-io/bitrise). If you use the `bitrise.yml` included in this repository, all you have to do is: + +1. In your Terminal / Command Line `cd` into this directory (where the `bitrise.yml` of the step is located) +1. Run: `bitrise run test` to test the step +1. Run: `bitrise run audit-this-step` to audit the `step.yml` +1. Check the `share-this-step` workflow in the `bitrise.yml`, and fill out the + `envs` if you haven't done so already (don't forget to bump the version number if this is an update + of your step!) +1. Then run: `bitrise run share-this-step` to share the step (version) you specified in the `envs` +1. Send the Pull Request, as described in the logs of `bitrise run share-this-step` + +That's all ;) \ No newline at end of file diff --git a/bitrise.yml b/bitrise.yml new file mode 100755 index 0000000..5eb4f3b --- /dev/null +++ b/bitrise.yml @@ -0,0 +1,201 @@ +format_version: 5 +default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git + +app: + envs: + - BITRISE_STEP_ID: android-unit-test + - BITRISE_STEP_VERSION: "0.9.0" + - BITRISE_STEP_GIT_CLONE_URL: https://github.com/bitrise-steplib/bitrise-step-android-unit-test.git + - MY_STEPLIB_REPO_FORK_GIT_URL: $MY_STEPLIB_REPO_FORK_GIT_URL + - SAMPLE_REPO_GIT_CLONE_URL: https://github.com/bitrise-samples/android-multiple-test-results-sample.git + +workflows: + test: + title: Test simple android project & mono repo projects + before_run: + - audit-this-step + - go-tests + after_run: + - test-in-src-dir + - test-in-root-tmp-dir + - check-artifacts + + test-in-src-dir: + title: Test android project & mono repo projects in source dir + steps: + - script: + inputs: + - content: rm -rf _tmp + - change-workdir: + title: cd $BITRISE_SOURCE_DIR/_tmp + run_if: true + inputs: + - path: ./_tmp + - is_create_path: true + - script: + inputs: + - content: git clone -b no-failures $SAMPLE_REPO_GIT_CLONE_URL . + - path::./: + title: Test simple android project + + test-in-root-tmp-dir: + title: Test android project & mono repo projects in /tmp dir + steps: + - script: + inputs: + - content: rm -rf /tmp/_tmp + - change-workdir: + title: cd /tmp/_tmp + run_if: true + inputs: + - path: /tmp/_tmp + - is_create_path: true + - script: + inputs: + - content: git clone -b no-failures $SAMPLE_REPO_GIT_CLONE_URL . + - path::./: + title: Test simple repo + + check-artifacts: + steps: + - script: + title: Check if files are existing + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + + # without timestamps + if [ ! -f $BITRISE_DEPLOY_DIR/another_app-reports.zip ]; then + exit 1 + fi + if [ ! -f $BITRISE_DEPLOY_DIR/app-reports.zip ]; then + exit 1 + fi + if [ ! -f $BITRISE_DEPLOY_DIR/another_app-test-results.zip ]; then + exit 1 + fi + if [ ! -f $BITRISE_DEPLOY_DIR/app-test-results.zip ]; then + exit 1 + fi + + # with timestamps + if [ ! -f $(ls $BITRISE_DEPLOY_DIR/another_app-reports-20*.zip) ]; then + exit 1 + fi + if [ ! -f $(ls $BITRISE_DEPLOY_DIR/app-reports-20*.zip) ]; then + exit 1 + fi + if [ ! -f $(ls $BITRISE_DEPLOY_DIR/another_app-test-results-20*.zip) ]; then + exit 1 + fi + if [ ! -f $(ls $BITRISE_DEPLOY_DIR/app-test-results-20*.zip) ]; then + exit 1 + fi + + go-tests: + steps: + - script: + title: Export go files to test + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + + no_vendor_paths="$(go list ./... | grep -v vendor)" + envman add --key GOLIST_WITHOUT_VENDOR --value "$no_vendor_paths" + - script: + title: Err check + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + go get -u -v github.com/kisielk/errcheck + errcheck -asserts=true -blank=true $GOLIST_WITHOUT_VENDOR + - script: + title: Go lint + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + go get -u -v github.com/golang/lint/golint + while read -r line; do + echo "-> Linting: $line" + golint_out="$(golint $line)" + if [[ "${golint_out}" != "" ]] ; then + echo "=> Golint issues found:" + echo "${golint_out}" + exit 1 + fi + done <<< "$GOLIST_WITHOUT_VENDOR" + - script: + title: Go test + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + go test ./... + + # ---------------------------------------------------------------- + # --- workflows to Share this step into a Step Library + audit-this-step: + steps: + - script: + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + stepman audit --step-yml ./step.yml + + # ---------------------------------------------------------------- + # --- workflows to create Release + create-release: + steps: + - script: + title: + inputs: + - content: | + #!/usr/bin/env bash + set -e + export CI=true + releaseman create --version $BITRISE_STEP_VERSION + + share-this-step: + envs: + # if you want to share this step into a StepLib + - MY_STEPLIB_REPO_FORK_GIT_URL: $MY_STEPLIB_REPO_FORK_GIT_URL + - BITRISE_STEP_ID: $BITRISE_STEP_ID + - BITRISE_STEP_VERSION: $BITRISE_STEP_VERSION + - BITRISE_STEP_GIT_CLONE_URL: $BITRISE_STEP_GIT_CLONE_URL + description: |- + If this is the first time you try to share a Step you should + first call: $ bitrise share + + This will print you a guide, and information about how Step sharing + works. Please read it at least once! + + As noted in the Step sharing guide you'll have to fork the + StepLib you want to share this step into. Once you're done with forking + the repository you should set your own fork's git clone URL + in the `.bitrise.secrets.yml` file, or here in the `envs` section, + as the value of the `MY_STEPLIB_REPO_FORK_GIT_URL` environment. + + You're now ready to share this Step, just make sure that + the `BITRISE_STEP_ID` and `BITRISE_STEP_VERSION` + environments are set to the desired values! + + To share this Step into a StepLib you can just run: $ bitrise run share-this-step + + Once it finishes the only thing left is to actually create a Pull Request, + the way described in the guide printed at the end of the process. + before_run: + - audit-this-step + steps: + - script: + inputs: + - content: |- + #!/usr/bin/env bash + set -ex + bitrise share start -c "${MY_STEPLIB_REPO_FORK_GIT_URL}" + bitrise share create --stepid "${BITRISE_STEP_ID}" --tag "${BITRISE_STEP_VERSION}" --git "${BITRISE_STEP_GIT_CLONE_URL}" + bitrise share finish diff --git a/main.go b/main.go new file mode 100755 index 0000000..d52b685 --- /dev/null +++ b/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/sliceutil" + "github.com/bitrise-tools/go-android/gradle" + "github.com/bitrise-tools/go-steputils/stepconf" +) + +// Configs ... +type Configs struct { + ProjectLocation string `env:"project_location,required"` + ReportPathPattern string `env:"report_path_pattern"` + ResultPathPattern string `env:"result_path_pattern"` + Variant string `env:"variant"` + Module string `env:"module"` +} + +func failf(f string, args ...interface{}) { + log.Errorf(f, args...) + os.Exit(1) +} + +func main() { + var config Configs + + if err := stepconf.Parse(&config); err != nil { + failf("Couldn't create step config: %v\n", err) + } + + stepconf.Print(config) + + deployDir := os.Getenv("BITRISE_DEPLOY_DIR") + + log.Printf("- Deploy dir: %s", deployDir) + fmt.Println() + + gradleProject, err := gradle.NewProject(config.ProjectLocation) + if err != nil { + failf("Failed to open project, error: %s", err) + } + + testTask := gradleProject. + GetModule(config.Module). + GetTask("test") + + log.Infof("Variants:") + fmt.Println() + + variants, err := testTask.GetVariants() + if err != nil { + failf("Failed to fetch variants, error: %s", err) + } + + filteredVariants := variants.Filter(config.Variant) + + for _, variant := range variants { + if sliceutil.IsStringInSlice(variant, filteredVariants) { + log.Donef("✓ %s", variant) + } else { + log.Printf("- %s", variant) + } + } + + fmt.Println() + + if len(filteredVariants) == 0 { + errMsg := fmt.Sprintf("No variant matching for: (%s)", config.Variant) + if config.Module != "" { + errMsg += fmt.Sprintf(" in module: [%s]", config.Module) + } + failf(errMsg) + } + + if config.Variant == "" { + log.Warnf("No variant specified, test will run on all variants") + fmt.Println() + } + + started := time.Now() + + log.Infof("Run test:") + testErr := testTask.Run(filteredVariants) + if testErr != nil { + log.Errorf("Test task failed, error: %v", testErr) + } + fmt.Println() + + log.Infof("Export reports:") + fmt.Println() + + reports, err := gradleProject.FindDirs(started, config.ReportPathPattern, true) + if err != nil { + failf("failed to find reports, error: %v", err) + } + + if len(reports) == 0 { + log.Warnf("No reports found with pattern: %s", config.ReportPathPattern) + log.Warnf("If you have changed default report export path in your gradle files then you might need to change ReportPathPattern accordingly.") + os.Exit(0) + } + + for _, report := range reports { + report.Name += ".zip" + + exists, err := pathutil.IsPathExists(filepath.Join(deployDir, report.Name)) + if err != nil { + failf("failed to check path, error: %v", err) + } + + artifactName := filepath.Base(report.Path) + + if exists { + timestamp := time.Now().Format("20060102150405") + ext := filepath.Ext(report.Name) + name := strings.TrimSuffix(filepath.Base(report.Name), ext) + report.Name = fmt.Sprintf("%s-%s%s", name, timestamp, ext) + } + + log.Printf(" Export [ %s => $BITRISE_DEPLOY_DIR/%s ]", artifactName, report.Name) + + if err := report.ExportZIP(deployDir); err != nil { + log.Warnf("failed to export report (%s), error: %v", report.Path, err) + continue + } + } + + fmt.Println() + + log.Infof("Export results:") + fmt.Println() + + results, err := gradleProject.FindDirs(started, config.ResultPathPattern, true) + if err != nil { + failf("failed to find results, error: %v", err) + } + + if len(results) == 0 { + log.Warnf("No results found with pattern: %s", config.ResultPathPattern) + log.Warnf("If you have changed default report export path in your gradle files then you might need to change ResultPathPattern accordingly.") + os.Exit(0) + } + + for _, result := range results { + result.Name += ".zip" + + exists, err := pathutil.IsPathExists(filepath.Join(deployDir, result.Name)) + if err != nil { + failf("failed to check path, error: %v", err) + } + + artifactName := filepath.Base(result.Path) + + if exists { + timestamp := time.Now().Format("20060102150405") + ext := filepath.Ext(result.Name) + name := strings.TrimSuffix(filepath.Base(result.Name), ext) + result.Name = fmt.Sprintf("%s-%s%s", name, timestamp, ext) + } + + log.Printf(" Export [ %s => $BITRISE_DEPLOY_DIR/%s ]", artifactName, result.Name) + + if err := result.ExportZIP(deployDir); err != nil { + log.Warnf("failed to export result (%s), error: %v", result.Path, err) + continue + } + } + + if testErr != nil { + os.Exit(1) + } +} diff --git a/release_config.yml b/release_config.yml new file mode 100755 index 0000000..f4f4ec6 --- /dev/null +++ b/release_config.yml @@ -0,0 +1,12 @@ +release: + development_branch: master + release_branch: master +changelog: + path: CHANGELOG.md + content_template: |- + {{range .ContentItems}}### {{.EndTaggedCommit.Tag}} ({{.EndTaggedCommit.Date.Format "2006 Jan 02"}}) + {{range .Commits}}* [{{firstChars .Hash 7}}] {{.Message}} + {{end}} + {{end}} + header_template: '## Changelog (Current version: {{.Version}})' + footer_template: 'Updated: {{.CurrentDate.Format "2006 Jan 02"}}' \ No newline at end of file diff --git a/step.yml b/step.yml new file mode 100755 index 0000000..986de7a --- /dev/null +++ b/step.yml @@ -0,0 +1,62 @@ +title: Android Unit Test +summary: Runs your Android project's unit tests. +description: Runs your Android project's unit tests. +website: https://github.com/bitrise-steplib/bitrise-step-android-unit-test +source_code_url: https://github.com/bitrise-steplib/bitrise-step-android-unit-test +support_url: https://github.com/bitrise-steplib/bitrise-step-android-unit-test/issues +host_os_tags: + - osx-10.10 + - ubuntu-16.04 + +project_type_tags: + - android + - react-native + +type_tags: + - test + +is_requires_admin_user: true +is_always_run: false +is_skippable: false + +toolkit: + go: + package_name: github.com/bitrise-steplib/bitrise-step-android-unit-test + +inputs: + - project_location: $BITRISE_SOURCE_DIR + opts: + title: Project Location + summary: "The root directory of your android project, e.g.: where your root build gradle file exist (also gradlew, settings.gradle, etc...)" + description: "The root directory of your android project, e.g.: where your root build gradle file exist (also gradlew, settings.gradle, etc...)" + is_required: true + - module: "" + opts: + title: Module + summary: | + Set the module that you want to test. To see your available modules please open your project in Android Studio and go in [Project Structure] and see the list on the left. + description: | + Set the module that you want to test. To see your available modules please open your project in Android Studio and go in [Project Structure] and see the list on the left. + is_required: false + - variant: "" + opts: + title: Variant + summary: | + Set the variant that you want to test. To see your available variants please open your project in Android Studio and go in [Project Structure] -> variants section. + description: | + Set the variant that you want to test. To see your available variants please open your project in Android Studio and go in [Project Structure] -> variants section. + is_required: false + - report_path_pattern: "*build/reports" + opts: + category: Export Options + title: Report location pattern + summary: Will find the report dir with the given pattern. + description: Will find the report dir with the given pattern. + is_required: true + - result_path_pattern: "*build/test-results" + opts: + category: Export Options + title: Test results location pattern + summary: Will find the test-results dir with the given pattern. + description: Will find test-results dir with the given pattern. + is_required: true \ No newline at end of file diff --git a/vendor/github.com/bitrise-io/go-utils/LICENSE b/vendor/github.com/bitrise-io/go-utils/LICENSE new file mode 100644 index 0000000..a6a5c39 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go b/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go new file mode 100644 index 0000000..5f31fa9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/colorstring/colorstring.go @@ -0,0 +1,110 @@ +package colorstring + +import ( + "fmt" +) + +// Color ... +// ANSI color escape sequences +type Color string + +const ( + blackColor Color = "\x1b[30;1m" + redColor Color = "\x1b[31;1m" + greenColor Color = "\x1b[32;1m" + yellowColor Color = "\x1b[33;1m" + blueColor Color = "\x1b[34;1m" + magentaColor Color = "\x1b[35;1m" + cyanColor Color = "\x1b[36;1m" + resetColor Color = "\x1b[0m" +) + +// ColorFunc ... +type ColorFunc func(a ...interface{}) string + +func addColor(color Color, msg string) string { + return string(color) + msg + string(resetColor) +} + +// NoColor ... +func NoColor(a ...interface{}) string { + return fmt.Sprint(a...) +} + +// Black ... +func Black(a ...interface{}) string { + return addColor(blackColor, fmt.Sprint(a...)) +} + +// Red ... +func Red(a ...interface{}) string { + return addColor(redColor, fmt.Sprint(a...)) +} + +// Green ... +func Green(a ...interface{}) string { + return addColor(greenColor, fmt.Sprint(a...)) +} + +// Yellow ... +func Yellow(a ...interface{}) string { + return addColor(yellowColor, fmt.Sprint(a...)) +} + +// Blue ... +func Blue(a ...interface{}) string { + return addColor(blueColor, fmt.Sprint(a...)) +} + +// Magenta ... +func Magenta(a ...interface{}) string { + return addColor(magentaColor, fmt.Sprint(a...)) +} + +// Cyan ... +func Cyan(a ...interface{}) string { + return addColor(cyanColor, fmt.Sprint(a...)) +} + +// ColorfFunc ... +type ColorfFunc func(format string, a ...interface{}) string + +// NoColorf ... +func NoColorf(format string, a ...interface{}) string { + return NoColor(fmt.Sprintf(format, a...)) +} + +// Blackf ... +func Blackf(format string, a ...interface{}) string { + return Black(fmt.Sprintf(format, a...)) +} + +// Redf ... +func Redf(format string, a ...interface{}) string { + return Red(fmt.Sprintf(format, a...)) +} + +// Greenf ... +func Greenf(format string, a ...interface{}) string { + return Green(fmt.Sprintf(format, a...)) +} + +// Yellowf ... +func Yellowf(format string, a ...interface{}) string { + return Yellow(fmt.Sprintf(format, a...)) +} + +// Bluef ... +func Bluef(format string, a ...interface{}) string { + return Blue(fmt.Sprintf(format, a...)) +} + +// Magentaf ... +func Magentaf(format string, a ...interface{}) string { + return Magenta(fmt.Sprintf(format, a...)) +} + +// Cyanf ... +func Cyanf(format string, a ...interface{}) string { + return Cyan(fmt.Sprintf(format, a...)) +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/command.go b/vendor/github.com/bitrise-io/go-utils/command/command.go new file mode 100644 index 0000000..4cd005a --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/command.go @@ -0,0 +1,263 @@ +package command + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/errorutil" +) + +// ---------- + +// Model ... +type Model struct { + cmd *exec.Cmd +} + +// New ... +func New(name string, args ...string) *Model { + return &Model{ + cmd: exec.Command(name, args...), + } +} + +// NewWithStandardOuts - same as NewCommand, but sets the command's +// stdout and stderr to the standard (OS) out (os.Stdout) and err (os.Stderr) +func NewWithStandardOuts(name string, args ...string) *Model { + return New(name, args...).SetStdout(os.Stdout).SetStderr(os.Stderr) +} + +// NewWithParams ... +func NewWithParams(params ...string) (*Model, error) { + if len(params) == 0 { + return nil, errors.New("no command provided") + } else if len(params) == 1 { + return New(params[0]), nil + } + + return New(params[0], params[1:]...), nil +} + +// NewFromSlice ... +func NewFromSlice(slice []string) (*Model, error) { + return NewWithParams(slice...) +} + +// NewWithCmd ... +func NewWithCmd(cmd *exec.Cmd) *Model { + return &Model{ + cmd: cmd, + } +} + +// GetCmd ... +func (m *Model) GetCmd() *exec.Cmd { + return m.cmd +} + +// SetDir ... +func (m *Model) SetDir(dir string) *Model { + m.cmd.Dir = dir + return m +} + +// SetEnvs ... +func (m *Model) SetEnvs(envs ...string) *Model { + m.cmd.Env = envs + return m +} + +// AppendEnvs - appends the envs to the current os.Environ() +// Calling this multiple times will NOT appens the envs one by one, +// only the last "envs" set will be appended to os.Environ()! +func (m *Model) AppendEnvs(envs ...string) *Model { + return m.SetEnvs(append(os.Environ(), envs...)...) +} + +// SetStdin ... +func (m *Model) SetStdin(in io.Reader) *Model { + m.cmd.Stdin = in + return m +} + +// SetStdout ... +func (m *Model) SetStdout(out io.Writer) *Model { + m.cmd.Stdout = out + return m +} + +// SetStderr ... +func (m *Model) SetStderr(err io.Writer) *Model { + m.cmd.Stderr = err + return m +} + +// Run ... +func (m Model) Run() error { + return m.cmd.Run() +} + +// RunAndReturnExitCode ... +func (m Model) RunAndReturnExitCode() (int, error) { + return RunCmdAndReturnExitCode(m.cmd) +} + +// RunAndReturnTrimmedOutput ... +func (m Model) RunAndReturnTrimmedOutput() (string, error) { + return RunCmdAndReturnTrimmedOutput(m.cmd) +} + +// RunAndReturnTrimmedCombinedOutput ... +func (m Model) RunAndReturnTrimmedCombinedOutput() (string, error) { + return RunCmdAndReturnTrimmedCombinedOutput(m.cmd) +} + +// PrintableCommandArgs ... +func (m Model) PrintableCommandArgs() string { + return PrintableCommandArgs(false, m.cmd.Args) +} + +// ---------- + +// PrintableCommandArgs ... +func PrintableCommandArgs(isQuoteFirst bool, fullCommandArgs []string) string { + cmdArgsDecorated := []string{} + for idx, anArg := range fullCommandArgs { + quotedArg := strconv.Quote(anArg) + if idx == 0 && !isQuoteFirst { + quotedArg = anArg + } + cmdArgsDecorated = append(cmdArgsDecorated, quotedArg) + } + + return strings.Join(cmdArgsDecorated, " ") +} + +// RunCmdAndReturnExitCode ... +func RunCmdAndReturnExitCode(cmd *exec.Cmd) (int, error) { + err := cmd.Run() + if err != nil { + exitCode, castErr := errorutil.CmdExitCodeFromError(err) + if castErr != nil { + return 1, fmt.Errorf("failed get exit code from error: %s, error: %s", err, castErr) + } + + return exitCode, err + } + + return 0, nil +} + +// RunCmdAndReturnTrimmedOutput ... +func RunCmdAndReturnTrimmedOutput(cmd *exec.Cmd) (string, error) { + outBytes, err := cmd.Output() + outStr := string(outBytes) + return strings.TrimSpace(outStr), err +} + +// RunCmdAndReturnTrimmedCombinedOutput ... +func RunCmdAndReturnTrimmedCombinedOutput(cmd *exec.Cmd) (string, error) { + outBytes, err := cmd.CombinedOutput() + outStr := string(outBytes) + return strings.TrimSpace(outStr), err +} + +// RunCommandWithReaderAndWriters ... +func RunCommandWithReaderAndWriters(inReader io.Reader, outWriter, errWriter io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = inReader + cmd.Stdout = outWriter + cmd.Stderr = errWriter + return cmd.Run() +} + +// RunCommandWithWriters ... +func RunCommandWithWriters(outWriter, errWriter io.Writer, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdout = outWriter + cmd.Stderr = errWriter + return cmd.Run() +} + +// RunCommandInDirWithEnvsAndReturnExitCode ... +func RunCommandInDirWithEnvsAndReturnExitCode(envs []string, dir, name string, args ...string) (int, error) { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if dir != "" { + cmd.Dir = dir + } + if len(envs) > 0 { + cmd.Env = envs + } + + return RunCmdAndReturnExitCode(cmd) +} + +// RunCommandInDirAndReturnExitCode ... +func RunCommandInDirAndReturnExitCode(dir, name string, args ...string) (int, error) { + return RunCommandInDirWithEnvsAndReturnExitCode([]string{}, dir, name, args...) +} + +// RunCommandWithEnvsAndReturnExitCode ... +func RunCommandWithEnvsAndReturnExitCode(envs []string, name string, args ...string) (int, error) { + return RunCommandInDirWithEnvsAndReturnExitCode(envs, "", name, args...) +} + +// RunCommandInDir ... +func RunCommandInDir(dir, name string, args ...string) error { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if dir != "" { + cmd.Dir = dir + } + return cmd.Run() +} + +// RunCommand ... +func RunCommand(name string, args ...string) error { + return RunCommandInDir("", name, args...) +} + +// RunCommandAndReturnStdout .. +func RunCommandAndReturnStdout(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + return RunCmdAndReturnTrimmedOutput(cmd) +} + +// RunCommandInDirAndReturnCombinedStdoutAndStderr ... +func RunCommandInDirAndReturnCombinedStdoutAndStderr(dir, name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + if dir != "" { + cmd.Dir = dir + } + return RunCmdAndReturnTrimmedCombinedOutput(cmd) +} + +// RunCommandAndReturnCombinedStdoutAndStderr .. +func RunCommandAndReturnCombinedStdoutAndStderr(name string, args ...string) (string, error) { + return RunCommandInDirAndReturnCombinedStdoutAndStderr("", name, args...) +} + +// RunBashCommand ... +func RunBashCommand(cmdStr string) error { + return RunCommand("bash", "-c", cmdStr) +} + +// RunBashCommandLines ... +func RunBashCommandLines(cmdLines []string) error { + for _, aLine := range cmdLines { + if err := RunCommand("bash", "-c", aLine); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/file.go b/vendor/github.com/bitrise-io/go-utils/command/file.go new file mode 100644 index 0000000..6887e3f --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/file.go @@ -0,0 +1,57 @@ +package command + +import ( + "errors" + "os" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// CopyFile ... +func CopyFile(src, dst string) error { + // replace with a pure Go implementation? + // Golang proposal was: https://go-review.googlesource.com/#/c/1591/5/src/io/ioutil/ioutil.go + isDir, err := pathutil.IsDirExists(src) + if err != nil { + return err + } + if isDir { + return errors.New("Source is a directory: " + src) + } + args := []string{src, dst} + return RunCommand("rsync", args...) +} + +// CopyDir ... +func CopyDir(src, dst string, isOnlyContent bool) error { + if isOnlyContent && !strings.HasSuffix(src, "/") { + src = src + "/" + } + args := []string{"-ar", src, dst} + return RunCommand("rsync", args...) +} + +// RemoveDir ... +func RemoveDir(dirPth string) error { + if exist, err := pathutil.IsPathExists(dirPth); err != nil { + return err + } else if exist { + if err := os.RemoveAll(dirPth); err != nil { + return err + } + } + return nil +} + +// RemoveFile ... +func RemoveFile(pth string) error { + if exist, err := pathutil.IsPathExists(pth); err != nil { + return err + } else if exist { + if err := os.Remove(pth); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/command/zip.go b/vendor/github.com/bitrise-io/go-utils/command/zip.go new file mode 100644 index 0000000..b3e899c --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/command/zip.go @@ -0,0 +1,115 @@ +package command + +import ( + "archive/zip" + "errors" + "io" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// UnZIP ... +func UnZIP(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer func() { + if err := r.Close(); err != nil { + log.Fatal(err) + } + }() + + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func() { + if err := rc.Close(); err != nil { + log.Fatal(err) + } + }() + + path := filepath.Join(dest, f.Name) + + if f.FileInfo().IsDir() { + if err := os.MkdirAll(path, f.Mode()); err != nil { + return err + } + } else { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Fatal(err) + } + }() + + if _, err = io.Copy(f, rc); err != nil { + return err + } + } + return nil + } + + for _, f := range r.File { + if err := extractAndWriteFile(f); err != nil { + return err + } + } + return nil +} + +// DownloadAndUnZIP ... +func DownloadAndUnZIP(url, pth string) error { + tmpDir, err := pathutil.NormalizedOSTempDirPath("") + if err != nil { + return err + } + srcFilePath := tmpDir + "/target.zip" + srcFile, err := os.Create(srcFilePath) + if err != nil { + return err + } + defer func() { + if err := srcFile.Close(); err != nil { + log.Fatal("Failed to close srcFile:", err) + } + if err := os.Remove(srcFilePath); err != nil { + log.Fatal("Failed to remove srcFile:", err) + } + }() + + response, err := http.Get(url) + if err != nil { + return err + } + defer func() { + if err := response.Body.Close(); err != nil { + log.Fatal("Failed to close response body:", err) + } + }() + + if response.StatusCode != http.StatusOK { + errorMsg := "Failed to download target from: " + url + return errors.New(errorMsg) + } + + if _, err := io.Copy(srcFile, response.Body); err != nil { + return err + } + + return UnZIP(srcFilePath, pth) +} diff --git a/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go b/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go new file mode 100644 index 0000000..1128416 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/errorutil/errorutil.go @@ -0,0 +1,36 @@ +package errorutil + +import ( + "errors" + "os/exec" + "regexp" + "syscall" +) + +// IsExitStatusError ... +func IsExitStatusError(err error) bool { + return IsExitStatusErrorStr(err.Error()) +} + +// IsExitStatusErrorStr ... +func IsExitStatusErrorStr(errString string) bool { + // example exit status error string: exit status 1 + var rex = regexp.MustCompile(`^exit status [0-9]{1,3}$`) + return rex.MatchString(errString) +} + +// CmdExitCodeFromError ... +func CmdExitCodeFromError(err error) (int, error) { + cmdExitCode := 0 + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus, ok := exitError.Sys().(syscall.WaitStatus) + if !ok { + return 1, errors.New("Failed to cast exit status") + } + cmdExitCode = waitStatus.ExitStatus() + } + return cmdExitCode, nil + } + return 0, nil +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/json_logger.go b/vendor/github.com/bitrise-io/go-utils/log/json_logger.go new file mode 100644 index 0000000..43b8bfb --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/json_logger.go @@ -0,0 +1,31 @@ +package log + +import ( + "fmt" + "io" + "os" +) + +// JSONLoger ... +type JSONLoger struct { + writer io.Writer +} + +// NewJSONLoger ... +func NewJSONLoger(writer io.Writer) *JSONLoger { + return &JSONLoger{ + writer: writer, + } +} + +// NewDefaultJSONLoger ... +func NewDefaultJSONLoger() JSONLoger { + return JSONLoger{ + writer: os.Stdout, + } +} + +// Print ... +func (l JSONLoger) Print(f Formatable) { + fmt.Fprint(l.writer, f.JSON()) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/log.go b/vendor/github.com/bitrise-io/go-utils/log/log.go new file mode 100644 index 0000000..1b69028 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/log.go @@ -0,0 +1,34 @@ +package log + +import ( + "fmt" + "io" + "os" + "time" +) + +var outWriter io.Writer = os.Stdout + +// SetOutWriter ... +func SetOutWriter(writer io.Writer) { + outWriter = writer +} + +var enableDebugLog = false + +// SetEnableDebugLog ... +func SetEnableDebugLog(enable bool) { + enableDebugLog = enable +} + +var timestampLayout = "15:04:05" + +// SetTimestampLayout ... +func SetTimestampLayout(layout string) { + timestampLayout = layout +} + +func timestampField() string { + currentTime := time.Now() + return fmt.Sprintf("[%s]", currentTime.Format(timestampLayout)) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/logger.go b/vendor/github.com/bitrise-io/go-utils/log/logger.go new file mode 100644 index 0000000..4691122 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/logger.go @@ -0,0 +1,12 @@ +package log + +// Logger ... +type Logger interface { + Print(f Formatable) +} + +// Formatable ... +type Formatable interface { + String() string + JSON() string +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/print.go b/vendor/github.com/bitrise-io/go-utils/log/print.go new file mode 100644 index 0000000..3b7c1fa --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/print.go @@ -0,0 +1,84 @@ +package log + +import ( + "fmt" +) + +func printf(severity Severity, withTime bool, format string, v ...interface{}) { + colorFunc := severityColorFuncMap[severity] + message := colorFunc(format, v...) + if withTime { + message = fmt.Sprintf("%s %s", timestampField(), message) + } + + fmt.Fprintln(outWriter, message) +} + +// Successf ... +func Successf(format string, v ...interface{}) { + printf(successSeverity, false, format, v...) +} + +// Donef ... +func Donef(format string, v ...interface{}) { + Successf(format, v...) +} + +// Infof ... +func Infof(format string, v ...interface{}) { + printf(infoSeverity, false, format, v...) +} + +// Printf ... +func Printf(format string, v ...interface{}) { + printf(normalSeverity, false, format, v...) +} + +// Debugf ... +func Debugf(format string, v ...interface{}) { + if enableDebugLog { + printf(debugSeverity, false, format, v...) + } +} + +// Warnf ... +func Warnf(format string, v ...interface{}) { + printf(warnSeverity, false, format, v...) +} + +// Errorf ... +func Errorf(format string, v ...interface{}) { + printf(errorSeverity, false, format, v...) +} + +// TSuccessf ... +func TSuccessf(format string, v ...interface{}) { + printf(successSeverity, true, format, v...) +} + +// TInfof ... +func TInfof(format string, v ...interface{}) { + printf(infoSeverity, true, format, v...) +} + +// TPrintf ... +func TPrintf(format string, v ...interface{}) { + printf(normalSeverity, true, format, v...) +} + +// TDebugf ... +func TDebugf(format string, v ...interface{}) { + if enableDebugLog { + printf(debugSeverity, true, format, v...) + } +} + +// TWarnf ... +func TWarnf(format string, v ...interface{}) { + printf(warnSeverity, true, format, v...) +} + +// TErrorf ... +func TErrorf(format string, v ...interface{}) { + printf(errorSeverity, true, format, v...) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go b/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go new file mode 100644 index 0000000..82dc54e --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/raw_logger.go @@ -0,0 +1,31 @@ +package log + +import ( + "fmt" + "io" + "os" +) + +// RawLogger ... +type RawLogger struct { + writer io.Writer +} + +// NewRawLogger ... +func NewRawLogger(writer io.Writer) *RawLogger { + return &RawLogger{ + writer: writer, + } +} + +// NewDefaultRawLogger ... +func NewDefaultRawLogger() RawLogger { + return RawLogger{ + writer: os.Stdout, + } +} + +// Print ... +func (l RawLogger) Print(f Formatable) { + fmt.Fprintln(l.writer, f.String()) +} diff --git a/vendor/github.com/bitrise-io/go-utils/log/severity.go b/vendor/github.com/bitrise-io/go-utils/log/severity.go new file mode 100644 index 0000000..a1c4631 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/log/severity.go @@ -0,0 +1,35 @@ +package log + +import "github.com/bitrise-io/go-utils/colorstring" + +// Severity ... +type Severity uint8 + +const ( + errorSeverity Severity = iota + warnSeverity + normalSeverity + infoSeverity + successSeverity + debugSeverity +) + +type severityColorFunc colorstring.ColorfFunc + +var ( + successSeverityColorFunc severityColorFunc = colorstring.Greenf + infoSeverityColorFunc severityColorFunc = colorstring.Bluef + normalSeverityColorFunc severityColorFunc = colorstring.NoColorf + debugSeverityColorFunc severityColorFunc = colorstring.NoColorf + warnSeverityColorFunc severityColorFunc = colorstring.Yellowf + errorSeverityColorFunc severityColorFunc = colorstring.Redf +) + +var severityColorFuncMap = map[Severity]severityColorFunc{ + successSeverity: successSeverityColorFunc, + infoSeverity: infoSeverityColorFunc, + normalSeverity: normalSeverityColorFunc, + debugSeverity: debugSeverityColorFunc, + warnSeverity: warnSeverityColorFunc, + errorSeverity: errorSeverityColorFunc, +} diff --git a/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go new file mode 100644 index 0000000..08cec36 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/parseutil/parseutil.go @@ -0,0 +1,95 @@ +package parseutil + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/pointers" +) + +// ParseBool ... +func ParseBool(userInputStr string) (bool, error) { + if userInputStr == "" { + return false, errors.New("No string to parse") + } + userInputStr = strings.TrimSpace(userInputStr) + + lowercased := strings.ToLower(userInputStr) + if lowercased == "yes" || lowercased == "y" { + return true, nil + } + if lowercased == "no" || lowercased == "n" { + return false, nil + } + return strconv.ParseBool(lowercased) +} + +// CastToString ... +func CastToString(value interface{}) string { + casted, ok := value.(string) + + if !ok { + castedStr := fmt.Sprintf("%v", value) + casted = castedStr + } + + return casted +} + +// CastToStringPtr ... +func CastToStringPtr(value interface{}) *string { + castedValue := CastToString(value) + return pointers.NewStringPtr(castedValue) +} + +// CastToBool ... +func CastToBool(value interface{}) (bool, bool) { + casted, ok := value.(bool) + + if !ok { + castedStr := CastToString(value) + + castedBool, err := ParseBool(castedStr) + if err != nil { + return false, false + } + + casted = castedBool + } + + return casted, true +} + +// CastToBoolPtr ... +func CastToBoolPtr(value interface{}) (*bool, bool) { + castedValue, ok := CastToBool(value) + if !ok { + return nil, false + } + return pointers.NewBoolPtr(castedValue), true +} + +// CastToMapStringInterface ... +func CastToMapStringInterface(value interface{}) (map[string]interface{}, bool) { + castedValue, ok := value.(map[interface{}]interface{}) + desiredMap := map[string]interface{}{} + for key, value := range castedValue { + keyStr, ok := key.(string) + if !ok { + return map[string]interface{}{}, false + } + desiredMap[keyStr] = value + } + return desiredMap, ok +} + +// CastToMapStringInterfacePtr ... +func CastToMapStringInterfacePtr(value interface{}) (*map[string]interface{}, bool) { + casted, ok := CastToMapStringInterface(value) + if !ok { + return nil, false + } + return pointers.NewMapStringInterfacePtr(casted), true +} diff --git a/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go new file mode 100644 index 0000000..1ef74f9 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pathutil/pathutil.go @@ -0,0 +1,181 @@ +package pathutil + +import ( + "errors" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +// RevokableChangeDir ... +func RevokableChangeDir(dir string) (func() error, error) { + origDir, err := CurrentWorkingDirectoryAbsolutePath() + if err != nil { + return nil, err + } + + revokeFn := func() error { + return os.Chdir(origDir) + } + + return revokeFn, os.Chdir(dir) +} + +// ChangeDirForFunction ... +func ChangeDirForFunction(dir string, fn func()) error { + revokeFn, err := RevokableChangeDir(dir) + if err != nil { + return err + } + + fn() + + return revokeFn() +} + +// IsRelativePath ... +func IsRelativePath(pth string) bool { + if strings.HasPrefix(pth, "./") { + return true + } + + if strings.HasPrefix(pth, "/") { + return false + } + + if strings.HasPrefix(pth, "$") { + return false + } + + return true +} + +// EnsureDirExist ... +func EnsureDirExist(dir string) error { + exist, err := IsDirExists(dir) + if !exist || err != nil { + return os.MkdirAll(dir, 0777) + } + return nil +} + +func genericIsPathExists(pth string) (os.FileInfo, bool, error) { + if pth == "" { + return nil, false, errors.New("No path provided") + } + fileInf, err := os.Lstat(pth) + if err == nil { + return fileInf, true, nil + } + if os.IsNotExist(err) { + return nil, false, nil + } + return fileInf, false, err +} + +// IsPathExists ... +func IsPathExists(pth string) (bool, error) { + _, isExists, err := genericIsPathExists(pth) + return isExists, err +} + +// PathCheckAndInfos ... +// Returns: +// 1. file info or nil +// 2. bool, indicating whether the path exists +// 3. error, if any error happens during the check +func PathCheckAndInfos(pth string) (os.FileInfo, bool, error) { + return genericIsPathExists(pth) +} + +// IsDirExists ... +func IsDirExists(pth string) (bool, error) { + fileInf, isExists, err := genericIsPathExists(pth) + if err != nil { + return false, err + } + if !isExists { + return false, nil + } + if fileInf == nil { + return false, errors.New("No file info available") + } + return fileInf.IsDir(), nil +} + +// AbsPath expands ENV vars and the ~ character +// then call Go's Abs +func AbsPath(pth string) (string, error) { + if pth == "" { + return "", errors.New("No Path provided") + } + + pth, err := ExpandTilde(pth) + if err != nil { + return "", err + } + + return filepath.Abs(os.ExpandEnv(pth)) +} + +// ExpandTilde ... +func ExpandTilde(pth string) (string, error) { + if pth == "" { + return "", errors.New("No Path provided") + } + + if strings.HasPrefix(pth, "~") { + pth = strings.TrimPrefix(pth, "~") + + if len(pth) == 0 || strings.HasPrefix(pth, "/") { + return os.ExpandEnv("$HOME" + pth), nil + } + + splitPth := strings.Split(pth, "/") + username := splitPth[0] + + usr, err := user.Lookup(username) + if err != nil { + return "", err + } + + pathInUsrHome := strings.Join(splitPth[1:], "/") + + return filepath.Join(usr.HomeDir, pathInUsrHome), nil + } + + return pth, nil +} + +// CurrentWorkingDirectoryAbsolutePath ... +func CurrentWorkingDirectoryAbsolutePath() (string, error) { + return filepath.Abs("./") +} + +// UserHomeDir ... +func UserHomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +// NormalizedOSTempDirPath ... +// Creates a temp dir, and returns its path. +// If tmpDirNamePrefix is provided it'll be used +// as the tmp dir's name prefix. +// Normalized: it's guaranteed that the path won't end with '/'. +func NormalizedOSTempDirPath(tmpDirNamePrefix string) (retPth string, err error) { + retPth, err = ioutil.TempDir("", tmpDirNamePrefix) + if strings.HasSuffix(retPth, "/") { + retPth = retPth[:len(retPth)-1] + } + return +} diff --git a/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go new file mode 100644 index 0000000..e26647d --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/pointers/pointers.go @@ -0,0 +1,98 @@ +package pointers + +import "time" + +// NewBoolPtr ... +func NewBoolPtr(val bool) *bool { + ptrValue := new(bool) + *ptrValue = val + return ptrValue +} + +// NewStringPtr ... +func NewStringPtr(val string) *string { + ptrValue := new(string) + *ptrValue = val + return ptrValue +} + +// NewTimePtr ... +func NewTimePtr(val time.Time) *time.Time { + ptrValue := new(time.Time) + *ptrValue = val + return ptrValue +} + +// NewIntPtr ... +func NewIntPtr(val int) *int { + ptrValue := new(int) + *ptrValue = val + return ptrValue +} + +// NewInt64Ptr ... +func NewInt64Ptr(val int64) *int64 { + ptrValue := new(int64) + *ptrValue = val + return ptrValue +} + +// NewMapStringInterfacePtr ... +func NewMapStringInterfacePtr(val map[string]interface{}) *map[string]interface{} { + ptrValue := new(map[string]interface{}) + *ptrValue = map[string]interface{}{} + for key, value := range val { + (*ptrValue)[key] = value + } + return ptrValue +} + +// ------------------------------------------------------ +// --- Safe Getters + +// Bool ... +func Bool(val *bool) bool { + return BoolWithDefault(val, false) +} + +// BoolWithDefault ... +func BoolWithDefault(val *bool, defaultValue bool) bool { + if val == nil { + return defaultValue + } + return *val +} + +// String ... +func String(val *string) string { + return StringWithDefault(val, "") +} + +// StringWithDefault ... +func StringWithDefault(val *string, defaultValue string) string { + if val == nil { + return defaultValue + } + return *val +} + +// TimeWithDefault ... +func TimeWithDefault(val *time.Time, defaultValue time.Time) time.Time { + if val == nil { + return defaultValue + } + return *val +} + +// Int ... +func Int(val *int) int { + return IntWithDefault(val, 0) +} + +// IntWithDefault ... +func IntWithDefault(val *int, defaultValue int) int { + if val == nil { + return defaultValue + } + return *val +} diff --git a/vendor/github.com/bitrise-io/go-utils/sliceutil/sliceutil.go b/vendor/github.com/bitrise-io/go-utils/sliceutil/sliceutil.go new file mode 100644 index 0000000..9f51cb5 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/sliceutil/sliceutil.go @@ -0,0 +1,32 @@ +package sliceutil + +// UniqueStringSlice - returns a cleaned up list, +// where every item is unique. +// Does NOT guarantee any ordering, the result can +// be in any order! +func UniqueStringSlice(strs []string) []string { + lookupMap := map[string]interface{}{} + for _, aStr := range strs { + lookupMap[aStr] = 1 + } + uniqueStrs := []string{} + for k := range lookupMap { + uniqueStrs = append(uniqueStrs, k) + } + return uniqueStrs +} + +// IndexOfStringInSlice ... +func IndexOfStringInSlice(searchFor string, searchIn []string) int { + for idx, anItm := range searchIn { + if anItm == searchFor { + return idx + } + } + return -1 +} + +// IsStringInSlice ... +func IsStringInSlice(searchFor string, searchIn []string) bool { + return IndexOfStringInSlice(searchFor, searchIn) >= 0 +} diff --git a/vendor/github.com/bitrise-io/go-utils/ziputil/ziputil.go b/vendor/github.com/bitrise-io/go-utils/ziputil/ziputil.go new file mode 100644 index 0000000..29a6252 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/ziputil/ziputil.go @@ -0,0 +1,71 @@ +package ziputil + +import ( + "fmt" + "path/filepath" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/pathutil" +) + +// ZipDir ... +func ZipDir(sourceDirPth, destinationZipPth string, isContentOnly bool) error { + if exist, err := pathutil.IsDirExists(sourceDirPth); err != nil { + return err + } else if !exist { + return fmt.Errorf("dir (%s) not exist", sourceDirPth) + } + + workDir := filepath.Dir(sourceDirPth) + if isContentOnly { + workDir = sourceDirPth + } + + zipTarget := filepath.Base(sourceDirPth) + if isContentOnly { + zipTarget = "." + } + + // -r - Travel the directory structure recursively + // -T - Test the integrity of the new zip file + // -y - Store symbolic links as such in the zip archive, instead of compressing and storing the file referred to by the link + cmd := command.New("/usr/bin/zip", "-rTy", destinationZipPth, zipTarget) + cmd.SetDir(workDir) + if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return fmt.Errorf("command: (%s) failed, output: %s, error: %s", cmd.PrintableCommandArgs(), out, err) + } + + return nil +} + +// ZipFile ... +func ZipFile(sourceFilePth, destinationZipPth string) error { + if exist, err := pathutil.IsPathExists(sourceFilePth); err != nil { + return err + } else if !exist { + return fmt.Errorf("file (%s) not exist", sourceFilePth) + } + + workDir := filepath.Dir(sourceFilePth) + zipTarget := filepath.Base(sourceFilePth) + + // -T - Test the integrity of the new zip file + // -y - Store symbolic links as such in the zip archive, instead of compressing and storing the file referred to by the link + cmd := command.New("/usr/bin/zip", "-Ty", destinationZipPth, zipTarget) + cmd.SetDir(workDir) + if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return fmt.Errorf("command: (%s) failed, output: %s, error: %s", cmd.PrintableCommandArgs(), out, err) + } + + return nil +} + +// UnZip ... +func UnZip(zip, intoDir string) error { + cmd := command.New("/usr/bin/unzip", zip, "-d", intoDir) + if out, err := cmd.RunAndReturnTrimmedCombinedOutput(); err != nil { + return fmt.Errorf("command: (%s) failed, output: %s, error: %s", cmd.PrintableCommandArgs(), out, err) + } + + return nil +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/artifact.go b/vendor/github.com/bitrise-tools/go-android/gradle/artifact.go new file mode 100644 index 0000000..46a466f --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/artifact.go @@ -0,0 +1,24 @@ +package gradle + +import ( + "path/filepath" + + "github.com/bitrise-io/go-utils/command" + "github.com/bitrise-io/go-utils/ziputil" +) + +// Artifact ... +type Artifact struct { + Path string + Name string +} + +// Export ... +func (artifact Artifact) Export(destination string) error { + return command.CopyFile(artifact.Path, filepath.Join(destination, artifact.Name)) +} + +// ExportZIP ... +func (artifact Artifact) ExportZIP(destination string) error { + return ziputil.ZipDir(artifact.Path, filepath.Join(destination, artifact.Name), true) +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/common.go b/vendor/github.com/bitrise-tools/go-android/gradle/common.go new file mode 100644 index 0000000..d1071ea --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/common.go @@ -0,0 +1,37 @@ +package gradle + +import ( + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/command" +) + +// If we parse tasks that starts with lint, we will have tasks that starts +// with lintVital also. So list here each conflicting tasks. (only overlapping ones) +var conflicts = map[string][]string{ + "lint": []string{ + "lintVital", + }, +} + +func getGradleOutput(projPath string, tasks ...string) (string, error) { + c := command.New(filepath.Join(projPath, "gradlew"), tasks...) + return c.RunAndReturnTrimmedCombinedOutput() +} + +func runGradleCommand(projPath string, tasks ...string) error { + return command.NewWithStandardOuts(filepath.Join(projPath, "gradlew"), tasks...). + SetDir(projPath). + Run() +} + +func cleanStringSlice(in []string) (out []string) { + for _, s := range in { + s = strings.TrimSpace(s) + if s != "" { + out = append(out, s) + } + } + return +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/module.go b/vendor/github.com/bitrise-tools/go-android/gradle/module.go new file mode 100644 index 0000000..42ee5a5 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/module.go @@ -0,0 +1,16 @@ +package gradle + +// Module ... +type Module struct { + project Project + name string + tasks []Task +} + +// GetTask ... +func (module Module) GetTask(name string) *Task { + return &Task{ + module: module, + name: name, + } +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/project.go b/vendor/github.com/bitrise-tools/go-android/gradle/project.go new file mode 100644 index 0000000..d2ace4d --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/project.go @@ -0,0 +1,146 @@ +package gradle + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + glob "github.com/ryanuber/go-glob" +) + +// Project ... +type Project struct { + location string + monoRepo bool +} + +// NewProject ... +func NewProject(location string) (Project, error) { + var err error + location, err = filepath.Abs(location) + if err != nil { + return Project{}, err + } + + buildGradleFound, err := pathutil.IsPathExists(filepath.Join(location, "build.gradle")) + if err != nil { + return Project{}, err + } + + if !buildGradleFound { + return Project{}, fmt.Errorf("no build.gradle file found in (%s)", location) + } + + if location == "/" { + return Project{location: location, monoRepo: false}, nil + } + + root := filepath.Join(location, "..") + + files, err := ioutil.ReadDir(root) + if err != nil { + return Project{}, err + } + + projectsCount := 0 + for _, file := range files { + if file.IsDir() { + if exists, err := pathutil.IsPathExists(filepath.Join(root, file.Name(), "build.gradle")); err != nil { + return Project{}, err + } else if exists { + projectsCount++ + } + } + } + + return Project{location: location, monoRepo: (projectsCount > 1)}, nil +} + +func getGradleModule(configModule string) string { + if configModule != "" { + return fmt.Sprintf(":%s:", configModule) + } + return "" +} + +// GetModule ... +func (proj Project) GetModule(module string) Module { + return Module{ + project: proj, + name: getGradleModule(module), + } +} + +// FindArtifacts ... +func (proj Project) FindArtifacts(generatedAfter time.Time, pattern string, includeModuleInName bool) ([]Artifact, error) { + var a []Artifact + return a, filepath.Walk(proj.location, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("failed to walk path: %s", err) + return nil + } + + if info.ModTime().Before(generatedAfter) || info.IsDir() || !glob.Glob(pattern, path) { + return nil + } + + name, err := proj.extractArtifactName(path, includeModuleInName) + if err != nil { + return err + } + + a = append(a, Artifact{Name: name, Path: path}) + return nil + }) +} + +// FindDirs ... +func (proj Project) FindDirs(generatedAfter time.Time, pattern string, includeModuleInName bool) ([]Artifact, error) { + var a []Artifact + return a, filepath.Walk(proj.location, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Warnf("failed to walk path: %s", err) + return nil + } + + if info.ModTime().Before(generatedAfter) || !info.IsDir() || !glob.Glob(pattern, path) { + return nil + } + + name, err := proj.extractArtifactName(path, includeModuleInName) + if err != nil { + return err + } + + a = append(a, Artifact{Name: name, Path: path}) + return nil + }) +} + +func (proj Project) extractArtifactName(path string, includeModuleInName bool) (string, error) { + relPath, err := filepath.Rel(proj.location, path) + if err != nil { + return "", err + } + + fileName := filepath.Base(relPath) + + if includeModuleInName { + fileName = strings.Split(relPath, "/")[0] + "-" + fileName + } + + if proj.monoRepo { + split := strings.Split(proj.location, "/") + prefix := split[len(split)-1] + if prefix != "" { + fileName = prefix + "-" + fileName + } + } + + return fileName, nil +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/task.go b/vendor/github.com/bitrise-tools/go-android/gradle/task.go new file mode 100644 index 0000000..069dd14 --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/task.go @@ -0,0 +1,68 @@ +package gradle + +import ( + "fmt" + "strings" +) + +// Task ... +type Task struct { + name string + module Module +} + +// GetVariants ... +func (task *Task) GetVariants() (Variants, error) { + tasksOutput, err := getGradleOutput(task.module.project.location, task.module.name+"tasks") + if err != nil { + return nil, fmt.Errorf("%s, %s", tasksOutput, err) + } + return task.parseVariants(tasksOutput), nil +} + +func (task *Task) parseVariants(gradleOutput string) Variants { + //example gradleOutput: + //" + // lintMyflavorokStaging - Runs lint on the MyflavorokStaging build. + // lintMyflavorRelease - Runs lint on the MyflavorRelease build. + // lintVitalMyflavorRelease - Runs lint on the MyflavorRelease build. + // lintMyflavorStaging - Runs lint on the MyflavorStaging build." + var tasks []string +lines: + for _, l := range strings.Split(gradleOutput, "\n") { + // l: " lintMyflavorokStaging - Runs lint on the MyflavorokStaging build." + l = strings.TrimSpace(l) + // l: "lintMyflavorokStaging - Runs lint on the MyflavorokStaging build." + if l == "" { + continue + } + l = strings.Split(l, " ")[0] + // l: "lintMyflavorokStaging" + if strings.HasPrefix(l, task.name) { + // task.name: "lint" + // strings.HasPrefix will match lint and lintVital prefix also, we won't need lintVital so it is a conflict + for _, conflict := range conflicts[task.name] { + if strings.HasPrefix(l, conflict) { + // if line has conflicting prefix don't do further checks with this line, skip... + continue lines + } + } + l = strings.TrimPrefix(l, task.name) + // l: "MyflavorokStaging" + if l == "" { + continue + } + tasks = append(tasks, l) + } + } + return cleanStringSlice(tasks) +} + +// Run ... +func (task *Task) Run(variants Variants) error { + var args []string + for _, variant := range variants { + args = append(args, task.module.name+task.name+variant) + } + return runGradleCommand(task.module.project.location, args...) +} diff --git a/vendor/github.com/bitrise-tools/go-android/gradle/variant.go b/vendor/github.com/bitrise-tools/go-android/gradle/variant.go new file mode 100644 index 0000000..ea5ddda --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-android/gradle/variant.go @@ -0,0 +1,30 @@ +package gradle + +import ( + "strings" + + "github.com/bitrise-io/go-utils/sliceutil" +) + +// Variants ... +type Variants []string + +// Filter ... +func (variants Variants) Filter(filter string) Variants { + cleanedFilters := cleanStringSlice(strings.Split(filter, "\n")) + + if len(cleanedFilters) == 0 { + return variants + } + + var cleanedVariants []string + for _, variant := range variants { + for _, filter := range cleanedFilters { + if strings.Contains(strings.ToLower(variant), strings.ToLower(filter)) && + !sliceutil.IsStringInSlice(variant, cleanedVariants) { + cleanedVariants = append(cleanedVariants, variant) + } + } + } + return cleanedVariants +} diff --git a/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go b/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go new file mode 100644 index 0000000..169035f --- /dev/null +++ b/vendor/github.com/bitrise-tools/go-steputils/stepconf/stepconf.go @@ -0,0 +1,199 @@ +package stepconf + +import ( + "errors" + "fmt" + "os" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/parseutil" +) + +// ErrNotStructPtr indicates a type is not a pointer to a struct. +var ErrNotStructPtr = errors.New("must be a pointer to a struct") + +// ParseError occurs when a struct field cannot be set. +type ParseError struct { + Field string + Value string + Err error +} + +// Error implements builtin errors.Error. +func (e *ParseError) Error() string { + segments := []string{e.Field} + if e.Value != "" { + segments = append(segments, e.Value) + } + segments = append(segments, e.Err.Error()) + return strings.Join(segments, ": ") +} + +// Secret variables are not shown in the printed output. +type Secret string + +const secret = "*****" + +// String implements fmt.Stringer.String. +// When a Secret is printed, it's masking the underlying string with asterisks. +func (s Secret) String() string { + if s == "" { + return "" + } + return secret +} + +// Print the name of the struct in blue color followed by a newline, +// then print all fields formatted as '- field name: field value` separated by newline. +func Print(config interface{}) { + v := reflect.ValueOf(config) + t := reflect.TypeOf(config) + + log.Infof("%s:", t.Name()) + for i := 0; i < t.NumField(); i++ { + fmt.Printf("- %s: %v\n", t.Field(i).Name, v.Field(i).Interface()) + } +} + +// parseTag splits a struct field's env tag into its name and option. +func parseTag(tag string) (string, string) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tag[idx+1:] + } + return tag, "" +} + +// Parse populates a struct with the retrieved values from environment variables +// described by struct tags and applies the defined validations. +func Parse(conf interface{}) error { + c := reflect.ValueOf(conf) + if c.Kind() != reflect.Ptr { + return ErrNotStructPtr + } + c = c.Elem() + if c.Kind() != reflect.Struct { + return ErrNotStructPtr + } + t := c.Type() + + var errs []*ParseError + for i := 0; i < c.NumField(); i++ { + tag, ok := t.Field(i).Tag.Lookup("env") + if !ok { + continue + } + key, constraint := parseTag(tag) + value := os.Getenv(key) + + if err := setField(c.Field(i), value, constraint); err != nil { + errs = append(errs, &ParseError{t.Field(i).Name, value, err}) + } + } + if len(errs) > 0 { + errorString := "failed to parse config:" + for _, err := range errs { + errorString += fmt.Sprintf("\n- %s", err) + } + return errors.New(errorString) + } + + return nil +} + +func setField(field reflect.Value, value, constraint string) error { + switch constraint { + case "": + break + case "required": + if value == "" { + return errors.New("required variable is not present") + } + case "file", "dir": + if err := checkPath(value, constraint == "dir"); err != nil { + return err + } + // TODO: use FindStringSubmatch to distinguish no match and match for empty string. + case regexp.MustCompile(`^opt\[.*\]$`).FindString(constraint): + if !contains(value, constraint) { + // TODO: print only the value options, not the whole string. + return fmt.Errorf("value is not in value options (%s)", constraint) + } + default: + return fmt.Errorf("invalid constraint (%s)", constraint) + } + + if value == "" { + return nil + } + + switch field.Kind() { + case reflect.String: + field.SetString(value) + case reflect.Bool: + b, err := parseutil.ParseBool(value) + if err != nil { + return errors.New("can't convert to bool") + } + field.SetBool(b) + case reflect.Int: + n, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return errors.New("can't convert to int") + } + field.SetInt(n) + case reflect.Slice: + field.Set(reflect.ValueOf(strings.Split(value, "|"))) + default: + return fmt.Errorf("type is not supported (%s)", field.Kind()) + } + return nil +} + +func checkPath(path string, dir bool) error { + file, err := os.Stat(path) + if err != nil { + // TODO: check case when file exist but os.Stat fails. + return os.ErrNotExist + } + if dir && !file.IsDir() { + return errors.New("not a directory") + } + return nil +} + +// contains reports whether s is within the value options, where value options +// are parsed from opt, which format's is opt[item1,item2,item3]. If an option +// contains commas, it should be single quoted (eg. opt[item1,'item2,item3']). +func contains(s, opt string) bool { + opt = strings.TrimSuffix(strings.TrimPrefix(opt, "opt["), "]") + var valueOpts []string + if strings.Contains(opt, "'") { + // The single quotes separate the options with comma and without comma + // Eg. "a,b,'c,d',e" will results "a,b," "c,d" and ",e" strings. + for _, s := range strings.Split(opt, "'") { + switch { + case s == "," || s == "": + case !strings.HasPrefix(s, ",") && !strings.HasSuffix(s, ","): + // If a string doesn't starts nor ends with a comma it means it's an option which + // contains comma, so we just append it to valueOpts as it is. Eg. "c,d" from above. + valueOpts = append(valueOpts, s) + default: + // If a string starts or ends with comma it means that it contains options without comma. + // So we split the string at commas to get the options. Eg. "a,b," and ",e" from above. + valueOpts = append(valueOpts, strings.Split(strings.Trim(s, ","), ",")...) + } + } + } else { + valueOpts = strings.Split(opt, ",") + } + for _, valOpt := range valueOpts { + if valOpt == s { + return true + } + } + return false +} diff --git a/vendor/github.com/ryanuber/go-glob/.travis.yml b/vendor/github.com/ryanuber/go-glob/.travis.yml new file mode 100644 index 0000000..9d1ca3c --- /dev/null +++ b/vendor/github.com/ryanuber/go-glob/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - tip +script: + - go test -v ./... diff --git a/vendor/github.com/ryanuber/go-glob/LICENSE b/vendor/github.com/ryanuber/go-glob/LICENSE new file mode 100644 index 0000000..bdfbd95 --- /dev/null +++ b/vendor/github.com/ryanuber/go-glob/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ryan Uber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/ryanuber/go-glob/README.md b/vendor/github.com/ryanuber/go-glob/README.md new file mode 100644 index 0000000..48f7fcb --- /dev/null +++ b/vendor/github.com/ryanuber/go-glob/README.md @@ -0,0 +1,29 @@ +# String globbing in golang [![Build Status](https://travis-ci.org/ryanuber/go-glob.svg)](https://travis-ci.org/ryanuber/go-glob) + +`go-glob` is a single-function library implementing basic string glob support. + +Globs are an extremely user-friendly way of supporting string matching without +requiring knowledge of regular expressions or Go's particular regex engine. Most +people understand that if you put a `*` character somewhere in a string, it is +treated as a wildcard. Surprisingly, this functionality isn't found in Go's +standard library, except for `path.Match`, which is intended to be used while +comparing paths (not arbitrary strings), and contains specialized logic for this +use case. A better solution might be a POSIX basic (non-ERE) regular expression +engine for Go, which doesn't exist currently. + +Example +======= + +``` +package main + +import "github.com/ryanuber/go-glob" + +func main() { + glob.Glob("*World!", "Hello, World!") // true + glob.Glob("Hello,*", "Hello, World!") // true + glob.Glob("*ello,*", "Hello, World!") // true + glob.Glob("World!", "Hello, World!") // false + glob.Glob("/home/*", "/home/ryanuber/.bashrc") // true +} +``` diff --git a/vendor/github.com/ryanuber/go-glob/glob.go b/vendor/github.com/ryanuber/go-glob/glob.go new file mode 100644 index 0000000..d9d4637 --- /dev/null +++ b/vendor/github.com/ryanuber/go-glob/glob.go @@ -0,0 +1,51 @@ +package glob + +import "strings" + +// The character which is treated like a glob +const GLOB = "*" + +// Glob will test a string pattern, potentially containing globs, against a +// subject string. The result is a simple true/false, determining whether or +// not the glob pattern matched the subject text. +func Glob(pattern, subj string) bool { + // Empty pattern can only match empty subject + if pattern == "" { + return subj == pattern + } + + // If the pattern _is_ a glob, it matches everything + if pattern == GLOB { + return true + } + + parts := strings.Split(pattern, GLOB) + + if len(parts) == 1 { + // No globs in pattern, so test for equality + return subj == pattern + } + + leadingGlob := strings.HasPrefix(pattern, GLOB) + trailingGlob := strings.HasSuffix(pattern, GLOB) + end := len(parts) - 1 + + // Check the first section. Requires special handling. + if !leadingGlob && !strings.HasPrefix(subj, parts[0]) { + return false + } + + // Go over the middle parts and ensure they match. + for i := 1; i < end; i++ { + if !strings.Contains(subj, parts[i]) { + return false + } + + // Trim evaluated text from subj as we loop over the pattern. + idx := strings.Index(subj, parts[i]) + len(parts[i]) + subj = subj[idx:] + } + + // Reached the last section. Requires special handling. + return trailingGlob || strings.HasSuffix(subj, parts[end]) +}