From 831a8e5ee02d10c403d9bf3e2a4f63887d489f72 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Sun, 6 Dec 2015 23:44:06 -0800 Subject: [PATCH] auto deploy release files --- .drone.sec | 1 + .drone.yml | 33 +- .gitignore | 1 + vendor/github.com/BurntSushi/toml/COMPATIBLE | 3 + vendor/github.com/BurntSushi/toml/COPYING | 14 + vendor/github.com/BurntSushi/toml/Makefile | 14 + vendor/github.com/BurntSushi/toml/README.md | 218 ++++ vendor/github.com/BurntSushi/toml/decode.go | 565 +++++++++++ .../github.com/BurntSushi/toml/decode_test.go | 445 ++++++++ vendor/github.com/BurntSushi/toml/doc.go | 10 + vendor/github.com/BurntSushi/toml/encode.go | 525 ++++++++++ .../github.com/BurntSushi/toml/encode_test.go | 282 +++++ .../BurntSushi/toml/encoding_types.go | 10 + .../BurntSushi/toml/encoding_types_1.1.go | 11 + vendor/github.com/BurntSushi/toml/lex.go | 725 +++++++++++++ vendor/github.com/BurntSushi/toml/lex_test.go | 59 ++ vendor/github.com/BurntSushi/toml/out_test.go | 19 + vendor/github.com/BurntSushi/toml/parse.go | 417 ++++++++ .../github.com/BurntSushi/toml/parse_test.go | 73 ++ vendor/github.com/BurntSushi/toml/session.vim | 1 + .../BurntSushi/toml/toml-test-encoder/COPYING | 14 + .../toml/toml-test-encoder/README.md | 14 + .../BurntSushi/toml/toml-test-encoder/main.go | 129 +++ .../BurntSushi/toml/toml-test-go/COPYING | 14 + .../BurntSushi/toml/toml-test-go/README.md | 14 + .../BurntSushi/toml/toml-test-go/main.go | 88 ++ .../github.com/BurntSushi/toml/tomlv/COPYING | 14 + .../BurntSushi/toml/tomlv/README.md | 22 + .../github.com/BurntSushi/toml/tomlv/main.go | 60 ++ .../github.com/BurntSushi/toml/type_check.go | 78 ++ .../github.com/BurntSushi/toml/type_fields.go | 241 +++++ vendor/github.com/codegangsta/cli/LICENSE | 21 + vendor/github.com/codegangsta/cli/README.md | 315 ++++++ vendor/github.com/codegangsta/cli/app.go | 333 ++++++ vendor/github.com/codegangsta/cli/app_test.go | 960 ++++++++++++++++++ .../cli/autocomplete/bash_autocomplete | 14 + .../cli/autocomplete/zsh_autocomplete | 5 + vendor/github.com/codegangsta/cli/cli.go | 40 + vendor/github.com/codegangsta/cli/cli_test.go | 98 ++ vendor/github.com/codegangsta/cli/command.go | 210 ++++ .../codegangsta/cli/command_test.go | 47 + vendor/github.com/codegangsta/cli/context.go | 388 +++++++ .../codegangsta/cli/context_test.go | 113 +++ vendor/github.com/codegangsta/cli/flag.go | 497 +++++++++ .../github.com/codegangsta/cli/flag_test.go | 740 ++++++++++++++ vendor/github.com/codegangsta/cli/help.go | 246 +++++ .../github.com/codegangsta/cli/help_test.go | 94 ++ .../codegangsta/cli/helpers_test.go | 19 + .../google/go-github/github/activity.go | 14 + .../go-github/github/activity_events.go | 305 ++++++ .../go-github/github/activity_events_test.go | 305 ++++++ .../github/activity_notifications.go | 225 ++++ .../github/activity_notifications_test.go | 205 ++++ .../google/go-github/github/activity_star.go | 123 +++ .../go-github/github/activity_star_test.go | 170 ++++ .../go-github/github/activity_watching.go | 131 +++ .../github/activity_watching_test.go | 177 ++++ .../github.com/google/go-github/github/doc.go | 126 +++ .../google/go-github/github/gists.go | 281 +++++ .../google/go-github/github/gists_comments.go | 118 +++ .../go-github/github/gists_comments_test.go | 155 +++ .../google/go-github/github/gists_test.go | 411 ++++++++ .../github.com/google/go-github/github/git.go | 14 + .../google/go-github/github/git_blobs.go | 47 + .../google/go-github/github/git_blobs_test.go | 92 ++ .../google/go-github/github/git_commits.go | 112 ++ .../go-github/github/git_commits_test.go | 82 ++ .../google/go-github/github/git_refs.go | 162 +++ .../google/go-github/github/git_refs_test.go | 280 +++++ .../google/go-github/github/git_tags.go | 73 ++ .../google/go-github/github/git_tags_test.go | 68 ++ .../google/go-github/github/git_trees.go | 89 ++ .../google/go-github/github/git_trees_test.go | 189 ++++ .../google/go-github/github/github.go | 588 +++++++++++ .../google/go-github/github/github_test.go | 679 +++++++++++++ .../google/go-github/github/gitignore.go | 63 ++ .../google/go-github/github/gitignore_test.go | 58 ++ .../google/go-github/github/issues.go | 261 +++++ .../go-github/github/issues_assignees.go | 46 + .../go-github/github/issues_assignees_test.go | 98 ++ .../go-github/github/issues_comments.go | 138 +++ .../go-github/github/issues_comments_test.go | 184 ++++ .../google/go-github/github/issues_events.go | 149 +++ .../go-github/github/issues_events_test.go | 86 ++ .../google/go-github/github/issues_labels.go | 222 ++++ .../go-github/github/issues_labels_test.go | 313 ++++++ .../go-github/github/issues_milestones.go | 140 +++ .../github/issues_milestones_test.go | 157 +++ .../google/go-github/github/issues_test.go | 242 +++++ .../google/go-github/github/licenses.go | 81 ++ .../google/go-github/github/licenses_test.go | 64 ++ .../google/go-github/github/misc.go | 197 ++++ .../google/go-github/github/misc_test.go | 170 ++++ .../google/go-github/github/orgs.go | 137 +++ .../google/go-github/github/orgs_hooks.go | 104 ++ .../go-github/github/orgs_hooks_test.go | 134 +++ .../google/go-github/github/orgs_members.go | 276 +++++ .../go-github/github/orgs_members_test.go | 356 +++++++ .../google/go-github/github/orgs_teams.go | 396 ++++++++ .../go-github/github/orgs_teams_test.go | 506 +++++++++ .../google/go-github/github/orgs_test.go | 120 +++ .../google/go-github/github/pulls.go | 275 +++++ .../google/go-github/github/pulls_comments.go | 145 +++ .../go-github/github/pulls_comments_test.go | 189 ++++ .../google/go-github/github/pulls_test.go | 365 +++++++ .../google/go-github/github/repos.go | 496 +++++++++ .../go-github/github/repos_collaborators.go | 95 ++ .../github/repos_collaborators_test.go | 135 +++ .../google/go-github/github/repos_comments.go | 150 +++ .../go-github/github/repos_comments_test.go | 180 ++++ .../google/go-github/github/repos_commits.go | 168 +++ .../go-github/github/repos_commits_test.go | 191 ++++ .../google/go-github/github/repos_contents.go | 248 +++++ .../go-github/github/repos_contents_test.go | 304 ++++++ .../go-github/github/repos_deployments.go | 162 +++ .../github/repos_deployments_test.go | 87 ++ .../google/go-github/github/repos_forks.go | 73 ++ .../go-github/github/repos_forks_test.go | 73 ++ .../google/go-github/github/repos_hooks.go | 194 ++++ .../go-github/github/repos_hooks_test.go | 187 ++++ .../google/go-github/github/repos_keys.go | 108 ++ .../go-github/github/repos_keys_test.go | 153 +++ .../google/go-github/github/repos_merging.go | 37 + .../go-github/github/repos_merging_test.go | 47 + .../google/go-github/github/repos_pages.go | 90 ++ .../go-github/github/repos_pages_test.go | 73 ++ .../google/go-github/github/repos_releases.go | 301 ++++++ .../go-github/github/repos_releases_test.go | 337 ++++++ .../google/go-github/github/repos_stats.go | 214 ++++ .../go-github/github/repos_stats_test.go | 210 ++++ .../google/go-github/github/repos_statuses.go | 128 +++ .../go-github/github/repos_statuses_test.go | 96 ++ .../google/go-github/github/repos_test.go | 409 ++++++++ .../google/go-github/github/search.go | 158 +++ .../google/go-github/github/search_test.go | 196 ++++ .../google/go-github/github/strings.go | 93 ++ .../google/go-github/github/strings_test.go | 137 +++ .../google/go-github/github/timestamp.go | 41 + .../google/go-github/github/timestamp_test.go | 181 ++++ .../google/go-github/github/users.go | 144 +++ .../go-github/github/users_administration.go | 64 ++ .../github/users_administration_test.go | 71 ++ .../google/go-github/github/users_emails.go | 69 ++ .../go-github/github/users_emails_test.go | 94 ++ .../go-github/github/users_followers.go | 116 +++ .../go-github/github/users_followers_test.go | 222 ++++ .../google/go-github/github/users_keys.go | 104 ++ .../go-github/github/users_keys_test.go | 124 +++ .../google/go-github/github/users_test.go | 150 +++ .../google/go-querystring/query/encode.go | 274 +++++ .../go-querystring/query/encode_test.go | 238 +++++ .../github.com/lgtmco/lgtm-go/lgtm/client.go | 198 ++++ .../lgtmco/lgtm-go/lgtm/interface.go | 13 + .../github.com/lgtmco/lgtm-go/lgtm/types.go | 46 + vendor/golang.org/x/net/context/context.go | 447 ++++++++ .../golang.org/x/net/context/context_test.go | 575 +++++++++++ .../x/net/context/ctxhttp/cancelreq.go | 18 + .../x/net/context/ctxhttp/cancelreq_go14.go | 23 + .../x/net/context/ctxhttp/ctxhttp.go | 79 ++ .../x/net/context/ctxhttp/ctxhttp_test.go | 72 ++ .../x/net/context/withtimeout_test.go | 26 + vendor/golang.org/x/oauth2/AUTHORS | 3 + vendor/golang.org/x/oauth2/CONTRIBUTING.md | 31 + vendor/golang.org/x/oauth2/CONTRIBUTORS | 3 + vendor/golang.org/x/oauth2/LICENSE | 27 + vendor/golang.org/x/oauth2/README.md | 64 ++ .../x/oauth2/bitbucket/bitbucket.go | 16 + .../golang.org/x/oauth2/client_appengine.go | 25 + .../clientcredentials/clientcredentials.go | 112 ++ .../clientcredentials_test.go | 96 ++ vendor/golang.org/x/oauth2/example_test.go | 45 + .../golang.org/x/oauth2/facebook/facebook.go | 16 + vendor/golang.org/x/oauth2/github/github.go | 16 + .../golang.org/x/oauth2/google/appengine.go | 86 ++ .../x/oauth2/google/appengine_hook.go | 13 + .../x/oauth2/google/appenginevm_hook.go | 14 + vendor/golang.org/x/oauth2/google/default.go | 155 +++ .../x/oauth2/google/example_test.go | 150 +++ vendor/golang.org/x/oauth2/google/google.go | 145 +++ .../golang.org/x/oauth2/google/google_test.go | 67 ++ vendor/golang.org/x/oauth2/google/jwt.go | 71 ++ vendor/golang.org/x/oauth2/google/sdk.go | 168 +++ vendor/golang.org/x/oauth2/google/sdk_test.go | 46 + vendor/golang.org/x/oauth2/internal/oauth2.go | 76 ++ .../x/oauth2/internal/oauth2_test.go | 62 ++ vendor/golang.org/x/oauth2/internal/token.go | 214 ++++ .../x/oauth2/internal/token_test.go | 28 + .../golang.org/x/oauth2/internal/transport.go | 67 ++ vendor/golang.org/x/oauth2/jws/jws.go | 159 +++ .../golang.org/x/oauth2/jwt/example_test.go | 31 + vendor/golang.org/x/oauth2/jwt/jwt.go | 153 +++ vendor/golang.org/x/oauth2/jwt/jwt_test.go | 134 +++ .../golang.org/x/oauth2/linkedin/linkedin.go | 16 + vendor/golang.org/x/oauth2/oauth2.go | 325 ++++++ vendor/golang.org/x/oauth2/oauth2_test.go | 471 +++++++++ .../x/oauth2/odnoklassniki/odnoklassniki.go | 16 + vendor/golang.org/x/oauth2/paypal/paypal.go | 22 + vendor/golang.org/x/oauth2/token.go | 158 +++ vendor/golang.org/x/oauth2/token_test.go | 72 ++ vendor/golang.org/x/oauth2/transport.go | 132 +++ vendor/golang.org/x/oauth2/transport_test.go | 86 ++ vendor/golang.org/x/oauth2/vk/vk.go | 16 + 202 files changed, 31660 insertions(+), 13 deletions(-) create mode 100644 .drone.sec create mode 100644 vendor/github.com/BurntSushi/toml/COMPATIBLE create mode 100644 vendor/github.com/BurntSushi/toml/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/Makefile create mode 100644 vendor/github.com/BurntSushi/toml/README.md create mode 100644 vendor/github.com/BurntSushi/toml/decode.go create mode 100644 vendor/github.com/BurntSushi/toml/decode_test.go create mode 100644 vendor/github.com/BurntSushi/toml/doc.go create mode 100644 vendor/github.com/BurntSushi/toml/encode.go create mode 100644 vendor/github.com/BurntSushi/toml/encode_test.go create mode 100644 vendor/github.com/BurntSushi/toml/encoding_types.go create mode 100644 vendor/github.com/BurntSushi/toml/encoding_types_1.1.go create mode 100644 vendor/github.com/BurntSushi/toml/lex.go create mode 100644 vendor/github.com/BurntSushi/toml/lex_test.go create mode 100644 vendor/github.com/BurntSushi/toml/out_test.go create mode 100644 vendor/github.com/BurntSushi/toml/parse.go create mode 100644 vendor/github.com/BurntSushi/toml/parse_test.go create mode 100644 vendor/github.com/BurntSushi/toml/session.vim create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-encoder/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-encoder/README.md create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-encoder/main.go create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-go/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-go/README.md create mode 100644 vendor/github.com/BurntSushi/toml/toml-test-go/main.go create mode 100644 vendor/github.com/BurntSushi/toml/tomlv/COPYING create mode 100644 vendor/github.com/BurntSushi/toml/tomlv/README.md create mode 100644 vendor/github.com/BurntSushi/toml/tomlv/main.go create mode 100644 vendor/github.com/BurntSushi/toml/type_check.go create mode 100644 vendor/github.com/BurntSushi/toml/type_fields.go create mode 100644 vendor/github.com/codegangsta/cli/LICENSE create mode 100644 vendor/github.com/codegangsta/cli/README.md create mode 100644 vendor/github.com/codegangsta/cli/app.go create mode 100644 vendor/github.com/codegangsta/cli/app_test.go create mode 100644 vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete create mode 100644 vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete create mode 100644 vendor/github.com/codegangsta/cli/cli.go create mode 100644 vendor/github.com/codegangsta/cli/cli_test.go create mode 100644 vendor/github.com/codegangsta/cli/command.go create mode 100644 vendor/github.com/codegangsta/cli/command_test.go create mode 100644 vendor/github.com/codegangsta/cli/context.go create mode 100644 vendor/github.com/codegangsta/cli/context_test.go create mode 100644 vendor/github.com/codegangsta/cli/flag.go create mode 100644 vendor/github.com/codegangsta/cli/flag_test.go create mode 100644 vendor/github.com/codegangsta/cli/help.go create mode 100644 vendor/github.com/codegangsta/cli/help_test.go create mode 100644 vendor/github.com/codegangsta/cli/helpers_test.go create mode 100644 vendor/github.com/google/go-github/github/activity.go create mode 100644 vendor/github.com/google/go-github/github/activity_events.go create mode 100644 vendor/github.com/google/go-github/github/activity_events_test.go create mode 100644 vendor/github.com/google/go-github/github/activity_notifications.go create mode 100644 vendor/github.com/google/go-github/github/activity_notifications_test.go create mode 100644 vendor/github.com/google/go-github/github/activity_star.go create mode 100644 vendor/github.com/google/go-github/github/activity_star_test.go create mode 100644 vendor/github.com/google/go-github/github/activity_watching.go create mode 100644 vendor/github.com/google/go-github/github/activity_watching_test.go create mode 100644 vendor/github.com/google/go-github/github/doc.go create mode 100644 vendor/github.com/google/go-github/github/gists.go create mode 100644 vendor/github.com/google/go-github/github/gists_comments.go create mode 100644 vendor/github.com/google/go-github/github/gists_comments_test.go create mode 100644 vendor/github.com/google/go-github/github/gists_test.go create mode 100644 vendor/github.com/google/go-github/github/git.go create mode 100644 vendor/github.com/google/go-github/github/git_blobs.go create mode 100644 vendor/github.com/google/go-github/github/git_blobs_test.go create mode 100644 vendor/github.com/google/go-github/github/git_commits.go create mode 100644 vendor/github.com/google/go-github/github/git_commits_test.go create mode 100644 vendor/github.com/google/go-github/github/git_refs.go create mode 100644 vendor/github.com/google/go-github/github/git_refs_test.go create mode 100644 vendor/github.com/google/go-github/github/git_tags.go create mode 100644 vendor/github.com/google/go-github/github/git_tags_test.go create mode 100644 vendor/github.com/google/go-github/github/git_trees.go create mode 100644 vendor/github.com/google/go-github/github/git_trees_test.go create mode 100644 vendor/github.com/google/go-github/github/github.go create mode 100644 vendor/github.com/google/go-github/github/github_test.go create mode 100644 vendor/github.com/google/go-github/github/gitignore.go create mode 100644 vendor/github.com/google/go-github/github/gitignore_test.go create mode 100644 vendor/github.com/google/go-github/github/issues.go create mode 100644 vendor/github.com/google/go-github/github/issues_assignees.go create mode 100644 vendor/github.com/google/go-github/github/issues_assignees_test.go create mode 100644 vendor/github.com/google/go-github/github/issues_comments.go create mode 100644 vendor/github.com/google/go-github/github/issues_comments_test.go create mode 100644 vendor/github.com/google/go-github/github/issues_events.go create mode 100644 vendor/github.com/google/go-github/github/issues_events_test.go create mode 100644 vendor/github.com/google/go-github/github/issues_labels.go create mode 100644 vendor/github.com/google/go-github/github/issues_labels_test.go create mode 100644 vendor/github.com/google/go-github/github/issues_milestones.go create mode 100644 vendor/github.com/google/go-github/github/issues_milestones_test.go create mode 100644 vendor/github.com/google/go-github/github/issues_test.go create mode 100644 vendor/github.com/google/go-github/github/licenses.go create mode 100644 vendor/github.com/google/go-github/github/licenses_test.go create mode 100644 vendor/github.com/google/go-github/github/misc.go create mode 100644 vendor/github.com/google/go-github/github/misc_test.go create mode 100644 vendor/github.com/google/go-github/github/orgs.go create mode 100644 vendor/github.com/google/go-github/github/orgs_hooks.go create mode 100644 vendor/github.com/google/go-github/github/orgs_hooks_test.go create mode 100644 vendor/github.com/google/go-github/github/orgs_members.go create mode 100644 vendor/github.com/google/go-github/github/orgs_members_test.go create mode 100644 vendor/github.com/google/go-github/github/orgs_teams.go create mode 100644 vendor/github.com/google/go-github/github/orgs_teams_test.go create mode 100644 vendor/github.com/google/go-github/github/orgs_test.go create mode 100644 vendor/github.com/google/go-github/github/pulls.go create mode 100644 vendor/github.com/google/go-github/github/pulls_comments.go create mode 100644 vendor/github.com/google/go-github/github/pulls_comments_test.go create mode 100644 vendor/github.com/google/go-github/github/pulls_test.go create mode 100644 vendor/github.com/google/go-github/github/repos.go create mode 100644 vendor/github.com/google/go-github/github/repos_collaborators.go create mode 100644 vendor/github.com/google/go-github/github/repos_collaborators_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_comments.go create mode 100644 vendor/github.com/google/go-github/github/repos_comments_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_commits.go create mode 100644 vendor/github.com/google/go-github/github/repos_commits_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_contents.go create mode 100644 vendor/github.com/google/go-github/github/repos_contents_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_deployments.go create mode 100644 vendor/github.com/google/go-github/github/repos_deployments_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_forks.go create mode 100644 vendor/github.com/google/go-github/github/repos_forks_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_hooks.go create mode 100644 vendor/github.com/google/go-github/github/repos_hooks_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_keys.go create mode 100644 vendor/github.com/google/go-github/github/repos_keys_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_merging.go create mode 100644 vendor/github.com/google/go-github/github/repos_merging_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_pages.go create mode 100644 vendor/github.com/google/go-github/github/repos_pages_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_releases.go create mode 100644 vendor/github.com/google/go-github/github/repos_releases_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_stats.go create mode 100644 vendor/github.com/google/go-github/github/repos_stats_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_statuses.go create mode 100644 vendor/github.com/google/go-github/github/repos_statuses_test.go create mode 100644 vendor/github.com/google/go-github/github/repos_test.go create mode 100644 vendor/github.com/google/go-github/github/search.go create mode 100644 vendor/github.com/google/go-github/github/search_test.go create mode 100644 vendor/github.com/google/go-github/github/strings.go create mode 100644 vendor/github.com/google/go-github/github/strings_test.go create mode 100644 vendor/github.com/google/go-github/github/timestamp.go create mode 100644 vendor/github.com/google/go-github/github/timestamp_test.go create mode 100644 vendor/github.com/google/go-github/github/users.go create mode 100644 vendor/github.com/google/go-github/github/users_administration.go create mode 100644 vendor/github.com/google/go-github/github/users_administration_test.go create mode 100644 vendor/github.com/google/go-github/github/users_emails.go create mode 100644 vendor/github.com/google/go-github/github/users_emails_test.go create mode 100644 vendor/github.com/google/go-github/github/users_followers.go create mode 100644 vendor/github.com/google/go-github/github/users_followers_test.go create mode 100644 vendor/github.com/google/go-github/github/users_keys.go create mode 100644 vendor/github.com/google/go-github/github/users_keys_test.go create mode 100644 vendor/github.com/google/go-github/github/users_test.go create mode 100644 vendor/github.com/google/go-querystring/query/encode.go create mode 100644 vendor/github.com/google/go-querystring/query/encode_test.go create mode 100644 vendor/github.com/lgtmco/lgtm-go/lgtm/client.go create mode 100644 vendor/github.com/lgtmco/lgtm-go/lgtm/interface.go create mode 100644 vendor/github.com/lgtmco/lgtm-go/lgtm/types.go create mode 100644 vendor/golang.org/x/net/context/context.go create mode 100644 vendor/golang.org/x/net/context/context_test.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/cancelreq.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go create mode 100644 vendor/golang.org/x/net/context/withtimeout_test.go create mode 100644 vendor/golang.org/x/oauth2/AUTHORS create mode 100644 vendor/golang.org/x/oauth2/CONTRIBUTING.md create mode 100644 vendor/golang.org/x/oauth2/CONTRIBUTORS create mode 100644 vendor/golang.org/x/oauth2/LICENSE create mode 100644 vendor/golang.org/x/oauth2/README.md create mode 100644 vendor/golang.org/x/oauth2/bitbucket/bitbucket.go create mode 100644 vendor/golang.org/x/oauth2/client_appengine.go create mode 100644 vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go create mode 100644 vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go create mode 100644 vendor/golang.org/x/oauth2/example_test.go create mode 100644 vendor/golang.org/x/oauth2/facebook/facebook.go create mode 100644 vendor/golang.org/x/oauth2/github/github.go create mode 100644 vendor/golang.org/x/oauth2/google/appengine.go create mode 100644 vendor/golang.org/x/oauth2/google/appengine_hook.go create mode 100644 vendor/golang.org/x/oauth2/google/appenginevm_hook.go create mode 100644 vendor/golang.org/x/oauth2/google/default.go create mode 100644 vendor/golang.org/x/oauth2/google/example_test.go create mode 100644 vendor/golang.org/x/oauth2/google/google.go create mode 100644 vendor/golang.org/x/oauth2/google/google_test.go create mode 100644 vendor/golang.org/x/oauth2/google/jwt.go create mode 100644 vendor/golang.org/x/oauth2/google/sdk.go create mode 100644 vendor/golang.org/x/oauth2/google/sdk_test.go create mode 100644 vendor/golang.org/x/oauth2/internal/oauth2.go create mode 100644 vendor/golang.org/x/oauth2/internal/oauth2_test.go create mode 100644 vendor/golang.org/x/oauth2/internal/token.go create mode 100644 vendor/golang.org/x/oauth2/internal/token_test.go create mode 100644 vendor/golang.org/x/oauth2/internal/transport.go create mode 100644 vendor/golang.org/x/oauth2/jws/jws.go create mode 100644 vendor/golang.org/x/oauth2/jwt/example_test.go create mode 100644 vendor/golang.org/x/oauth2/jwt/jwt.go create mode 100644 vendor/golang.org/x/oauth2/jwt/jwt_test.go create mode 100644 vendor/golang.org/x/oauth2/linkedin/linkedin.go create mode 100644 vendor/golang.org/x/oauth2/oauth2.go create mode 100644 vendor/golang.org/x/oauth2/oauth2_test.go create mode 100644 vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go create mode 100644 vendor/golang.org/x/oauth2/paypal/paypal.go create mode 100644 vendor/golang.org/x/oauth2/token.go create mode 100644 vendor/golang.org/x/oauth2/token_test.go create mode 100644 vendor/golang.org/x/oauth2/transport.go create mode 100644 vendor/golang.org/x/oauth2/transport_test.go create mode 100644 vendor/golang.org/x/oauth2/vk/vk.go diff --git a/.drone.sec b/.drone.sec new file mode 100644 index 0000000..6272b84 --- /dev/null +++ b/.drone.sec @@ -0,0 +1 @@ +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.lG84en0N7UeCNd8ZJXVmwTvLhb0nIPTJdRhP_aTMcAtDVnXAJgHFOcgYkhc1YWW_dMRrSWA8gCgucFesar5DMvFeXj580Jyc17nUfSeShoZpsVJ-RIDyQ5k1zFCoskJNrazSQtelQKtz0IpQvo-OLmDsqqn8u44jt-XiCcXwgb5Wa-kBLGchuCeUc3bqmRj1oRwYkx616rwHybjHQg08g2gHHXDVFWQEvup1cu9Y6KYaepKKKnpn0HEFAh2TYBGSnUMre5DAbQRGpuK3CCCP2B9a8xUp0mJuIPwF58uzUsXNJHiEBJ6CvR02gR2GPzG83Xwx5_5cMR2A1pICx59lLA.-U379Q7zmakJUI8Y.qUJtiwBo5yuf5wcMmSnYBqyq8b2hUHP5DlWae4TS_jacnpbBfEFKQY2EMyqxyvpooHDwGSi_8l2tls1EoQW355Xat3BnnPCsZ84glo5DnnMbJQhOUxtLyCwqHORnLUmjwreGl_TPJxgUL-W00gP0D8XCtYSB4aOHx77p7X1Q6jdiCL3BMQ7F2iY1mlsDBl5mFA.MAmz3dPyJQEIQkm_V1Jlmw \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index ba859ed..ee07bc5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,18 +5,25 @@ build: commands: # compile lgtm for all architectures - - GOOS=linux GOARCH=amd64 go build -o ./bin/linux_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm - - GOOS=linux GOARCH=386 go build -o ./bin/linux_386/lgtm github.com/lgtmco/lgtm-cli/lgtm - - GOOS=linux GOARCH=arm go build -o ./bin/linux_arm/lgtm github.com/lgtmco/lgtm-cli/lgtm - - GOOS=darwin GOARCH=amd64 go build -o ./bin/darwin_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm - - GOOS=windows GOARCH=386 go build -o ./bin/windows_386/lgtm github.com/lgtmco/lgtm-cli/lgtm - - GOOS=windows GOARCH=amd64 go build -o ./bin/windows_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=linux GOARCH=amd64 go build -o ./release/linux_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=linux GOARCH=386 go build -o ./release/linux_386/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=linux GOARCH=arm go build -o ./release/linux_arm/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=darwin GOARCH=amd64 go build -o ./release/darwin_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=windows GOARCH=386 go build -o ./release/windows_386/lgtm github.com/lgtmco/lgtm-cli/lgtm + - GOOS=windows GOARCH=amd64 go build -o ./release/windows_amd64/lgtm github.com/lgtmco/lgtm-cli/lgtm # tar binary files prior to upload - - mkdir dist - - tar -cvzf dist/lgtm_linux_amd64.tar.gz --directory=bin/linux_amd64 lgtm - - tar -cvzf dist/lgtm_linux_386.tar.gz --directory=bin/linux_386 lgtm - - tar -cvzf dist/lgtm_linux_arm.tar.gz --directory=bin/linux_arm lgtm - - tar -cvzf dist/lgtm_darwin_amd64.tar.gz --directory=bin/darwin_amd64 lgtm - - tar -cvzf dist/lgtm_windows_386.tar.gz --directory=bin/windows_386 lgtm - - tar -cvzf dist/lgtm_windows_amd64.tar.gz --directory=bin/windows_amd64 lgtm + - tar -cvzf release/lgtm_linux_amd64.tar.gz --directory=release/linux_amd64 lgtm + - tar -cvzf release/lgtm_linux_386.tar.gz --directory=release/linux_386 lgtm + - tar -cvzf release/lgtm_linux_arm.tar.gz --directory=release/linux_arm lgtm + - tar -cvzf release/lgtm_darwin_amd64.tar.gz --directory=release/darwin_amd64 lgtm + - tar -cvzf release/lgtm_windows_386.tar.gz --directory=release/windows_386 lgtm + - tar -cvzf release/lgtm_windows_amd64.tar.gz --directory=release/windows_amd64 lgtm + +publish: + github_release: + api_key: $$GITHUB_TOKEN + files: + - release/*.tar.gz + when: + event: tag diff --git a/.gitignore b/.gitignore index 995b15e..f378947 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ _testmain.go *.test *.prof +release/ lgtm/lgtm diff --git a/vendor/github.com/BurntSushi/toml/COMPATIBLE b/vendor/github.com/BurntSushi/toml/COMPATIBLE new file mode 100644 index 0000000..21e0938 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/COMPATIBLE @@ -0,0 +1,3 @@ +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + diff --git a/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/BurntSushi/toml/COPYING new file mode 100644 index 0000000..5a8e332 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/Makefile b/vendor/github.com/BurntSushi/toml/Makefile new file mode 100644 index 0000000..e6adc3e --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/Makefile @@ -0,0 +1,14 @@ +install: + go install + +fmt: + gofmt -w *.go */*.go + colcheck *.go */*.go + +tags: + find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS + +push: + git push origin master + git push github master + diff --git a/vendor/github.com/BurntSushi/toml/README.md b/vendor/github.com/BurntSushi/toml/README.md new file mode 100644 index 0000000..49f43f3 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/README.md @@ -0,0 +1,218 @@ +# TOML parser and encoder for Go with reflection + +TOML stands for Tom's Obvious, Minimal Language. This Go package provides a +reflection interface similar to Go's standard library `json` and `xml` +packages. This package also supports the `encoding.TextUnmarshaler` and +`encoding.TextMarshaler` interfaces so that you can define custom data +representations. (There is an example of this below.) + +Spec: https://github.com/mojombo/toml + +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + +Documentation: http://godoc.org/github.com/BurntSushi/toml + +Installation: + +```bash +go get github.com/BurntSushi/toml +``` + +Try the toml validator: + +```bash +go get github.com/BurntSushi/toml/tomlv +tomlv some-toml-file.toml +``` + + +## Testing + +This package passes all tests in +[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder +and the encoder. + +## Examples + +This package works similarly to how the Go standard library handles `XML` +and `JSON`. Namely, data is loaded into Go values via reflection. + +For the simplest example, consider some TOML file as just a list of keys +and values: + +```toml +Age = 25 +Cats = [ "Cauchy", "Plato" ] +Pi = 3.14 +Perfection = [ 6, 28, 496, 8128 ] +DOB = 1987-07-05T05:45:00Z +``` + +Which could be defined in Go as: + +```go +type Config struct { + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time // requires `import time` +} +``` + +And then decoded with: + +```go +var conf Config +if _, err := toml.Decode(tomlData, &conf); err != nil { + // handle error +} +``` + +You can also use struct tags if your struct field name doesn't map to a TOML +key value directly: + +```toml +some_key_NAME = "wat" +``` + +```go +type TOML struct { + ObscureKey string `toml:"some_key_NAME"` +} +``` + +## Using the `encoding.TextUnmarshaler` interface + +Here's an example that automatically parses duration strings into +`time.Duration` values: + +```toml +[[song]] +name = "Thunder Road" +duration = "4m49s" + +[[song]] +name = "Stairway to Heaven" +duration = "8m03s" +``` + +Which can be decoded with: + +```go +type song struct { + Name string + Duration duration +} +type songs struct { + Song []song +} +var favorites songs +if _, err := Decode(blob, &favorites); err != nil { + log.Fatal(err) +} + +for _, s := range favorites.Song { + fmt.Printf("%s (%s)\n", s.Name, s.Duration) +} +``` + +And you'll also need a `duration` type that satisfies the +`encoding.TextUnmarshaler` interface: + +```go +type duration struct { + time.Duration +} + +func (d *duration) UnmarshalText(text []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(text)) + return err +} +``` + +## More complex usage + +Here's an example of how to load the example from the official spec page: + +```toml +# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] +``` + +And the corresponding Go types are: + +```go +type tomlConfig struct { + Title string + Owner ownerInfo + DB database `toml:"database"` + Servers map[string]server + Clients clients +} + +type ownerInfo struct { + Name string + Org string `toml:"organization"` + Bio string + DOB time.Time +} + +type database struct { + Server string + Ports []int + ConnMax int `toml:"connection_max"` + Enabled bool +} + +type server struct { + IP string + DC string +} + +type clients struct { + Data [][]interface{} + Hosts []string +} +``` + +Note that a case insensitive match will be tried if an exact match can't be +found. + +A working example of the above can be found in `_examples/example.{go,toml}`. + diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go new file mode 100644 index 0000000..84c3d2f --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/decode.go @@ -0,0 +1,565 @@ +package toml + +import ( + "fmt" + "io" + "io/ioutil" + "reflect" + "strings" + "time" +) + +var e = fmt.Errorf + +// Primitive is a TOML value that hasn't been decoded into a Go value. +// When using the various `Decode*` functions, the type `Primitive` may +// be given to any value, and its decoding will be delayed. +// +// A `Primitive` value can be decoded using the `PrimitiveDecode` function. +// +// The underlying representation of a `Primitive` value is subject to change. +// Do not rely on it. +// +// N.B. Primitive values are still parsed, so using them will only avoid +// the overhead of reflection. They can be useful when you don't know the +// exact type of TOML data until run time. +type Primitive interface{} + +// PrimitiveDecode is just like the other `Decode*` functions, except it +// decodes a TOML value that has already been parsed. Valid primitive values +// can *only* be obtained from values filled by the decoder functions, +// including `PrimitiveDecode`. (i.e., `v` may contain more `Primitive` +// values.) +// +// Meta data for primitive values is included in the meta data returned by +// the `Decode*` functions. +func PrimitiveDecode(primValue Primitive, v interface{}) error { + return unify(primValue, rvalue(v)) +} + +// Decode will decode the contents of `data` in TOML format into a pointer +// `v`. +// +// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be +// used interchangeably.) +// +// TOML arrays of tables correspond to either a slice of structs or a slice +// of maps. +// +// TOML datetimes correspond to Go `time.Time` values. +// +// All other TOML types (float, string, int, bool and array) correspond +// to the obvious Go types. +// +// An exception to the above rules is if a type implements the +// encoding.TextUnmarshaler interface. In this case, any primitive TOML value +// (floats, strings, integers, booleans and datetimes) will be converted to +// a byte string and given to the value's UnmarshalText method. Here's an +// example for parsing durations: +// +// type duration struct { +// time.Duration +// } +// +// func (d *duration) UnmarshalText(text []byte) error { +// var err error +// d.Duration, err = time.ParseDuration(string(text)) +// return err +// } +// +// func ExampleUnmarshaler() { +// blob := ` +// [[song]] +// name = "Thunder Road" +// duration = "4m49s" +// +// [[song]] +// name = "Stairway to Heaven" +// duration = "8m03s" +// ` +// type song struct { +// Name string +// Duration duration +// } +// type songs struct { +// Song []song +// } +// var favorites songs +// if _, err := Decode(blob, &favorites); err != nil { +// log.Fatal(err) +// } +// +// for _, s := range favorites.Song { +// fmt.Printf("%s (%s)\n", s.Name, s.Duration) +// } +// // Output: +// // Thunder Road (4m49s) +// // Stairway to Heaven (8m3s) +// } +// +// Key mapping +// +// TOML keys can map to either keys in a Go map or field names in a Go +// struct. The special `toml` struct tag may be used to map TOML keys to +// struct fields that don't match the key name exactly. (See the example.) +// A case insensitive match to struct names will be tried if an exact match +// can't be found. +// +// The mapping between TOML values and Go values is loose. That is, there +// may exist TOML values that cannot be placed into your representation, and +// there may be parts of your representation that do not correspond to +// TOML values. +// +// This decoder will not handle cyclic types. If a cyclic type is passed, +// `Decode` will not terminate. +func Decode(data string, v interface{}) (MetaData, error) { + p, err := parse(data) + if err != nil { + return MetaData{}, err + } + return MetaData{p.mapping, p.types, p.ordered}, unify(p.mapping, rvalue(v)) +} + +// DecodeFile is just like Decode, except it will automatically read the +// contents of the file at `fpath` and decode it for you. +func DecodeFile(fpath string, v interface{}) (MetaData, error) { + bs, err := ioutil.ReadFile(fpath) + if err != nil { + return MetaData{}, err + } + return Decode(string(bs), v) +} + +// DecodeReader is just like Decode, except it will consume all bytes +// from the reader and decode it for you. +func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { + bs, err := ioutil.ReadAll(r) + if err != nil { + return MetaData{}, err + } + return Decode(string(bs), v) +} + +// unify performs a sort of type unification based on the structure of `rv`, +// which is the client representation. +// +// Any type mismatch produces an error. Finding a type that we don't know +// how to handle produces an unsupported type error. +func unify(data interface{}, rv reflect.Value) error { + // Special case. Look for a `Primitive` value. + if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { + return unifyAnything(data, rv) + } + + // Special case. Look for a value satisfying the TextUnmarshaler interface. + if v, ok := rv.Interface().(TextUnmarshaler); ok { + return unifyText(data, v) + } + // BUG(burntsushi) + // The behavior here is incorrect whenever a Go type satisfies the + // encoding.TextUnmarshaler interface but also corresponds to a TOML + // hash or array. In particular, the unmarshaler should only be applied + // to primitive TOML values. But at this point, it will be applied to + // all kinds of values and produce an incorrect error whenever those values + // are hashes or arrays (including arrays of tables). + + k := rv.Kind() + + // laziness + if k >= reflect.Int && k <= reflect.Uint64 { + return unifyInt(data, rv) + } + switch k { + case reflect.Ptr: + elem := reflect.New(rv.Type().Elem()) + err := unify(data, reflect.Indirect(elem)) + if err != nil { + return err + } + rv.Set(elem) + return nil + case reflect.Struct: + return unifyStruct(data, rv) + case reflect.Map: + return unifyMap(data, rv) + case reflect.Array: + return unifyArray(data, rv) + case reflect.Slice: + return unifySlice(data, rv) + case reflect.String: + return unifyString(data, rv) + case reflect.Bool: + return unifyBool(data, rv) + case reflect.Interface: + // we only support empty interfaces. + if rv.NumMethod() > 0 { + return e("Unsupported type '%s'.", rv.Kind()) + } + return unifyAnything(data, rv) + case reflect.Float32: + fallthrough + case reflect.Float64: + return unifyFloat64(data, rv) + } + return e("Unsupported type '%s'.", rv.Kind()) +} + +func unifyStruct(mapping interface{}, rv reflect.Value) error { + tmap, ok := mapping.(map[string]interface{}) + if !ok { + return mismatch(rv, "map", mapping) + } + + for key, datum := range tmap { + var f *field + fields := cachedTypeFields(rv.Type()) + for i := range fields { + ff := &fields[i] + if ff.name == key { + f = ff + break + } + if f == nil && strings.EqualFold(ff.name, key) { + f = ff + } + } + if f != nil { + subv := rv + for _, i := range f.index { + if subv.Kind() == reflect.Ptr { + if subv.IsNil() { + subv.Set(reflect.New(subv.Type().Elem())) + } + subv = subv.Elem() + } + subv = subv.Field(i) + } + sf := indirect(subv) + + if isUnifiable(sf) { + if err := unify(datum, sf); err != nil { + return e("Type mismatch for '%s.%s': %s", + rv.Type().String(), f.name, err) + } + } else if f.name != "" { + // Bad user! No soup for you! + return e("Field '%s.%s' is unexported, and therefore cannot "+ + "be loaded with reflection.", rv.Type().String(), f.name) + } + } + } + return nil +} + +func unifyMap(mapping interface{}, rv reflect.Value) error { + tmap, ok := mapping.(map[string]interface{}) + if !ok { + return badtype("map", mapping) + } + if rv.IsNil() { + rv.Set(reflect.MakeMap(rv.Type())) + } + for k, v := range tmap { + rvkey := indirect(reflect.New(rv.Type().Key())) + rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) + if err := unify(v, rvval); err != nil { + return err + } + + rvkey.SetString(k) + rv.SetMapIndex(rvkey, rvval) + } + return nil +} + +func unifyArray(data interface{}, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + return badtype("slice", data) + } + sliceLen := datav.Len() + if sliceLen != rv.Len() { + return e("expected array length %d; got TOML array of length %d", + rv.Len(), sliceLen) + } + for i := 0; i < sliceLen; i++ { + v := datav.Index(i).Interface() + sliceval := indirect(rv.Index(i)) + if err := unify(v, sliceval); err != nil { + return err + } + } + return nil +} + +func unifySlice(data interface{}, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + return badtype("slice", data) + } + sliceLen := datav.Len() + if rv.IsNil() { + rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen)) + } + for i := 0; i < sliceLen; i++ { + v := datav.Index(i).Interface() + sliceval := indirect(rv.Index(i)) + if err := unify(v, sliceval); err != nil { + return err + } + } + return nil +} + +func unifyDatetime(data interface{}, rv reflect.Value) error { + if _, ok := data.(time.Time); ok { + rv.Set(reflect.ValueOf(data)) + return nil + } + return badtype("time.Time", data) +} + +func unifyString(data interface{}, rv reflect.Value) error { + if s, ok := data.(string); ok { + rv.SetString(s) + return nil + } + return badtype("string", data) +} + +func unifyFloat64(data interface{}, rv reflect.Value) error { + if num, ok := data.(float64); ok { + switch rv.Kind() { + case reflect.Float32: + fallthrough + case reflect.Float64: + rv.SetFloat(num) + default: + panic("bug") + } + return nil + } + return badtype("float", data) +} + +func unifyInt(data interface{}, rv reflect.Value) error { + if num, ok := data.(int64); ok { + switch rv.Kind() { + case reflect.Int: + fallthrough + case reflect.Int8: + fallthrough + case reflect.Int16: + fallthrough + case reflect.Int32: + fallthrough + case reflect.Int64: + rv.SetInt(int64(num)) + case reflect.Uint: + fallthrough + case reflect.Uint8: + fallthrough + case reflect.Uint16: + fallthrough + case reflect.Uint32: + fallthrough + case reflect.Uint64: + rv.SetUint(uint64(num)) + default: + panic("bug") + } + return nil + } + return badtype("integer", data) +} + +func unifyBool(data interface{}, rv reflect.Value) error { + if b, ok := data.(bool); ok { + rv.SetBool(b) + return nil + } + return badtype("boolean", data) +} + +func unifyAnything(data interface{}, rv reflect.Value) error { + // too awesome to fail + rv.Set(reflect.ValueOf(data)) + return nil +} + +func unifyText(data interface{}, v TextUnmarshaler) error { + var s string + switch sdata := data.(type) { + case TextMarshaler: + text, err := sdata.MarshalText() + if err != nil { + return err + } + s = string(text) + case fmt.Stringer: + s = sdata.String() + case string: + s = sdata + case bool: + s = fmt.Sprintf("%v", sdata) + case int64: + s = fmt.Sprintf("%d", sdata) + case float64: + s = fmt.Sprintf("%f", sdata) + default: + return badtype("primitive (string-like)", data) + } + if err := v.UnmarshalText([]byte(s)); err != nil { + return err + } + return nil +} + +// rvalue returns a reflect.Value of `v`. All pointers are resolved. +func rvalue(v interface{}) reflect.Value { + return indirect(reflect.ValueOf(v)) +} + +// indirect returns the value pointed to by a pointer. +// Pointers are followed until the value is not a pointer. +// New values are allocated for each nil pointer. +// +// An exception to this rule is if the value satisfies an interface of +// interest to us (like encoding.TextUnmarshaler). +func indirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr { + if v.CanAddr() { + pv := v.Addr() + if _, ok := pv.Interface().(TextUnmarshaler); ok { + return pv + } + } + return v + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return indirect(reflect.Indirect(v)) +} + +func isUnifiable(rv reflect.Value) bool { + if rv.CanSet() { + return true + } + if _, ok := rv.Interface().(TextUnmarshaler); ok { + return true + } + return false +} + +func tstring(rv reflect.Value) string { + return rv.Type().String() +} + +func badtype(expected string, data interface{}) error { + return e("Expected %s but found '%T'.", expected, data) +} + +func mismatch(user reflect.Value, expected string, data interface{}) error { + return e("Type mismatch for %s. Expected %s but found '%T'.", + tstring(user), expected, data) +} + +func insensitiveGet( + tmap map[string]interface{}, kname string) (interface{}, bool) { + + if datum, ok := tmap[kname]; ok { + return datum, true + } + for k, v := range tmap { + if strings.EqualFold(kname, k) { + return v, true + } + } + return nil, false +} + +// MetaData allows access to meta information about TOML data that may not +// be inferrable via reflection. In particular, whether a key has been defined +// and the TOML type of a key. +type MetaData struct { + mapping map[string]interface{} + types map[string]tomlType + keys []Key +} + +// IsDefined returns true if the key given exists in the TOML data. The key +// should be specified hierarchially. e.g., +// +// // access the TOML key 'a.b.c' +// IsDefined("a", "b", "c") +// +// IsDefined will return false if an empty key given. Keys are case sensitive. +func (md MetaData) IsDefined(key ...string) bool { + var hashOrVal interface{} + var hash map[string]interface{} + var ok bool + + if len(key) == 0 { + return false + } + + hashOrVal = md.mapping + for _, k := range key { + if hash, ok = hashOrVal.(map[string]interface{}); !ok { + return false + } + if hashOrVal, ok = hash[k]; !ok { + return false + } + } + return true +} + +// Type returns a string representation of the type of the key specified. +// +// Type will return the empty string if given an empty key or a key that +// does not exist. Keys are case sensitive. +func (md MetaData) Type(key ...string) string { + fullkey := strings.Join(key, ".") + if typ, ok := md.types[fullkey]; ok { + return typ.typeString() + } + return "" +} + +// Key is the type of any TOML key, including key groups. Use (MetaData).Keys +// to get values of this type. +type Key []string + +func (k Key) String() string { + return strings.Join(k, ".") +} + +func (k Key) add(piece string) Key { + newKey := make(Key, len(k)) + copy(newKey, k) + return append(newKey, piece) +} + +// Keys returns a slice of every key in the TOML data, including key groups. +// Each key is itself a slice, where the first element is the top of the +// hierarchy and the last is the most specific. +// +// The list will have the same order as the keys appeared in the TOML data. +// +// All keys returned are non-empty. +func (md MetaData) Keys() []Key { + return md.keys +} + +func allKeys(m map[string]interface{}, context Key) []Key { + keys := make([]Key, 0, len(m)) + for k, v := range m { + keys = append(keys, context.add(k)) + if t, ok := v.(map[string]interface{}); ok { + keys = append(keys, allKeys(t, context.add(k))...) + } + } + return keys +} diff --git a/vendor/github.com/BurntSushi/toml/decode_test.go b/vendor/github.com/BurntSushi/toml/decode_test.go new file mode 100644 index 0000000..f813891 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/decode_test.go @@ -0,0 +1,445 @@ +package toml + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "testing" + "time" +) + +func init() { + log.SetFlags(0) +} + +var testSimple = ` +age = 250 + +andrew = "gallant" +kait = "brady" +now = 1987-07-05T05:45:00Z +yesOrNo = true +pi = 3.14 +colors = [ + ["red", "green", "blue"], + ["cyan", "magenta", "yellow", "black"], +] + +[Annoying.Cats] +plato = "smelly" +cauchy = "stupido" + +` + +type kitties struct { + Plato string + Cauchy string +} + +type simple struct { + Age int + Colors [][]string + Pi float64 + YesOrNo bool + Now time.Time + Andrew string + Kait string + Annoying map[string]kitties +} + +func TestDecode(t *testing.T) { + var val simple + + md, err := Decode(testSimple, &val) + if err != nil { + t.Fatal(err) + } + + testf("Is 'Annoying.Cats.plato' defined? %v\n", + md.IsDefined("Annoying", "Cats", "plato")) + testf("Is 'Cats.Stinky' defined? %v\n", md.IsDefined("Cats", "Stinky")) + testf("Type of 'colors'? %s\n\n", md.Type("colors")) + + testf("%v\n", val) +} + +func TestDecodeEmbedded(t *testing.T) { + type Dog struct{ Name string } + + tests := map[string]struct { + input string + decodeInto interface{} + wantDecoded interface{} + }{ + "embedded struct": { + input: `Name = "milton"`, + decodeInto: &struct{ Dog }{}, + wantDecoded: &struct{ Dog }{Dog{"milton"}}, + }, + "embedded non-nil pointer to struct": { + input: `Name = "milton"`, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, + }, + "embedded nil pointer to struct": { + input: ``, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{nil}, + }, + } + + for label, test := range tests { + _, err := Decode(test.input, test.decodeInto) + if err != nil { + t.Fatal(err) + } + + want, got := jsonstr(test.wantDecoded), jsonstr(test.decodeInto) + if want != got { + t.Errorf("%s: want decoded == %+v, got %+v", label, want, got) + } + } +} + +// jsonstr allows comparison of deeply nested structs with pointer members. +func jsonstr(o interface{}) string { + s, err := json.MarshalIndent(o, "", " ") + if err != nil { + panic(err.Error()) + } + return string(s) +} + +var tomlTableArrays = ` +[[albums]] +name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] +name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark" +` + +type Music struct { + Albums []Album +} + +type Album struct { + Name string + Songs []Song +} + +type Song struct { + Name string +} + +func TestTableArrays(t *testing.T) { + expected := Music{[]Album{ + {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, + {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, + }} + var got Music + if _, err := Decode(tomlTableArrays, &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, got) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) + } +} + +// Case insensitive matching tests. +// A bit more comprehensive than needed given the current implementation, +// but implementations change. +// Probably still missing demonstrations of some ugly corner cases regarding +// case insensitive matching and multiple fields. +var caseToml = ` +tOpString = "string" +tOpInt = 1 +tOpFloat = 1.1 +tOpBool = true +tOpdate = 2006-01-02T15:04:05Z +tOparray = [ "array" ] +Match = "i should be in Match only" +MatcH = "i should be in MatcH only" +once = "just once" +[nEst.eD] +nEstedString = "another string" +` + +type Insensitive struct { + TopString string + TopInt int + TopFloat float64 + TopBool bool + TopDate time.Time + TopArray []string + Match string + MatcH string + Once string + OncE string + Nest InsensitiveNest +} + +type InsensitiveNest struct { + Ed InsensitiveEd +} + +type InsensitiveEd struct { + NestedString string +} + +func TestCase(t *testing.T) { + tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) + if err != nil { + panic(err) + } + expected := Insensitive{ + TopString: "string", + TopInt: 1, + TopFloat: 1.1, + TopBool: true, + TopDate: tme, + TopArray: []string{"array"}, + MatcH: "i should be in MatcH only", + Match: "i should be in Match only", + Once: "just once", + OncE: "", + Nest: InsensitiveNest{ + Ed: InsensitiveEd{NestedString: "another string"}, + }, + } + var got Insensitive + _, err = Decode(caseToml, &got) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, got) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) + } +} + +func TestPointers(t *testing.T) { + type Object struct { + Type string + Description string + } + + type Dict struct { + NamedObject map[string]*Object + BaseObject *Object + Strptr *string + Strptrs []*string + } + s1, s2, s3 := "blah", "abc", "def" + expected := &Dict{ + Strptr: &s1, + Strptrs: []*string{&s2, &s3}, + NamedObject: map[string]*Object{ + "foo": {"FOO", "fooooo!!!"}, + "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, + }, + BaseObject: &Object{"BASE", "da base"}, + } + + ex1 := ` +Strptr = "blah" +Strptrs = ["abc", "def"] + +[NamedObject.foo] +Type = "FOO" +Description = "fooooo!!!" + +[NamedObject.bar] +Type = "BAR" +Description = "ba-ba-ba-ba-barrrr!!!" + +[BaseObject] +Type = "BASE" +Description = "da base" +` + dict := new(Dict) + _, err := Decode(ex1, dict) + if err != nil { + t.Errorf("Decode error: %v", err) + } + if !reflect.DeepEqual(expected, dict) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) + } +} + +type sphere struct { + Center [3]float64 + Radius float64 +} + +func TestDecodeArrays(t *testing.T) { + var s1 sphere + if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil { + t.Fatal(err) + } + + var s2 sphere + if _, err := Decode(`center = [0.1, 2.3]`, &s2); err == nil { + t.Fatal("Expected array type mismatch error") + } +} + +func ExamplePrimitiveDecode() { + var md MetaData + var err error + + var tomlBlob = ` +ranking = ["Springsteen", "J Geils"] + +[bands.Springsteen] +started = 1973 +albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] + +[bands.J Geils] +started = 1970 +albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] +` + + type band struct { + Started int + Albums []string + } + + type classics struct { + Ranking []string + Bands map[string]Primitive + } + + // Do the initial decode. Reflection is delayed on Primitive values. + var music classics + if md, err = Decode(tomlBlob, &music); err != nil { + log.Fatal(err) + } + + // MetaData still includes information on Primitive values. + fmt.Printf("Is `bands.Springsteen` defined? %v\n", + md.IsDefined("bands", "Springsteen")) + + // Decode primitive data into Go values. + for _, artist := range music.Ranking { + // A band is a primitive value, so we need to decode it to get a + // real `band` value. + primValue := music.Bands[artist] + + var aBand band + if err = PrimitiveDecode(primValue, &aBand); err != nil { + log.Fatal(err) + } + fmt.Printf("%s started in %d.\n", artist, aBand.Started) + } + + // Output: + // Is `bands.Springsteen` defined? true + // Springsteen started in 1973. + // J Geils started in 1970. +} + +func ExampleDecode() { + var tomlBlob = ` +# Some comments. +[alpha] +ip = "10.0.0.1" + + [alpha.config] + Ports = [ 8001, 8002 ] + Location = "Toronto" + Created = 1987-07-05T05:45:00Z + +[beta] +ip = "10.0.0.2" + + [beta.config] + Ports = [ 9001, 9002 ] + Location = "New Jersey" + Created = 1887-01-05T05:55:00Z +` + + type serverConfig struct { + Ports []int + Location string + Created time.Time + } + + type server struct { + IP string `toml:"ip"` + Config serverConfig `toml:"config"` + } + + type servers map[string]server + + var config servers + if _, err := Decode(tomlBlob, &config); err != nil { + log.Fatal(err) + } + + for _, name := range []string{"alpha", "beta"} { + s := config[name] + fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", + name, s.IP, s.Config.Location, + s.Config.Created.Format("2006-01-02")) + fmt.Printf("Ports: %v\n", s.Config.Ports) + } + + // Output: + // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 + // Ports: [8001 8002] + // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 + // Ports: [9001 9002] +} + +type duration struct { + time.Duration +} + +func (d *duration) UnmarshalText(text []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(text)) + return err +} + +// Example Unmarshaler blah blah. +func ExampleUnmarshaler() { + blob := ` +[[song]] +name = "Thunder Road" +duration = "4m49s" + +[[song]] +name = "Stairway to Heaven" +duration = "8m03s" +` + type song struct { + Name string + Duration duration + } + type songs struct { + Song []song + } + var favorites songs + if _, err := Decode(blob, &favorites); err != nil { + log.Fatal(err) + } + + for _, s := range favorites.Song { + fmt.Printf("%s (%s)\n", s.Name, s.Duration) + } + // Output: + // Thunder Road (4m49s) + // Stairway to Heaven (8m3s) +} diff --git a/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/BurntSushi/toml/doc.go new file mode 100644 index 0000000..1c2d7df --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/doc.go @@ -0,0 +1,10 @@ +/* +Package toml provides facilities for decoding TOML configuration files +via reflection. + +Specification: https://github.com/mojombo/toml + +Use github.com/BurntSushi/toml/tomlv to check whether a file is valid +TOML or not, with helpful error messages. +*/ +package toml diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go new file mode 100644 index 0000000..2813ee8 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encode.go @@ -0,0 +1,525 @@ +package toml + +import ( + "bufio" + "errors" + "fmt" + "io" + "reflect" + "sort" + "strconv" + "strings" +) + +var ( + ErrArrayMixedElementTypes = errors.New( + "can't encode array with mixed element types") + ErrArrayNilElement = errors.New( + "can't encode array with nil element") +) + +type Encoder struct { + // A single indentation level. By default it is two spaces. + Indent string + + w *bufio.Writer + + // hasWritten is whether we have written any output to w yet. + hasWritten bool +} + +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: bufio.NewWriter(w), + Indent: " ", + } +} + +func (enc *Encoder) Encode(v interface{}) error { + rv := eindirect(reflect.ValueOf(v)) + if err := enc.encode(Key([]string{}), rv); err != nil { + return err + } + return enc.w.Flush() +} + +func (enc *Encoder) encode(key Key, rv reflect.Value) error { + // Special case. If we can marshal the type to text, then we used that. + if _, ok := rv.Interface().(TextMarshaler); ok { + err := enc.eKeyEq(key) + if err != nil { + return err + } + return enc.eElement(rv) + } + + k := rv.Kind() + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, + reflect.Float32, reflect.Float64, + reflect.String, reflect.Bool: + err := enc.eKeyEq(key) + if err != nil { + return err + } + return enc.eElement(rv) + case reflect.Array, reflect.Slice: + return enc.eArrayOrSlice(key, rv) + case reflect.Interface: + if rv.IsNil() { + return nil + } + return enc.encode(key, rv.Elem()) + case reflect.Map: + if rv.IsNil() { + return nil + } + return enc.eTable(key, rv) + case reflect.Ptr: + if rv.IsNil() { + return nil + } + return enc.encode(key, rv.Elem()) + case reflect.Struct: + return enc.eTable(key, rv) + } + return e("Unsupported type for key '%s': %s", key, k) +} + +// eElement encodes any value that can be an array element (primitives and +// arrays). +func (enc *Encoder) eElement(rv reflect.Value) error { + ws := func(s string) error { + _, err := io.WriteString(enc.w, s) + return err + } + // By the TOML spec, all floats must have a decimal with at least one + // number on either side. + floatAddDecimal := func(fstr string) string { + if !strings.Contains(fstr, ".") { + return fstr + ".0" + } + return fstr + } + + // Special case. Use text marshaler if it's available for this value. + if v, ok := rv.Interface().(TextMarshaler); ok { + s, err := v.MarshalText() + if err != nil { + return err + } + return ws(string(s)) + } + + var err error + k := rv.Kind() + switch k { + case reflect.Bool: + err = ws(strconv.FormatBool(rv.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + err = ws(strconv.FormatInt(rv.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64: + err = ws(strconv.FormatUint(rv.Uint(), 10)) + case reflect.Float32: + err = ws(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) + case reflect.Float64: + err = ws(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) + case reflect.Array, reflect.Slice: + return enc.eArrayOrSliceElement(rv) + case reflect.Interface: + return enc.eElement(rv.Elem()) + case reflect.String: + s := rv.String() + s = strings.NewReplacer( + "\t", "\\t", + "\n", "\\n", + "\r", "\\r", + "\"", "\\\"", + "\\", "\\\\", + ).Replace(s) + err = ws("\"" + s + "\"") + default: + return e("Unexpected primitive type: %s", k) + } + return err +} + +func (enc *Encoder) eArrayOrSlice(key Key, rv reflect.Value) error { + // Determine whether this is an array of tables or of primitives. + elemV := reflect.ValueOf(nil) + if rv.Len() > 0 { + elemV = rv.Index(0) + } + isTableType, err := isTOMLTableType(rv.Type().Elem(), elemV) + if err != nil { + return err + } + + if len(key) > 0 && isTableType { + return enc.eArrayOfTables(key, rv) + } + + err = enc.eKeyEq(key) + if err != nil { + return err + } + return enc.eArrayOrSliceElement(rv) +} + +func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) error { + if _, err := enc.w.Write([]byte{'['}); err != nil { + return err + } + + length := rv.Len() + if length > 0 { + arrayElemType, isNil := tomlTypeName(rv.Index(0)) + if isNil { + return ErrArrayNilElement + } + + for i := 0; i < length; i++ { + elem := rv.Index(i) + + // Ensure that the array's elements each have the same TOML type. + elemType, isNil := tomlTypeName(elem) + if isNil { + return ErrArrayNilElement + } + if elemType != arrayElemType { + return ErrArrayMixedElementTypes + } + + if err := enc.eElement(elem); err != nil { + return err + } + if i != length-1 { + if _, err := enc.w.Write([]byte(", ")); err != nil { + return err + } + } + } + } + + if _, err := enc.w.Write([]byte{']'}); err != nil { + return err + } + return nil +} + +func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) error { + if enc.hasWritten { + _, err := enc.w.Write([]byte{'\n'}) + if err != nil { + return err + } + } + + for i := 0; i < rv.Len(); i++ { + trv := rv.Index(i) + if isNil(trv) { + continue + } + + _, err := fmt.Fprintf(enc.w, "%s[[%s]]\n", + strings.Repeat(enc.Indent, len(key)-1), key.String()) + if err != nil { + return err + } + + err = enc.eMapOrStruct(key, trv) + if err != nil { + return err + } + + if i != rv.Len()-1 { + if _, err := enc.w.Write([]byte("\n\n")); err != nil { + return err + } + } + enc.hasWritten = true + } + return nil +} + +func isStructOrMap(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Interface, reflect.Ptr: + return isStructOrMap(rv.Elem()) + case reflect.Map, reflect.Struct: + return true + default: + return false + } +} + +func (enc *Encoder) eTable(key Key, rv reflect.Value) error { + if enc.hasWritten { + _, err := enc.w.Write([]byte{'\n'}) + if err != nil { + return err + } + } + if len(key) > 0 { + _, err := fmt.Fprintf(enc.w, "%s[%s]\n", + strings.Repeat(enc.Indent, len(key)-1), key.String()) + if err != nil { + return err + } + } + return enc.eMapOrStruct(key, rv) +} + +func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) error { + switch rv.Kind() { + case reflect.Map: + return enc.eMap(key, rv) + case reflect.Struct: + return enc.eStruct(key, rv) + case reflect.Ptr, reflect.Interface: + return enc.eMapOrStruct(key, rv.Elem()) + default: + panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) + } +} + +func (enc *Encoder) eMap(key Key, rv reflect.Value) error { + rt := rv.Type() + if rt.Key().Kind() != reflect.String { + return errors.New("can't encode a map with non-string key type") + } + + // Sort keys so that we have deterministic output. And write keys directly + // underneath this key first, before writing sub-structs or sub-maps. + var mapKeysDirect, mapKeysSub []string + for _, mapKey := range rv.MapKeys() { + k := mapKey.String() + mrv := rv.MapIndex(mapKey) + if isStructOrMap(mrv) { + mapKeysSub = append(mapKeysSub, k) + } else { + mapKeysDirect = append(mapKeysDirect, k) + } + } + + var writeMapKeys = func(mapKeys []string) error { + sort.Strings(mapKeys) + for i, mapKey := range mapKeys { + mrv := rv.MapIndex(reflect.ValueOf(mapKey)) + if isNil(mrv) { + // Don't write anything for nil fields. + continue + } + if err := enc.encode(key.add(mapKey), mrv); err != nil { + return err + } + + if i != len(mapKeys)-1 { + if _, err := enc.w.Write([]byte{'\n'}); err != nil { + return err + } + } + enc.hasWritten = true + } + + return nil + } + + err := writeMapKeys(mapKeysDirect) + if err != nil { + return err + } + err = writeMapKeys(mapKeysSub) + if err != nil { + return err + } + return nil +} + +func (enc *Encoder) eStruct(key Key, rv reflect.Value) error { + // Write keys for fields directly under this key first, because if we write + // a field that creates a new table, then all keys under it will be in that + // table (not the one we're writing here). + rt := rv.Type() + var fieldsDirect, fieldsSub [][]int + var addFields func(rt reflect.Type, rv reflect.Value, start []int) error + addFields = func(rt reflect.Type, rv reflect.Value, start []int) error { + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + frv := rv.Field(i) + if f.Anonymous { + t := frv.Type() + if t.Kind() == reflect.Ptr { + t = t.Elem() + frv = frv.Elem() + } + if t.Kind() != reflect.Struct { + return errors.New( + "can't encode an anonymous field that is not a struct") + } + if err := addFields(t, frv, f.Index); err != nil { + return err + } + } else if isStructOrMap(frv) { + fieldsSub = append(fieldsSub, append(start, f.Index...)) + } else { + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + } + } + return nil + } + if err := addFields(rt, rv, nil); err != nil { + return err + } + + var writeFields = func(fields [][]int) error { + for i, fieldIndex := range fields { + sft := rt.FieldByIndex(fieldIndex) + sf := rv.FieldByIndex(fieldIndex) + if isNil(sf) { + // Don't write anything for nil fields. + continue + } + + keyName := sft.Tag.Get("toml") + if keyName == "-" { + continue + } + if keyName == "" { + keyName = sft.Name + } + + if err := enc.encode(key.add(keyName), sf); err != nil { + return err + } + + if i != len(fields)-1 { + if _, err := enc.w.Write([]byte{'\n'}); err != nil { + return err + } + } + enc.hasWritten = true + } + return nil + } + + err := writeFields(fieldsDirect) + if err != nil { + return err + } + if len(fieldsDirect) > 0 && len(fieldsSub) > 0 { + _, err = enc.w.Write([]byte{'\n'}) + if err != nil { + return err + } + } + err = writeFields(fieldsSub) + if err != nil { + return err + } + return nil +} + +// tomlTypeName returns the TOML type name of the Go value's type. It is used to +// determine whether the types of array elements are mixed (which is forbidden). +// If the Go value is nil, then it is illegal for it to be an array element, and +// valueIsNil is returned as true. +func tomlTypeName(rv reflect.Value) (typeName string, valueIsNil bool) { + if isNil(rv) { + return "", true + } + k := rv.Kind() + switch k { + case reflect.Bool: + return "bool", false + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + return "integer", false + case reflect.Float32, reflect.Float64: + return "float", false + case reflect.Array, reflect.Slice: + return "array", false + case reflect.Ptr, reflect.Interface: + return tomlTypeName(rv.Elem()) + case reflect.String: + return "string", false + case reflect.Map, reflect.Struct: + return "table", false + default: + panic("unexpected reflect.Kind: " + k.String()) + } +} + +// isTOMLTableType returns whether this type and value represents a TOML table +// type (true) or element type (false). Both rt and rv are needed to determine +// this, in case the Go type is interface{} or in case rv is nil. If there is +// some other impossible situation detected, an error is returned. +func isTOMLTableType(rt reflect.Type, rv reflect.Value) (bool, error) { + k := rt.Kind() + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, + reflect.Float32, reflect.Float64, + reflect.String, reflect.Bool: + return false, nil + case reflect.Array, reflect.Slice: + // Make sure that these eventually contain an underlying non-table type + // element. + elemV := reflect.ValueOf(nil) + if rv.Len() > 0 { + elemV = rv.Index(0) + } + hasUnderlyingTableType, err := isTOMLTableType(rt.Elem(), elemV) + if err != nil { + return false, err + } + if hasUnderlyingTableType { + return true, errors.New("TOML array element can't contain a table") + } + return false, nil + case reflect.Ptr: + return isTOMLTableType(rt.Elem(), rv.Elem()) + case reflect.Interface: + if rv.Kind() == reflect.Interface { + return false, nil + } + return isTOMLTableType(rv.Type(), rv) + case reflect.Map, reflect.Struct: + return true, nil + default: + panic("unexpected reflect.Kind: " + k.String()) + } +} + +func isNil(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return rv.IsNil() + default: + return false + } +} + +func (enc *Encoder) eKeyEq(key Key) error { + _, err := io.WriteString(enc.w, strings.Repeat(enc.Indent, len(key)-1)) + if err != nil { + return err + } + _, err = io.WriteString(enc.w, key[len(key)-1]+" = ") + if err != nil { + return err + } + return nil +} + +func eindirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr { + return v + } + return eindirect(reflect.Indirect(v)) +} diff --git a/vendor/github.com/BurntSushi/toml/encode_test.go b/vendor/github.com/BurntSushi/toml/encode_test.go new file mode 100644 index 0000000..27cea0f --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encode_test.go @@ -0,0 +1,282 @@ +package toml + +import ( + "bytes" + "testing" +) + +// XXX(burntsushi) +// I think these tests probably should be removed. They are good, but they +// ought to be obsolete by toml-test. +func TestEncode(t *testing.T) { + tests := map[string]struct { + input interface{} + wantOutput string + wantError error + }{ + "bool field": { + input: struct { + BoolTrue bool + BoolFalse bool + }{true, false}, + wantOutput: "BoolTrue = true\nBoolFalse = false", + }, + "int fields": { + input: struct { + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + }{1, 2, 3, 4, 5}, + wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5", + }, + "uint fields": { + input: struct { + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + }{1, 2, 3, 4, 5}, + wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + + "\nUint64 = 5", + }, + "float fields": { + input: struct { + Float32 float32 + Float64 float64 + }{1.5, 2.5}, + wantOutput: "Float32 = 1.5\nFloat64 = 2.5", + }, + "string field": { + input: struct{ String string }{"foo"}, + wantOutput: `String = "foo"`, + }, + "array fields": { + input: struct { + IntArray0 [0]int + IntArray3 [3]int + }{[0]int{}, [3]int{1, 2, 3}}, + wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]", + }, + "slice fields": { + input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ + nil, []int{}, []int{1, 2, 3}, + }, + wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]", + }, + "nested arrays and slices": { + input: struct { + SliceOfArrays [][2]int + ArrayOfSlices [2][]int + SliceOfArraysOfSlices [][2][]int + ArrayOfSlicesOfArrays [2][][2]int + SliceOfMixedArrays [][2]interface{} + ArrayOfMixedSlices [2][]interface{} + }{ + [][2]int{{1, 2}, {3, 4}}, + [2][]int{{1, 2}, {3, 4}}, + [][2][]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [2][][2]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [][2]interface{}{ + {1, 2}, {"a", "b"}, + }, + [2][]interface{}{ + {1, 2}, {"a", "b"}, + }, + }, + wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] +ArrayOfSlices = [[1, 2], [3, 4]] +SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +SliceOfMixedArrays = [[1, 2], ["a", "b"]] +ArrayOfMixedSlices = [[1, 2], ["a", "b"]]`, + }, + "(error) slice with element type mismatch (string and integer)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, + wantError: ErrArrayMixedElementTypes, + }, + "(error) slice with element type mismatch (integer and float)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, + wantError: ErrArrayMixedElementTypes, + }, + "slice with elems of differing Go types, same TOML types": { + input: struct { + MixedInts []interface{} + MixedFloats []interface{} + }{ + []interface{}{ + int(1), int8(2), int16(3), int32(4), int64(5), + uint(1), uint8(2), uint16(3), uint32(4), uint64(5), + }, + []interface{}{float32(1.5), float64(2.5)}, + }, + wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + + "MixedFloats = [1.5, 2.5]", + }, + "(error) slice w/ element type mismatch (one is nested array)": { + input: struct{ Mixed []interface{} }{ + []interface{}{1, []interface{}{2}}, + }, + wantError: ErrArrayMixedElementTypes, + }, + "(error) slice with 1 nil element": { + input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, + wantError: ErrArrayNilElement, + }, + "(error) slice with 1 nil element (and other non-nil elements)": { + input: struct{ NilElement []interface{} }{ + []interface{}{1, nil}, + }, + wantError: ErrArrayNilElement, + }, + "simple map": { + input: map[string]int{"a": 1, "b": 2}, + wantOutput: "a = 1\nb = 2", + }, + "map with interface{} value type": { + input: map[string]interface{}{"a": 1, "b": "c"}, + wantOutput: "a = 1\nb = \"c\"", + }, + "map with interface{} value type, some of which are structs": { + input: map[string]interface{}{ + "a": struct{ Int int }{2}, + "b": 1, + }, + wantOutput: "b = 1\n[a]\n Int = 2", + }, + "nested map": { + input: map[string]map[string]int{ + "a": {"b": 1}, + "c": {"d": 2}, + }, + wantOutput: "[a]\n b = 1\n\n[c]\n d = 2", + }, + "nested struct": { + input: struct{ Struct struct{ Int int } }{ + struct{ Int int }{1}, + }, + wantOutput: "[Struct]\n Int = 1", + }, + "nested struct and non-struct field": { + input: struct { + Struct struct{ Int int } + Bool bool + }{struct{ Int int }{1}, true}, + wantOutput: "Bool = true\n\n[Struct]\n Int = 1", + }, + "2 nested structs": { + input: struct{ Struct1, Struct2 struct{ Int int } }{ + struct{ Int int }{1}, struct{ Int int }{2}, + }, + wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2", + }, + "deeply nested structs": { + input: struct { + Struct1, Struct2 struct{ Struct3 *struct{ Int int } } + }{ + struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, + struct{ Struct3 *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + + "\n\n[Struct2]\n", + }, + "nested struct with nil struct elem": { + input: struct { + Struct struct{ Inner *struct{ Int int } } + }{ + struct{ Inner *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct]\n", + }, + "nested struct with no fields": { + input: struct { + Struct struct{ Inner struct{} } + }{ + struct{ Inner struct{} }{struct{}{}}, + }, + wantOutput: "[Struct]\n [Struct.Inner]\n", + }, + "struct with tags": { + input: struct { + Struct struct { + Int int `toml:"_int"` + } `toml:"_struct"` + Bool bool `toml:"_bool"` + }{ + struct { + Int int `toml:"_int"` + }{1}, true, + }, + wantOutput: "_bool = true\n\n[_struct]\n _int = 1", + }, + "embedded struct": { + input: struct{ Embedded }{Embedded{1}}, + wantOutput: "_int = 1", + }, + "embedded *struct": { + input: struct{ *Embedded }{&Embedded{1}}, + wantOutput: "_int = 1", + }, + "nested embedded struct": { + input: struct { + Struct struct{ Embedded } `toml:"_struct"` + }{struct{ Embedded }{Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1", + }, + "nested embedded *struct": { + input: struct { + Struct struct{ *Embedded } `toml:"_struct"` + }{struct{ *Embedded }{&Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1", + }, + "array of tables": { + input: struct { + Structs []*struct{ Int int } `toml:"struct"` + }{ + []*struct{ Int int }{ + {1}, nil, {3}, + }, + }, + wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3", + }, + } + for label, test := range tests { + var buf bytes.Buffer + e := NewEncoder(&buf) + err := e.Encode(test.input) + if err != test.wantError { + if test.wantError != nil { + t.Errorf("%s: want Encode error %v, got %v", + label, test.wantError, err) + } else { + t.Errorf("%s: Encode failed: %s", label, err) + } + } + if err != nil { + continue + } + if got := buf.String(); test.wantOutput != got { + t.Errorf("%s: want %q, got %q", label, test.wantOutput, got) + } + } +} + +type Embedded struct { + Int int `toml:"_int"` +} diff --git a/vendor/github.com/BurntSushi/toml/encoding_types.go b/vendor/github.com/BurntSushi/toml/encoding_types.go new file mode 100644 index 0000000..6500c9a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encoding_types.go @@ -0,0 +1,10 @@ +// +build go1.2 + +package toml + +import ( + "encoding" +) + +type TextMarshaler encoding.TextMarshaler +type TextUnmarshaler encoding.TextUnmarshaler diff --git a/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go b/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go new file mode 100644 index 0000000..6211cea --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go @@ -0,0 +1,11 @@ +// +build !go1.2 + +package toml + +type TextMarshaler interface { + MarshalText() (text []byte, err error) +} + +type TextUnmarshaler interface { + UnmarshalText(text []byte) error +} diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go new file mode 100644 index 0000000..01fcb49 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/lex.go @@ -0,0 +1,725 @@ +package toml + +import ( + "fmt" + "unicode/utf8" +) + +type itemType int + +const ( + itemError itemType = iota + itemNIL // used in the parser to indicate no type + itemEOF + itemText + itemString + itemBool + itemInteger + itemFloat + itemDatetime + itemArray // the start of an array + itemArrayEnd + itemTableStart + itemTableEnd + itemArrayTableStart + itemArrayTableEnd + itemKeyStart + itemCommentStart +) + +const ( + eof = 0 + tableStart = '[' + tableEnd = ']' + arrayTableStart = '[' + arrayTableEnd = ']' + tableSep = '.' + keySep = '=' + arrayStart = '[' + arrayEnd = ']' + arrayValTerm = ',' + commentStart = '#' + stringStart = '"' + stringEnd = '"' +) + +type stateFn func(lx *lexer) stateFn + +type lexer struct { + input string + start int + pos int + width int + line int + state stateFn + items chan item + + // A stack of state functions used to maintain context. + // The idea is to reuse parts of the state machine in various places. + // For example, values can appear at the top level or within arbitrarily + // nested arrays. The last state on the stack is used after a value has + // been lexed. Similarly for comments. + stack []stateFn +} + +type item struct { + typ itemType + val string + line int +} + +func (lx *lexer) nextItem() item { + for { + select { + case item := <-lx.items: + return item + default: + lx.state = lx.state(lx) + } + } +} + +func lex(input string) *lexer { + lx := &lexer{ + input: input + "\n", + state: lexTop, + line: 1, + items: make(chan item, 10), + stack: make([]stateFn, 0, 10), + } + return lx +} + +func (lx *lexer) push(state stateFn) { + lx.stack = append(lx.stack, state) +} + +func (lx *lexer) pop() stateFn { + if len(lx.stack) == 0 { + return lx.errorf("BUG in lexer: no states to pop.") + } + last := lx.stack[len(lx.stack)-1] + lx.stack = lx.stack[0 : len(lx.stack)-1] + return last +} + +func (lx *lexer) current() string { + return lx.input[lx.start:lx.pos] +} + +func (lx *lexer) emit(typ itemType) { + lx.items <- item{typ, lx.current(), lx.line} + lx.start = lx.pos +} + +func (lx *lexer) next() (r rune) { + if lx.pos >= len(lx.input) { + lx.width = 0 + return eof + } + + if lx.input[lx.pos] == '\n' { + lx.line++ + } + r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) + lx.pos += lx.width + return r +} + +// ignore skips over the pending input before this point. +func (lx *lexer) ignore() { + lx.start = lx.pos +} + +// backup steps back one rune. Can be called only once per call of next. +func (lx *lexer) backup() { + lx.pos -= lx.width + if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { + lx.line-- + } +} + +// accept consumes the next rune if it's equal to `valid`. +func (lx *lexer) accept(valid rune) bool { + if lx.next() == valid { + return true + } + lx.backup() + return false +} + +// peek returns but does not consume the next rune in the input. +func (lx *lexer) peek() rune { + r := lx.next() + lx.backup() + return r +} + +// errorf stops all lexing by emitting an error and returning `nil`. +// Note that any value that is a character is escaped if it's a special +// character (new lines, tabs, etc.). +func (lx *lexer) errorf(format string, values ...interface{}) stateFn { + lx.items <- item{ + itemError, + fmt.Sprintf(format, values...), + lx.line, + } + return nil +} + +// lexTop consumes elements at the top level of TOML data. +func lexTop(lx *lexer) stateFn { + r := lx.next() + if isWhitespace(r) || isNL(r) { + return lexSkip(lx, lexTop) + } + + switch r { + case commentStart: + lx.push(lexTop) + return lexCommentStart + case tableStart: + return lexTableStart + case eof: + if lx.pos > lx.start { + return lx.errorf("Unexpected EOF.") + } + lx.emit(itemEOF) + return nil + } + + // At this point, the only valid item can be a key, so we back up + // and let the key lexer do the rest. + lx.backup() + lx.push(lexTopEnd) + return lexKeyStart +} + +// lexTopEnd is entered whenever a top-level item has been consumed. (A value +// or a table.) It must see only whitespace, and will turn back to lexTop +// upon a new line. If it sees EOF, it will quit the lexer successfully. +func lexTopEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == commentStart: + // a comment will read to a new line for us. + lx.push(lexTop) + return lexCommentStart + case isWhitespace(r): + return lexTopEnd + case isNL(r): + lx.ignore() + return lexTop + case r == eof: + lx.ignore() + return lexTop + } + return lx.errorf("Expected a top-level item to end with a new line, "+ + "comment or EOF, but got %q instead.", r) +} + +// lexTable lexes the beginning of a table. Namely, it makes sure that +// it starts with a character other than '.' and ']'. +// It assumes that '[' has already been consumed. +// It also handles the case that this is an item in an array of tables. +// e.g., '[[name]]'. +func lexTableStart(lx *lexer) stateFn { + if lx.peek() == arrayTableStart { + lx.next() + lx.emit(itemArrayTableStart) + lx.push(lexArrayTableEnd) + } else { + lx.emit(itemTableStart) + lx.push(lexTableEnd) + } + return lexTableNameStart +} + +func lexTableEnd(lx *lexer) stateFn { + lx.emit(itemTableEnd) + return lexTopEnd +} + +func lexArrayTableEnd(lx *lexer) stateFn { + if r := lx.next(); r != arrayTableEnd { + return lx.errorf("Expected end of table array name delimiter %q, "+ + "but got %q instead.", arrayTableEnd, r) + } + lx.emit(itemArrayTableEnd) + return lexTopEnd +} + +func lexTableNameStart(lx *lexer) stateFn { + switch lx.next() { + case tableEnd: + return lx.errorf("Unexpected end of table. (Tables cannot " + + "be empty.)") + case tableSep: + return lx.errorf("Unexpected table separator. (Tables cannot " + + "be empty.)") + } + return lexTableName +} + +// lexTableName lexes the name of a table. It assumes that at least one +// valid character for the table has already been read. +func lexTableName(lx *lexer) stateFn { + switch lx.peek() { + case tableStart: + return lx.errorf("Table names cannot contain %q or %q.", + tableStart, tableEnd) + case tableEnd: + lx.emit(itemText) + lx.next() + return lx.pop() + case tableSep: + lx.emit(itemText) + lx.next() + lx.ignore() + return lexTableNameStart + } + lx.next() + return lexTableName +} + +// lexKeyStart consumes a key name up until the first non-whitespace character. +// lexKeyStart will ignore whitespace. +func lexKeyStart(lx *lexer) stateFn { + r := lx.peek() + switch { + case r == keySep: + return lx.errorf("Unexpected key separator %q.", keySep) + case isWhitespace(r) || isNL(r): + lx.next() + return lexSkip(lx, lexKeyStart) + } + + lx.ignore() + lx.emit(itemKeyStart) + lx.next() + return lexKey +} + +// lexKey consumes the text of a key. Assumes that the first character (which +// is not whitespace) has already been consumed. +func lexKey(lx *lexer) stateFn { + r := lx.peek() + + // XXX: Possible divergence from spec? + // "Keys start with the first non-whitespace character and end with the + // last non-whitespace character before the equals sign." + // Note here that whitespace is either a tab or a space. + // But we'll call it quits if we see a new line too. + if isWhitespace(r) || isNL(r) { + lx.emit(itemText) + return lexKeyEnd + } + + // Let's also call it quits if we see an equals sign. + if r == keySep { + lx.emit(itemText) + return lexKeyEnd + } + + lx.next() + return lexKey +} + +// lexKeyEnd consumes the end of a key (up to the key separator). +// Assumes that the first whitespace character after a key (or the '=' +// separator) has NOT been consumed. +func lexKeyEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexKeyEnd) + case r == keySep: + return lexSkip(lx, lexValue) + } + return lx.errorf("Expected key separator %q, but got %q instead.", + keySep, r) +} + +// lexValue starts the consumption of a value anywhere a value is expected. +// lexValue will ignore whitespace. +// After a value is lexed, the last state on the next is popped and returned. +func lexValue(lx *lexer) stateFn { + // We allow whitespace to precede a value, but NOT new lines. + // In array syntax, the array states are responsible for ignoring new lines. + r := lx.next() + if isWhitespace(r) { + return lexSkip(lx, lexValue) + } + + switch { + case r == arrayStart: + lx.ignore() + lx.emit(itemArray) + return lexArrayValue + case r == stringStart: + lx.ignore() // ignore the '"' + return lexString + case r == 't': + return lexTrue + case r == 'f': + return lexFalse + case r == '-': + return lexNumberStart + case isDigit(r): + lx.backup() // avoid an extra state and use the same as above + return lexNumberOrDateStart + case r == '.': // special error case, be kind to users + return lx.errorf("Floats must start with a digit, not '.'.") + } + return lx.errorf("Expected value but found %q instead.", r) +} + +// lexArrayValue consumes one value in an array. It assumes that '[' or ',' +// have already been consumed. All whitespace and new lines are ignored. +func lexArrayValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValue) + case r == commentStart: + lx.push(lexArrayValue) + return lexCommentStart + case r == arrayValTerm: + return lx.errorf("Unexpected array value terminator %q.", + arrayValTerm) + case r == arrayEnd: + return lexArrayEnd + } + + lx.backup() + lx.push(lexArrayValueEnd) + return lexValue +} + +// lexArrayValueEnd consumes the cruft between values of an array. Namely, +// it ignores whitespace and expects either a ',' or a ']'. +func lexArrayValueEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValueEnd) + case r == commentStart: + lx.push(lexArrayValueEnd) + return lexCommentStart + case r == arrayValTerm: + lx.ignore() + return lexArrayValue // move on to the next value + case r == arrayEnd: + return lexArrayEnd + } + return lx.errorf("Expected an array value terminator %q or an array "+ + "terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) +} + +// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has +// just been consumed. +func lexArrayEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemArrayEnd) + return lx.pop() +} + +// lexString consumes the inner contents of a string. It assumes that the +// beginning '"' has already been consumed and ignored. +func lexString(lx *lexer) stateFn { + r := lx.next() + switch { + case isNL(r): + return lx.errorf("Strings cannot contain new lines.") + case r == '\\': + return lexStringEscape + case r == stringEnd: + lx.backup() + lx.emit(itemString) + lx.next() + lx.ignore() + return lx.pop() + } + return lexString +} + +// lexStringEscape consumes an escaped character. It assumes that the preceding +// '\\' has already been consumed. +func lexStringEscape(lx *lexer) stateFn { + r := lx.next() + switch r { + case 'b': + fallthrough + case 't': + fallthrough + case 'n': + fallthrough + case 'f': + fallthrough + case 'r': + fallthrough + case '"': + fallthrough + case '/': + fallthrough + case '\\': + return lexString + case 'u': + return lexStringUnicode + } + return lx.errorf("Invalid escape character %q. Only the following "+ + "escape characters are allowed: "+ + "\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, and \\uXXXX.", r) +} + +// lexStringBinary consumes two hexadecimal digits following '\x'. It assumes +// that the '\x' has already been consumed. +func lexStringUnicode(lx *lexer) stateFn { + var r rune + + for i := 0; i < 4; i++ { + r = lx.next() + if !isHexadecimal(r) { + return lx.errorf("Expected four hexadecimal digits after '\\x', "+ + "but got '%s' instead.", lx.current()) + } + } + return lexString +} + +// lexNumberOrDateStart consumes either a (positive) integer, float or datetime. +// It assumes that NO negative sign has been consumed. +func lexNumberOrDateStart(lx *lexer) stateFn { + r := lx.next() + if !isDigit(r) { + if r == '.' { + return lx.errorf("Floats must start with a digit, not '.'.") + } else { + return lx.errorf("Expected a digit but got %q.", r) + } + } + return lexNumberOrDate +} + +// lexNumberOrDate consumes either a (positive) integer, float or datetime. +func lexNumberOrDate(lx *lexer) stateFn { + r := lx.next() + switch { + case r == '-': + if lx.pos-lx.start != 5 { + return lx.errorf("All ISO8601 dates must be in full Zulu form.") + } + return lexDateAfterYear + case isDigit(r): + return lexNumberOrDate + case r == '.': + return lexFloatStart + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format. +// It assumes that "YYYY-" has already been consumed. +func lexDateAfterYear(lx *lexer) stateFn { + formats := []rune{ + // digits are '0'. + // everything else is direct equality. + '0', '0', '-', '0', '0', + 'T', + '0', '0', ':', '0', '0', ':', '0', '0', + 'Z', + } + for _, f := range formats { + r := lx.next() + if f == '0' { + if !isDigit(r) { + return lx.errorf("Expected digit in ISO8601 datetime, "+ + "but found %q instead.", r) + } + } else if f != r { + return lx.errorf("Expected %q in ISO8601 datetime, "+ + "but found %q instead.", f, r) + } + } + lx.emit(itemDatetime) + return lx.pop() +} + +// lexNumberStart consumes either an integer or a float. It assumes that a +// negative sign has already been read, but that *no* digits have been consumed. +// lexNumberStart will move to the appropriate integer or float states. +func lexNumberStart(lx *lexer) stateFn { + // we MUST see a digit. Even floats have to start with a digit. + r := lx.next() + if !isDigit(r) { + if r == '.' { + return lx.errorf("Floats must start with a digit, not '.'.") + } else { + return lx.errorf("Expected a digit but got %q.", r) + } + } + return lexNumber +} + +// lexNumber consumes an integer or a float after seeing the first digit. +func lexNumber(lx *lexer) stateFn { + r := lx.next() + switch { + case isDigit(r): + return lexNumber + case r == '.': + return lexFloatStart + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexFloatStart starts the consumption of digits of a float after a '.'. +// Namely, at least one digit is required. +func lexFloatStart(lx *lexer) stateFn { + r := lx.next() + if !isDigit(r) { + return lx.errorf("Floats must have a digit after the '.', but got "+ + "%q instead.", r) + } + return lexFloat +} + +// lexFloat consumes the digits of a float after a '.'. +// Assumes that one digit has been consumed after a '.' already. +func lexFloat(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexFloat + } + + lx.backup() + lx.emit(itemFloat) + return lx.pop() +} + +// lexConst consumes the s[1:] in s. It assumes that s[0] has already been +// consumed. +func lexConst(lx *lexer, s string) stateFn { + for i := range s[1:] { + if r := lx.next(); r != rune(s[i+1]) { + return lx.errorf("Expected %q, but found %q instead.", s[:i+1], + s[:i]+string(r)) + } + } + return nil +} + +// lexTrue consumes the "rue" in "true". It assumes that 't' has already +// been consumed. +func lexTrue(lx *lexer) stateFn { + if fn := lexConst(lx, "true"); fn != nil { + return fn + } + lx.emit(itemBool) + return lx.pop() +} + +// lexFalse consumes the "alse" in "false". It assumes that 'f' has already +// been consumed. +func lexFalse(lx *lexer) stateFn { + if fn := lexConst(lx, "false"); fn != nil { + return fn + } + lx.emit(itemBool) + return lx.pop() +} + +// lexCommentStart begins the lexing of a comment. It will emit +// itemCommentStart and consume no characters, passing control to lexComment. +func lexCommentStart(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemCommentStart) + return lexComment +} + +// lexComment lexes an entire comment. It assumes that '#' has been consumed. +// It will consume *up to* the first new line character, and pass control +// back to the last state on the stack. +func lexComment(lx *lexer) stateFn { + r := lx.peek() + if isNL(r) || r == eof { + lx.emit(itemText) + return lx.pop() + } + lx.next() + return lexComment +} + +// lexSkip ignores all slurped input and moves on to the next state. +func lexSkip(lx *lexer, nextState stateFn) stateFn { + return func(lx *lexer) stateFn { + lx.ignore() + return nextState + } +} + +// isWhitespace returns true if `r` is a whitespace character according +// to the spec. +func isWhitespace(r rune) bool { + return r == '\t' || r == ' ' +} + +func isNL(r rune) bool { + return r == '\n' || r == '\r' +} + +func isDigit(r rune) bool { + return r >= '0' && r <= '9' +} + +func isHexadecimal(r rune) bool { + return (r >= '0' && r <= '9') || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + +func (itype itemType) String() string { + switch itype { + case itemError: + return "Error" + case itemNIL: + return "NIL" + case itemEOF: + return "EOF" + case itemText: + return "Text" + case itemString: + return "String" + case itemBool: + return "Bool" + case itemInteger: + return "Integer" + case itemFloat: + return "Float" + case itemDatetime: + return "DateTime" + case itemTableStart: + return "TableStart" + case itemTableEnd: + return "TableEnd" + case itemKeyStart: + return "KeyStart" + case itemArray: + return "Array" + case itemArrayEnd: + return "ArrayEnd" + case itemCommentStart: + return "CommentStart" + } + panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) +} + +func (item item) String() string { + return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val) +} diff --git a/vendor/github.com/BurntSushi/toml/lex_test.go b/vendor/github.com/BurntSushi/toml/lex_test.go new file mode 100644 index 0000000..6cfa210 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/lex_test.go @@ -0,0 +1,59 @@ +package toml + +import ( + "log" + "testing" +) + +func init() { + log.SetFlags(0) +} + +var testSmall = ` +# This is a TOML document. Boom. + +[owner] +[owner] # Whoa there. +andrew = "gallant # poopy" # weeeee +predicate = false +num = -5192 +f = -0.5192 +zulu = 1979-05-27T07:32:00Z +whoop = "poop" +arrs = [ + 1987-07-05T05:45:00Z, + 5, + "wat?", + "hehe \n\r kewl", + [6], [], + 5.0, + # sweetness +] # more comments +# hehe +` + +var testSmaller = ` +[a.b] # Do you ignore me? +andrew = "ga# ll\"ant" # what about me? +kait = "brady" +awesomeness = true +pi = 3.14 +dob = 1987-07-05T17:45:00Z +perfection = [ + [6, 28], + [496, 8128] +] +` + +func TestLexer(t *testing.T) { + lx := lex(testSmaller) + for { + item := lx.nextItem() + if item.typ == itemEOF { + break + } else if item.typ == itemError { + t.Fatal(item.val) + } + testf("%s\n", item) + } +} diff --git a/vendor/github.com/BurntSushi/toml/out_test.go b/vendor/github.com/BurntSushi/toml/out_test.go new file mode 100644 index 0000000..ab121e3 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/out_test.go @@ -0,0 +1,19 @@ +package toml + +import ( + "flag" + "fmt" +) + +var flagOut = false + +func init() { + flag.BoolVar(&flagOut, "out", flagOut, "Print debug output.") + flag.Parse() +} + +func testf(format string, v ...interface{}) { + if flagOut { + fmt.Printf(format, v...) + } +} diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go new file mode 100644 index 0000000..43afe3c --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/parse.go @@ -0,0 +1,417 @@ +package toml + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +type parser struct { + mapping map[string]interface{} + types map[string]tomlType + lx *lexer + + // A list of keys in the order that they appear in the TOML data. + ordered []Key + + // the full key for the current hash in scope + context Key + + // the base key name for everything except hashes + currentKey string + + // rough approximation of line number + approxLine int + + // A map of 'key.group.names' to whether they were created implicitly. + implicits map[string]bool +} + +type parseError string + +func (pe parseError) Error() string { + return string(pe) +} + +func parse(data string) (p *parser, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + if err, ok = r.(parseError); ok { + return + } + panic(r) + } + }() + + p = &parser{ + mapping: make(map[string]interface{}), + types: make(map[string]tomlType), + lx: lex(data), + ordered: make([]Key, 0), + implicits: make(map[string]bool), + } + for { + item := p.next() + if item.typ == itemEOF { + break + } + p.topLevel(item) + } + + return p, nil +} + +func (p *parser) panicf(format string, v ...interface{}) { + msg := fmt.Sprintf("Near line %d, key '%s': %s", + p.approxLine, p.current(), fmt.Sprintf(format, v...)) + panic(parseError(msg)) +} + +func (p *parser) next() item { + it := p.lx.nextItem() + if it.typ == itemError { + p.panicf("Near line %d: %s", it.line, it.val) + } + return it +} + +func (p *parser) bug(format string, v ...interface{}) { + log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...)) +} + +func (p *parser) expect(typ itemType) item { + it := p.next() + p.assertEqual(typ, it.typ) + return it +} + +func (p *parser) assertEqual(expected, got itemType) { + if expected != got { + p.bug("Expected '%s' but got '%s'.", expected, got) + } +} + +func (p *parser) topLevel(item item) { + switch item.typ { + case itemCommentStart: + p.approxLine = item.line + p.expect(itemText) + case itemTableStart: + kg := p.expect(itemText) + p.approxLine = kg.line + + key := make(Key, 0) + for ; kg.typ == itemText; kg = p.next() { + key = append(key, kg.val) + } + p.assertEqual(itemTableEnd, kg.typ) + + p.establishContext(key, false) + p.setType("", tomlHash) + p.ordered = append(p.ordered, key) + case itemArrayTableStart: + kg := p.expect(itemText) + p.approxLine = kg.line + + key := make(Key, 0) + for ; kg.typ == itemText; kg = p.next() { + key = append(key, kg.val) + } + p.assertEqual(itemArrayTableEnd, kg.typ) + + p.establishContext(key, true) + p.setType("", tomlArrayHash) + p.ordered = append(p.ordered, key) + case itemKeyStart: + kname := p.expect(itemText) + p.currentKey = kname.val + p.approxLine = kname.line + + val, typ := p.value(p.next()) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ) + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + p.currentKey = "" + default: + p.bug("Unexpected type at top level: %s", item.typ) + } +} + +// value translates an expected value from the lexer into a Go value wrapped +// as an empty interface. +func (p *parser) value(it item) (interface{}, tomlType) { + switch it.typ { + case itemString: + return p.replaceUnicode(replaceEscapes(it.val)), p.typeOfPrimitive(it) + case itemBool: + switch it.val { + case "true": + return true, p.typeOfPrimitive(it) + case "false": + return false, p.typeOfPrimitive(it) + } + p.bug("Expected boolean value, but got '%s'.", it.val) + case itemInteger: + num, err := strconv.ParseInt(it.val, 10, 64) + if err != nil { + // See comment below for floats describing why we make a + // distinction between a bug and a user error. + if e, ok := err.(*strconv.NumError); ok && + e.Err == strconv.ErrRange { + + p.panicf("Integer '%s' is out of the range of 64-bit "+ + "signed integers.", it.val) + } else { + p.bug("Expected integer value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) + case itemFloat: + num, err := strconv.ParseFloat(it.val, 64) + if err != nil { + // Distinguish float values. Normally, it'd be a bug if the lexer + // provides an invalid float, but it's possible that the float is + // out of range of valid values (which the lexer cannot determine). + // So mark the former as a bug but the latter as a legitimate user + // error. + // + // This is also true for integers. + if e, ok := err.(*strconv.NumError); ok && + e.Err == strconv.ErrRange { + + p.panicf("Float '%s' is out of the range of 64-bit "+ + "IEEE-754 floating-point numbers.", it.val) + } else { + p.bug("Expected float value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) + case itemDatetime: + t, err := time.Parse("2006-01-02T15:04:05Z", it.val) + if err != nil { + p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val) + } + return t, p.typeOfPrimitive(it) + case itemArray: + array := make([]interface{}, 0) + types := make([]tomlType, 0) + + for it = p.next(); it.typ != itemArrayEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + val, typ := p.value(it) + array = append(array, val) + types = append(types, typ) + } + return array, p.typeOfArray(types) + } + p.bug("Unexpected value type: %s", it.typ) + panic("unreachable") +} + +// establishContext sets the current context of the parser, +// where the context is either a hash or an array of hashes. Which one is +// set depends on the value of the `array` parameter. +// +// Establishing the context also makes sure that the key isn't a duplicate, and +// will create implicit hashes automatically. +func (p *parser) establishContext(key Key, array bool) { + var ok bool + + // Always start at the top level and drill down for our context. + hashContext := p.mapping + keyContext := make(Key, 0) + + // We only need implicit hashes for key[0:-1] + for _, k := range key[0 : len(key)-1] { + _, ok = hashContext[k] + keyContext = append(keyContext, k) + + // No key? Make an implicit hash and move on. + if !ok { + p.addImplicit(keyContext) + hashContext[k] = make(map[string]interface{}) + } + + // If the hash context is actually an array of tables, then set + // the hash context to the last element in that array. + // + // Otherwise, it better be a table, since this MUST be a key group (by + // virtue of it not being the last element in a key). + switch t := hashContext[k].(type) { + case []map[string]interface{}: + hashContext = t[len(t)-1] + case map[string]interface{}: + hashContext = t + default: + p.panicf("Key '%s' was already created as a hash.", keyContext) + } + } + + p.context = keyContext + if array { + // If this is the first element for this array, then allocate a new + // list of tables for it. + k := key[len(key)-1] + if _, ok := hashContext[k]; !ok { + hashContext[k] = make([]map[string]interface{}, 0, 5) + } + + // Add a new table. But make sure the key hasn't already been used + // for something else. + if hash, ok := hashContext[k].([]map[string]interface{}); ok { + hashContext[k] = append(hash, make(map[string]interface{})) + } else { + p.panicf("Key '%s' was already created and cannot be used as "+ + "an array.", keyContext) + } + } else { + p.setValue(key[len(key)-1], make(map[string]interface{})) + } + p.context = append(p.context, key[len(key)-1]) +} + +// setValue sets the given key to the given value in the current context. +// It will make sure that the key hasn't already been defined, account for +// implicit key groups. +func (p *parser) setValue(key string, value interface{}) { + var tmpHash interface{} + var ok bool + + hash := p.mapping + keyContext := make(Key, 0) + for _, k := range p.context { + keyContext = append(keyContext, k) + if tmpHash, ok = hash[k]; !ok { + p.bug("Context for key '%s' has not been established.", keyContext) + } + switch t := tmpHash.(type) { + case []map[string]interface{}: + // The context is a table of hashes. Pick the most recent table + // defined as the current hash. + hash = t[len(t)-1] + case map[string]interface{}: + hash = t + default: + p.bug("Expected hash to have type 'map[string]interface{}', but "+ + "it has '%T' instead.", tmpHash) + } + } + keyContext = append(keyContext, key) + + if _, ok := hash[key]; ok { + // Typically, if the given key has already been set, then we have + // to raise an error since duplicate keys are disallowed. However, + // it's possible that a key was previously defined implicitly. In this + // case, it is allowed to be redefined concretely. (See the + // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) + // + // But we have to make sure to stop marking it as an implicit. (So that + // another redefinition provokes an error.) + // + // Note that since it has already been defined (as a hash), we don't + // want to overwrite it. So our business is done. + if p.isImplicit(keyContext) { + p.removeImplicit(keyContext) + return + } + + // Otherwise, we have a concrete key trying to override a previous + // key, which is *always* wrong. + p.panicf("Key '%s' has already been defined.", keyContext) + } + hash[key] = value +} + +// setType sets the type of a particular value at a given key. +// It should be called immediately AFTER setValue. +// +// Note that if `key` is empty, then the type given will be applied to the +// current context (which is either a table or an array of tables). +func (p *parser) setType(key string, typ tomlType) { + keyContext := make(Key, 0, len(p.context)+1) + for _, k := range p.context { + keyContext = append(keyContext, k) + } + if len(key) > 0 { // allow type setting for hashes + keyContext = append(keyContext, key) + } + p.types[keyContext.String()] = typ +} + +// addImplicit sets the given Key as having been created implicitly. +func (p *parser) addImplicit(key Key) { + p.implicits[key.String()] = true +} + +// removeImplicit stops tagging the given key as having been implicitly created. +func (p *parser) removeImplicit(key Key) { + p.implicits[key.String()] = false +} + +// isImplicit returns true if the key group pointed to by the key was created +// implicitly. +func (p *parser) isImplicit(key Key) bool { + return p.implicits[key.String()] +} + +// current returns the full key name of the current context. +func (p *parser) current() string { + if len(p.currentKey) == 0 { + return p.context.String() + } + if len(p.context) == 0 { + return p.currentKey + } + return fmt.Sprintf("%s.%s", p.context, p.currentKey) +} + +func replaceEscapes(s string) string { + return strings.NewReplacer( + "\\b", "\u0008", + "\\t", "\u0009", + "\\n", "\u000A", + "\\f", "\u000C", + "\\r", "\u000D", + "\\\"", "\u0022", + "\\/", "\u002F", + "\\\\", "\u005C", + ).Replace(s) +} + +func (p *parser) replaceUnicode(s string) string { + indexEsc := func() int { + return strings.Index(s, "\\u") + } + for i := indexEsc(); i != -1; i = indexEsc() { + asciiBytes := s[i+2 : i+6] + s = strings.Replace(s, s[i:i+6], p.asciiEscapeToUnicode(asciiBytes), -1) + } + return s +} + +func (p *parser) asciiEscapeToUnicode(s string) string { + hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) + if err != nil { + p.bug("Could not parse '%s' as a hexadecimal number, but the "+ + "lexer claims it's OK: %s", s, err) + } + + // BUG(burntsushi) + // I honestly don't understand how this works. I can't seem + // to find a way to make this fail. I figured this would fail on invalid + // UTF-8 characters like U+DCFF, but it doesn't. + r := string(rune(hex)) + if !utf8.ValidString(r) { + p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) + } + return string(r) +} diff --git a/vendor/github.com/BurntSushi/toml/parse_test.go b/vendor/github.com/BurntSushi/toml/parse_test.go new file mode 100644 index 0000000..09c5d67 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/parse_test.go @@ -0,0 +1,73 @@ +package toml + +import ( + "strings" + "testing" +) + +var testParseSmall = ` +# This is a TOML document. Boom. + +wat = "chipper" + +[owner.andrew.gallant] +hmm = "hi" + +[owner] # Whoa there. +andreW = "gallant # poopy" # weeeee +predicate = false +num = -5192 +f = -0.5192 +zulu = 1979-05-27T07:32:00Z +whoop = "poop" +tests = [ [1, 2, 3], ["abc", "xyz"] ] +arrs = [ # hmm + # more comments are awesome. + 1987-07-05T05:45:00Z, + # say wat? + 1987-07-05T05:45:00Z, + 1987-07-05T05:45:00Z, + # sweetness +] # more comments +# hehe +` + +var testParseSmall2 = ` +[a] +better = 43 + +[a.b.c] +answer = 42 +` + +func TestParse(t *testing.T) { + m, err := parse(testParseSmall) + if err != nil { + t.Fatal(err) + } + printMap(m.mapping, 0) +} + +func printMap(m map[string]interface{}, depth int) { + for k, v := range m { + testf("%s%s\n", strings.Repeat(" ", depth), k) + switch subm := v.(type) { + case map[string]interface{}: + printMap(subm, depth+1) + default: + testf("%s%v\n", strings.Repeat(" ", depth+1), v) + } + } +} + +var testParseSmall3 = ` +foo = [1, 2,3] +` + +func TestParseSmall3(t *testing.T) { + m, err := parse(testParseSmall3) + if err != nil { + t.Fatal(err) + } + printMap(m.mapping, 0) +} diff --git a/vendor/github.com/BurntSushi/toml/session.vim b/vendor/github.com/BurntSushi/toml/session.vim new file mode 100644 index 0000000..562164b --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/session.vim @@ -0,0 +1 @@ +au BufWritePost *.go silent!make tags > /dev/null 2>&1 diff --git a/vendor/github.com/BurntSushi/toml/toml-test-encoder/COPYING b/vendor/github.com/BurntSushi/toml/toml-test-encoder/COPYING new file mode 100644 index 0000000..5a8e332 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-encoder/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/toml-test-encoder/README.md b/vendor/github.com/BurntSushi/toml/toml-test-encoder/README.md new file mode 100644 index 0000000..45a603f --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-encoder/README.md @@ -0,0 +1,14 @@ +# Implements the TOML test suite interface for TOML encoders + +This is an implementation of the interface expected by +[toml-test](https://github.com/BurntSushi/toml-test) for the +[TOML encoder](https://github.com/BurntSushi/toml). +In particular, it maps JSON data on `stdin` to a TOML format on `stdout`. + + +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + +Compatible with `toml-test` version +[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) + diff --git a/vendor/github.com/BurntSushi/toml/toml-test-encoder/main.go b/vendor/github.com/BurntSushi/toml/toml-test-encoder/main.go new file mode 100644 index 0000000..b7897e7 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-encoder/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "encoding/json" + "flag" + "log" + "os" + "path" + "strconv" + "time" + + "github.com/BurntSushi/toml" +) + +func init() { + log.SetFlags(0) + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() != 0 { + flag.Usage() + } + + var tmp interface{} + if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil { + log.Fatalf("Error decoding JSON: %s", err) + } + + tomlData := translate(tmp) + if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil { + log.Fatalf("Error encoding TOML: %s", err) + } +} + +func translate(typedJson interface{}) interface{} { + switch v := typedJson.(type) { + case map[string]interface{}: + if len(v) == 2 && in("type", v) && in("value", v) { + return untag(v) + } + m := make(map[string]interface{}, len(v)) + for k, v2 := range v { + m[k] = translate(v2) + } + return m + case []interface{}: + tabArray := make([]map[string]interface{}, len(v)) + for i := range v { + if m, ok := translate(v[i]).(map[string]interface{}); ok { + tabArray[i] = m + } else { + log.Fatalf("JSON arrays may only contain objects. This " + + "corresponds to only tables being allowed in " + + "TOML table arrays.") + } + } + return tabArray + } + log.Fatalf("Unrecognized JSON format '%T'.", typedJson) + panic("unreachable") +} + +func untag(typed map[string]interface{}) interface{} { + t := typed["type"].(string) + v := typed["value"] + switch t { + case "string": + return v.(string) + case "integer": + v := v.(string) + n, err := strconv.Atoi(v) + if err != nil { + log.Fatalf("Could not parse '%s' as integer: %s", v, err) + } + return n + case "float": + v := v.(string) + f, err := strconv.ParseFloat(v, 64) + if err != nil { + log.Fatalf("Could not parse '%s' as float64: %s", v, err) + } + return f + case "datetime": + v := v.(string) + t, err := time.Parse("2006-01-02T15:04:05Z", v) + if err != nil { + log.Fatalf("Could not parse '%s' as a datetime: %s", v, err) + } + return t + case "bool": + v := v.(string) + switch v { + case "true": + return true + case "false": + return false + } + log.Fatalf("Could not parse '%s' as a boolean.", v) + case "array": + v := v.([]interface{}) + array := make([]interface{}, len(v)) + for i := range v { + if m, ok := v[i].(map[string]interface{}); ok { + array[i] = untag(m) + } else { + log.Fatalf("Arrays may only contain other arrays or "+ + "primitive values, but found a '%T'.", m) + } + } + return array + } + log.Fatalf("Unrecognized tag type '%s'.", t) + panic("unreachable") +} + +func in(key string, m map[string]interface{}) bool { + _, ok := m[key] + return ok +} diff --git a/vendor/github.com/BurntSushi/toml/toml-test-go/COPYING b/vendor/github.com/BurntSushi/toml/toml-test-go/COPYING new file mode 100644 index 0000000..5a8e332 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-go/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/toml-test-go/README.md b/vendor/github.com/BurntSushi/toml/toml-test-go/README.md new file mode 100644 index 0000000..24421eb --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-go/README.md @@ -0,0 +1,14 @@ +# Implements the TOML test suite interface + +This is an implementation of the interface expected by +[toml-test](https://github.com/BurntSushi/toml-test) for my +[toml parser written in Go](https://github.com/BurntSushi/toml). +In particular, it maps TOML data on `stdin` to a JSON format on `stdout`. + + +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + +Compatible with `toml-test` version +[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) + diff --git a/vendor/github.com/BurntSushi/toml/toml-test-go/main.go b/vendor/github.com/BurntSushi/toml/toml-test-go/main.go new file mode 100644 index 0000000..eb0f95b --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/toml-test-go/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "path" + "time" + + "github.com/BurntSushi/toml" +) + +func init() { + log.SetFlags(0) + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() != 0 { + flag.Usage() + } + + var tmp interface{} + if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil { + log.Fatalf("Error decoding TOML: %s", err) + } + + typedTmp := translate(tmp) + if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil { + log.Fatalf("Error encoding JSON: %s", err) + } +} + +func translate(tomlData interface{}) interface{} { + switch orig := tomlData.(type) { + case map[string]interface{}: + typed := make(map[string]interface{}, len(orig)) + for k, v := range orig { + typed[k] = translate(v) + } + return typed + case []map[string]interface{}: + typed := make([]map[string]interface{}, len(orig)) + for i, v := range orig { + typed[i] = translate(v).(map[string]interface{}) + } + return typed + case []interface{}: + typed := make([]interface{}, len(orig)) + for i, v := range orig { + typed[i] = translate(v) + } + + // We don't really need to tag arrays, but let's be future proof. + // (If TOML ever supports tuples, we'll need this.) + return tag("array", typed) + case time.Time: + return tag("datetime", orig.Format("2006-01-02T15:04:05Z")) + case bool: + return tag("bool", fmt.Sprintf("%v", orig)) + case int64: + return tag("integer", fmt.Sprintf("%d", orig)) + case float64: + return tag("float", fmt.Sprintf("%v", orig)) + case string: + return tag("string", orig) + } + + panic(fmt.Sprintf("Unknown type: %T", tomlData)) +} + +func tag(typeName string, data interface{}) map[string]interface{} { + return map[string]interface{}{ + "type": typeName, + "value": data, + } +} diff --git a/vendor/github.com/BurntSushi/toml/tomlv/COPYING b/vendor/github.com/BurntSushi/toml/tomlv/COPYING new file mode 100644 index 0000000..5a8e332 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/tomlv/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/vendor/github.com/BurntSushi/toml/tomlv/README.md b/vendor/github.com/BurntSushi/toml/tomlv/README.md new file mode 100644 index 0000000..bcc3f47 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/tomlv/README.md @@ -0,0 +1,22 @@ +# TOML Validator + +If Go is installed, it's simple to try it out: + +```bash +go get github.com/BurntSushi/toml/tomlv +tomlv some-toml-file.toml +``` + +You can see the types of every key in a TOML file with: + +```bash +tomlv -types some-toml-file.toml +``` + +At the moment, only one error message is reported at a time. Error messages +include line numbers. No output means that the files given are valid TOML, or +there is a bug in `tomlv`. + +Compatible with TOML version +[v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md) + diff --git a/vendor/github.com/BurntSushi/toml/tomlv/main.go b/vendor/github.com/BurntSushi/toml/tomlv/main.go new file mode 100644 index 0000000..a52086e --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/tomlv/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path" + "strings" + "text/tabwriter" + + "github.com/BurntSushi/toml" +) + +var ( + flagTypes = false +) + +func init() { + log.SetFlags(0) + + flag.BoolVar(&flagTypes, "types", flagTypes, + "When set, the types of every defined key will be shown.") + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s toml-file [ toml-file ... ]\n", + path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() < 1 { + flag.Usage() + } + for _, f := range flag.Args() { + var tmp interface{} + md, err := toml.DecodeFile(f, &tmp) + if err != nil { + log.Fatalf("Error in '%s': %s", f, err) + } + if flagTypes { + printTypes(md) + } + } +} + +func printTypes(md toml.MetaData) { + tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + for _, key := range md.Keys() { + fmt.Fprintf(tabw, "%s%s\t%s\n", + strings.Repeat(" ", len(key)-1), key, md.Type(key...)) + } + tabw.Flush() +} diff --git a/vendor/github.com/BurntSushi/toml/type_check.go b/vendor/github.com/BurntSushi/toml/type_check.go new file mode 100644 index 0000000..026ac6a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_check.go @@ -0,0 +1,78 @@ +package toml + +// tomlType represents any Go type that corresponds to a TOML type. +// While the first draft of the TOML spec has a simplistic type system that +// probably doesn't need this level of sophistication, we seem to be militating +// toward adding real composite types. +type tomlType interface { + typeString() string +} + +// typeEqual accepts any two types and returns true if they are equal. +func typeEqual(t1, t2 tomlType) bool { + return t1.typeString() == t2.typeString() +} + +type tomlBaseType string + +func (btype tomlBaseType) typeString() string { + return string(btype) +} + +func (btype tomlBaseType) String() string { + return btype.typeString() +} + +var ( + tomlInteger tomlBaseType = "Integer" + tomlFloat tomlBaseType = "Float" + tomlDatetime tomlBaseType = "Datetime" + tomlString tomlBaseType = "String" + tomlBool tomlBaseType = "Bool" + tomlArray tomlBaseType = "Array" + tomlHash tomlBaseType = "Hash" + tomlArrayHash tomlBaseType = "ArrayHash" +) + +// typeOfPrimitive returns a tomlType of any primitive value in TOML. +// Primitive values are: Integer, Float, Datetime, String and Bool. +// +// Passing a lexer item other than the following will cause a BUG message +// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. +func (p *parser) typeOfPrimitive(lexItem item) tomlType { + switch lexItem.typ { + case itemInteger: + return tomlInteger + case itemFloat: + return tomlFloat + case itemDatetime: + return tomlDatetime + case itemString: + return tomlString + case itemBool: + return tomlBool + } + p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) + panic("unreachable") +} + +// typeOfArray returns a tomlType for an array given a list of types of its +// values. +// +// In the current spec, if an array is homogeneous, then its type is always +// "Array". If the array is not homogeneous, an error is generated. +func (p *parser) typeOfArray(types []tomlType) tomlType { + // Empty arrays are cool. + if len(types) == 0 { + return tomlArray + } + + theType := types[0] + for _, t := range types[1:] { + if !typeEqual(theType, t) { + p.panicf("Array contains values of type '%s' and '%s', but arrays "+ + "must be homogeneous.", theType, t) + } + } + return tomlArray +} diff --git a/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/BurntSushi/toml/type_fields.go new file mode 100644 index 0000000..138fc00 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_fields.go @@ -0,0 +1,241 @@ +package toml + +// Struct field handling is adapted from code in encoding/json: +// +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string // the name of the field (`toml` tag included) + tag bool // whether field has a `toml` tag + index []int // represents the depth of an anonymous field + typ reflect.Type // the type of the field +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from toml tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that TOML should recognize for the given +// type. The algorithm is breadth-first search over the set of structs to +// include - the top struct and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + name := sf.Tag.Get("toml") + if name == "-" { + continue + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + f := field{name: ft.Name(), index: index, typ: ft} + next = append(next, f) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with TOML tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// TOML tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/vendor/github.com/codegangsta/cli/LICENSE b/vendor/github.com/codegangsta/cli/LICENSE new file mode 100644 index 0000000..5515ccf --- /dev/null +++ b/vendor/github.com/codegangsta/cli/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2013 Jeremy Saenz +All Rights Reserved. + +MIT LICENSE + +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/codegangsta/cli/README.md b/vendor/github.com/codegangsta/cli/README.md new file mode 100644 index 0000000..2346557 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/README.md @@ -0,0 +1,315 @@ +[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) +[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) +[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) + +# cli.go +`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. + +## Overview +Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. + +**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! + +## Installation +Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). + +To install `cli.go`, simply run: +``` +$ go get github.com/codegangsta/cli +``` + +Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: +``` +export PATH=$PATH:$GOPATH/bin +``` + +## Getting Started +One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + cli.NewApp().Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) { + println("boom! I say!") + } + + app.Run(os.Args) +} +``` + +Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. + +## Example + +Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! + +Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) { + println("Hello friend!") + } + + app.Run(os.Args) +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +`cli.go` also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments +You can lookup arguments by calling the `Args` function on `cli.Context`. + +``` go +... +app.Action = func(c *cli.Context) { + println("Hello", c.Args()[0]) +} +... +``` + +### Flags +Setting and querying flags is simple. +``` go +... +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, +} +app.Action = func(c *cli.Context) { + name := "someone" + if len(c.Args()) > 0 { + name = c.Args()[0] + } + if c.String("lang") == "spanish" { + println("Hola", name) + } else { + println("Hello", name) + } +} +... +``` + +See full list of flags at http://godoc.org/github.com/codegangsta/cli + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, +} +``` + +### Subcommands + +Subcommands can be defined for a more git-like command line app. +```go +... +app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) { + println("added task: ", c.Args().First()) + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) { + println("completed task: ", c.Args().First()) + }, + }, + { + Name: "template", + Aliases: []string{"r"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) { + println("new task template: ", c.Args().First()) + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) { + println("removed task template: ", c.Args().First()) + }, + }, + }, + }, +} +... +``` + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. +```go +... +var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} +app := cli.NewApp() +app.EnableBashCompletion = true +app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) { + println("completed task: ", c.Args().First()) + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if len(c.Args()) > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + } +} +... +``` + +#### To Enable + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### To Distribute + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` + sudo cp src/bash_autocomplete /etc/bash_completion.d/ + source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +## Contribution Guidelines +Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. + +If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. diff --git a/vendor/github.com/codegangsta/cli/app.go b/vendor/github.com/codegangsta/cli/app.go new file mode 100644 index 0000000..9a15c0c --- /dev/null +++ b/vendor/github.com/codegangsta/cli/app.go @@ -0,0 +1,333 @@ +package cli + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "time" +) + +// App is the main structure of a cli application. It is recomended that +// an app be created with the cli.NewApp() function +type App struct { + // The name of the program. Defaults to os.Args[0] + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // List of commands to execute + Commands []Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command + HideHelp bool + // Boolean to hide built-in version flag + HideVersion bool + // An action to execute when the bash-completion flag is set + BashComplete func(context *Context) + // An action to execute before any subcommands are run, but after the context is ready + // If a non-nil error is returned, no subcommands are run + Before func(context *Context) error + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After func(context *Context) error + // The action to execute when no subcommands are specified + Action func(context *Context) + // Execute this function if the proper command cannot be found + CommandNotFound func(context *Context, command string) + // Compilation date + Compiled time.Time + // List of all authors who contributed + Authors []Author + // Copyright of the binary if any + Copyright string + // Name of Author (Note: Use App.Authors, this is deprecated) + Author string + // Email of Author (Note: Use App.Authors, this is deprecated) + Email string + // Writer writer to write output to + Writer io.Writer +} + +// Tries to find out when this binary was compiled. +// Returns the current time if it fails to find it. +func compileTime() time.Time { + info, err := os.Stat(os.Args[0]) + if err != nil { + return time.Now() + } + return info.ModTime() +} + +// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. +func NewApp() *App { + return &App{ + Name: os.Args[0], + HelpName: os.Args[0], + Usage: "A new cli application", + Version: "0.0.0", + BashComplete: DefaultAppComplete, + Action: helpCommand.Action, + Compiled: compileTime(), + Writer: os.Stdout, + } +} + +// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +func (a *App) Run(arguments []string) (err error) { + if a.Author != "" || a.Email != "" { + a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + // append help to commands + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + + //append version/help flags + if a.EnableBashCompletion { + a.appendFlag(BashCompletionFlag) + } + + if !a.HideVersion { + a.appendFlag(VersionFlag) + } + + // parse flags + set := flagSet(a.Name, a.Flags) + set.SetOutput(ioutil.Discard) + err = set.Parse(arguments[1:]) + nerr := normalizeFlags(a.Flags, set) + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + context := NewContext(a, set, nil) + ShowAppHelp(context) + return nerr + } + context := NewContext(a, set, nil) + + if err != nil { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowAppHelp(context) + return err + } + + if checkCompletions(context) { + return nil + } + + if !a.HideHelp && checkHelp(context) { + ShowAppHelp(context) + return nil + } + + if !a.HideVersion && checkVersion(context) { + ShowVersion(context) + return nil + } + + if a.After != nil { + defer func() { + afterErr := a.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + err := a.Before(context) + if err != nil { + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + // Run default Action + a.Action(context) + return nil +} + +// Another entry point to the cli app, takes care of passing arguments and error handling +func (a *App) RunAndExitOnError() { + if err := a.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags +func (a *App) RunAsSubcommand(ctx *Context) (err error) { + // append help to commands + if len(a.Commands) > 0 { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + // append flags + if a.EnableBashCompletion { + a.appendFlag(BashCompletionFlag) + } + + // parse flags + set := flagSet(a.Name, a.Flags) + set.SetOutput(ioutil.Discard) + err = set.Parse(ctx.Args().Tail()) + nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, ctx) + + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + fmt.Fprintln(a.Writer) + if len(a.Commands) > 0 { + ShowSubcommandHelp(context) + } else { + ShowCommandHelp(ctx, context.Args().First()) + } + return nerr + } + + if err != nil { + fmt.Fprintln(a.Writer, "Incorrect Usage.") + fmt.Fprintln(a.Writer) + ShowSubcommandHelp(context) + return err + } + + if checkCompletions(context) { + return nil + } + + if len(a.Commands) > 0 { + if checkSubcommandHelp(context) { + return nil + } + } else { + if checkCommandHelp(ctx, context.Args().First()) { + return nil + } + } + + if a.After != nil { + defer func() { + afterErr := a.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + err := a.Before(context) + if err != nil { + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + // Run default Action + a.Action(context) + + return nil +} + +// Returns the named command on App. Returns nil if the command does not exist +func (a *App) Command(name string) *Command { + for _, c := range a.Commands { + if c.HasName(name) { + return &c + } + } + + return nil +} + +func (a *App) hasFlag(flag Flag) bool { + for _, f := range a.Flags { + if flag == f { + return true + } + } + + return false +} + +func (a *App) appendFlag(flag Flag) { + if !a.hasFlag(flag) { + a.Flags = append(a.Flags, flag) + } +} + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = "<" + a.Email + "> " + } + + return fmt.Sprintf("%v %v", a.Name, e) +} diff --git a/vendor/github.com/codegangsta/cli/app_test.go b/vendor/github.com/codegangsta/cli/app_test.go new file mode 100644 index 0000000..ada5d69 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/app_test.go @@ -0,0 +1,960 @@ +package cli + +import ( + "bytes" + "flag" + "fmt" + "io" + "os" + "strings" + "testing" +) + +func ExampleApp() { + // set args for examples sake + os.Args = []string{"greet", "--name", "Jeremy"} + + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Action = func(c *Context) { + fmt.Printf("Hello %v\n", c.String("name")) + } + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Run(os.Args) + // Output: + // Hello Jeremy +} + +func ExampleAppSubcommand() { + // set args for examples sake + os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} + app := NewApp() + app.Name = "say" + app.Commands = []Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []Flag{ + StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *Context) { + fmt.Println("Hello,", c.String("name")) + }, + }, + }, + }, + } + + app.Run(os.Args) + // Output: + // Hello, Jeremy +} + +func ExampleAppHelp() { + // set args for examples sake + os.Args = []string{"greet", "h", "describeit"} + + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) { + fmt.Printf("i like to describe things") + }, + }, + } + app.Run(os.Args) + // Output: + // NAME: + // greet describeit - use it to see a description + // + // USAGE: + // greet describeit [arguments...] + // + // DESCRIPTION: + // This is how we describe describeit the function +} + +func ExampleAppBashComplete() { + // set args for examples sake + os.Args = []string{"greet", "--generate-bash-completion"} + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) { + fmt.Printf("i like to describe things") + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating bash completion", + Action: func(c *Context) { + fmt.Printf("the next example") + }, + }, + } + + app.Run(os.Args) + // Output: + // describeit + // d + // next + // help + // h +} + +func TestApp_Run(t *testing.T) { + s := "" + + app := NewApp() + app.Action = func(c *Context) { + s = s + c.Args().First() + } + + err := app.Run([]string{"command", "foo"}) + expect(t, err, nil) + err = app.Run([]string{"command", "bar"}) + expect(t, err, nil) + expect(t, s, "foobar") +} + +var commandAppTests = []struct { + name string + expected bool +}{ + {"foobar", true}, + {"batbaz", true}, + {"b", true}, + {"f", true}, + {"bat", false}, + {"nothing", false}, +} + +func TestApp_Command(t *testing.T) { + app := NewApp() + fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []Command{ + fooCommand, + batCommand, + } + + for _, test := range commandAppTests { + expect(t, app.Command(test.name) != nil, test.expected) + } +} + +func TestApp_CommandWithArgBeforeFlags(t *testing.T) { + var parsedOption, firstArg string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) { + parsedOption = c.String("option") + firstArg = c.Args().First() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) + + expect(t, parsedOption, "my-option") + expect(t, firstArg, "my-arg") +} + +func TestApp_RunAsSubcommandParseFlags(t *testing.T) { + var context *Context + + a := NewApp() + a.Commands = []Command{ + { + Name: "foo", + Action: func(c *Context) { + context = c + }, + Flags: []Flag{ + StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Before: func(_ *Context) error { return nil }, + }, + } + a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) + + expect(t, context.Args().Get(0), "abcd") + expect(t, context.String("lang"), "spanish") +} + +func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { + var parsedOption string + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) { + parsedOption = c.String("option") + args = c.Args() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + + expect(t, parsedOption, "my-option") + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "--notARealFlag") +} + +func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Action: func(c *Context) { + args = c.Args() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "notAFlagAtAll") +} + +func TestApp_Float64Flag(t *testing.T) { + var meters float64 + + app := NewApp() + app.Flags = []Flag{ + Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + } + app.Action = func(c *Context) { + meters = c.Float64("height") + } + + app.Run([]string{"", "--height", "1.93"}) + expect(t, meters, 1.93) +} + +func TestApp_ParseSliceFlags(t *testing.T) { + var parsedOption, firstArg string + var parsedIntSlice []int + var parsedStringSlice []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, + StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, + }, + Action: func(c *Context) { + parsedIntSlice = c.IntSlice("p") + parsedStringSlice = c.StringSlice("ip") + parsedOption = c.String("option") + firstArg = c.Args().First() + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) + + IntsEquals := func(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true + } + + StrsEquals := func(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true + } + var expectedIntSlice = []int{22, 80} + var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} + + if !IntsEquals(parsedIntSlice, expectedIntSlice) { + t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) + } + + if !StrsEquals(parsedStringSlice, expectedStringSlice) { + t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) + } +} + +func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { + var parsedIntSlice []int + var parsedStringSlice []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + IntSliceFlag{Name: "a", Usage: "set numbers"}, + StringSliceFlag{Name: "str", Usage: "set strings"}, + }, + Action: func(c *Context) { + parsedIntSlice = c.IntSlice("a") + parsedStringSlice = c.StringSlice("str") + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + + var expectedIntSlice = []int{2} + var expectedStringSlice = []string{"A"} + + if parsedIntSlice[0] != expectedIntSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } + + if parsedStringSlice[0] != expectedStringSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } +} + +func TestApp_DefaultStdout(t *testing.T) { + app := NewApp() + + if app.Writer != os.Stdout { + t.Error("Default output writer not set.") + } +} + +type mockWriter struct { + written []byte +} + +func (fw *mockWriter) Write(p []byte) (n int, err error) { + if fw.written == nil { + fw.written = p + } else { + fw.written = append(fw.written, p...) + } + + return len(p), nil +} + +func (fw *mockWriter) GetWritten() (b []byte) { + return fw.written +} + +func TestApp_SetStdout(t *testing.T) { + w := &mockWriter{} + + app := NewApp() + app.Name = "test" + app.Writer = w + + err := app.Run([]string{"help"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if len(w.written) == 0 { + t.Error("App did not write output to desired writer.") + } +} + +func TestApp_BeforeFunc(t *testing.T) { + beforeRun, subcommandRun := false, false + beforeError := fmt.Errorf("fail") + var err error + + app := NewApp() + + app.Before = func(c *Context) error { + beforeRun = true + s := c.String("opt") + if s == "fail" { + return beforeError + } + + return nil + } + + app.Commands = []Command{ + Command{ + Name: "sub", + Action: func(c *Context) { + subcommandRun = true + }, + }, + } + + app.Flags = []Flag{ + StringFlag{Name: "opt"}, + } + + // run with the Before() func succeeding + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if beforeRun == false { + t.Errorf("Before() not executed when expected") + } + + if subcommandRun == false { + t.Errorf("Subcommand not executed when expected") + } + + // reset + beforeRun, subcommandRun = false, false + + // run with the Before() func failing + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if err != beforeError { + t.Errorf("Run error expected, but not received") + } + + if beforeRun == false { + t.Errorf("Before() not executed when expected") + } + + if subcommandRun == true { + t.Errorf("Subcommand executed when NOT expected") + } + +} + +func TestApp_AfterFunc(t *testing.T) { + afterRun, subcommandRun := false, false + afterError := fmt.Errorf("fail") + var err error + + app := NewApp() + + app.After = func(c *Context) error { + afterRun = true + s := c.String("opt") + if s == "fail" { + return afterError + } + + return nil + } + + app.Commands = []Command{ + Command{ + Name: "sub", + Action: func(c *Context) { + subcommandRun = true + }, + }, + } + + app.Flags = []Flag{ + StringFlag{Name: "opt"}, + } + + // run with the After() func succeeding + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if afterRun == false { + t.Errorf("After() not executed when expected") + } + + if subcommandRun == false { + t.Errorf("Subcommand not executed when expected") + } + + // reset + afterRun, subcommandRun = false, false + + // run with the Before() func failing + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if err != afterError { + t.Errorf("Run error expected, but not received") + } + + if afterRun == false { + t.Errorf("After() not executed when expected") + } + + if subcommandRun == false { + t.Errorf("Subcommand not executed when expected") + } +} + +func TestAppNoHelpFlag(t *testing.T) { + oldFlag := HelpFlag + defer func() { + HelpFlag = oldFlag + }() + + HelpFlag = BoolFlag{} + + app := NewApp() + err := app.Run([]string{"test", "-h"}) + + if err != flag.ErrHelp { + t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) + } +} + +func TestAppHelpPrinter(t *testing.T) { + oldPrinter := HelpPrinter + defer func() { + HelpPrinter = oldPrinter + }() + + var wasCalled = false + HelpPrinter = func(w io.Writer, template string, data interface{}) { + wasCalled = true + } + + app := NewApp() + app.Run([]string{"-h"}) + + if wasCalled == false { + t.Errorf("Help printer expected to be called, but was not") + } +} + +func TestAppVersionPrinter(t *testing.T) { + oldPrinter := VersionPrinter + defer func() { + VersionPrinter = oldPrinter + }() + + var wasCalled = false + VersionPrinter = func(c *Context) { + wasCalled = true + } + + app := NewApp() + ctx := NewContext(app, nil, nil) + ShowVersion(ctx) + + if wasCalled == false { + t.Errorf("Version printer expected to be called, but was not") + } +} + +func TestAppCommandNotFound(t *testing.T) { + beforeRun, subcommandRun := false, false + app := NewApp() + + app.CommandNotFound = func(c *Context, command string) { + beforeRun = true + } + + app.Commands = []Command{ + Command{ + Name: "bar", + Action: func(c *Context) { + subcommandRun = true + }, + }, + } + + app.Run([]string{"command", "foo"}) + + expect(t, beforeRun, true) + expect(t, subcommandRun, false) +} + +func TestGlobalFlag(t *testing.T) { + var globalFlag string + var globalFlagSet bool + app := NewApp() + app.Flags = []Flag{ + StringFlag{Name: "global, g", Usage: "global"}, + } + app.Action = func(c *Context) { + globalFlag = c.GlobalString("global") + globalFlagSet = c.GlobalIsSet("global") + } + app.Run([]string{"command", "-g", "foo"}) + expect(t, globalFlag, "foo") + expect(t, globalFlagSet, true) + +} + +func TestGlobalFlagsInSubcommands(t *testing.T) { + subcommandRun := false + parentFlag := false + app := NewApp() + + app.Flags = []Flag{ + BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + } + + app.Commands = []Command{ + Command{ + Name: "foo", + Flags: []Flag{ + BoolFlag{Name: "parent, p", Usage: "Parent flag"}, + }, + Subcommands: []Command{ + { + Name: "bar", + Action: func(c *Context) { + if c.GlobalBool("debug") { + subcommandRun = true + } + if c.GlobalBool("parent") { + parentFlag = true + } + }, + }, + }, + }, + } + + app.Run([]string{"command", "-d", "foo", "-p", "bar"}) + + expect(t, subcommandRun, true) + expect(t, parentFlag, true) +} + +func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { + var subcommandHelpTopics = [][]string{ + {"command", "foo", "--help"}, + {"command", "foo", "-h"}, + {"command", "foo", "help"}, + } + + for _, flagSet := range subcommandHelpTopics { + t.Logf("==> checking with flags %v", flagSet) + + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + + subCmdBar := Command{ + Name: "bar", + Usage: "does bar things", + } + subCmdBaz := Command{ + Name: "baz", + Usage: "does baz things", + } + cmd := Command{ + Name: "foo", + Description: "descriptive wall of text about how it does foo things", + Subcommands: []Command{subCmdBar, subCmdBaz}, + } + + app.Commands = []Command{cmd} + err := app.Run(flagSet) + + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if strings.Contains(output, "No help topic for") { + t.Errorf("expect a help topic, got none: \n%q", output) + } + + for _, shouldContain := range []string{ + cmd.Name, cmd.Description, + subCmdBar.Name, subCmdBar.Usage, + subCmdBaz.Name, subCmdBaz.Usage, + } { + if !strings.Contains(output, shouldContain) { + t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output) + } + } + } +} + +func TestApp_Run_SubcommandFullPath(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "command foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_SubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "custom - does bar things") { + t.Errorf("expected HelpName for subcommand: %s", output) + } + if !strings.Contains(output, "custom [arguments...]") { + t.Errorf("expected HelpName to subcommand: %s", output) + } +} + +func TestApp_Run_CommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + HelpName: "custom", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "command foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "base" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "base foo - foo commands") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "base foo command [command options] [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_Help(t *testing.T) { + var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} + + for _, args := range helpArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Writer = buf + app.Action = func(c *Context) { + buf.WriteString("boom I say!") + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "boom - make an explosive entrance") { + t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) + } + } +} + +func TestApp_Run_Version(t *testing.T) { + var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}} + + for _, args := range versionArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Version = "0.1.0" + app.Writer = buf + app.Action = func(c *Context) { + buf.WriteString("boom I say!") + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "0.1.0") { + t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output) + } + } +} + +func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Action = func(c *Context) {} + app.Before = func(c *Context) error { return fmt.Errorf("before error") } + app.After = func(c *Context) error { return fmt.Errorf("after error") } + + err := app.Run([]string{"foo"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} + +func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + Command{ + Name: "bar", + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to recieve error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} diff --git a/vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete b/vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete new file mode 100644 index 0000000..21a232f --- /dev/null +++ b/vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete @@ -0,0 +1,14 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + } + + complete -F _cli_bash_autocomplete $PROG diff --git a/vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete b/vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete new file mode 100644 index 0000000..5430a18 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete @@ -0,0 +1,5 @@ +autoload -U compinit && compinit +autoload -U bashcompinit && bashcompinit + +script_dir=$(dirname $0) +source ${script_dir}/bash_autocomplete diff --git a/vendor/github.com/codegangsta/cli/cli.go b/vendor/github.com/codegangsta/cli/cli.go new file mode 100644 index 0000000..31dc912 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/cli.go @@ -0,0 +1,40 @@ +// Package cli provides a minimal framework for creating and organizing command line +// Go applications. cli is designed to be easy to understand and write, the most simple +// cli application can be written as follows: +// func main() { +// cli.NewApp().Run(os.Args) +// } +// +// Of course this application does not do much, so let's make this an actual application: +// func main() { +// app := cli.NewApp() +// app.Name = "greet" +// app.Usage = "say a greeting" +// app.Action = func(c *cli.Context) { +// println("Greetings") +// } +// +// app.Run(os.Args) +// } +package cli + +import ( + "strings" +) + +type MultiError struct { + Errors []error +} + +func NewMultiError(err ...error) MultiError { + return MultiError{Errors: err} +} + +func (m MultiError) Error() string { + errs := make([]string, len(m.Errors)) + for i, err := range m.Errors { + errs[i] = err.Error() + } + + return strings.Join(errs, "\n") +} diff --git a/vendor/github.com/codegangsta/cli/cli_test.go b/vendor/github.com/codegangsta/cli/cli_test.go new file mode 100644 index 0000000..e54f8e2 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/cli_test.go @@ -0,0 +1,98 @@ +package cli + +import ( + "os" +) + +func Example() { + app := NewApp() + app.Name = "todo" + app.Usage = "task list on the command line" + app.Commands = []Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *Context) { + println("added task: ", c.Args().First()) + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *Context) { + println("completed task: ", c.Args().First()) + }, + }, + } + + app.Run(os.Args) +} + +func ExampleSubcommand() { + app := NewApp() + app.Name = "say" + app.Commands = []Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []Flag{ + StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *Context) { + println("Hello, ", c.String("name")) + }, + }, { + Name: "spanish", + Aliases: []string{"sp"}, + Usage: "sends a greeting in spanish", + Flags: []Flag{ + StringFlag{ + Name: "surname", + Value: "Jones", + Usage: "Surname of the person to greet", + }, + }, + Action: func(c *Context) { + println("Hola, ", c.String("surname")) + }, + }, { + Name: "french", + Aliases: []string{"fr"}, + Usage: "sends a greeting in french", + Flags: []Flag{ + StringFlag{ + Name: "nickname", + Value: "Stevie", + Usage: "Nickname of the person to greet", + }, + }, + Action: func(c *Context) { + println("Bonjour, ", c.String("nickname")) + }, + }, + }, + }, { + Name: "bye", + Usage: "says goodbye", + Action: func(c *Context) { + println("bye") + }, + }, + } + + app.Run(os.Args) +} diff --git a/vendor/github.com/codegangsta/cli/command.go b/vendor/github.com/codegangsta/cli/command.go new file mode 100644 index 0000000..fac754d --- /dev/null +++ b/vendor/github.com/codegangsta/cli/command.go @@ -0,0 +1,210 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "strings" +) + +// Command is a subcommand for a cli.App. +type Command struct { + // The name of the command + Name string + // short name of the command. Typically one character (deprecated, use `Aliases`) + ShortName string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The function to call when checking for bash command completions + BashComplete func(context *Context) + // An action to execute before any sub-subcommands are run, but after the context is ready + // If a non-nil error is returned, no sub-subcommands are run + Before func(context *Context) error + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After func(context *Context) error + // The function to call when this command is invoked + Action func(context *Context) + // List of child commands + Subcommands []Command + // List of flags to parse + Flags []Flag + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Boolean to hide built-in help command + HideHelp bool + + // Full name of command for help, defaults to full command name, including parent commands. + HelpName string + commandNamePath []string +} + +// Returns the full name of the command. +// For subcommands this ensures that parent commands are part of the command path +func (c Command) FullName() string { + if c.commandNamePath == nil { + return c.Name + } + return strings.Join(c.commandNamePath, " ") +} + +// Invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c Command) Run(ctx *Context) error { + if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { + return c.startApp(ctx) + } + + if !c.HideHelp && (HelpFlag != BoolFlag{}) { + // append help to flags + c.Flags = append( + c.Flags, + HelpFlag, + ) + } + + if ctx.App.EnableBashCompletion { + c.Flags = append(c.Flags, BashCompletionFlag) + } + + set := flagSet(c.Name, c.Flags) + set.SetOutput(ioutil.Discard) + + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + + var err error + if firstFlagIndex > -1 && !c.SkipFlagParsing { + args := ctx.Args() + regularArgs := make([]string, len(args[1:firstFlagIndex])) + copy(regularArgs, args[1:firstFlagIndex]) + + var flagArgs []string + if terminatorIndex > -1 { + flagArgs = args[firstFlagIndex:terminatorIndex] + regularArgs = append(regularArgs, args[terminatorIndex:]...) + } else { + flagArgs = args[firstFlagIndex:] + } + + err = set.Parse(append(flagArgs, regularArgs...)) + } else { + err = set.Parse(ctx.Args().Tail()) + } + + if err != nil { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } + + nerr := normalizeFlags(c.Flags, set) + if nerr != nil { + fmt.Fprintln(ctx.App.Writer, nerr) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return nerr + } + context := NewContext(ctx.App, set, ctx) + + if checkCommandCompletions(context, c.Name) { + return nil + } + + if checkCommandHelp(context, c.Name) { + return nil + } + context.Command = c + c.Action(context) + return nil +} + +func (c Command) Names() []string { + names := []string{c.Name} + + if c.ShortName != "" { + names = append(names, c.ShortName) + } + + return append(names, c.Aliases...) +} + +// Returns true if Command.Name or Command.ShortName matches given name +func (c Command) HasName(name string) bool { + for _, n := range c.Names() { + if n == name { + return true + } + } + return false +} + +func (c Command) startApp(ctx *Context) error { + app := NewApp() + + // set the name and usage + app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + if c.HelpName == "" { + app.HelpName = c.HelpName + } else { + app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + } + + if c.Description != "" { + app.Usage = c.Description + } else { + app.Usage = c.Usage + } + + // set CommandNotFound + app.CommandNotFound = ctx.App.CommandNotFound + + // set the flags and commands + app.Commands = c.Subcommands + app.Flags = c.Flags + app.HideHelp = c.HideHelp + + app.Version = ctx.App.Version + app.HideVersion = ctx.App.HideVersion + app.Compiled = ctx.App.Compiled + app.Author = ctx.App.Author + app.Email = ctx.App.Email + app.Writer = ctx.App.Writer + + // bash completion + app.EnableBashCompletion = ctx.App.EnableBashCompletion + if c.BashComplete != nil { + app.BashComplete = c.BashComplete + } + + // set the actions + app.Before = c.Before + app.After = c.After + if c.Action != nil { + app.Action = c.Action + } else { + app.Action = helpSubcommand.Action + } + + var newCmds []Command + for _, cc := range app.Commands { + cc.commandNamePath = []string{c.Name, cc.Name} + newCmds = append(newCmds, cc) + } + app.Commands = newCmds + + return app.RunAsSubcommand(ctx) +} diff --git a/vendor/github.com/codegangsta/cli/command_test.go b/vendor/github.com/codegangsta/cli/command_test.go new file mode 100644 index 0000000..fd39548 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/command_test.go @@ -0,0 +1,47 @@ +package cli + +import ( + "flag" + "testing" +) + +func TestCommandDoNotIgnoreFlags(t *testing.T) { + app := NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"blah", "blah", "-break"} + set.Parse(test) + + c := NewContext(app, set, nil) + + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + } + err := command.Run(c) + + expect(t, err.Error(), "flag provided but not defined: -break") +} + +func TestCommandIgnoreFlags(t *testing.T) { + app := NewApp() + set := flag.NewFlagSet("test", 0) + test := []string{"blah", "blah", "-break"} + set.Parse(test) + + c := NewContext(app, set, nil) + + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) {}, + SkipFlagParsing: true, + } + err := command.Run(c) + + expect(t, err, nil) +} diff --git a/vendor/github.com/codegangsta/cli/context.go b/vendor/github.com/codegangsta/cli/context.go new file mode 100644 index 0000000..f541f41 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/context.go @@ -0,0 +1,388 @@ +package cli + +import ( + "errors" + "flag" + "strconv" + "strings" + "time" +) + +// Context is a type that is passed through to +// each Handler action in a cli application. Context +// can be used to retrieve context-specific Args and +// parsed command-line options. +type Context struct { + App *App + Command Command + flagSet *flag.FlagSet + setFlags map[string]bool + globalSetFlags map[string]bool + parentContext *Context +} + +// Creates a new context. For use in when invoking an App or Command action. +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { + return &Context{App: app, flagSet: set, parentContext: parentCtx} +} + +// Looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int(name string) int { + return lookupInt(name, c.flagSet) +} + +// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) Duration(name string) time.Duration { + return lookupDuration(name, c.flagSet) +} + +// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists +func (c *Context) Float64(name string) float64 { + return lookupFloat64(name, c.flagSet) +} + +// Looks up the value of a local bool flag, returns false if no bool flag exists +func (c *Context) Bool(name string) bool { + return lookupBool(name, c.flagSet) +} + +// Looks up the value of a local boolT flag, returns false if no bool flag exists +func (c *Context) BoolT(name string) bool { + return lookupBoolT(name, c.flagSet) +} + +// Looks up the value of a local string flag, returns "" if no string flag exists +func (c *Context) String(name string) string { + return lookupString(name, c.flagSet) +} + +// Looks up the value of a local string slice flag, returns nil if no string slice flag exists +func (c *Context) StringSlice(name string) []string { + return lookupStringSlice(name, c.flagSet) +} + +// Looks up the value of a local int slice flag, returns nil if no int slice flag exists +func (c *Context) IntSlice(name string) []int { + return lookupIntSlice(name, c.flagSet) +} + +// Looks up the value of a local generic flag, returns nil if no generic flag exists +func (c *Context) Generic(name string) interface{} { + return lookupGeneric(name, c.flagSet) +} + +// Looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalInt(name string) int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 +} + +// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) GlobalDuration(name string) time.Duration { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 +} + +// Looks up the value of a global bool flag, returns false if no bool flag exists +func (c *Context) GlobalBool(name string) bool { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false +} + +// Looks up the value of a global string flag, returns "" if no string flag exists +func (c *Context) GlobalString(name string) string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" +} + +// Looks up the value of a global string slice flag, returns nil if no string slice flag exists +func (c *Context) GlobalStringSlice(name string) []string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil +} + +// Looks up the value of a global int slice flag, returns nil if no int slice flag exists +func (c *Context) GlobalIntSlice(name string) []int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupIntSlice(name, fs) + } + return nil +} + +// Looks up the value of a global generic flag, returns nil if no generic flag exists +func (c *Context) GlobalGeneric(name string) interface{} { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil +} + +// Returns the number of flags set +func (c *Context) NumFlags() int { + return c.flagSet.NFlag() +} + +// Determines if the flag was actually set +func (c *Context) IsSet(name string) bool { + if c.setFlags == nil { + c.setFlags = make(map[string]bool) + c.flagSet.Visit(func(f *flag.Flag) { + c.setFlags[f.Name] = true + }) + } + return c.setFlags[name] == true +} + +// Determines if the global flag was actually set +func (c *Context) GlobalIsSet(name string) bool { + if c.globalSetFlags == nil { + c.globalSetFlags = make(map[string]bool) + ctx := c + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { + ctx.flagSet.Visit(func(f *flag.Flag) { + c.globalSetFlags[f.Name] = true + }) + } + } + return c.globalSetFlags[name] +} + +// Returns a slice of flag names used in this context. +func (c *Context) FlagNames() (names []string) { + for _, flag := range c.Command.Flags { + name := strings.Split(flag.getName(), ",")[0] + if name == "help" { + continue + } + names = append(names, name) + } + return +} + +// Returns a slice of global flag names used by the app. +func (c *Context) GlobalFlagNames() (names []string) { + for _, flag := range c.App.Flags { + name := strings.Split(flag.getName(), ",")[0] + if name == "help" || name == "version" { + continue + } + names = append(names, name) + } + return +} + +// Returns the parent context, if any +func (c *Context) Parent() *Context { + return c.parentContext +} + +type Args []string + +// Returns the command line arguments associated with the context. +func (c *Context) Args() Args { + args := Args(c.flagSet.Args()) + return args +} + +// Returns the nth argument, or else a blank string +func (a Args) Get(n int) string { + if len(a) > n { + return a[n] + } + return "" +} + +// Returns the first argument, or else a blank string +func (a Args) First() string { + return a.Get(0) +} + +// Return the rest of the arguments (not the first one) +// or else an empty string slice +func (a Args) Tail() []string { + if len(a) >= 2 { + return []string(a)[1:] + } + return []string{} +} + +// Checks if there are any arguments present +func (a Args) Present() bool { + return len(a) != 0 +} + +// Swaps arguments at the given indexes +func (a Args) Swap(from, to int) error { + if from >= len(a) || to >= len(a) { + return errors.New("index out of range") + } + a[from], a[to] = a[to], a[from] + return nil +} + +func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil; ctx = ctx.parentContext { + if f := ctx.flagSet.Lookup(name); f != nil { + return ctx.flagSet + } + } + return nil +} + +func lookupInt(name string, set *flag.FlagSet) int { + f := set.Lookup(name) + if f != nil { + val, err := strconv.Atoi(f.Value.String()) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + val, err := time.ParseDuration(f.Value.String()) + if err == nil { + return val + } + } + + return 0 +} + +func lookupFloat64(name string, set *flag.FlagSet) float64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseFloat(f.Value.String(), 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupString(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + return f.Value.String() + } + + return "" +} + +func lookupStringSlice(name string, set *flag.FlagSet) []string { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*StringSlice)).Value() + + } + + return nil +} + +func lookupIntSlice(name string, set *flag.FlagSet) []int { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*IntSlice)).Value() + + } + + return nil +} + +func lookupGeneric(name string, set *flag.FlagSet) interface{} { + f := set.Lookup(name) + if f != nil { + return f.Value + } + return nil +} + +func lookupBool(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return val + } + + return false +} + +func lookupBoolT(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return true + } + return val + } + + return false +} + +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case *StringSlice: + default: + set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := strings.Split(f.getName(), ",") + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} diff --git a/vendor/github.com/codegangsta/cli/context_test.go b/vendor/github.com/codegangsta/cli/context_test.go new file mode 100644 index 0000000..7f8e928 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/context_test.go @@ -0,0 +1,113 @@ +package cli + +import ( + "flag" + "testing" + "time" +) + +func TestNewContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Int("myflag", 42, "doc") + globalCtx := NewContext(nil, globalSet, nil) + command := Command{Name: "mycommand"} + c := NewContext(nil, set, globalCtx) + c.Command = command + expect(t, c.Int("myflag"), 12) + expect(t, c.GlobalInt("myflag"), 42) + expect(t, c.Command.Name, "mycommand") +} + +func TestContext_Int(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Int("myflag"), 12) +} + +func TestContext_Duration(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Duration("myflag", time.Duration(12*time.Second), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) +} + +func TestContext_String(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("myflag", "hello world", "doc") + c := NewContext(nil, set, nil) + expect(t, c.String("myflag"), "hello world") +} + +func TestContext_Bool(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Bool("myflag"), false) +} + +func TestContext_BoolT(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", true, "doc") + c := NewContext(nil, set, nil) + expect(t, c.BoolT("myflag"), true) +} + +func TestContext_Args(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + set.Parse([]string{"--myflag", "bat", "baz"}) + expect(t, len(c.Args()), 2) + expect(t, c.Bool("myflag"), true) +} + +func TestContext_IsSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) + expect(t, c.IsSet("myflag"), true) + expect(t, c.IsSet("otherflag"), false) + expect(t, c.IsSet("bogusflag"), false) + expect(t, c.IsSet("myflagGlobal"), false) +} + +func TestContext_GlobalIsSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalSet.Bool("myflagGlobalUnset", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) + expect(t, c.GlobalIsSet("myflag"), false) + expect(t, c.GlobalIsSet("otherflag"), false) + expect(t, c.GlobalIsSet("bogusflag"), false) + expect(t, c.GlobalIsSet("myflagGlobal"), true) + expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) + expect(t, c.GlobalIsSet("bogusGlobal"), false) +} + +func TestContext_NumFlags(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "--otherflag=foo"}) + globalSet.Parse([]string{"--myflagGlobal"}) + expect(t, c.NumFlags(), 2) +} diff --git a/vendor/github.com/codegangsta/cli/flag.go b/vendor/github.com/codegangsta/cli/flag.go new file mode 100644 index 0000000..531b091 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/flag.go @@ -0,0 +1,497 @@ +package cli + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + "time" +) + +// This flag enables bash-completion for all commands and subcommands +var BashCompletionFlag = BoolFlag{ + Name: "generate-bash-completion", +} + +// This flag prints the version for the application +var VersionFlag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} + +// This flag prints the help for all commands and subcommands +// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand +// unless HideHelp is set to true) +var HelpFlag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} + +// Flag is a common interface related to parsing flags in cli. +// For more advanced flag parsing techniques, it is recomended that +// this interface be implemented. +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) + getName() string +} + +func flagSet(name string, flags []Flag) *flag.FlagSet { + set := flag.NewFlagSet(name, flag.ContinueOnError) + + for _, f := range flags { + f.Apply(set) + } + return set +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// GenericFlag is the flag type for types implementing Generic +type GenericFlag struct { + Name string + Value Generic + Usage string + EnvVar string +} + +// String returns the string representation of the generic flag to display the +// help text to the user (uses the String() method of the generic flag to show +// the value) +func (f GenericFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) +} + +// Apply takes the flagset and calls Set on the generic flag with the value +// provided by the user for parsing by the flag +func (f GenericFlag) Apply(set *flag.FlagSet) { + val := f.Value + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + val.Set(envVal) + break + } + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) +} + +func (f GenericFlag) getName() string { + return f.Name +} + +// StringSlice is an opaque type for []string to satisfy flag.Value +type StringSlice []string + +// Set appends the string value to the list of values +func (f *StringSlice) Set(value string) error { + *f = append(*f, value) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *StringSlice) String() string { + return fmt.Sprintf("%s", *f) +} + +// Value returns the slice of strings set by this flag +func (f *StringSlice) Value() []string { + return *f +} + +// StringSlice is a string flag that can be specified multiple times on the +// command-line +type StringSliceFlag struct { + Name string + Value *StringSlice + Usage string + EnvVar string +} + +// String returns the usage +func (f StringSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f StringSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + newVal.Set(s) + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &StringSlice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +func (f StringSliceFlag) getName() string { + return f.Name +} + +// StringSlice is an opaque type for []int to satisfy flag.Value +type IntSlice []int + +// Set parses the value into an integer and appends it to the list of values +func (f *IntSlice) Set(value string) error { + tmp, err := strconv.Atoi(value) + if err != nil { + return err + } else { + *f = append(*f, tmp) + } + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *IntSlice) String() string { + return fmt.Sprintf("%d", *f) +} + +// Value returns the slice of ints set by this flag +func (f *IntSlice) Value() []int { + return *f +} + +// IntSliceFlag is an int flag that can be specified multiple times on the +// command-line +type IntSliceFlag struct { + Name string + Value *IntSlice + Usage string + EnvVar string +} + +// String returns the usage +func (f IntSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f IntSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &IntSlice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +func (f IntSliceFlag) getName() string { + return f.Name +} + +// BoolFlag is a switch that defaults to false +type BoolFlag struct { + Name string + Usage string + EnvVar string +} + +// String returns a readable representation of this value (for usage defaults) +func (f BoolFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f BoolFlag) Apply(set *flag.FlagSet) { + val := false + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + break + } + } + } + + eachName(f.Name, func(name string) { + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolFlag) getName() string { + return f.Name +} + +// BoolTFlag this represents a boolean flag that is true by default, but can +// still be set to false by --some-flag=false +type BoolTFlag struct { + Name string + Usage string + EnvVar string +} + +// String returns a readable representation of this value (for usage defaults) +func (f BoolTFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f BoolTFlag) Apply(set *flag.FlagSet) { + val := true + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + break + } + } + } + } + + eachName(f.Name, func(name string) { + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolTFlag) getName() string { + return f.Name +} + +// StringFlag represents a flag that takes as string value +type StringFlag struct { + Name string + Value string + Usage string + EnvVar string +} + +// String returns the usage +func (f StringFlag) String() string { + var fmtString string + fmtString = "%s %v\t%v" + + if len(f.Value) > 0 { + fmtString = "%s \"%v\"\t%v" + } else { + fmtString = "%s %v\t%v" + } + + return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + f.Value = envVal + break + } + } + } + + eachName(f.Name, func(name string) { + set.String(name, f.Value, f.Usage) + }) +} + +func (f StringFlag) getName() string { + return f.Name +} + +// IntFlag is a flag that takes an integer +// Errors if the value provided cannot be parsed +type IntFlag struct { + Name string + Value int + Usage string + EnvVar string +} + +// String returns the usage +func (f IntFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f IntFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err == nil { + f.Value = int(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + set.Int(name, f.Value, f.Usage) + }) +} + +func (f IntFlag) getName() string { + return f.Name +} + +// DurationFlag is a flag that takes a duration specified in Go's duration +// format: https://golang.org/pkg/time/#ParseDuration +type DurationFlag struct { + Name string + Value time.Duration + Usage string + EnvVar string +} + +// String returns a readable representation of this value (for usage defaults) +func (f DurationFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f DurationFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValDuration, err := time.ParseDuration(envVal) + if err == nil { + f.Value = envValDuration + break + } + } + } + } + + eachName(f.Name, func(name string) { + set.Duration(name, f.Value, f.Usage) + }) +} + +func (f DurationFlag) getName() string { + return f.Name +} + +// Float64Flag is a flag that takes an float value +// Errors if the value provided cannot be parsed +type Float64Flag struct { + Name string + Value float64 + Usage string + EnvVar string +} + +// String returns the usage +func (f Float64Flag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f Float64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } + } + } + } + + eachName(f.Name, func(name string) { + set.Float64(name, f.Value, f.Usage) + }) +} + +func (f Float64Flag) getName() string { + return f.Name +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +func prefixedNames(fullName string) (prefixed string) { + parts := strings.Split(fullName, ",") + for i, name := range parts { + name = strings.Trim(name, " ") + prefixed += prefixFor(name) + name + if i < len(parts)-1 { + prefixed += ", " + } + } + return +} + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) + } + return str + envText +} diff --git a/vendor/github.com/codegangsta/cli/flag_test.go b/vendor/github.com/codegangsta/cli/flag_test.go new file mode 100644 index 0000000..3606102 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/flag_test.go @@ -0,0 +1,740 @@ +package cli + +import ( + "fmt" + "os" + "reflect" + "strings" + "testing" +) + +var boolFlagTests = []struct { + name string + expected string +}{ + {"help", "--help\t"}, + {"h", "-h\t"}, +} + +func TestBoolFlagHelpOutput(t *testing.T) { + + for _, test := range boolFlagTests { + flag := BoolFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +var stringFlagTests = []struct { + name string + value string + expected string +}{ + {"help", "", "--help \t"}, + {"h", "", "-h \t"}, + {"h", "", "-h \t"}, + {"test", "Something", "--test \"Something\"\t"}, +} + +func TestStringFlagHelpOutput(t *testing.T) { + + for _, test := range stringFlagTests { + flag := StringFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { + flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_FOO]") { + t.Errorf("%s does not end with [$APP_FOO]", output) + } + } +} + +var stringSliceFlagTests = []struct { + name string + value *StringSlice + expected string +}{ + {"help", func() *StringSlice { + s := &StringSlice{} + s.Set("") + return s + }(), "--help [--help option --help option]\t"}, + {"h", func() *StringSlice { + s := &StringSlice{} + s.Set("") + return s + }(), "-h [-h option -h option]\t"}, + {"h", func() *StringSlice { + s := &StringSlice{} + s.Set("") + return s + }(), "-h [-h option -h option]\t"}, + {"test", func() *StringSlice { + s := &StringSlice{} + s.Set("Something") + return s + }(), "--test [--test option --test option]\t"}, +} + +func TestStringSliceFlagHelpOutput(t *testing.T) { + + for _, test := range stringSliceFlagTests { + flag := StringSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { + flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_QWWX]") { + t.Errorf("%q does not end with [$APP_QWWX]", output) + } + } +} + +var intFlagTests = []struct { + name string + expected string +}{ + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, +} + +func TestIntFlagHelpOutput(t *testing.T) { + + for _, test := range intFlagTests { + flag := IntFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { + flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAR]") { + t.Errorf("%s does not end with [$APP_BAR]", output) + } + } +} + +var durationFlagTests = []struct { + name string + expected string +}{ + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, +} + +func TestDurationFlagHelpOutput(t *testing.T) { + + for _, test := range durationFlagTests { + flag := DurationFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { + flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAR]") { + t.Errorf("%s does not end with [$APP_BAR]", output) + } + } +} + +var intSliceFlagTests = []struct { + name string + value *IntSlice + expected string +}{ + {"help", &IntSlice{}, "--help [--help option --help option]\t"}, + {"h", &IntSlice{}, "-h [-h option -h option]\t"}, + {"h", &IntSlice{}, "-h [-h option -h option]\t"}, + {"test", func() *IntSlice { + i := &IntSlice{} + i.Set("9") + return i + }(), "--test [--test option --test option]\t"}, +} + +func TestIntSliceFlagHelpOutput(t *testing.T) { + + for _, test := range intSliceFlagTests { + flag := IntSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { + flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_SMURF]") { + t.Errorf("%q does not end with [$APP_SMURF]", output) + } + } +} + +var float64FlagTests = []struct { + name string + expected string +}{ + {"help", "--help \"0\"\t"}, + {"h", "-h \"0\"\t"}, +} + +func TestFloat64FlagHelpOutput(t *testing.T) { + + for _, test := range float64FlagTests { + flag := Float64Flag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { + flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAZ]") { + t.Errorf("%s does not end with [$APP_BAZ]", output) + } + } +} + +var genericFlagTests = []struct { + name string + value Generic + expected string +}{ + {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, + {"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"}, +} + +func TestGenericFlagHelpOutput(t *testing.T) { + + for _, test := range genericFlagTests { + flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { + flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_ZAP]") { + t.Errorf("%s does not end with [$APP_ZAP]", output) + } + } +} + +func TestParseMultiString(t *testing.T) { + (&App{ + Flags: []Flag{ + StringFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) { + if ctx.String("serve") != "10" { + t.Errorf("main name not set") + } + if ctx.String("s") != "10" { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10"}) +} + +func TestParseMultiStringFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_COUNT", "20") + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + }, + Action: func(ctx *Context) { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_COUNT", "20") + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + }, + Action: func(ctx *Context) { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSlice(t *testing.T) { + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiStringSliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt(t *testing.T) { + a := App{ + Flags: []Flag{ + IntFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) { + if ctx.Int("serve") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("s") != 10 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "-s", "10"}) +} + +func TestParseMultiIntFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiIntFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiIntSlice(t *testing.T) { + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiFloat64(t *testing.T) { + a := App{ + Flags: []Flag{ + Float64Flag{Name: "serve, s"}, + }, + Action: func(ctx *Context) { + if ctx.Float64("serve") != 10.2 { + t.Errorf("main name not set") + } + if ctx.Float64("s") != 10.2 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "-s", "10.2"}) +} + +func TestParseMultiFloat64FromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiFloat64FromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBool(t *testing.T) { + a := App{ + Flags: []Flag{ + BoolFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("serve") != true { + t.Errorf("main name not set") + } + if ctx.Bool("s") != true { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseMultiBoolFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "1") + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "1") + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolT(t *testing.T) { + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) { + if ctx.BoolT("serve") != true { + t.Errorf("main name not set") + } + if ctx.BoolT("s") != true { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *Context) { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolTFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *Context) { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +type Parser [2]string + +func (p *Parser) Set(value string) error { + parts := strings.Split(value, ",") + if len(parts) != 2 { + return fmt.Errorf("invalid format") + } + + (*p)[0] = parts[0] + (*p)[1] = parts[1] + + return nil +} + +func (p *Parser) String() string { + return fmt.Sprintf("%s,%s", p[0], p[1]) +} + +func TestParseGeneric(t *testing.T) { + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "-s", "10,20"}) +} + +func TestParseGenericFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SERVE", "20,30") + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseGenericFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "99,2000") + a := App{ + Flags: []Flag{ + GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + }, + Action: func(ctx *Context) { + if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { + t.Errorf("value not set from env") + } + }, + } + a.Run([]string{"run"}) +} diff --git a/vendor/github.com/codegangsta/cli/help.go b/vendor/github.com/codegangsta/cli/help.go new file mode 100644 index 0000000..a246f63 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/help.go @@ -0,0 +1,246 @@ +package cli + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + "text/template" +) + +// The text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} + +USAGE: + {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if .Version}} +VERSION: + {{.Version}} + {{end}}{{if len .Authors}} +AUTHOR(S): + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: + {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{end}}{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}} +` + +// The text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .Flags}} + +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{ end }} +` + +// The text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + +COMMANDS: + {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{if .Flags}} +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` + +var helpCommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowAppHelp(c) + } + }, +} + +var helpSubcommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowSubcommandHelp(c) + } + }, +} + +// Prints help for the App or Command +type helpPrinter func(w io.Writer, templ string, data interface{}) + +var HelpPrinter helpPrinter = printHelp + +// Prints version for the App +var VersionPrinter = printVersion + +func ShowAppHelp(c *Context) { + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) +} + +// Prints the list of subcommands as the default app completion method +func DefaultAppComplete(c *Context) { + for _, command := range c.App.Commands { + for _, name := range command.Names() { + fmt.Fprintln(c.App.Writer, name) + } + } +} + +// Prints help for the given command +func ShowCommandHelp(ctx *Context, command string) { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) + return + } + + for _, c := range ctx.App.Commands { + if c.HasName(command) { + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + return + } + } + + if ctx.App.CommandNotFound != nil { + ctx.App.CommandNotFound(ctx, command) + } else { + fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) + } +} + +// Prints help for the given subcommand +func ShowSubcommandHelp(c *Context) { + ShowCommandHelp(c, c.Command.Name) +} + +// Prints the version number of the App +func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { + fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) +} + +// Prints the lists of commands within a given context +func ShowCompletions(c *Context) { + a := c.App + if a != nil && a.BashComplete != nil { + a.BashComplete(c) + } +} + +// Prints the custom completions for a given command +func ShowCommandCompletions(ctx *Context, command string) { + c := ctx.App.Command(command) + if c != nil && c.BashComplete != nil { + c.BashComplete(ctx) + } +} + +func printHelp(out io.Writer, templ string, data interface{}) { + funcMap := template.FuncMap{ + "join": strings.Join, + } + + w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + err := t.Execute(w, data) + if err != nil { + panic(err) + } + w.Flush() +} + +func checkVersion(c *Context) bool { + found := false + if VersionFlag.Name != "" { + eachName(VersionFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkHelp(c *Context) bool { + found := false + if HelpFlag.Name != "" { + eachName(HelpFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkCommandHelp(c *Context, name string) bool { + if c.Bool("h") || c.Bool("help") { + ShowCommandHelp(c, name) + return true + } + + return false +} + +func checkSubcommandHelp(c *Context) bool { + if c.GlobalBool("h") || c.GlobalBool("help") { + ShowSubcommandHelp(c) + return true + } + + return false +} + +func checkCompletions(c *Context) bool { + if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { + ShowCompletions(c) + return true + } + + return false +} + +func checkCommandCompletions(c *Context, name string) bool { + if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { + ShowCommandCompletions(c, name) + return true + } + + return false +} diff --git a/vendor/github.com/codegangsta/cli/help_test.go b/vendor/github.com/codegangsta/cli/help_test.go new file mode 100644 index 0000000..350e263 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/help_test.go @@ -0,0 +1,94 @@ +package cli + +import ( + "bytes" + "testing" +) + +func Test_ShowAppHelp_NoAuthor(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") + } +} + +func Test_ShowAppHelp_NoVersion(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + app.Version = "" + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") + } +} + +func Test_Help_Custom_Flags(t *testing.T) { + oldFlag := HelpFlag + defer func() { + HelpFlag = oldFlag + }() + + HelpFlag = BoolFlag{ + Name: "help, x", + Usage: "show help", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, h"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("h") != true { + t.Errorf("custom help flag not set") + } + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-h"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} + +func Test_Version_Custom_Flags(t *testing.T) { + oldFlag := VersionFlag + defer func() { + VersionFlag = oldFlag + }() + + VersionFlag = BoolFlag{ + Name: "version, V", + Usage: "show version", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, v"}, + }, + Action: func(ctx *Context) { + if ctx.Bool("v") != true { + t.Errorf("custom version flag not set") + } + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-v"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} diff --git a/vendor/github.com/codegangsta/cli/helpers_test.go b/vendor/github.com/codegangsta/cli/helpers_test.go new file mode 100644 index 0000000..3ce8e93 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/helpers_test.go @@ -0,0 +1,19 @@ +package cli + +import ( + "reflect" + "testing" +) + +/* Test Helpers */ +func expect(t *testing.T, a interface{}, b interface{}) { + if a != b { + t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} diff --git a/vendor/github.com/google/go-github/github/activity.go b/vendor/github.com/google/go-github/github/activity.go new file mode 100644 index 0000000..355de62 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity.go @@ -0,0 +1,14 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +// ActivityService handles communication with the activity related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/activity/ +type ActivityService struct { + client *Client +} diff --git a/vendor/github.com/google/go-github/github/activity_events.go b/vendor/github.com/google/go-github/github/activity_events.go new file mode 100644 index 0000000..2a40d3e --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_events.go @@ -0,0 +1,305 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "time" +) + +// Event represents a GitHub event. +type Event struct { + Type *string `json:"type,omitempty"` + Public *bool `json:"public"` + RawPayload *json.RawMessage `json:"payload,omitempty"` + Repo *Repository `json:"repo,omitempty"` + Actor *User `json:"actor,omitempty"` + Org *Organization `json:"org,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + ID *string `json:"id,omitempty"` +} + +func (e Event) String() string { + return Stringify(e) +} + +// Payload returns the parsed event payload. For recognized event types +// (PushEvent), a value of the corresponding struct type will be returned. +func (e *Event) Payload() (payload interface{}) { + switch *e.Type { + case "PushEvent": + payload = &PushEvent{} + } + if err := json.Unmarshal(*e.RawPayload, &payload); err != nil { + panic(err.Error()) + } + return payload +} + +// PushEvent represents a git push to a GitHub repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/types/#pushevent +type PushEvent struct { + PushID *int `json:"push_id,omitempty"` + Head *string `json:"head,omitempty"` + Ref *string `json:"ref,omitempty"` + Size *int `json:"size,omitempty"` + Commits []PushEventCommit `json:"commits,omitempty"` + Repo *Repository `json:"repository,omitempty"` +} + +func (p PushEvent) String() string { + return Stringify(p) +} + +// PushEventCommit represents a git commit in a GitHub PushEvent. +type PushEventCommit struct { + SHA *string `json:"sha,omitempty"` + Message *string `json:"message,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + URL *string `json:"url,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Modified []string `json:"modified,omitempty"` +} + +func (p PushEventCommit) String() string { + return Stringify(p) +} + +//PullRequestEvent represents the payload delivered by PullRequestEvent webhook +type PullRequestEvent struct { + Action *string `json:"action,omitempty"` + Number *int `json:"number,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// IssueActivityEvent represents the payload delivered by Issue webhook +type IssueActivityEvent struct { + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// IssueCommentEvent represents the payload delivered by IssueComment webhook +// +// This webhook also gets fired for comments on pull requests +type IssueCommentEvent struct { + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Comment *IssueComment `json:"comment,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// ListEvents drinks from the firehose of all public events across GitHub. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events +func (s *ActivityService) ListEvents(opt *ListOptions) ([]Event, *Response, error) { + u, err := addOptions("events", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListRepositoryEvents lists events for a repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-repository-events +func (s *ActivityService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListIssueEventsForRepository lists issue events for a repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-issue-events-for-a-repository +func (s *ActivityService) ListIssueEventsForRepository(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsForRepoNetwork lists public events for a network of repositories. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories +func (s *ActivityService) ListEventsForRepoNetwork(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("networks/%v/%v/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsForOrganization lists public events for an organization. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-an-organization +func (s *ActivityService) ListEventsForOrganization(org string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("orgs/%v/events", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-performed-by-a-user +func (s *ActivityService) ListEventsPerformedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/events/public", user) + } else { + u = fmt.Sprintf("users/%v/events", user) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsReceivedByUser lists the events received by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received +func (s *ActivityService) ListEventsReceivedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/received_events/public", user) + } else { + u = fmt.Sprintf("users/%v/received_events", user) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListUserEventsForOrganization provides the user’s organization dashboard. You +// must be authenticated as the user to view this. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-for-an-organization +func (s *ActivityService) ListUserEventsForOrganization(org, user string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("users/%v/events/orgs/%v", user, org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} diff --git a/vendor/github.com/google/go-github/github/activity_events_test.go b/vendor/github.com/google/go-github/github/activity_events_test.go new file mode 100644 index 0000000..8d1d1ff --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_events_test.go @@ -0,0 +1,305 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestActivityService_ListEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEvents(opt) + if err != nil { + t.Errorf("Activities.ListEvents returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListRepositoryEvents("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListRepositoryEvents returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListRepositoryEvents("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListIssueEventsForRepository(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListIssueEventsForRepository("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListIssueEventsForRepository returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListIssueEventsForRepository returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListIssueEventsForRepository_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListIssueEventsForRepository("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForRepoNetwork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/networks/o/r/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsForRepoNetwork("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListEventsForRepoNetwork returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForRepoNetwork returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForRepoNetwork_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListEventsForRepoNetwork("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForOrganization(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsForOrganization("o", opt) + if err != nil { + t.Errorf("Activities.ListEventsForOrganization returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForOrganization_invalidOrg(t *testing.T) { + _, _, err := client.Activity.ListEventsForOrganization("%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsPerformedByUser_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsPerformedByUser("u", false, opt) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_publicOnly(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsPerformedByUser("u", true, nil) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListEventsPerformedByUser("%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsReceivedByUser_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListEventsReceivedByUser("u", false, opt) + if err != nil { + t.Errorf("Events.ListReceivedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListReceivedUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsReceivedByUser_publicOnly(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsReceivedByUser("u", true, nil) + if err != nil { + t.Errorf("Events.ListReceivedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListReceivedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsReceivedByUser_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListEventsReceivedByUser("%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListUserEventsForOrganization(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + opt := &ListOptions{Page: 2} + events, _, err := client.Activity.ListUserEventsForOrganization("o", "u", opt) + if err != nil { + t.Errorf("Activities.ListUserEventsForOrganization returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListUserEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivity_EventPayload_typed(t *testing.T) { + raw := []byte(`{"type": "PushEvent","payload":{"push_id": 1}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := &PushEvent{PushID: Int(1)} + if !reflect.DeepEqual(event.Payload(), want) { + t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want) + } +} + +// TestEvent_Payload_untyped checks that unrecognized events are parsed to an +// interface{} value (instead of being discarded or throwing an error), for +// forward compatibility with new event types. +func TestActivity_EventPayload_untyped(t *testing.T) { + raw := []byte(`{"type": "UnrecognizedEvent","payload":{"field": "val"}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := map[string]interface{}{"field": "val"} + if !reflect.DeepEqual(event.Payload(), want) { + t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want) + } +} diff --git a/vendor/github.com/google/go-github/github/activity_notifications.go b/vendor/github.com/google/go-github/github/activity_notifications.go new file mode 100644 index 0000000..290b954 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_notifications.go @@ -0,0 +1,225 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// Notification identifies a GitHub notification for a user. +type Notification struct { + ID *string `json:"id,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Subject *NotificationSubject `json:"subject,omitempty"` + + // Reason identifies the event that triggered the notification. + // + // GitHub API Docs: https://developer.github.com/v3/activity/notifications/#notification-reasons + Reason *string `json:"reason,omitempty"` + + Unread *bool `json:"unread,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + LastReadAt *time.Time `json:"last_read_at,omitempty"` + URL *string `json:"url,omitempty"` +} + +// NotificationSubject identifies the subject of a notification. +type NotificationSubject struct { + Title *string `json:"title,omitempty"` + URL *string `json:"url,omitempty"` + LatestCommentURL *string `json:"latest_comment_url,omitempty"` + Type *string `json:"type,omitempty"` +} + +// NotificationListOptions specifies the optional parameters to the +// ActivityService.ListNotifications method. +type NotificationListOptions struct { + All bool `url:"all,omitempty"` + Participating bool `url:"participating,omitempty"` + Since time.Time `url:"since,omitempty"` + Before time.Time `url:"before,omitempty"` +} + +// ListNotifications lists all notifications for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications +func (s *ActivityService) ListNotifications(opt *NotificationListOptions) ([]Notification, *Response, error) { + u := fmt.Sprintf("notifications") + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var notifications []Notification + resp, err := s.client.Do(req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, err +} + +// ListRepositoryNotifications lists all notifications in a given repository +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository +func (s *ActivityService) ListRepositoryNotifications(owner, repo string, opt *NotificationListOptions) ([]Notification, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var notifications []Notification + resp, err := s.client.Do(req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, err +} + +type markReadOptions struct { + LastReadAt time.Time `url:"last_read_at,omitempty"` +} + +// MarkNotificationsRead marks all notifications up to lastRead as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-as-read +func (s *ActivityService) MarkNotificationsRead(lastRead time.Time) (*Response, error) { + u := fmt.Sprintf("notifications") + u, err := addOptions(u, markReadOptions{lastRead}) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// MarkRepositoryNotificationsRead marks all notifications up to lastRead in +// the specified repository as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository +func (s *ActivityService) MarkRepositoryNotificationsRead(owner, repo string, lastRead time.Time) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + u, err := addOptions(u, markReadOptions{lastRead}) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// GetThread gets the specified notification thread. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread +func (s *ActivityService) GetThread(id string) (*Notification, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + notification := new(Notification) + resp, err := s.client.Do(req, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} + +// MarkThreadRead marks the specified thread as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read +func (s *ActivityService) MarkThreadRead(id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// GetThreadSubscription checks to see if the authenticated user is subscribed +// to a thread. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription +func (s *ActivityService) GetThreadSubscription(id string) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// SetThreadSubscription sets the subscription for the specified thread for the +// authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription +func (s *ActivityService) SetThreadSubscription(id string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// DeleteThreadSubscription deletes the subscription for the specified thread +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription +func (s *ActivityService) DeleteThreadSubscription(id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_notifications_test.go b/vendor/github.com/google/go-github/github/activity_notifications_test.go new file mode 100644 index 0000000..f72ee41 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_notifications_test.go @@ -0,0 +1,205 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestActivityService_ListNotification(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "all": "true", + "participating": "true", + "since": "2006-01-02T15:04:05Z", + "before": "2007-03-04T15:04:05Z", + }) + + fmt.Fprint(w, `[{"id":"1", "subject":{"title":"t"}}]`) + }) + + opt := &NotificationListOptions{ + All: true, + Participating: true, + Since: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), + Before: time.Date(2007, 03, 04, 15, 04, 05, 0, time.UTC), + } + notifications, _, err := client.Activity.ListNotifications(opt) + if err != nil { + t.Errorf("Activity.ListNotifications returned error: %v", err) + } + + want := []Notification{{ID: String("1"), Subject: &NotificationSubject{Title: String("t")}}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_ListRepositoryNotification(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"}]`) + }) + + notifications, _, err := client.Activity.ListRepositoryNotifications("o", "r", nil) + if err != nil { + t.Errorf("Activity.ListRepositoryNotifications returned error: %v", err) + } + + want := []Notification{{ID: String("1")}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListRepositoryNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_MarkNotificationsRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testFormValues(t, r, values{ + "last_read_at": "2006-01-02T15:04:05Z", + }) + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkNotificationsRead(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_MarkRepositoryNotificationsRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testFormValues(t, r, values{ + "last_read_at": "2006-01-02T15:04:05Z", + }) + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkRepositoryNotificationsRead("o", "r", time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkRepositoryNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_GetThread(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":"1"}`) + }) + + notification, _, err := client.Activity.GetThread("1") + if err != nil { + t.Errorf("Activity.GetThread returned error: %v", err) + } + + want := &Notification{ID: String("1")} + if !reflect.DeepEqual(notification, want) { + t.Errorf("Activity.GetThread returned %+v, want %+v", notification, want) + } +} + +func TestActivityService_MarkThreadRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkThreadRead("1") + if err != nil { + t.Errorf("Activity.MarkThreadRead returned error: %v", err) + } +} + +func TestActivityService_GetThreadSubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetThreadSubscription("1") + if err != nil { + t.Errorf("Activity.GetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_SetThreadSubscription(t *testing.T) { + setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetThreadSubscription("1", input) + if err != nil { + t.Errorf("Activity.SetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteThreadSubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteThreadSubscription("1") + if err != nil { + t.Errorf("Activity.DeleteThreadSubscription returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/activity_star.go b/vendor/github.com/google/go-github/github/activity_star.go new file mode 100644 index 0000000..fac4f41 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_star.go @@ -0,0 +1,123 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// StarredRepository is returned by ListStarred. +type StarredRepository struct { + StarredAt *Timestamp `json:"starred_at,omitempty"` + Repository *Repository `json:"repo,omitempty"` +} + +// ListStargazers lists people who have starred the specified repo. +// +// GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers +func (s *ActivityService) ListStargazers(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/stargazers", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + stargazers := new([]User) + resp, err := s.client.Do(req, stargazers) + if err != nil { + return nil, resp, err + } + + return *stargazers, resp, err +} + +// ActivityListStarredOptions specifies the optional parameters to the +// ActivityService.ListStarred method. +type ActivityListStarredOptions struct { + // How to sort the repository list. Possible values are: created, updated, + // pushed, full_name. Default is "full_name". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Possible values are: asc, desc. + // Default is "asc" when sort is "full_name", otherwise default is "desc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// ListStarred lists all the repos starred by a user. Passing the empty string +// will list the starred repositories for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/activity/starring/#list-repositories-being-starred +func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]StarredRepository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/starred", user) + } else { + u = "user/starred" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeStarringPreview) + + repos := new([]StarredRepository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// IsStarred checks if a repository is starred by authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository +func (s *ActivityService) IsStarred(owner, repo string) (bool, *Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Star a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository +func (s *ActivityService) Star(owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// Unstar a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository +func (s *ActivityService) Unstar(owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_star_test.go b/vendor/github.com/google/go-github/github/activity_star_test.go new file mode 100644 index 0000000..eb2c405 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_star_test.go @@ -0,0 +1,170 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestActivityService_ListStargazers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stargazers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + stargazers, _, err := client.Activity.ListStargazers("o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListStargazers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(stargazers, want) { + t.Errorf("Activity.ListStargazers returned %+v, want %+v", stargazers, want) + } +} + +func TestActivityService_ListStarred_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeStarringPreview) + fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":1}}]`) + }) + + repos, _, err := client.Activity.ListStarred("", nil) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(1)}}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeStarringPreview) + testFormValues(t, r, values{ + "sort": "created", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":2}}]`) + }) + + opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}} + repos, _, err := client.Activity.ListStarred("u", opt) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(2)}}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListStarred("%", nil) + testURLParseError(t, err) +} + +func TestActivityService_IsStarred_hasStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, _, err := client.Activity.IsStarred("o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_noStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, _, err := client.Activity.IsStarred("o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_invalidID(t *testing.T) { + _, _, err := client.Activity.IsStarred("%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Star(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Activity.Star("o", "r") + if err != nil { + t.Errorf("Activity.Star returned error: %v", err) + } +} + +func TestActivityService_Star_invalidID(t *testing.T) { + _, err := client.Activity.Star("%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Unstar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Activity.Unstar("o", "r") + if err != nil { + t.Errorf("Activity.Unstar returned error: %v", err) + } +} + +func TestActivityService_Unstar_invalidID(t *testing.T) { + _, err := client.Activity.Unstar("%", "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/activity_watching.go b/vendor/github.com/google/go-github/github/activity_watching.go new file mode 100644 index 0000000..150cf66 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_watching.go @@ -0,0 +1,131 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Subscription identifies a repository or thread subscription. +type Subscription struct { + Subscribed *bool `json:"subscribed,omitempty"` + Ignored *bool `json:"ignored,omitempty"` + Reason *string `json:"reason,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + URL *string `json:"url,omitempty"` + + // only populated for repository subscriptions + RepositoryURL *string `json:"repository_url,omitempty"` + + // only populated for thread subscriptions + ThreadURL *string `json:"thread_url,omitempty"` +} + +// ListWatchers lists watchers of a particular repo. +// +// GitHub API Docs: http://developer.github.com/v3/activity/watching/#list-watchers +func (s *ActivityService) ListWatchers(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscribers", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + watchers := new([]User) + resp, err := s.client.Do(req, watchers) + if err != nil { + return nil, resp, err + } + + return *watchers, resp, err +} + +// ListWatched lists the repositories the specified user is watching. Passing +// the empty string will fetch watched repos for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched +func (s *ActivityService) ListWatched(user string) ([]Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/subscriptions", user) + } else { + u = "user/subscriptions" + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + watched := new([]Repository) + resp, err := s.client.Do(req, watched) + if err != nil { + return nil, resp, err + } + + return *watched, resp, err +} + +// GetRepositorySubscription returns the subscription for the specified +// repository for the authenticated user. If the authenticated user is not +// watching the repository, a nil Subscription is returned. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#get-a-repository-subscription +func (s *ActivityService) GetRepositorySubscription(owner, repo string) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + // if it's just a 404, don't return that as an error + _, err = parseBoolResponse(err) + return nil, resp, err + } + + return sub, resp, err +} + +// SetRepositorySubscription sets the subscription for the specified repository +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#set-a-repository-subscription +func (s *ActivityService) SetRepositorySubscription(owner, repo string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// DeleteRepositorySubscription deletes the subscription for the specified +// repository for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription +func (s *ActivityService) DeleteRepositorySubscription(owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/activity_watching_test.go b/vendor/github.com/google/go-github/github/activity_watching_test.go new file mode 100644 index 0000000..8046ee2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/activity_watching_test.go @@ -0,0 +1,177 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestActivityService_ListWatchers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscribers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + watchers, _, err := client.Activity.ListWatchers("o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListWatchers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(watchers, want) { + t.Errorf("Activity.ListWatchers returned %+v, want %+v", watchers, want) + } +} + +func TestActivityService_ListWatched_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched("") + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_ListWatched_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched("u") + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_GetRepositorySubscription_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + sub, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + var want *Subscription + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusBadRequest) + }) + + _, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } +} + +func TestActivityService_SetRepositorySubscription(t *testing.T) { + setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetRepositorySubscription("o", "r", input) + if err != nil { + t.Errorf("Activity.SetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteRepositorySubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.DeleteRepositorySubscription returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/doc.go b/vendor/github.com/google/go-github/github/doc.go new file mode 100644 index 0000000..b4ac8e6 --- /dev/null +++ b/vendor/github.com/google/go-github/github/doc.go @@ -0,0 +1,126 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package github provides a client for using the GitHub API. + +Construct a new GitHub client, then use the various services on the client to +access different parts of the GitHub API. For example: + + client := github.NewClient(nil) + + // list all organizations for user "willnorris" + orgs, _, err := client.Organizations.List("willnorris", nil) + +Set optional parameters for an API method by passing an Options object. + + // list recently updated repositories for org "github" + opt := &github.RepositoryListByOrgOptions{Sort: "updated"} + repos, _, err := client.Repositories.ListByOrg("github", opt) + +The services of a client divide the API into logical chunks and correspond to +the structure of the GitHub API documentation at +http://developer.github.com/v3/. + +Authentication + +The go-github library does not directly handle authentication. Instead, when +creating a new client, pass an http.Client that can handle authentication for +you. The easiest and recommended way to do this is using the golang.org/x/oauth2 +library, but you can always use any other library that provides an http.Client. +If you have an OAuth2 access token (for example, a personal API token), you can +use it with the oauth2 library using: + + import "golang.org/x/oauth2" + + func main() { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: "... your access token ..."}, + ) + tc := oauth2.NewClient(oauth2.NoContext, ts) + + client := github.NewClient(tc) + + // list all repositories for the authenticated user + repos, _, err := client.Repositories.List("", nil) + } + +Note that when using an authenticated Client, all calls made by the client will +include the specified OAuth token. Therefore, authenticated clients should +almost never be shared between different users. + +Rate Limiting + +GitHub imposes a rate limit on all API clients. Unauthenticated clients are +limited to 60 requests per hour, while authenticated clients can make up to +5,000 requests per hour. To receive the higher rate limit when making calls +that are not issued on behalf of a user, use the +UnauthenticatedRateLimitedTransport. + +The Rate field on a client tracks the rate limit information based on the most +recent API call. This is updated on every call, but may be out of date if it's +been some time since the last API call and other clients have made subsequent +requests since then. You can always call RateLimit() directly to get the most +up-to-date rate limit data for the client. + +Learn more about GitHub rate limiting at +http://developer.github.com/v3/#rate-limiting. + +Conditional Requests + +The GitHub API has good support for conditional requests which will help +prevent you from burning through your rate limit, as well as help speed up your +application. go-github does not handle conditional requests directly, but is +instead designed to work with a caching http.Transport. We recommend using +https://github.com/gregjones/httpcache, which can be used in conjuction with +https://github.com/sourcegraph/apiproxy to provide additional flexibility and +control of caching rules. + +Learn more about GitHub conditional requests at +https://developer.github.com/v3/#conditional-requests. + +Creating and Updating Resources + +All structs for GitHub resources use pointer values for all non-repeated fields. +This allows distinguishing between unset fields and those set to a zero-value. +Helper functions have been provided to easily create these pointers for string, +bool, and int values. For example: + + // create a new private repository named "foo" + repo := &github.Repository{ + Name: github.String("foo"), + Private: github.Bool(true), + } + client.Repositories.Create("", repo) + +Users who have worked with protocol buffers should find this pattern familiar. + +Pagination + +All requests for resource collections (repos, pull requests, issues, etc) +support pagination. Pagination options are described in the +ListOptions struct and passed to the list methods directly or as an +embedded type of a more specific list options struct (for example +PullRequestListOptions). Pages information is available via Response struct. + + opt := &github.RepositoryListByOrgOptions{ + ListOptions: github.ListOptions{PerPage: 10}, + } + // get all pages of results + var allRepos []github.Repository + for { + repos, resp, err := client.Repositories.ListByOrg("github", opt) + if err != nil { + return err + } + allRepos = append(allRepos, repos...) + if resp.NextPage == 0 { + break + } + opt.ListOptions.Page = resp.NextPage + } + +*/ +package github diff --git a/vendor/github.com/google/go-github/github/gists.go b/vendor/github.com/google/go-github/github/gists.go new file mode 100644 index 0000000..a662d35 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists.go @@ -0,0 +1,281 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// GistsService handles communication with the Gist related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/gists/ +type GistsService struct { + client *Client +} + +// Gist represents a GitHub's gist. +type Gist struct { + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + Public *bool `json:"public,omitempty"` + Owner *User `json:"owner,omitempty"` + Files map[GistFilename]GistFile `json:"files,omitempty"` + Comments *int `json:"comments,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GitPullURL *string `json:"git_pull_url,omitempty"` + GitPushURL *string `json:"git_push_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (g Gist) String() string { + return Stringify(g) +} + +// GistFilename represents filename on a gist. +type GistFilename string + +// GistFile represents a file on a gist. +type GistFile struct { + Size *int `json:"size,omitempty"` + Filename *string `json:"filename,omitempty"` + RawURL *string `json:"raw_url,omitempty"` + Content *string `json:"content,omitempty"` +} + +func (g GistFile) String() string { + return Stringify(g) +} + +// GistListOptions specifies the optional parameters to the +// GistsService.List, GistsService.ListAll, and GistsService.ListStarred methods. +type GistListOptions struct { + // Since filters Gists by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// List gists for a user. Passing the empty string will list +// all public gists if called anonymously. However, if the call +// is authenticated, it will returns all gists for the authenticated +// user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) List(user string, opt *GistListOptions) ([]Gist, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/gists", user) + } else { + u = "gists" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, err +} + +// ListAll lists all public gists. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListAll(opt *GistListOptions) ([]Gist, *Response, error) { + u, err := addOptions("gists/public", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, err +} + +// ListStarred lists starred gists of authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListStarred(opt *GistListOptions) ([]Gist, *Response, error) { + u, err := addOptions("gists/starred", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, err +} + +// Get a single gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#get-a-single-gist +func (s *GistsService) Get(id string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + gist := new(Gist) + resp, err := s.client.Do(req, gist) + if err != nil { + return nil, resp, err + } + + return gist, resp, err +} + +// GetRevision gets a specific revision of a gist. +// +// GitHub API docs: https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist +func (s *GistsService) GetRevision(id, sha string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v/%v", id, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + gist := new(Gist) + resp, err := s.client.Do(req, gist) + if err != nil { + return nil, resp, err + } + + return gist, resp, err +} + +// Create a gist for authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist +func (s *GistsService) Create(gist *Gist) (*Gist, *Response, error) { + u := "gists" + req, err := s.client.NewRequest("POST", u, gist) + if err != nil { + return nil, nil, err + } + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// Edit a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#edit-a-gist +func (s *GistsService) Edit(id string, gist *Gist) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("PATCH", u, gist) + if err != nil { + return nil, nil, err + } + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// Delete a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#delete-a-gist +func (s *GistsService) Delete(id string) (*Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// Star a gist on behalf of authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#star-a-gist +func (s *GistsService) Star(id string) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// Unstar a gist on a behalf of authenticated user. +// +// Github API docs: http://developer.github.com/v3/gists/#unstar-a-gist +func (s *GistsService) Unstar(id string) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// IsStarred checks if a gist is starred by authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#check-if-a-gist-is-starred +func (s *GistsService) IsStarred(id string) (bool, *Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Fork a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#fork-a-gist +func (s *GistsService) Fork(id string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v/forks", id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} diff --git a/vendor/github.com/google/go-github/github/gists_comments.go b/vendor/github.com/google/go-github/github/gists_comments.go new file mode 100644 index 0000000..c5c21bd --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_comments.go @@ -0,0 +1,118 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// GistComment represents a Gist comment. +type GistComment struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +func (g GistComment) String() string { + return Stringify(g) +} + +// ListComments lists all comments for a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#list-comments-on-a-gist +func (s *GistsService) ListComments(gistID string, opt *ListOptions) ([]GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comments := new([]GistComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment retrieves a single comment from a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#get-a-single-comment +func (s *GistsService) GetComment(gistID string, commentID int) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// CreateComment creates a comment for a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#create-a-comment +func (s *GistsService) CreateComment(gistID string, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment edits an existing gist comment. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#edit-a-comment +func (s *GistsService) EditComment(gistID string, commentID int, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a gist comment. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#delete-a-comment +func (s *GistsService) DeleteComment(gistID string, commentID int) (*Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/gists_comments_test.go b/vendor/github.com/google/go-github/github/gists_comments_test.go new file mode 100644 index 0000000..b2bbf23 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_comments_test.go @@ -0,0 +1,155 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGistsService_ListComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id": 1}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Gists.ListComments("1", opt) + + if err != nil { + t.Errorf("Gists.Comments returned error: %v", err) + } + + want := []GistComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Gists.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestGistsService_ListComments_invalidID(t *testing.T) { + _, _, err := client.Gists.ListComments("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": 1}`) + }) + + comment, _, err := client.Gists.GetComment("1", 2) + + if err != nil { + t.Errorf("Gists.GetComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_GetComment_invalidID(t *testing.T) { + _, _, err := client.Gists.GetComment("%", 1) + testURLParseError(t, err) +} + +func TestGistsService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &GistComment{ID: Int(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Gists.CreateComment("1", input) + if err != nil { + t.Errorf("Gists.CreateComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_CreateComment_invalidID(t *testing.T) { + _, _, err := client.Gists.CreateComment("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &GistComment{ID: Int(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Gists.EditComment("1", 2, input) + if err != nil { + t.Errorf("Gists.EditComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_EditComment_invalidID(t *testing.T) { + _, _, err := client.Gists.EditComment("%", 1, nil) + testURLParseError(t, err) +} + +func TestGistsService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.DeleteComment("1", 2) + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_DeleteComment_invalidID(t *testing.T) { + _, err := client.Gists.DeleteComment("%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/gists_test.go b/vendor/github.com/google/go-github/github/gists_test.go new file mode 100644 index 0000000..5731201 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gists_test.go @@ -0,0 +1,411 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestGistsService_List_specifiedUser(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/users/u/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.List("u", opt) + + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + gists, _, err := client.Gists.List("", nil) + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_invalidUser(t *testing.T) { + _, _, err := client.Gists.List("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_ListAll(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.ListAll(opt) + + if err != nil { + t.Errorf("Gists.ListAll returned error: %v", err) + } + + want := []Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListAll returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_ListStarred(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, _, err := client.Gists.ListStarred(opt) + + if err != nil { + t.Errorf("Gists.ListStarred returned error: %v", err) + } + + want := []Gist{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListStarred returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": "1"}`) + }) + + gist, _, err := client.Gists.Get("1") + + if err != nil { + t.Errorf("Gists.Get returned error: %v", err) + } + + want := &Gist{ID: String("1")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Get_invalidID(t *testing.T) { + _, _, err := client.Gists.Get("%") + testURLParseError(t, err) +} + +func TestGistsService_GetRevision(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": "1"}`) + }) + + gist, _, err := client.Gists.GetRevision("1", "s") + + if err != nil { + t.Errorf("Gists.Get returned error: %v", err) + } + + want := &Gist{ID: String("1")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_GetRevision_invalidID(t *testing.T) { + _, _, err := client.Gists.GetRevision("%", "%") + testURLParseError(t, err) +} + +func TestGistsService_Create(t *testing.T) { + setup() + defer teardown() + + input := &Gist{ + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Content: String("Gist file content")}, + }, + } + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, + ` + { + "id": "1", + "description": "Gist description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + } + } + }`) + }) + + gist, _, err := client.Gists.Create(input) + if err != nil { + t.Errorf("Gists.Create returned error: %v", err) + } + + want := &Gist{ + ID: String("1"), + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("test.txt")}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Create returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &Gist{ + Description: String("New description"), + Files: map[GistFilename]GistFile{ + "new.txt": {Content: String("new file content")}, + }, + } + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, + ` + { + "id": "1", + "description": "new description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + }, + "new.txt": { + "filename": "new.txt" + } + } + }`) + }) + + gist, _, err := client.Gists.Edit("1", input) + if err != nil { + t.Errorf("Gists.Edit returned error: %v", err) + } + + want := &Gist{ + ID: String("1"), + Description: String("new description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("test.txt")}, + "new.txt": {Filename: String("new.txt")}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Edit returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit_invalidID(t *testing.T) { + _, _, err := client.Gists.Edit("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.Delete("1") + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_Delete_invalidID(t *testing.T) { + _, err := client.Gists.Delete("%") + testURLParseError(t, err) +} + +func TestGistsService_Star(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Gists.Star("1") + if err != nil { + t.Errorf("Gists.Star returned error: %v", err) + } +} + +func TestGistsService_Star_invalidID(t *testing.T) { + _, err := client.Gists.Star("%") + testURLParseError(t, err) +} + +func TestGistsService_Unstar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.Unstar("1") + if err != nil { + t.Errorf("Gists.Unstar returned error: %v", err) + } +} + +func TestGistsService_Unstar_invalidID(t *testing.T) { + _, err := client.Gists.Unstar("%") + testURLParseError(t, err) +} + +func TestGistsService_IsStarred_hasStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, _, err := client.Gists.IsStarred("1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_IsStarred_noStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, _, err := client.Gists.IsStarred("1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_IsStarred_invalidID(t *testing.T) { + _, _, err := client.Gists.IsStarred("%") + testURLParseError(t, err) +} + +func TestGistsService_Fork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id": "2"}`) + }) + + gist, _, err := client.Gists.Fork("1") + + if err != nil { + t.Errorf("Gists.Fork returned error: %v", err) + } + + want := &Gist{ID: String("2")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Fork returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Fork_invalidID(t *testing.T) { + _, _, err := client.Gists.Fork("%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git.go b/vendor/github.com/google/go-github/github/git.go new file mode 100644 index 0000000..a80e55b --- /dev/null +++ b/vendor/github.com/google/go-github/github/git.go @@ -0,0 +1,14 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +// GitService handles communication with the git data related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/git/ +type GitService struct { + client *Client +} diff --git a/vendor/github.com/google/go-github/github/git_blobs.go b/vendor/github.com/google/go-github/github/git_blobs.go new file mode 100644 index 0000000..133780b --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_blobs.go @@ -0,0 +1,47 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Blob represents a blob object. +type Blob struct { + Content *string `json:"content,omitempty"` + Encoding *string `json:"encoding,omitempty"` + SHA *string `json:"sha,omitempty"` + Size *int `json:"size,omitempty"` + URL *string `json:"url,omitempty"` +} + +// GetBlob fetchs a blob from a repo given a SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/blobs/#get-a-blob +func (s *GitService) GetBlob(owner string, repo string, sha string) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + blob := new(Blob) + resp, err := s.client.Do(req, blob) + return blob, resp, err +} + +// CreateBlob creates a blob object. +// +// GitHub API docs: http://developer.github.com/v3/git/blobs/#create-a-blob +func (s *GitService) CreateBlob(owner string, repo string, blob *Blob) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs", owner, repo) + req, err := s.client.NewRequest("POST", u, blob) + if err != nil { + return nil, nil, err + } + + t := new(Blob) + resp, err := s.client.Do(req, t) + return t, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_blobs_test.go b/vendor/github.com/google/go-github/github/git_blobs_test.go new file mode 100644 index 0000000..994549f --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_blobs_test.go @@ -0,0 +1,92 @@ +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetBlob(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/blobs/s", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content" + }`) + }) + + blob, _, err := client.Git.GetBlob("o", "r", "s") + if err != nil { + t.Errorf("Git.GetBlob returned error: %v", err) + } + + want := Blob{ + SHA: String("s"), + Content: String("blob content"), + } + + if !reflect.DeepEqual(*blob, want) { + t.Errorf("Blob.Get returned %+v, want %+v", *blob, want) + } +} + +func TestGitService_GetBlob_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetBlob("%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_CreateBlob(t *testing.T) { + setup() + defer teardown() + + input := &Blob{ + SHA: String("s"), + Content: String("blob content"), + Encoding: String("utf-8"), + Size: Int(12), + } + + mux.HandleFunc("/repos/o/r/git/blobs", func(w http.ResponseWriter, r *http.Request) { + v := new(Blob) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := input + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateBlob request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content", + "encoding": "utf-8", + "size": 12 + }`) + }) + + blob, _, err := client.Git.CreateBlob("o", "r", input) + if err != nil { + t.Errorf("Git.CreateBlob returned error: %v", err) + } + + want := input + + if !reflect.DeepEqual(*blob, *want) { + t.Errorf("Git.CreateBlob returned %+v, want %+v", *blob, *want) + } +} + +func TestGitService_CreateBlob_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateBlob("%", "%", &Blob{}) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git_commits.go b/vendor/github.com/google/go-github/github/git_commits.go new file mode 100644 index 0000000..6584b77 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_commits.go @@ -0,0 +1,112 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// Commit represents a GitHub commit. +type Commit struct { + SHA *string `json:"sha,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *Tree `json:"tree,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Stats *CommitStats `json:"stats,omitempty"` + URL *string `json:"url,omitempty"` + + // CommentCount is the number of GitHub comments on the commit. This + // is only populated for requests that fetch GitHub data like + // Pulls.ListCommits, Repositories.ListCommits, etc. + CommentCount *int `json:"comment_count,omitempty"` +} + +func (c Commit) String() string { + return Stringify(c) +} + +// CommitAuthor represents the author or committer of a commit. The commit +// author may not correspond to a GitHub User. +type CommitAuthor struct { + Date *time.Time `json:"date,omitempty"` + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` +} + +func (c CommitAuthor) String() string { + return Stringify(c) +} + +// GetCommit fetchs the Commit object for a given SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/commits/#get-a-commit +func (s *GitService) GetCommit(owner string, repo string, sha string) (*Commit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/commits/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// createCommit represents the body of a CreateCommit request. +type createCommit struct { + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *string `json:"tree,omitempty"` + Parents []string `json:"parents,omitempty"` +} + +// CreateCommit creates a new commit in a repository. +// +// The commit.Committer is optional and will be filled with the commit.Author +// data if omitted. If the commit.Author is omitted, it will be filled in with +// the authenticated user’s information and the current date. +// +// GitHub API docs: http://developer.github.com/v3/git/commits/#create-a-commit +func (s *GitService) CreateCommit(owner string, repo string, commit *Commit) (*Commit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo) + + body := &createCommit{} + if commit != nil { + parents := make([]string, len(commit.Parents)) + for i, parent := range commit.Parents { + parents[i] = *parent.SHA + } + + body = &createCommit{ + Author: commit.Author, + Committer: commit.Committer, + Message: commit.Message, + Tree: commit.Tree.SHA, + Parents: parents, + } + } + + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_commits_test.go b/vendor/github.com/google/go-github/github/git_commits_test.go new file mode 100644 index 0000000..538f523 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_commits_test.go @@ -0,0 +1,82 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetCommit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"sha":"s","message":"m","author":{"name":"n"}}`) + }) + + commit, _, err := client.Git.GetCommit("o", "r", "s") + if err != nil { + t.Errorf("Git.GetCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s"), Message: String("m"), Author: &CommitAuthor{Name: String("n")}} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.GetCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_GetCommit_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetCommit("%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_CreateCommit(t *testing.T) { + setup() + defer teardown() + + input := &Commit{ + Message: String("m"), + Tree: &Tree{SHA: String("t")}, + Parents: []Commit{{SHA: String("p")}}, + } + + mux.HandleFunc("/repos/o/r/git/commits", func(w http.ResponseWriter, r *http.Request) { + v := new(createCommit) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := &createCommit{ + Message: input.Message, + Tree: String("t"), + Parents: []string{"p"}, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"sha":"s"}`) + }) + + commit, _, err := client.Git.CreateCommit("o", "r", input) + if err != nil { + t.Errorf("Git.CreateCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.CreateCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_CreateCommit_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateCommit("%", "%", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/git_refs.go b/vendor/github.com/google/go-github/github/git_refs.go new file mode 100644 index 0000000..3d2f6c8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_refs.go @@ -0,0 +1,162 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "strings" +) + +// Reference represents a GitHub reference. +type Reference struct { + Ref *string `json:"ref"` + URL *string `json:"url"` + Object *GitObject `json:"object"` +} + +func (r Reference) String() string { + return Stringify(r) +} + +// GitObject represents a Git object. +type GitObject struct { + Type *string `json:"type"` + SHA *string `json:"sha"` + URL *string `json:"url"` +} + +func (o GitObject) String() string { + return Stringify(o) +} + +// createRefRequest represents the payload for creating a reference. +type createRefRequest struct { + Ref *string `json:"ref"` + SHA *string `json:"sha"` +} + +// updateRefRequest represents the payload for updating a reference. +type updateRefRequest struct { + SHA *string `json:"sha"` + Force *bool `json:"force"` +} + +// GetRef fetches the Reference object for a given Git ref. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#get-a-reference +func (s *GitService) GetRef(owner string, repo string, ref string) (*Reference, *Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// ReferenceListOptions specifies optional parameters to the +// GitService.ListRefs method. +type ReferenceListOptions struct { + Type string `url:"-"` + + ListOptions +} + +// ListRefs lists all refs in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#get-all-references +func (s *GitService) ListRefs(owner, repo string, opt *ReferenceListOptions) ([]Reference, *Response, error) { + var u string + if opt != nil && opt.Type != "" { + u = fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, opt.Type) + } else { + u = fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var rs []Reference + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// CreateRef creates a new ref in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#create-a-reference +func (s *GitService) CreateRef(owner string, repo string, ref *Reference) (*Reference, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + req, err := s.client.NewRequest("POST", u, &createRefRequest{ + // back-compat with previous behavior that didn't require 'refs/' prefix + Ref: String("refs/" + strings.TrimPrefix(*ref.Ref, "refs/")), + SHA: ref.Object.SHA, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// UpdateRef updates an existing ref in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#update-a-reference +func (s *GitService) UpdateRef(owner string, repo string, ref *Reference, force bool) (*Reference, *Response, error) { + refPath := strings.TrimPrefix(*ref.Ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, refPath) + req, err := s.client.NewRequest("PATCH", u, &updateRefRequest{ + SHA: ref.Object.SHA, + Force: &force, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// DeleteRef deletes a ref from a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#delete-a-reference +func (s *GitService) DeleteRef(owner string, repo string, ref string) (*Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/git_refs_test.go b/vendor/github.com/google/go-github/github/git_refs_test.go new file mode 100644 index 0000000..e66bf54 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_refs_test.go @@ -0,0 +1,280 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetRef(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.GetRef("o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.GetRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.GetRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + if _, _, err := client.Git.GetRef("o", "r", "heads/b"); err != nil { + t.Errorf("Git.GetRef returned error: %v", err) + } +} + +func TestGitService_ListRefs(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + [ + { + "ref": "refs/heads/branchA", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchA", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }, + { + "ref": "refs/heads/branchB", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchB", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + } + ]`) + }) + + refs, _, err := client.Git.ListRefs("o", "r", nil) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []Reference{ + { + Ref: String("refs/heads/branchA"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchA"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + { + Ref: String("refs/heads/branchB"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchB"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + } + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_ListRefs_options(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"ref": "r"}]`) + }) + + opt := &ReferenceListOptions{Type: "t", ListOptions: ListOptions{Page: 2}} + refs, _, err := client.Git.ListRefs("o", "r", opt) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []Reference{{Ref: String("r")}} + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_CreateRef(t *testing.T) { + setup() + defer teardown() + + args := &createRefRequest{ + Ref: String("refs/heads/b"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + } + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + v := new(createRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.CreateRef("o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.CreateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.CreateRef("o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } +} + +func TestGitService_UpdateRef(t *testing.T) { + setup() + defer teardown() + + args := &updateRefRequest{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + Force: Bool(true), + } + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + v := new(updateRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.UpdateRef("o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.UpdateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.UpdateRef("o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } +} + +func TestGitService_DeleteRef(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Git.DeleteRef("o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } + + // without 'refs/' prefix + if _, err := client.Git.DeleteRef("o", "r", "heads/b"); err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/git_tags.go b/vendor/github.com/google/go-github/github/git_tags.go new file mode 100644 index 0000000..7b53f5c --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_tags.go @@ -0,0 +1,73 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" +) + +// Tag represents a tag object. +type Tag struct { + Tag *string `json:"tag,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + Message *string `json:"message,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` + Object *GitObject `json:"object,omitempty"` +} + +// createTagRequest represents the body of a CreateTag request. This is mostly +// identical to Tag with the exception that the object SHA and Type are +// top-level fields, rather than being nested inside a JSON object. +type createTagRequest struct { + Tag *string `json:"tag,omitempty"` + Message *string `json:"message,omitempty"` + Object *string `json:"object,omitempty"` + Type *string `json:"type,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` +} + +// GetTag fetchs a tag from a repo given a SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/tags/#get-a-tag +func (s *GitService) GetTag(owner string, repo string, sha string) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + tag := new(Tag) + resp, err := s.client.Do(req, tag) + return tag, resp, err +} + +// CreateTag creates a tag object. +// +// GitHub API docs: http://developer.github.com/v3/git/tags/#create-a-tag-object +func (s *GitService) CreateTag(owner string, repo string, tag *Tag) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags", owner, repo) + + // convert Tag into a createTagRequest + tagRequest := &createTagRequest{ + Tag: tag.Tag, + Message: tag.Message, + Tagger: tag.Tagger, + } + if tag.Object != nil { + tagRequest.Object = tag.Object.SHA + tagRequest.Type = tag.Object.Type + } + + req, err := s.client.NewRequest("POST", u, tagRequest) + if err != nil { + return nil, nil, err + } + + t := new(Tag) + resp, err := s.client.Do(req, t) + return t, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_tags_test.go b/vendor/github.com/google/go-github/github/git_tags_test.go new file mode 100644 index 0000000..fb41bf3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_tags_test.go @@ -0,0 +1,68 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetTag(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/tags/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.GetTag("o", "r", "s") + + if err != nil { + t.Errorf("Git.GetTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} + +func TestGitService_CreateTag(t *testing.T) { + setup() + defer teardown() + + input := &createTagRequest{Tag: String("t"), Object: String("s")} + + mux.HandleFunc("/repos/o/r/git/tags", func(w http.ResponseWriter, r *http.Request) { + v := new(createTagRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.CreateTag("o", "r", &Tag{ + Tag: input.Tag, + Object: &GitObject{SHA: input.Object}, + }) + if err != nil { + t.Errorf("Git.CreateTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} diff --git a/vendor/github.com/google/go-github/github/git_trees.go b/vendor/github.com/google/go-github/github/git_trees.go new file mode 100644 index 0000000..9efa4b3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_trees.go @@ -0,0 +1,89 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Tree represents a GitHub tree. +type Tree struct { + SHA *string `json:"sha,omitempty"` + Entries []TreeEntry `json:"tree,omitempty"` +} + +func (t Tree) String() string { + return Stringify(t) +} + +// TreeEntry represents the contents of a tree structure. TreeEntry can +// represent either a blob, a commit (in the case of a submodule), or another +// tree. +type TreeEntry struct { + SHA *string `json:"sha,omitempty"` + Path *string `json:"path,omitempty"` + Mode *string `json:"mode,omitempty"` + Type *string `json:"type,omitempty"` + Size *int `json:"size,omitempty"` + Content *string `json:"content,omitempty"` +} + +func (t TreeEntry) String() string { + return Stringify(t) +} + +// GetTree fetches the Tree object for a given sha hash from a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/trees/#get-a-tree +func (s *GitService) GetTree(owner string, repo string, sha string, recursive bool) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees/%v", owner, repo, sha) + if recursive { + u += "?recursive=1" + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// createTree represents the body of a CreateTree request. +type createTree struct { + BaseTree string `json:"base_tree,omitempty"` + Entries []TreeEntry `json:"tree"` +} + +// CreateTree creates a new tree in a repository. If both a tree and a nested +// path modifying that tree are specified, it will overwrite the contents of +// that tree with the new path contents and write a new tree out. +// +// GitHub API docs: http://developer.github.com/v3/git/trees/#create-a-tree +func (s *GitService) CreateTree(owner string, repo string, baseTree string, entries []TreeEntry) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees", owner, repo) + + body := &createTree{ + BaseTree: baseTree, + Entries: entries, + } + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} diff --git a/vendor/github.com/google/go-github/github/git_trees_test.go b/vendor/github.com/google/go-github/github/git_trees_test.go new file mode 100644 index 0000000..99ec4f3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/git_trees_test.go @@ -0,0 +1,189 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetTree(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/trees/s", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{ + "sha": "s", + "tree": [ { "type": "blob" } ] + }`) + }) + + tree, _, err := client.Git.GetTree("o", "r", "s", true) + if err != nil { + t.Errorf("Git.GetTree returned error: %v", err) + } + + want := Tree{ + SHA: String("s"), + Entries: []TreeEntry{ + { + Type: String("blob"), + }, + }, + } + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Tree.Get returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_GetTree_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetTree("%", "%", "%", false) + testURLParseError(t, err) +} + +func TestGitService_CreateTree(t *testing.T) { + setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "cd8274d15fa3ae2ab983129fb037999f264ba9a7", + "tree": [ + { + "path": "file.rb", + "mode": "100644", + "type": "blob", + "size": 132, + "sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree("o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("cd8274d15fa3ae2ab983129fb037999f264ba9a7"), + []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(132), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + }, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_Content(t *testing.T) { + setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Content: String("file content"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11", + "url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11", + "tree": [ + { + "mode": "100644", + "type": "blob", + "sha": "aad8feacf6f8063150476a7b2bd9770f2794c08b", + "path": "content.md", + "size": 12, + "url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree("o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("5c6780ad2c68743383b740fd1dab6f6a33202b11"), + []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(12), + SHA: String("aad8feacf6f8063150476a7b2bd9770f2794c08b"), + }, + }, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateTree("%", "%", "", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/github.go b/vendor/github.com/google/go-github/github/github.go new file mode 100644 index 0000000..30b8390 --- /dev/null +++ b/vendor/github.com/google/go-github/github/github.go @@ -0,0 +1,588 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "time" + + "github.com/google/go-querystring/query" +) + +const ( + libraryVersion = "0.1" + defaultBaseURL = "https://api.github.com/" + uploadBaseURL = "https://uploads.github.com/" + userAgent = "go-github/" + libraryVersion + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" + + mediaTypeV3 = "application/vnd.github.v3+json" + defaultMediaType = "application/octet-stream" + + // Media Type values to access preview APIs + + // https://developer.github.com/changes/2015-03-09-licenses-api/ + mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json" + + // https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/ + mediaTypeStarringPreview = "application/vnd.github.v3.star+json" + + // https://developer.github.com/changes/2015-06-24-api-enhancements-for-working-with-organization-permissions/ + mediaTypeOrgPermissionPreview = "application/vnd.github.ironman-preview+json" + mediaTypeOrgPermissionRepoPreview = "application/vnd.github.ironman-preview.repository+json" +) + +// A Client manages communication with the GitHub API. +type Client struct { + // HTTP client used to communicate with the API. + client *http.Client + + // Base URL for API requests. Defaults to the public GitHub API, but can be + // set to a domain endpoint to use with GitHub Enterprise. BaseURL should + // always be specified with a trailing slash. + BaseURL *url.URL + + // Base URL for uploading files. + UploadURL *url.URL + + // User agent used when communicating with the GitHub API. + UserAgent string + + // Rate specifies the current rate limit for the client as determined by the + // most recent API call. If the client is used in a multi-user application, + // this rate may not always be up-to-date. Call RateLimits() to check the + // current rate. + Rate Rate + + // Services used for talking to different parts of the GitHub API. + Activity *ActivityService + Gists *GistsService + Git *GitService + Gitignores *GitignoresService + Issues *IssuesService + Organizations *OrganizationsService + PullRequests *PullRequestsService + Repositories *RepositoriesService + Search *SearchService + Users *UsersService + Licenses *LicensesService +} + +// ListOptions specifies the optional parameters to various List methods that +// support pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int `url:"page,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` +} + +// UploadOptions specifies the parameters to methods that support uploads. +type UploadOptions struct { + Name string `url:"name,omitempty"` +} + +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + return u.String(), nil +} + +// NewClient returns a new GitHub API client. If a nil httpClient is +// provided, http.DefaultClient will be used. To use API methods which require +// authentication, provide an http.Client that will perform the authentication +// for you (such as that provided by the golang.org/x/oauth2 library). +func NewClient(httpClient *http.Client) *Client { + if httpClient == nil { + httpClient = http.DefaultClient + } + baseURL, _ := url.Parse(defaultBaseURL) + uploadURL, _ := url.Parse(uploadBaseURL) + + c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL} + c.Activity = &ActivityService{client: c} + c.Gists = &GistsService{client: c} + c.Git = &GitService{client: c} + c.Gitignores = &GitignoresService{client: c} + c.Issues = &IssuesService{client: c} + c.Organizations = &OrganizationsService{client: c} + c.PullRequests = &PullRequestsService{client: c} + c.Repositories = &RepositoriesService{client: c} + c.Search = &SearchService{client: c} + c.Users = &UsersService{client: c} + c.Licenses = &LicensesService{client: c} + return c +} + +// NewRequest creates an API request. A relative URL can be provided in urlStr, +// in which case it is resolved relative to the BaseURL of the Client. +// Relative URLs should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.BaseURL.ResolveReference(rel) + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", mediaTypeV3) + if c.UserAgent != "" { + req.Header.Add("User-Agent", c.UserAgent) + } + return req, nil +} + +// NewUploadRequest creates an upload request. A relative URL can be provided in +// urlStr, in which case it is resolved relative to the UploadURL of the Client. +// Relative URLs should always be specified without a preceding slash. +func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.UploadURL.ResolveReference(rel) + req, err := http.NewRequest("POST", u.String(), reader) + if err != nil { + return nil, err + } + req.ContentLength = size + + if len(mediaType) == 0 { + mediaType = defaultMediaType + } + req.Header.Add("Content-Type", mediaType) + req.Header.Add("Accept", mediaTypeV3) + req.Header.Add("User-Agent", c.UserAgent) + return req, nil +} + +// Response is a GitHub API response. This wraps the standard http.Response +// returned from GitHub and provides convenient access to things like +// pagination links. +type Response struct { + *http.Response + + // These fields provide the page values for paginating through a set of + // results. Any or all of these may be set to the zero value for + // responses that are not part of a paginated set, or for which there + // are no additional pages. + + NextPage int + PrevPage int + FirstPage int + LastPage int + + Rate +} + +// newResponse creates a new Response for the provided http.Response. +func newResponse(r *http.Response) *Response { + response := &Response{Response: r} + response.populatePageValues() + response.populateRate() + return response +} + +// populatePageValues parses the HTTP Link response headers and populates the +// various pagination link values in the Reponse. +func (r *Response) populatePageValues() { + if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 { + for _, link := range strings.Split(links[0], ",") { + segments := strings.Split(strings.TrimSpace(link), ";") + + // link must at least have href and rel + if len(segments) < 2 { + continue + } + + // ensure href is properly formatted + if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { + continue + } + + // try to pull out page parameter + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + page := url.Query().Get("page") + if page == "" { + continue + } + + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.NextPage, _ = strconv.Atoi(page) + case `rel="prev"`: + r.PrevPage, _ = strconv.Atoi(page) + case `rel="first"`: + r.FirstPage, _ = strconv.Atoi(page) + case `rel="last"`: + r.LastPage, _ = strconv.Atoi(page) + } + + } + } + } +} + +// populateRate parses the rate related headers and populates the response Rate. +func (r *Response) populateRate() { + if limit := r.Header.Get(headerRateLimit); limit != "" { + r.Rate.Limit, _ = strconv.Atoi(limit) + } + if remaining := r.Header.Get(headerRateRemaining); remaining != "" { + r.Rate.Remaining, _ = strconv.Atoi(remaining) + } + if reset := r.Header.Get(headerRateReset); reset != "" { + if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { + r.Rate.Reset = Timestamp{time.Unix(v, 0)} + } + } +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + response := newResponse(resp) + + c.Rate = response.Rate + + err = CheckResponse(resp) + if err != nil { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + return response, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + io.Copy(w, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(v) + } + } + return response, err +} + +/* +An ErrorResponse reports one or more errors caused by an API request. + +GitHub API docs: http://developer.github.com/v3/#client-errors +*/ +type ErrorResponse struct { + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // error message + Errors []Error `json:"errors"` // more detail on individual errors +} + +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v %+v", + r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), + r.Response.StatusCode, r.Message, r.Errors) +} + +// sanitizeURL redacts the client_id and client_secret tokens from the URL which +// may be exposed to the user, specifically in the ErrorResponse error message. +func sanitizeURL(uri *url.URL) *url.URL { + if uri == nil { + return nil + } + params := uri.Query() + if len(params.Get("client_secret")) > 0 { + params.Set("client_secret", "REDACTED") + uri.RawQuery = params.Encode() + } + return uri +} + +/* +An Error reports more details on an individual error in an ErrorResponse. +These are the possible validation error codes: + + missing: + resource does not exist + missing_field: + a required field on a resource has not been set + invalid: + the formatting of a field is invalid + already_exists: + another resource has the same valid as this field + +GitHub API docs: http://developer.github.com/v3/#client-errors +*/ +type Error struct { + Resource string `json:"resource"` // resource on which the error occurred + Field string `json:"field"` // field on which the error occurred + Code string `json:"code"` // validation error code +} + +func (e *Error) Error() string { + return fmt.Sprintf("%v error caused by %v field on %v resource", + e.Code, e.Field, e.Resource) +} + +// CheckResponse checks the API response for errors, and returns them if +// present. A response is considered an error if it has a status code outside +// the 200 range. API error responses are expected to have either no response +// body, or a JSON response body that maps to ErrorResponse. Any other +// response body will be silently ignored. +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + json.Unmarshal(data, errorResponse) + } + return errorResponse +} + +// parseBoolResponse determines the boolean result from a GitHub API response. +// Several GitHub API methods return boolean responses indicated by the HTTP +// status code in the response (true indicated by a 204, false indicated by a +// 404). This helper function will determine that result and hide the 404 +// error if present. Any other error will be returned through as-is. +func parseBoolResponse(err error) (bool, error) { + if err == nil { + return true, nil + } + + if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound { + // Simply false. In this one case, we do not pass the error through. + return false, nil + } + + // some other real error occurred + return false, err +} + +// Rate represents the rate limit for the current client. +type Rate struct { + // The number of requests per hour the client is currently limited to. + Limit int `json:"limit"` + + // The number of remaining requests the client can make this hour. + Remaining int `json:"remaining"` + + // The time at which the current rate limit will reset. + Reset Timestamp `json:"reset"` +} + +func (r Rate) String() string { + return Stringify(r) +} + +// RateLimits represents the rate limits for the current client. +type RateLimits struct { + // The rate limit for non-search API requests. Unauthenticated + // requests are limited to 60 per hour. Authenticated requests are + // limited to 5,000 per hour. + Core *Rate `json:"core"` + + // The rate limit for search API requests. Unauthenticated requests + // are limited to 5 requests per minutes. Authenticated requests are + // limited to 20 per minute. + // + // GitHub API docs: https://developer.github.com/v3/search/#rate-limit + Search *Rate `json:"search"` +} + +func (r RateLimits) String() string { + return Stringify(r) +} + +// RateLimit is deprecated. Use RateLimits instead. +func (c *Client) RateLimit() (*Rate, *Response, error) { + limits, resp, err := c.RateLimits() + if limits == nil { + return nil, nil, err + } + + return limits.Core, resp, err +} + +// RateLimits returns the rate limits for the current client. +func (c *Client) RateLimits() (*RateLimits, *Response, error) { + req, err := c.NewRequest("GET", "rate_limit", nil) + if err != nil { + return nil, nil, err + } + + response := new(struct { + Resources *RateLimits `json:"resources"` + }) + resp, err := c.Do(req, response) + if err != nil { + return nil, nil, err + } + + return response.Resources, resp, err +} + +/* +UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls +that need to use a higher rate limit associated with your OAuth application. + + t := &github.UnauthenticatedRateLimitedTransport{ + ClientID: "your app's client ID", + ClientSecret: "your app's client secret", + } + client := github.NewClient(t.Client()) + +This will append the querystring params client_id=xxx&client_secret=yyy to all +requests. + +See http://developer.github.com/v3/#unauthenticated-rate-limited-requests for +more information. +*/ +type UnauthenticatedRateLimitedTransport struct { + // ClientID is the GitHub OAuth client ID of the current application, which + // can be found by selecting its entry in the list at + // https://github.com/settings/applications. + ClientID string + + // ClientSecret is the GitHub OAuth client secret of the current + // application. + ClientSecret string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. +func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.ClientID == "" { + return nil, errors.New("t.ClientID is empty") + } + if t.ClientSecret == "" { + return nil, errors.New("t.ClientSecret is empty") + } + + // To set extra querystring params, we must make a copy of the Request so + // that we don't modify the Request we were given. This is required by the + // specification of http.RoundTripper. + req = cloneRequest(req) + q := req.URL.Query() + q.Set("client_id", t.ClientID) + q.Set("client_secret", t.ClientSecret) + req.URL.RawQuery = q.Encode() + + // Make the HTTP request. + return t.transport().RoundTrip(req) +} + +// Client returns an *http.Client that makes requests which are subject to the +// rate limit of your OAuth application. +func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// cloneRequest returns a clone of the provided *http.Request. The clone is a +// shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header) + for k, s := range r.Header { + r2.Header[k] = s + } + return r2 +} + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + p := new(string) + *p = v + return p +} diff --git a/vendor/github.com/google/go-github/github/github_test.go b/vendor/github.com/google/go-github/github/github_test.go new file mode 100644 index 0000000..f4626f4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/github_test.go @@ -0,0 +1,679 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "reflect" + "strings" + "testing" + "time" +) + +var ( + // mux is the HTTP request multiplexer used with the test server. + mux *http.ServeMux + + // client is the GitHub client being tested. + client *Client + + // server is a test HTTP server used to provide mock API responses. + server *httptest.Server +) + +// setup sets up a test HTTP server along with a github.Client that is +// configured to talk to that test server. Tests should register handlers on +// mux which provide mock responses for the API method being tested. +func setup() { + // test server + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + // github client configured to use test server + client = NewClient(nil) + url, _ := url.Parse(server.URL) + client.BaseURL = url + client.UploadURL = url +} + +// teardown closes the test HTTP server. +func teardown() { + server.Close() +} + +// openTestFile creates a new file with the given name and content for testing. +// In order to ensure the exact file name, this function will create a new temp +// directory, and create the file in that directory. It is the caller's +// responsibility to remove the directy and its contents when no longer needed. +func openTestFile(name, content string) (file *os.File, dir string, err error) { + dir, err = ioutil.TempDir("", "go-github") + if err != nil { + return nil, dir, err + } + + file, err = os.OpenFile(path.Join(dir, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + return nil, dir, err + } + + fmt.Fprint(file, content) + + // close and re-open the file to keep file.Stat() happy + file.Close() + file, err = os.Open(file.Name()) + if err != nil { + return nil, dir, err + } + + return file, dir, err +} + +func testMethod(t *testing.T, r *http.Request, want string) { + if got := r.Method; got != want { + t.Errorf("Request method: %v, want %v", got, want) + } +} + +type values map[string]string + +func testFormValues(t *testing.T, r *http.Request, values values) { + want := url.Values{} + for k, v := range values { + want.Add(k, v) + } + + r.ParseForm() + if got := r.Form; !reflect.DeepEqual(got, want) { + t.Errorf("Request parameters: %v, want %v", got, want) + } +} + +func testHeader(t *testing.T, r *http.Request, header string, want string) { + if got := r.Header.Get(header); got != want { + t.Errorf("Header.Get(%q) returned %s, want %s", header, got, want) + } +} + +func testURLParseError(t *testing.T, err error) { + if err == nil { + t.Errorf("Expected error to be returned") + } + if err, ok := err.(*url.Error); !ok || err.Op != "parse" { + t.Errorf("Expected URL parse error, got %+v", err) + } +} + +func testBody(t *testing.T, r *http.Request, want string) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Error reading request body: %v", err) + } + if got := string(b); got != want { + t.Errorf("request Body is %s, want %s", got, want) + } +} + +// Helper function to test that a value is marshalled to JSON as expected. +func testJSONMarshal(t *testing.T, v interface{}, want string) { + j, err := json.Marshal(v) + if err != nil { + t.Errorf("Unable to marshal JSON for %v", v) + } + + w := new(bytes.Buffer) + err = json.Compact(w, []byte(want)) + if err != nil { + t.Errorf("String is not valid json: %s", want) + } + + if w.String() != string(j) { + t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w) + } + + // now go the other direction and make sure things unmarshal as expected + u := reflect.ValueOf(v).Interface() + if err := json.Unmarshal([]byte(want), u); err != nil { + t.Errorf("Unable to unmarshal JSON for %v", want) + } + + if !reflect.DeepEqual(v, u) { + t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v) + } +} + +func TestNewClient(t *testing.T) { + c := NewClient(nil) + + if got, want := c.BaseURL.String(), defaultBaseURL; got != want { + t.Errorf("NewClient BaseURL is %v, want %v", got, want) + } + if got, want := c.UserAgent, userAgent; got != want { + t.Errorf("NewClient UserAgent is %v, want %v", got, want) + } +} + +func TestNewRequest(t *testing.T) { + c := NewClient(nil) + + inURL, outURL := "/foo", defaultBaseURL+"foo" + inBody, outBody := &User{Login: String("l")}, `{"login":"l"}`+"\n" + req, _ := c.NewRequest("GET", inURL, inBody) + + // test that relative URL was expanded + if got, want := req.URL.String(), outURL; got != want { + t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want) + } + + // test that body was JSON encoded + body, _ := ioutil.ReadAll(req.Body) + if got, want := string(body), outBody; got != want { + t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want) + } + + // test that default user-agent is attached to the request + if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want { + t.Errorf("NewRequest() User-Agent is %v, want %v", got, want) + } +} + +func TestNewRequest_invalidJSON(t *testing.T) { + c := NewClient(nil) + + type T struct { + A map[int]interface{} + } + _, err := c.NewRequest("GET", "/", &T{}) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*json.UnsupportedTypeError); !ok { + t.Errorf("Expected a JSON error; got %#v.", err) + } +} + +func TestNewRequest_badURL(t *testing.T) { + c := NewClient(nil) + _, err := c.NewRequest("GET", ":", nil) + testURLParseError(t, err) +} + +// ensure that no User-Agent header is set if the client's UserAgent is empty. +// This caused a problem with Google's internal http client. +func TestNewRequest_emptyUserAgent(t *testing.T) { + c := NewClient(nil) + c.UserAgent = "" + req, err := c.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if _, ok := req.Header["User-Agent"]; ok { + t.Fatal("constructed request contains unexpected User-Agent header") + } +} + +// If a nil body is passed to github.NewRequest, make sure that nil is also +// passed to http.NewRequest. In most cases, passing an io.Reader that returns +// no content is fine, since there is no difference between an HTTP request +// body that is an empty string versus one that is not set at all. However in +// certain cases, intermediate systems may treat these differently resulting in +// subtle errors. +func TestNewRequest_emptyBody(t *testing.T) { + c := NewClient(nil) + req, err := c.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if req.Body != nil { + t.Fatalf("constructed request contains a non-nil Body") + } +} + +func TestResponse_populatePageValues(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`; rel="first",` + + ` ; rel="prev",` + + ` ; rel="next",` + + ` ; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 1; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 2; want != got { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 4; want != got { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 5; want != got { + t.Errorf("response.LastPage: %v, want %v", got, want) + } +} + +func TestResponse_populatePageValues_invalid(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`,` + + `; rel="first",` + + `https://api.github.com/?page=2; rel="prev",` + + `; rel="next",` + + `; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 0; got != want { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 0; got != want { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 0; got != want { + t.Errorf("response.LastPage: %v, want %v", got, want) + } + + // more invalid URLs + r = http.Response{ + Header: http.Header{ + "Link": {`; rel="first"`}, + }, + } + + response = newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } +} + +func TestDo(t *testing.T) { + setup() + defer teardown() + + type foo struct { + A string + } + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"A":"a"}`) + }) + + req, _ := client.NewRequest("GET", "/", nil) + body := new(foo) + client.Do(req, body) + + want := &foo{"a"} + if !reflect.DeepEqual(body, want) { + t.Errorf("Response body = %v, want %v", body, want) + } +} + +func TestDo_httpError(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", "/", nil) + _, err := client.Do(req, nil) + + if err == nil { + t.Error("Expected HTTP 400 error.") + } +} + +// Test handling of an error caused by the internal http client's Do() +// function. A redirect loop is pretty unlikely to occur within the GitHub +// API, but does allow us to exercise the right code path. +func TestDo_redirectLoop(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/", http.StatusFound) + }) + + req, _ := client.NewRequest("GET", "/", nil) + _, err := client.Do(req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*url.Error); !ok { + t.Errorf("Expected a URL error; got %#v.", err) + } +} + +func TestDo_rateLimit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add(headerRateLimit, "60") + w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") + }) + + if got, want := client.Rate.Limit, 0; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Remaining, 0; got != want { + t.Errorf("Client rate remaining = %v, got %v", got, want) + } + if !client.Rate.Reset.IsZero() { + t.Errorf("Client rate reset not initialized to zero value") + } + + req, _ := client.NewRequest("GET", "/", nil) + client.Do(req, nil) + + if got, want := client.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } +} + +// ensure rate limit is still parsed, even for error responses +func TestDo_rateLimit_errorResponse(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add(headerRateLimit, "60") + w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", "/", nil) + client.Do(req, nil) + + if got, want := client.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } +} + +func TestSanitizeURL(t *testing.T) { + tests := []struct { + in, want string + }{ + {"/?a=b", "/?a=b"}, + {"/?a=b&client_secret=secret", "/?a=b&client_secret=REDACTED"}, + {"/?a=b&client_id=id&client_secret=secret", "/?a=b&client_id=id&client_secret=REDACTED"}, + } + + for _, tt := range tests { + inURL, _ := url.Parse(tt.in) + want, _ := url.Parse(tt.want) + + if got := sanitizeURL(inURL); !reflect.DeepEqual(got, want) { + t.Errorf("sanitizeURL(%v) returned %v, want %v", tt.in, got, want) + } + } +} + +func TestCheckResponse(t *testing.T) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader(`{"message":"m", + "errors": [{"resource": "r", "field": "f", "code": "c"}]}`)), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + Message: "m", + Errors: []Error{{Resource: "r", Field: "f", Code: "c"}}, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +// ensure that we properly handle API errors that do not contain a response body +func TestCheckResponse_noBody(t *testing.T) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader("")), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +func TestParseBooleanResponse_true(t *testing.T) { + result, err := parseBoolResponse(nil) + + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := true; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_false(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusNotFound}} + result, err := parseBoolResponse(v) + + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_error(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusBadRequest}} + result, err := parseBoolResponse(v) + + if err == nil { + t.Errorf("Expected error to be returned.") + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestErrorResponse_Error(t *testing.T) { + res := &http.Response{Request: &http.Request{}} + err := ErrorResponse{Message: "m", Response: res} + if err.Error() == "" { + t.Errorf("Expected non-empty ErrorResponse.Error()") + } +} + +func TestError_Error(t *testing.T) { + err := Error{} + if err.Error() == "" { + t.Errorf("Expected non-empty Error.Error()") + } +} + +func TestRateLimit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + //fmt.Fprint(w, `{"resources":{"core": {"limit":2,"remaining":1,"reset":1372700873}}}`) + fmt.Fprint(w, `{"resources":{ + "core": {"limit":2,"remaining":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"reset":1372700874} + }}`) + }) + + rate, _, err := client.RateLimit() + if err != nil { + t.Errorf("Rate limit returned error: %v", err) + } + + want := &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()}, + } + if !reflect.DeepEqual(rate, want) { + t.Errorf("RateLimit returned %+v, want %+v", rate, want) + } +} + +func TestRateLimits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"resources":{ + "core": {"limit":2,"remaining":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"reset":1372700874} + }}`) + }) + + rate, _, err := client.RateLimits() + if err != nil { + t.Errorf("RateLimits returned error: %v", err) + } + + want := &RateLimits{ + Core: &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()}, + }, + Search: &Rate{ + Limit: 3, + Remaining: 2, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 54, 0, time.UTC).Local()}, + }, + } + if !reflect.DeepEqual(rate, want) { + t.Errorf("RateLimits returned %+v, want %+v", rate, want) + } +} + +func TestUnauthenticatedRateLimitedTransport(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var v, want string + q := r.URL.Query() + if v, want = q.Get("client_id"), "id"; v != want { + t.Errorf("OAuth Client ID = %v, want %v", v, want) + } + if v, want = q.Get("client_secret"), "secret"; v != want { + t.Errorf("OAuth Client Secret = %v, want %v", v, want) + } + }) + + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + unauthedClient := NewClient(tp.Client()) + unauthedClient.BaseURL = client.BaseURL + req, _ := unauthedClient.NewRequest("GET", "/", nil) + unauthedClient.Do(req, nil) +} + +func TestUnauthenticatedRateLimitedTransport_missingFields(t *testing.T) { + // missing ClientID + tp := &UnauthenticatedRateLimitedTransport{ + ClientSecret: "secret", + } + _, err := tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } + + // missing ClientSecret + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + } + _, err = tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } +} + +func TestUnauthenticatedRateLimitedTransport_transport(t *testing.T) { + // default transport + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} diff --git a/vendor/github.com/google/go-github/github/gitignore.go b/vendor/github.com/google/go-github/github/gitignore.go new file mode 100644 index 0000000..31d5902 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gitignore.go @@ -0,0 +1,63 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// GitignoresService provides access to the gitignore related functions in the +// GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/gitignore/ +type GitignoresService struct { + client *Client +} + +// Gitignore represents a .gitignore file as returned by the GitHub API. +type Gitignore struct { + Name *string `json:"name,omitempty"` + Source *string `json:"source,omitempty"` +} + +func (g Gitignore) String() string { + return Stringify(g) +} + +// List all available Gitignore templates. +// +// http://developer.github.com/v3/gitignore/#listing-available-templates +func (s GitignoresService) List() ([]string, *Response, error) { + req, err := s.client.NewRequest("GET", "gitignore/templates", nil) + if err != nil { + return nil, nil, err + } + + availableTemplates := new([]string) + resp, err := s.client.Do(req, availableTemplates) + if err != nil { + return nil, resp, err + } + + return *availableTemplates, resp, err +} + +// Get a Gitignore by name. +// +// http://developer.github.com/v3/gitignore/#get-a-single-template +func (s GitignoresService) Get(name string) (*Gitignore, *Response, error) { + u := fmt.Sprintf("gitignore/templates/%v", name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gitignore := new(Gitignore) + resp, err := s.client.Do(req, gitignore) + if err != nil { + return nil, resp, err + } + + return gitignore, resp, err +} diff --git a/vendor/github.com/google/go-github/github/gitignore_test.go b/vendor/github.com/google/go-github/github/gitignore_test.go new file mode 100644 index 0000000..6d49d00 --- /dev/null +++ b/vendor/github.com/google/go-github/github/gitignore_test.go @@ -0,0 +1,58 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitignoresService_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `["C", "Go"]`) + }) + + available, _, err := client.Gitignores.List() + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := []string{"C", "Go"} + if !reflect.DeepEqual(available, want) { + t.Errorf("Gitignores.List returned %+v, want %+v", available, want) + } +} + +func TestGitignoresService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates/name", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"Name","source":"template source"}`) + }) + + gitignore, _, err := client.Gitignores.Get("name") + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := &Gitignore{Name: String("Name"), Source: String("template source")} + if !reflect.DeepEqual(gitignore, want) { + t.Errorf("Gitignores.Get returned %+v, want %+v", gitignore, want) + } +} + +func TestGitignoresService_Get_invalidTemplate(t *testing.T) { + _, _, err := client.Gitignores.Get("%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues.go b/vendor/github.com/google/go-github/github/issues.go new file mode 100644 index 0000000..34807aa --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues.go @@ -0,0 +1,261 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// IssuesService handles communication with the issue related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/issues/ +type IssuesService struct { + client *Client +} + +// Issue represents a GitHub issue on a repository. +type Issue struct { + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + Labels []Label `json:"labels,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Comments *int `json:"comments,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (i Issue) String() string { + return Stringify(i) +} + +// IssueRequest represents a request to create/edit an issue. +// It is separate from Issue above because otherwise Labels +// and Assignee fail to serialize to the correct JSON. +type IssueRequest struct { + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + Labels *[]string `json:"labels,omitempty"` + Assignee *string `json:"assignee,omitempty"` + State *string `json:"state,omitempty"` + Milestone *int `json:"milestone,omitempty"` +} + +// IssueListOptions specifies the optional parameters to the IssuesService.List +// and IssuesService.ListByOrg methods. +type IssueListOptions struct { + // Filter specifies which issues to list. Possible values are: assigned, + // created, mentioned, subscribed, all. Default is "assigned". + Filter string `url:"filter,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed. Default is "open". + State string `url:"state,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,comma,omitempty"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// PullRequestLinks object is added to the Issue object when it's an issue included +// in the IssueCommentEvent webhook payload, if the webhooks is fired by a comment on a PR +type PullRequestLinks struct { + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` +} + +// List the issues for the authenticated user. If all is true, list issues +// across all the user's visible repositories including owned, member, and +// organization repositories; if false, list only owned and member +// repositories. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) List(all bool, opt *IssueListOptions) ([]Issue, *Response, error) { + var u string + if all { + u = "issues" + } else { + u = "user/issues" + } + return s.listIssues(u, opt) +} + +// ListByOrg fetches the issues in the specified organization for the +// authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) ListByOrg(org string, opt *IssueListOptions) ([]Issue, *Response, error) { + u := fmt.Sprintf("orgs/%v/issues", org) + return s.listIssues(u, opt) +} + +func (s *IssuesService) listIssues(u string, opt *IssueListOptions) ([]Issue, *Response, error) { + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + issues := new([]Issue) + resp, err := s.client.Do(req, issues) + if err != nil { + return nil, resp, err + } + + return *issues, resp, err +} + +// IssueListByRepoOptions specifies the optional parameters to the +// IssuesService.ListByRepo method. +type IssueListByRepoOptions struct { + // Milestone limits issues for the specified milestone. Possible values are + // a milestone number, "none" for issues with no milestone, "*" for issues + // with any milestone. + Milestone string `url:"milestone,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed. Default is "open". + State string `url:"state,omitempty"` + + // Assignee filters issues based on their assignee. Possible values are a + // user name, "none" for issues that are not assigned, "*" for issues with + // any assigned user. + Assignee string `url:"assignee,omitempty"` + + // Creator filters issues based on their creator. + Creator string `url:"creator,omitempty"` + + // Mentioned filters issues to those mentioned a specific user. + Mentioned string `url:"mentioned,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,omitempty,comma"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListByRepo lists the issues for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues-for-a-repository +func (s *IssuesService) ListByRepo(owner string, repo string, opt *IssueListByRepoOptions) ([]Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + issues := new([]Issue) + resp, err := s.client.Do(req, issues) + if err != nil { + return nil, resp, err + } + + return *issues, resp, err +} + +// Get a single issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/#get-a-single-issue +func (s *IssuesService) Get(owner string, repo string, number int) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + issue := new(Issue) + resp, err := s.client.Do(req, issue) + if err != nil { + return nil, resp, err + } + + return issue, resp, err +} + +// Create a new issue on the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/#create-an-issue +func (s *IssuesService) Create(owner string, repo string, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + req, err := s.client.NewRequest("POST", u, issue) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// Edit an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/#edit-an-issue +func (s *IssuesService) Edit(owner string, repo string, number int, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, issue) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} diff --git a/vendor/github.com/google/go-github/github/issues_assignees.go b/vendor/github.com/google/go-github/github/issues_assignees.go new file mode 100644 index 0000000..6338c22 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_assignees.go @@ -0,0 +1,46 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// ListAssignees fetches all available assignees (owners and collaborators) to +// which issues may be assigned. +// +// GitHub API docs: http://developer.github.com/v3/issues/assignees/#list-assignees +func (s *IssuesService) ListAssignees(owner string, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + assignees := new([]User) + resp, err := s.client.Do(req, assignees) + if err != nil { + return nil, resp, err + } + + return *assignees, resp, err +} + +// IsAssignee checks if a user is an assignee for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/assignees/#check-assignee +func (s *IssuesService) IsAssignee(owner string, repo string, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + assignee, err := parseBoolResponse(err) + return assignee, resp, err +} diff --git a/vendor/github.com/google/go-github/github/issues_assignees_test.go b/vendor/github.com/google/go-github/github/issues_assignees_test.go new file mode 100644 index 0000000..63e024d --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_assignees_test.go @@ -0,0 +1,98 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListAssignees(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assignees, _, err := client.Issues.ListAssignees("o", "r", opt) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(assignees, want) { + t.Errorf("Issues.ListAssignees returned %+v, want %+v", assignees, want) + } +} + +func TestIssuesService_ListAssignees_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListAssignees("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_IsAssignee_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := true; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_invalidOwner(t *testing.T) { + _, _, err := client.Issues.IsAssignee("%", "r", "u") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_comments.go b/vendor/github.com/google/go-github/github/issues_comments.go new file mode 100644 index 0000000..db48e14 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_comments.go @@ -0,0 +1,138 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// IssueComment represents a comment left on an issue. +type IssueComment struct { + ID *int `json:"id,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_url,omitempty"` +} + +func (i IssueComment) String() string { + return Stringify(i) +} + +// IssueListCommentsOptions specifies the optional parameters to the +// IssuesService.ListComments method. +type IssueListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified issue. Specifying an issue +// number of 0 will return all comments on all issues for the repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#list-comments-on-an-issue +func (s *IssuesService) ListComments(owner string, repo string, number int, opt *IssueListCommentsOptions) ([]IssueComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/issues/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + comments := new([]IssueComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment fetches the specified issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#get-a-single-comment +func (s *IssuesService) GetComment(owner string, repo string, id int) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + comment := new(IssueComment) + resp, err := s.client.Do(req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, err +} + +// CreateComment creates a new comment on the specified issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#create-a-comment +func (s *IssuesService) CreateComment(owner string, repo string, number int, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment updates an issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#edit-a-comment +func (s *IssuesService) EditComment(owner string, repo string, id int, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes an issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#delete-a-comment +func (s *IssuesService) DeleteComment(owner string, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/issues_comments_test.go b/vendor/github.com/google/go-github/github/issues_comments_test.go new file mode 100644 index 0000000..697f438 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_comments_test.go @@ -0,0 +1,184 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestIssuesService_ListComments_allIssues(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &IssueListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + comments, _, err := client.Issues.ListComments("o", "r", 0, opt) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []IssueComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_specificIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + comments, _, err := client.Issues.ListComments("o", "r", 1, nil) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []IssueComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListComments("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.GetComment("o", "r", 1) + if err != nil { + t.Errorf("Issues.GetComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_GetComment_invalidOrg(t *testing.T) { + _, _, err := client.Issues.GetComment("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.CreateComment("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.CreateComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_CreateComment_invalidOrg(t *testing.T) { + _, _, err := client.Issues.CreateComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.EditComment("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.EditComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_EditComment_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("Issues.DeleteComments returned error: %v", err) + } +} + +func TestIssuesService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteComment("%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_events.go b/vendor/github.com/google/go-github/github/issues_events.go new file mode 100644 index 0000000..9062d4d --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_events.go @@ -0,0 +1,149 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// IssueEvent represents an event that occurred around an Issue or Pull Request. +type IssueEvent struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // The User that generated this event. + Actor *User `json:"actor,omitempty"` + + // Event identifies the actual type of Event that occurred. Possible + // values are: + // + // closed + // The Actor closed the issue. + // If the issue was closed by commit message, CommitID holds the SHA1 hash of the commit. + // + // merged + // The Actor merged into master a branch containing a commit mentioning the issue. + // CommitID holds the SHA1 of the merge commit. + // + // referenced + // The Actor committed to master a commit mentioning the issue in its commit message. + // CommitID holds the SHA1 of the commit. + // + // reopened, locked, unlocked + // The Actor did that to the issue. + // + // renamed + // The Actor changed the issue title from Rename.From to Rename.To. + // + // mentioned + // Someone unspecified @mentioned the Actor [sic] in an issue comment body. + // + // assigned, unassigned + // The Actor assigned the issue to or removed the assignment from the Assignee. + // + // labeled, unlabeled + // The Actor added or removed the Label from the issue. + // + // milestoned, demilestoned + // The Actor added or removed the issue from the Milestone. + // + // subscribed, unsubscribed + // The Actor subscribed to or unsubscribed from notifications for an issue. + // + // head_ref_deleted, head_ref_restored + // The pull request’s branch was deleted or restored. + // + Event *string `json:"event,omitempty"` + + CreatedAt *time.Time `json:"created_at,omitempty"` + Issue *Issue `json:"issue,omitempty"` + + // Only present on certain events; see above. + Assignee *User `json:"assignee,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + Label *Label `json:"label,omitempty"` + Rename *Rename `json:"rename,omitempty"` +} + +// ListIssueEvents lists events for the specified issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-an-issue +func (s *IssuesService) ListIssueEvents(owner, repo string, number int, opt *ListOptions) ([]IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/events", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []IssueEvent + resp, err := s.client.Do(req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, err +} + +// ListRepositoryEvents lists events for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-a-repository +func (s *IssuesService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []IssueEvent + resp, err := s.client.Do(req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, err +} + +// GetEvent returns the specified issue event. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#get-a-single-event +func (s *IssuesService) GetEvent(owner, repo string, id int) (*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + event := new(IssueEvent) + resp, err := s.client.Do(req, event) + if err != nil { + return nil, resp, err + } + + return event, resp, err +} + +// Rename contains details for 'renamed' events. +type Rename struct { + From *string `json:"from,omitempty"` + To *string `json:"to,omitempty"` +} + +func (r Rename) String() string { + return Stringify(r) +} diff --git a/vendor/github.com/google/go-github/github/issues_events_test.go b/vendor/github.com/google/go-github/github/issues_events_test.go new file mode 100644 index 0000000..f90b64a --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_events_test.go @@ -0,0 +1,86 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListIssueEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListIssueEvents("o", "r", 1, opt) + + if err != nil { + t.Errorf("Issues.ListIssueEvents returned error: %v", err) + } + + want := []IssueEvent{{ID: Int(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListIssueEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_ListRepositoryEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListRepositoryEvents("o", "r", opt) + + if err != nil { + t.Errorf("Issues.ListRepositoryEvents returned error: %v", err) + } + + want := []IssueEvent{{ID: Int(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_GetEvent(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + event, _, err := client.Issues.GetEvent("o", "r", 1) + + if err != nil { + t.Errorf("Issues.GetEvent returned error: %v", err) + } + + want := &IssueEvent{ID: Int(1)} + if !reflect.DeepEqual(event, want) { + t.Errorf("Issues.GetEvent returned %+v, want %+v", event, want) + } +} diff --git a/vendor/github.com/google/go-github/github/issues_labels.go b/vendor/github.com/google/go-github/github/issues_labels.go new file mode 100644 index 0000000..88f9f3f --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_labels.go @@ -0,0 +1,222 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Label represents a GitHub label on an Issue +type Label struct { + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` +} + +func (l Label) String() string { + return fmt.Sprint(*l.Name) +} + +// ListLabels lists all labels for a repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) ListLabels(owner string, repo string, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} + +// GetLabel gets a single label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-a-single-label +func (s *IssuesService) GetLabel(owner string, repo string, name string) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + label := new(Label) + resp, err := s.client.Do(req, label) + if err != nil { + return nil, resp, err + } + + return label, resp, err +} + +// CreateLabel creates a new label on the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#create-a-label +func (s *IssuesService) CreateLabel(owner string, repo string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + req, err := s.client.NewRequest("POST", u, label) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// EditLabel edits a label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#update-a-label +func (s *IssuesService) EditLabel(owner string, repo string, name string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("PATCH", u, label) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// DeleteLabel deletes a label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#delete-a-label +func (s *IssuesService) DeleteLabel(owner string, repo string, name string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListLabelsByIssue lists all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) ListLabelsByIssue(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} + +// AddLabelsToIssue adds labels to an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) AddLabelsToIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("POST", u, labels) + if err != nil { + return nil, nil, err + } + + l := new([]Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return *l, resp, err +} + +// RemoveLabelForIssue removes a label for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue +func (s *IssuesService) RemoveLabelForIssue(owner string, repo string, number int, label string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels/%v", owner, repo, number, label) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ReplaceLabelsForIssue replaces all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue +func (s *IssuesService) ReplaceLabelsForIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("PUT", u, labels) + if err != nil { + return nil, nil, err + } + + l := new([]Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return *l, resp, err +} + +// RemoveLabelsForIssue removes all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue +func (s *IssuesService) RemoveLabelsForIssue(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListLabelsForMilestone lists labels for every issue in a milestone. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone +func (s *IssuesService) ListLabelsForMilestone(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d/labels", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} diff --git a/vendor/github.com/google/go-github/github/issues_labels_test.go b/vendor/github.com/google/go-github/github/issues_labels_test.go new file mode 100644 index 0000000..2243eb0 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_labels_test.go @@ -0,0 +1,313 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListLabels(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabels("o", "r", opt) + if err != nil { + t.Errorf("Issues.ListLabels returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabels returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabels_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabels("%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetLabel(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u", "name": "n", "color": "c"}`) + }) + + label, _, err := client.Issues.GetLabel("o", "r", "n") + if err != nil { + t.Errorf("Issues.GetLabel returned error: %v", err) + } + + want := &Label{URL: String("u"), Name: String("n"), Color: String("c")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.GetLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_GetLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.GetLabel("%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_CreateLabel(t *testing.T) { + setup() + defer teardown() + + input := &Label{Name: String("n")} + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + label, _, err := client.Issues.CreateLabel("o", "r", input) + if err != nil { + t.Errorf("Issues.CreateLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.CreateLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_CreateLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.CreateLabel("%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditLabel(t *testing.T) { + setup() + defer teardown() + + input := &Label{Name: String("z")} + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + label, _, err := client.Issues.EditLabel("o", "r", "n", input) + if err != nil { + t.Errorf("Issues.EditLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.EditLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_EditLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditLabel("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteLabel(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteLabel("o", "r", "n") + if err != nil { + t.Errorf("Issues.DeleteLabel returned error: %v", err) + } +} + +func TestIssuesService_DeleteLabel_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteLabel("%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsByIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsByIssue("o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsByIssue returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsByIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsByIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabelsByIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_AddLabelsToIssue(t *testing.T) { + setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.AddLabelsToIssue("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.AddLabelsToIssue returned error: %v", err) + } + + want := []Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.AddLabelsToIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_AddLabelsToIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.AddLabelsToIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelForIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels/l", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.RemoveLabelForIssue("o", "r", 1, "l") + if err != nil { + t.Errorf("Issues.RemoveLabelForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelForIssue_invalidOwner(t *testing.T) { + _, err := client.Issues.RemoveLabelForIssue("%", "%", 1, "%") + testURLParseError(t, err) +} + +func TestIssuesService_ReplaceLabelsForIssue(t *testing.T) { + setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.ReplaceLabelsForIssue("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.ReplaceLabelsForIssue returned error: %v", err) + } + + want := []Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ReplaceLabelsForIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ReplaceLabelsForIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ReplaceLabelsForIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelsForIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.RemoveLabelsForIssue("o", "r", 1) + if err != nil { + t.Errorf("Issues.RemoveLabelsForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelsForIssue_invalidOwner(t *testing.T) { + _, err := client.Issues.RemoveLabelsForIssue("%", "%", 1) + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsForMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsForMilestone("o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsForMilestone returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsForMilestone returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsForMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabelsForMilestone("%", "%", 1, nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_milestones.go b/vendor/github.com/google/go-github/github/issues_milestones.go new file mode 100644 index 0000000..cbd7920 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_milestones.go @@ -0,0 +1,140 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// Milestone represents a Github repository milestone. +type Milestone struct { + URL *string `json:"url,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + OpenIssues *int `json:"open_issues,omitempty"` + ClosedIssues *int `json:"closed_issues,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + DueOn *time.Time `json:"due_on,omitempty"` +} + +func (m Milestone) String() string { + return Stringify(m) +} + +// MilestoneListOptions specifies the optional parameters to the +// IssuesService.ListMilestones method. +type MilestoneListOptions struct { + // State filters milestones based on their state. Possible values are: + // open, closed. Default is "open". + State string `url:"state,omitempty"` + + // Sort specifies how to sort milestones. Possible values are: due_date, completeness. + // Default value is "due_date". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort milestones. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` +} + +// ListMilestones lists all milestones for a repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository +func (s *IssuesService) ListMilestones(owner string, repo string, opt *MilestoneListOptions) ([]Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + milestones := new([]Milestone) + resp, err := s.client.Do(req, milestones) + if err != nil { + return nil, resp, err + } + + return *milestones, resp, err +} + +// GetMilestone gets a single milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#get-a-single-milestone +func (s *IssuesService) GetMilestone(owner string, repo string, number int) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + milestone := new(Milestone) + resp, err := s.client.Do(req, milestone) + if err != nil { + return nil, resp, err + } + + return milestone, resp, err +} + +// CreateMilestone creates a new milestone on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#create-a-milestone +func (s *IssuesService) CreateMilestone(owner string, repo string, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones", owner, repo) + req, err := s.client.NewRequest("POST", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// EditMilestone edits a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#update-a-milestone +func (s *IssuesService) EditMilestone(owner string, repo string, number int, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// DeleteMilestone deletes a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#delete-a-milestone +func (s *IssuesService) DeleteMilestone(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/issues_milestones_test.go b/vendor/github.com/google/go-github/github/issues_milestones_test.go new file mode 100644 index 0000000..817fffe --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_milestones_test.go @@ -0,0 +1,157 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestIssuesService_ListMilestones(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "closed", + "sort": "due_date", + "direction": "asc", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &MilestoneListOptions{"closed", "due_date", "asc"} + milestones, _, err := client.Issues.ListMilestones("o", "r", opt) + if err != nil { + t.Errorf("IssuesService.ListMilestones returned error: %v", err) + } + + want := []Milestone{{Number: Int(1)}} + if !reflect.DeepEqual(milestones, want) { + t.Errorf("IssuesService.ListMilestones returned %+v, want %+v", milestones, want) + } +} + +func TestIssuesService_ListMilestones_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListMilestones("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.GetMilestone("o", "r", 1) + if err != nil { + t.Errorf("IssuesService.GetMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.GetMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_GetMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.GetMilestone("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateMilestone(t *testing.T) { + setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.CreateMilestone("o", "r", input) + if err != nil { + t.Errorf("IssuesService.CreateMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.CreateMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_CreateMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.CreateMilestone("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditMilestone(t *testing.T) { + setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.EditMilestone("o", "r", 1, input) + if err != nil { + t.Errorf("IssuesService.EditMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.EditMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_EditMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditMilestone("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteMilestone("o", "r", 1) + if err != nil { + t.Errorf("IssuesService.DeleteMilestone returned error: %v", err) + } +} + +func TestIssuesService_DeleteMilestone_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteMilestone("%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/issues_test.go b/vendor/github.com/google/go-github/github/issues_test.go new file mode 100644 index 0000000..f69efd3 --- /dev/null +++ b/vendor/github.com/google/go-github/github/issues_test.go @@ -0,0 +1,242 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestIssuesService_List_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "filter": "all", + "state": "closed", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListOptions{ + "all", "closed", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{Page: 1, PerPage: 2}, + } + issues, _, err := client.Issues.List(true, opt) + + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_List_owned(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.List(false, nil) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.ListByOrg("o", nil) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg_invalidOrg(t *testing.T) { + _, _, err := client.Issues.ListByOrg("%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_ListByRepo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "milestone": "*", + "state": "closed", + "assignee": "a", + "creator": "c", + "mentioned": "m", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListByRepoOptions{ + "*", "closed", "a", "c", "m", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{0, 0}, + } + issues, _, err := client.Issues.ListByRepo("o", "r", opt) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByRepo_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListByRepo("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1, "labels": [{"url": "u", "name": "n", "color": "c"}]}`) + }) + + issue, _, err := client.Issues.Get("o", "r", 1) + if err != nil { + t.Errorf("Issues.Get returned error: %v", err) + } + + want := &Issue{ + Number: Int(1), + Labels: []Label{{ + URL: String("u"), + Name: String("n"), + Color: String("c"), + }}, + } + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Get returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Get_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Get("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_Create(t *testing.T) { + setup() + defer teardown() + + input := &IssueRequest{ + Title: String("t"), + Body: String("b"), + Assignee: String("a"), + Labels: &[]string{"l1", "l2"}, + } + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + issue, _, err := client.Issues.Create("o", "r", input) + if err != nil { + t.Errorf("Issues.Create returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Create returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Create_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Create("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &IssueRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + issue, _, err := client.Issues.Edit("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.Edit returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Edit returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Edit("%", "r", 1, nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/licenses.go b/vendor/github.com/google/go-github/github/licenses.go new file mode 100644 index 0000000..fb2fb5a --- /dev/null +++ b/vendor/github.com/google/go-github/github/licenses.go @@ -0,0 +1,81 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// LicensesService handles communication with the license related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/pulls/ +type LicensesService struct { + client *Client +} + +// License represents an open source license. +type License struct { + Key *string `json:"key,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + + HTMLURL *string `json:"html_url,omitempty"` + Featured *bool `json:"featured,omitempty"` + Description *string `json:"description,omitempty"` + Category *string `json:"category,omitempty"` + Implementation *string `json:"implementation,omitempty"` + Required *[]string `json:"required,omitempty"` + Permitted *[]string `json:"permitted,omitempty"` + Forbidden *[]string `json:"forbidden,omitempty"` + Body *string `json:"body,omitempty"` +} + +func (l License) String() string { + return Stringify(l) +} + +// List popular open source licenses. +// +// GitHub API docs: https://developer.github.com/v3/licenses/#list-all-licenses +func (s *LicensesService) List() ([]License, *Response, error) { + req, err := s.client.NewRequest("GET", "licenses", nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeLicensesPreview) + + licenses := new([]License) + resp, err := s.client.Do(req, licenses) + if err != nil { + return nil, resp, err + } + + return *licenses, resp, err +} + +// Get extended metadata for one license. +// +// GitHub API docs: https://developer.github.com/v3/licenses/#get-an-individual-license +func (s *LicensesService) Get(licenseName string) (*License, *Response, error) { + u := fmt.Sprintf("licenses/%s", licenseName) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeLicensesPreview) + + license := new(License) + resp, err := s.client.Do(req, license) + if err != nil { + return nil, resp, err + } + + return license, resp, err +} diff --git a/vendor/github.com/google/go-github/github/licenses_test.go b/vendor/github.com/google/go-github/github/licenses_test.go new file mode 100644 index 0000000..dfecfeb --- /dev/null +++ b/vendor/github.com/google/go-github/github/licenses_test.go @@ -0,0 +1,64 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestLicensesService_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/licenses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + fmt.Fprint(w, `[{"key":"mit","name":"MIT","url":"https://api.github.com/licenses/mit"}]`) + }) + + licenses, _, err := client.Licenses.List() + if err != nil { + t.Errorf("Licenses.List returned error: %v", err) + } + + want := []License{{ + Key: String("mit"), + Name: String("MIT"), + URL: String("https://api.github.com/licenses/mit"), + }} + if !reflect.DeepEqual(licenses, want) { + t.Errorf("Licenses.List returned %+v, want %+v", licenses, want) + } +} + +func TestLicensesService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/licenses/mit", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + fmt.Fprint(w, `{"key":"mit","name":"MIT"}`) + }) + + license, _, err := client.Licenses.Get("mit") + if err != nil { + t.Errorf("Licenses.Get returned error: %v", err) + } + + want := &License{Key: String("mit"), Name: String("MIT")} + if !reflect.DeepEqual(license, want) { + t.Errorf("Licenses.Get returned %+v, want %+v", license, want) + } +} + +func TestLicensesService_Get_invalidTemplate(t *testing.T) { + _, _, err := client.Licenses.Get("%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/misc.go b/vendor/github.com/google/go-github/github/misc.go new file mode 100644 index 0000000..66e7f52 --- /dev/null +++ b/vendor/github.com/google/go-github/github/misc.go @@ -0,0 +1,197 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "fmt" + "net/url" +) + +// MarkdownOptions specifies optional parameters to the Markdown method. +type MarkdownOptions struct { + // Mode identifies the rendering mode. Possible values are: + // markdown - render a document as plain Markdown, just like + // README files are rendered. + // + // gfm - to render a document as user-content, e.g. like user + // comments or issues are rendered. In GFM mode, hard line breaks are + // always taken into account, and issue and user mentions are linked + // accordingly. + // + // Default is "markdown". + Mode string + + // Context identifies the repository context. Only taken into account + // when rendering as "gfm". + Context string +} + +type markdownRequest struct { + Text *string `json:"text,omitempty"` + Mode *string `json:"mode,omitempty"` + Context *string `json:"context,omitempty"` +} + +// Markdown renders an arbitrary Markdown document. +// +// GitHub API docs: https://developer.github.com/v3/markdown/ +func (c *Client) Markdown(text string, opt *MarkdownOptions) (string, *Response, error) { + request := &markdownRequest{Text: String(text)} + if opt != nil { + if opt.Mode != "" { + request.Mode = String(opt.Mode) + } + if opt.Context != "" { + request.Context = String(opt.Context) + } + } + + req, err := c.NewRequest("POST", "markdown", request) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// ListEmojis returns the emojis available to use on GitHub. +// +// GitHub API docs: https://developer.github.com/v3/emojis/ +func (c *Client) ListEmojis() (map[string]string, *Response, error) { + req, err := c.NewRequest("GET", "emojis", nil) + if err != nil { + return nil, nil, err + } + + var emoji map[string]string + resp, err := c.Do(req, &emoji) + if err != nil { + return nil, resp, err + } + + return emoji, resp, nil +} + +// APIMeta represents metadata about the GitHub API. +type APIMeta struct { + // An Array of IP addresses in CIDR format specifying the addresses + // that incoming service hooks will originate from on GitHub.com. + Hooks []string `json:"hooks,omitempty"` + + // An Array of IP addresses in CIDR format specifying the Git servers + // for GitHub.com. + Git []string `json:"git,omitempty"` + + // Whether authentication with username and password is supported. + // (GitHub Enterprise instances using CAS or OAuth for authentication + // will return false. Features like Basic Authentication with a + // username and password, sudo mode, and two-factor authentication are + // not supported on these servers.) + VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"` + + // An array of IP addresses in CIDR format specifying the addresses + // which serve GitHub Pages websites. + Pages []string `json:"pages,omitempty"` +} + +// APIMeta returns information about GitHub.com, the service. Or, if you access +// this endpoint on your organization’s GitHub Enterprise installation, this +// endpoint provides information about that installation. +// +// GitHub API docs: https://developer.github.com/v3/meta/ +func (c *Client) APIMeta() (*APIMeta, *Response, error) { + req, err := c.NewRequest("GET", "meta", nil) + if err != nil { + return nil, nil, err + } + + meta := new(APIMeta) + resp, err := c.Do(req, meta) + if err != nil { + return nil, resp, err + } + + return meta, resp, nil +} + +// Octocat returns an ASCII art octocat with the specified message in a speech +// bubble. If message is empty, a random zen phrase is used. +func (c *Client) Octocat(message string) (string, *Response, error) { + u := "octocat" + if message != "" { + u = fmt.Sprintf("%s?s=%s", u, url.QueryEscape(message)) + } + + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// Zen returns a random line from The Zen of GitHub. +// +// see also: http://warpspire.com/posts/taste/ +func (c *Client) Zen() (string, *Response, error) { + req, err := c.NewRequest("GET", "zen", nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// ServiceHook represents a hook that has configuration settings, a list of +// available events, and default events. +type ServiceHook struct { + Name *string `json:"name,omitempty"` + Events []string `json:"events,omitempty"` + SupportedEvents []string `json:"supported_events,omitempty"` + Schema [][]string `json:"schema,omitempty"` +} + +func (s *ServiceHook) String() string { + return Stringify(s) +} + +// ListServiceHooks lists all of the available service hooks. +// +// GitHub API docs: https://developer.github.com/webhooks/#services +func (c *Client) ListServiceHooks() ([]ServiceHook, *Response, error) { + u := "hooks" + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + hooks := new([]ServiceHook) + resp, err := c.Do(req, hooks) + if err != nil { + return nil, resp, err + } + + return *hooks, resp, err +} diff --git a/vendor/github.com/google/go-github/github/misc_test.go b/vendor/github.com/google/go-github/github/misc_test.go new file mode 100644 index 0000000..8ca58d2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/misc_test.go @@ -0,0 +1,170 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestMarkdown(t *testing.T) { + setup() + defer teardown() + + input := &markdownRequest{ + Text: String("# text #"), + Mode: String("gfm"), + Context: String("google/go-github"), + } + mux.HandleFunc("/markdown", func(w http.ResponseWriter, r *http.Request) { + v := new(markdownRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `

text

`) + }) + + md, _, err := client.Markdown("# text #", &MarkdownOptions{ + Mode: "gfm", + Context: "google/go-github", + }) + if err != nil { + t.Errorf("Markdown returned error: %v", err) + } + + if want := "

text

"; want != md { + t.Errorf("Markdown returned %+v, want %+v", md, want) + } +} + +func TestListEmojis(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/emojis", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"+1": "+1.png"}`) + }) + + emoji, _, err := client.ListEmojis() + if err != nil { + t.Errorf("ListEmojis returned error: %v", err) + } + + want := map[string]string{"+1": "+1.png"} + if !reflect.DeepEqual(want, emoji) { + t.Errorf("ListEmojis returned %+v, want %+v", emoji, want) + } +} + +func TestAPIMeta(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "pages":["p"], "verifiable_password_authentication": true}`) + }) + + meta, _, err := client.APIMeta() + if err != nil { + t.Errorf("APIMeta returned error: %v", err) + } + + want := &APIMeta{ + Hooks: []string{"h"}, + Git: []string{"g"}, + Pages: []string{"p"}, + VerifiablePasswordAuthentication: Bool(true), + } + if !reflect.DeepEqual(want, meta) { + t.Errorf("APIMeta returned %+v, want %+v", meta, want) + } +} + +func TestOctocat(t *testing.T) { + setup() + defer teardown() + + input := "input" + output := "sample text" + + mux.HandleFunc("/octocat", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"s": input}) + w.Header().Set("Content-Type", "application/octocat-stream") + fmt.Fprint(w, output) + }) + + got, _, err := client.Octocat(input) + if err != nil { + t.Errorf("Octocat returned error: %v", err) + } + + if want := output; got != want { + t.Errorf("Octocat returned %+v, want %+v", got, want) + } +} + +func TestZen(t *testing.T) { + setup() + defer teardown() + + output := "sample text" + + mux.HandleFunc("/zen", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + fmt.Fprint(w, output) + }) + + got, _, err := client.Zen() + if err != nil { + t.Errorf("Zen returned error: %v", err) + } + + if want := output; got != want { + t.Errorf("Zen returned %+v, want %+v", got, want) + } +} + +func TestRepositoriesService_ListServiceHooks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "name":"n", + "events":["e"], + "supported_events":["s"], + "schema":[ + ["a", "b"] + ] + }]`) + }) + + hooks, _, err := client.Repositories.ListServiceHooks() + if err != nil { + t.Errorf("Repositories.ListHooks returned error: %v", err) + } + + want := []ServiceHook{{ + Name: String("n"), + Events: []string{"e"}, + SupportedEvents: []string{"s"}, + Schema: [][]string{{"a", "b"}}, + }} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Repositories.ListServiceHooks returned %+v, want %+v", hooks, want) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs.go b/vendor/github.com/google/go-github/github/orgs.go new file mode 100644 index 0000000..7596873 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs.go @@ -0,0 +1,137 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// OrganizationsService provides access to the organization related functions +// in the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/orgs/ +type OrganizationsService struct { + client *Client +} + +// Organization represents a GitHub organization account. +type Organization struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + BillingEmail *string `json:"billing_email,omitempty"` + Type *string `json:"type,omitempty"` + Plan *Plan `json:"plan,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + MembersURL *string `json:"members_url,omitempty"` + PublicMembersURL *string `json:"public_members_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` +} + +func (o Organization) String() string { + return Stringify(o) +} + +// Plan represents the payment plan for an account. See plans at https://github.com/plans. +type Plan struct { + Name *string `json:"name,omitempty"` + Space *int `json:"space,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + PrivateRepos *int `json:"private_repos,omitempty"` +} + +func (p Plan) String() string { + return Stringify(p) +} + +// List the organizations for a user. Passing the empty string will list +// organizations for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#list-user-organizations +func (s *OrganizationsService) List(user string, opt *ListOptions) ([]Organization, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/orgs", user) + } else { + u = "user/orgs" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + orgs := new([]Organization) + resp, err := s.client.Do(req, orgs) + if err != nil { + return nil, resp, err + } + + return *orgs, resp, err +} + +// Get fetches an organization by name. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#get-an-organization +func (s *OrganizationsService) Get(org string) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", org) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + organization := new(Organization) + resp, err := s.client.Do(req, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, err +} + +// Edit an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#edit-an-organization +func (s *OrganizationsService) Edit(name string, org *Organization) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", name) + req, err := s.client.NewRequest("PATCH", u, org) + if err != nil { + return nil, nil, err + } + + o := new(Organization) + resp, err := s.client.Do(req, o) + if err != nil { + return nil, resp, err + } + + return o, resp, err +} diff --git a/vendor/github.com/google/go-github/github/orgs_hooks.go b/vendor/github.com/google/go-github/github/orgs_hooks.go new file mode 100644 index 0000000..3e7ad40 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_hooks.go @@ -0,0 +1,104 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// ListHooks lists all Hooks for the specified organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#list-hooks +func (s *OrganizationsService) ListHooks(org string, opt *ListOptions) ([]Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + hooks := new([]Hook) + resp, err := s.client.Do(req, hooks) + if err != nil { + return nil, resp, err + } + + return *hooks, resp, err +} + +// GetHook returns a single specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook +func (s *OrganizationsService) GetHook(org string, id int) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + hook := new(Hook) + resp, err := s.client.Do(req, hook) + return hook, resp, err +} + +// CreateHook creates a Hook for the specified org. +// Name and Config are required fields. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook +func (s *OrganizationsService) CreateHook(org string, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks", org) + req, err := s.client.NewRequest("POST", u, hook) + if err != nil { + return nil, nil, err + } + + h := new(Hook) + resp, err := s.client.Do(req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, err +} + +// EditHook updates a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#edit-a-hook +func (s *OrganizationsService) EditHook(org string, id int, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("PATCH", u, hook) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := s.client.Do(req, h) + return h, resp, err +} + +// PingHook triggers a 'ping' event to be sent to the Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook +func (s *OrganizationsService) PingHook(org string, id int) (*Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d/pings", org, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// DeleteHook deletes a specified Hook. +// +// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#delete-a-hook +func (s *OrganizationsService) DeleteHook(org string, id int) (*Response, error) { + u := fmt.Sprintf("orgs/%v/hooks/%d", org, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_hooks_test.go b/vendor/github.com/google/go-github/github/orgs_hooks_test.go new file mode 100644 index 0000000..1ebc07d --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_hooks_test.go @@ -0,0 +1,134 @@ +// Copyright 2015 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListHooks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + + hooks, _, err := client.Organizations.ListHooks("o", opt) + if err != nil { + t.Errorf("Organizations.ListHooks returned error: %v", err) + } + + want := []Hook{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Organizations.ListHooks returned %+v, want %+v", hooks, want) + } +} + +func TestOrganizationsService_ListHooks_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.ListHooks("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_GetHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Organizations.GetHook("o", 1) + if err != nil { + t.Errorf("Organizations.GetHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Organizations.GetHook returned %+v, want %+v", hook, want) + } +} + +func TestOrganizationsService_GetHook_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.GetHook("%", 1) + testURLParseError(t, err) +} + +func TestOrganizationsService_EditHook(t *testing.T) { + setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Organizations.EditHook("o", 1, input) + if err != nil { + t.Errorf("Organizations.EditHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Organizations.EditHook returned %+v, want %+v", hook, want) + } +} + +func TestOrganizationsService_EditHook_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.EditHook("%", 1, nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_PingHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Organizations.PingHook("o", 1) + if err != nil { + t.Errorf("Organizations.PingHook returned error: %v", err) + } +} + +func TestOrganizationsService_DeleteHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.DeleteHook("o", 1) + if err != nil { + t.Errorf("Organizations.DeleteHook returned error: %v", err) + } +} + +func TestOrganizationsService_DeleteHook_invalidOrg(t *testing.T) { + _, err := client.Organizations.DeleteHook("%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/orgs_members.go b/vendor/github.com/google/go-github/github/orgs_members.go new file mode 100644 index 0000000..c326ff8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_members.go @@ -0,0 +1,276 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Membership represents the status of a user's membership in an organization or team. +type Membership struct { + URL *string `json:"url,omitempty"` + + // State is the user's status within the organization or team. + // Possible values are: "active", "pending" + State *string `json:"state,omitempty"` + + // Role identifies the user's role within the organization or team. + // Possible values for organization membership: + // member - non-owner organization member + // admin - organization owner + // + // Possible values for team membership are: + // member - a normal member of the team + // maintainer - a team maintainer. Able to add/remove other team + // members, promote other team members to team + // maintainer, and edit the team’s name and description + Role *string `json:"role,omitempty"` + + // For organization membership, the API URL of the organization. + OrganizationURL *string `json:"organization_url,omitempty"` + + // For organization membership, the organization the membership is for. + Organization *Organization `json:"organization,omitempty"` + + // For organization membership, the user the membership is for. + User *User `json:"user,omitempty"` +} + +func (m Membership) String() string { + return Stringify(m) +} + +// ListMembersOptions specifies optional parameters to the +// OrganizationsService.ListMembers method. +type ListMembersOptions struct { + // If true (or if the authenticated user is not an owner of the + // organization), list only publicly visible members. + PublicOnly bool `url:"-"` + + // Filter members returned in the list. Possible values are: + // 2fa_disabled, all. Default is "all". + Filter string `url:"filter,omitempty"` + + // Role filters memebers returned by their role in the organization. + // Possible values are: + // all - all members of the organization, regardless of role + // admin - organization owners + // member - non-organization members + // + // Default is "all". + Role string `url:"role,omitempty"` + + ListOptions +} + +// ListMembers lists the members for an organization. If the authenticated +// user is an owner of the organization, this will return both concealed and +// public members, otherwise it will only return public members. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#members-list +func (s *OrganizationsService) ListMembers(org string, opt *ListMembersOptions) ([]User, *Response, error) { + var u string + if opt != nil && opt.PublicOnly { + u = fmt.Sprintf("orgs/%v/public_members", org) + } else { + u = fmt.Sprintf("orgs/%v/members", org) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + if opt != nil && opt.Role != "" { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + members := new([]User) + resp, err := s.client.Do(req, members) + if err != nil { + return nil, resp, err + } + + return *members, resp, err +} + +// IsMember checks if a user is a member of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-membership +func (s *OrganizationsService) IsMember(org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// IsPublicMember checks if a user is a public member of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-public-membership +func (s *OrganizationsService) IsPublicMember(org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// RemoveMember removes a user from all teams of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#remove-a-member +func (s *OrganizationsService) RemoveMember(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// PublicizeMembership publicizes a user's membership in an organization. (A +// user cannot publicize the membership for another user.) +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership +func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ConcealMembership conceals a user's membership in an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#conceal-a-users-membership +func (s *OrganizationsService) ConcealMembership(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListOrgMembershipsOptions specifies optional parameters to the +// OrganizationsService.ListOrgMemberships method. +type ListOrgMembershipsOptions struct { + // Filter memberships to include only those with the specified state. + // Possible values are: "active", "pending". + State string `url:"state,omitempty"` + + ListOptions +} + +// ListOrgMemberships lists the organization memberships for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-your-organization-memberships +func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions) ([]Membership, *Response, error) { + u := "user/memberships/orgs" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var memberships []Membership + resp, err := s.client.Do(req, &memberships) + if err != nil { + return nil, resp, err + } + + return memberships, resp, err +} + +// GetOrgMembership gets the membership for a user in a specified organization. +// Passing an empty string for user will get the membership for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-organization-membership +// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership +func (s *OrganizationsService) GetOrgMembership(user, org string) (*Membership, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("orgs/%v/memberships/%v", org, user) + } else { + u = fmt.Sprintf("user/memberships/orgs/%v", org) + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + membership := new(Membership) + resp, err := s.client.Do(req, membership) + if err != nil { + return nil, resp, err + } + + return membership, resp, err +} + +// EditOrgMembership edits the membership for user in specified organization. +// Passing an empty string for user will edit the membership for the +// authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#add-or-update-organization-membership +// GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership +func (s *OrganizationsService) EditOrgMembership(user, org string, membership *Membership) (*Membership, *Response, error) { + var u, method string + if user != "" { + u = fmt.Sprintf("orgs/%v/memberships/%v", org, user) + method = "PUT" + } else { + u = fmt.Sprintf("user/memberships/orgs/%v", org) + method = "PATCH" + } + + req, err := s.client.NewRequest(method, u, membership) + if err != nil { + return nil, nil, err + } + + m := new(Membership) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// RemoveOrgMembership removes user from the specified organization. If the +// user has been invited to the organization, this will cancel their invitation. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#remove-organization-membership +func (s *OrganizationsService) RemoveOrgMembership(user, org string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/memberships/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_members_test.go b/vendor/github.com/google/go-github/github/orgs_members_test.go new file mode 100644 index 0000000..6613efe --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_members_test.go @@ -0,0 +1,356 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListMembers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + testFormValues(t, r, values{ + "filter": "2fa_disabled", + "role": "admin", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{ + PublicOnly: false, + Filter: "2fa_disabled", + Role: "admin", + ListOptions: ListOptions{Page: 2}, + } + members, _, err := client.Organizations.ListMembers("o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_ListMembers_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.ListMembers("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_ListMembers_public(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{PublicOnly: true} + members, _, err := client.Organizations.ListMembers("o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsMember_notMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsMember_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.IsMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_IsPublicMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsPublicMember_notMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsPublicMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsPublicMember_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.IsPublicMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.RemoveMember("o", "u") + if err != nil { + t.Errorf("Organizations.RemoveMember returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) { + _, err := client.Organizations.RemoveMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ListOrgMemberships(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "active", + "page": "2", + }) + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + opt := &ListOrgMembershipsOptions{ + State: "active", + ListOptions: ListOptions{Page: 2}, + } + memberships, _, err := client.Organizations.ListOrgMemberships(opt) + if err != nil { + t.Errorf("Organizations.ListOrgMemberships returned error: %v", err) + } + + want := []Membership{{URL: String("u")}} + if !reflect.DeepEqual(memberships, want) { + t.Errorf("Organizations.ListOrgMemberships returned %+v, want %+v", memberships, want) + } +} + +func TestOrganizationsService_GetOrgMembership_AuthenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.GetOrgMembership("", "o") + if err != nil { + t.Errorf("Organizations.GetOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_GetOrgMembership_SpecifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.GetOrgMembership("u", "o") + if err != nil { + t.Errorf("Organizations.GetOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_EditOrgMembership_AuthenticatedUser(t *testing.T) { + setup() + defer teardown() + + input := &Membership{State: String("active")} + + mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { + v := new(Membership) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.EditOrgMembership("", "o", input) + if err != nil { + t.Errorf("Organizations.EditOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_EditOrgMembership_SpecifiedUser(t *testing.T) { + setup() + defer teardown() + + input := &Membership{State: String("active")} + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + v := new(Membership) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.EditOrgMembership("u", "o", input) + if err != nil { + t.Errorf("Organizations.EditOrgMembership returned error: %v", err) + } + + want := &Membership{URL: String("u")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_RemoveOrgMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveOrgMembership("u", "o") + if err != nil { + t.Errorf("Organizations.RemoveOrgMembership returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs_teams.go b/vendor/github.com/google/go-github/github/orgs_teams.go new file mode 100644 index 0000000..858c545 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_teams.go @@ -0,0 +1,396 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Team represents a team within a GitHub organization. Teams are used to +// manage access to an organization's repositories. +type Team struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + Slug *string `json:"slug,omitempty"` + + // Permission is deprecated when creating or editing a team in an org + // using the new GitHub permission model. It no longer identifies the + // permission a team has on its repos, but only specifies the default + // permission a repo is initially added with. Avoid confusion by + // specifying a permission value when calling AddTeamRepo. + Permission *string `json:"permission,omitempty"` + + // Privacy identifies the level of privacy this team should have. + // Possible values are: + // secret - only visible to organization owners and members of this team + // closed - visible to all members of this organization + // Default is "secret". + Privacy *string `json:"privacy,omitempty"` + + MembersCount *int `json:"members_count,omitempty"` + ReposCount *int `json:"repos_count,omitempty"` + Organization *Organization `json:"organization,omitempty"` +} + +func (t Team) String() string { + return Stringify(t) +} + +// ListTeams lists all of the teams for an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-teams +func (s *OrganizationsService) ListTeams(org string, opt *ListOptions) ([]Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// GetTeam fetches a team by ID. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team +func (s *OrganizationsService) GetTeam(team int) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// CreateTeam creates a new team within an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#create-team +func (s *OrganizationsService) CreateTeam(org string, team *Team) (*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + req, err := s.client.NewRequest("POST", u, team) + if err != nil { + return nil, nil, err + } + + if team.Privacy != nil { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// EditTeam edits a team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#edit-team +func (s *OrganizationsService) EditTeam(id int, team *Team) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", id) + req, err := s.client.NewRequest("PATCH", u, team) + if err != nil { + return nil, nil, err + } + + if team.Privacy != nil { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// DeleteTeam deletes a team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#delete-team +func (s *OrganizationsService) DeleteTeam(team int) (*Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// OrganizationListTeamMembersOptions specifies the optional parameters to the +// OrganizationsService.ListTeamMembers method. +type OrganizationListTeamMembersOptions struct { + // Role filters members returned by their role in the team. Possible + // values are "all", "member", "maintainer". Default is "all". + Role string `url:"role,omitempty"` + + ListOptions +} + +// ListTeamMembers lists all of the users who are members of the specified +// team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-members +func (s *OrganizationsService) ListTeamMembers(team int, opt *OrganizationListTeamMembersOptions) ([]User, *Response, error) { + u := fmt.Sprintf("teams/%v/members", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + if opt != nil && opt.Role != "" { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + members := new([]User) + resp, err := s.client.Do(req, members) + if err != nil { + return nil, resp, err + } + + return *members, resp, err +} + +// IsTeamMember checks if a user is a member of the specified team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-member +func (s *OrganizationsService) IsTeamMember(team int, user string) (bool, *Response, error) { + u := fmt.Sprintf("teams/%v/members/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// ListTeamRepos lists the repositories that the specified team has access to. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-repos +func (s *OrganizationsService) ListTeamRepos(team int, opt *ListOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos", team) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// IsTeamRepo checks if a team manages the specified repository. If the +// repository is managed by team, a Repository is returned which includes the +// permissions team has for that repo. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-repo +func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeOrgPermissionRepoPreview) + + repository := new(Repository) + resp, err := s.client.Do(req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, err +} + +// OrganizationAddTeamRepoOptions specifies the optional parameters to the +// OrganizationsService.AddTeamRepo method. +type OrganizationAddTeamRepoOptions struct { + // Permission specifies the permission to grant the team on this repository. + // Possible values are: + // pull - team members can pull, but not push to or administer this repository + // push - team members can pull and push, but not administer this repository + // admin - team members can pull, push and administer this repository + // + // If not specified, the team's permission attribute will be used. + Permission string `json:"permission,omitempty"` +} + +// AddTeamRepo adds a repository to be managed by the specified team. The +// specified repository must be owned by the organization to which the team +// belongs, or a direct fork of a repository owned by the organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-repo +func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string, opt *OrganizationAddTeamRepoOptions) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + if opt != nil { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + return s.client.Do(req, nil) +} + +// RemoveTeamRepo removes a repository from being managed by the specified +// team. Note that this does not delete the repository, it just removes it +// from the team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#remove-team-repo +func (s *OrganizationsService) RemoveTeamRepo(team int, owner string, repo string) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListUserTeams lists a user's teams +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams +func (s *OrganizationsService) ListUserTeams(opt *ListOptions) ([]Team, *Response, error) { + u := "user/teams" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// GetTeamMembership returns the membership status for a user in a team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership +func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Membership) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// OrganizationAddTeamMembershipOptions does stuff specifies the optional +// parameters to the OrganizationsService.AddTeamMembership method. +type OrganizationAddTeamMembershipOptions struct { + // Role specifies the role the user should have in the team. Possible + // values are: + // member - a normal member of the team + // maintainer - a team maintainer. Able to add/remove other team + // members, promote other team members to team + // maintainer, and edit the team’s name and description + // + // Default value is "member". + Role string `json:"role,omitempty"` +} + +// AddTeamMembership adds or invites a user to a team. +// +// In order to add a membership between a user and a team, the authenticated +// user must have 'admin' permissions to the team or be an owner of the +// organization that the team is associated with. +// +// If the user is already a part of the team's organization (meaning they're on +// at least one other team in the organization), this endpoint will add the +// user to the team. +// +// If the user is completely unaffiliated with the team's organization (meaning +// they're on none of the organization's teams), this endpoint will send an +// invitation to the user via email. This newly-created membership will be in +// the "pending" state until the user accepts the invitation, at which point +// the membership will transition to the "active" state and the user will be +// added as a member of the team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership +func (s *OrganizationsService) AddTeamMembership(team int, user string, opt *OrganizationAddTeamMembershipOptions) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + + if opt != nil { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + t := new(Membership) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// RemoveTeamMembership removes a user from a team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-membership +func (s *OrganizationsService) RemoveTeamMembership(team int, user string) (*Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/orgs_teams_test.go b/vendor/github.com/google/go-github/github/orgs_teams_test.go new file mode 100644 index 0000000..a258137 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_teams_test.go @@ -0,0 +1,506 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_ListTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Organizations.ListTeams("o", opt) + if err != nil { + t.Errorf("Organizations.ListTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Organizations.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestOrganizationsService_ListTeams_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.ListTeams("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_GetTeam(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "name":"n", "url":"u", "slug": "s", "permission":"p"}`) + }) + + team, _, err := client.Organizations.GetTeam(1) + if err != nil { + t.Errorf("Organizations.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int(1), Name: String("n"), URL: String("u"), Slug: String("s"), Permission: String("p")} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_CreateTeam(t *testing.T) { + setup() + defer teardown() + + input := &Team{Name: String("n"), Privacy: String("closed")} + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + v := new(Team) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Organizations.CreateTeam("o", input) + if err != nil { + t.Errorf("Organizations.CreateTeam returned error: %v", err) + } + + want := &Team{ID: Int(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.CreateTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_CreateTeam_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.CreateTeam("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_EditTeam(t *testing.T) { + setup() + defer teardown() + + input := &Team{Name: String("n"), Privacy: String("closed")} + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Team) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + team, _, err := client.Organizations.EditTeam(1, input) + if err != nil { + t.Errorf("Organizations.EditTeam returned error: %v", err) + } + + want := &Team{ID: Int(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.EditTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_DeleteTeam(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.DeleteTeam(1) + if err != nil { + t.Errorf("Organizations.DeleteTeam returned error: %v", err) + } +} + +func TestOrganizationsService_ListTeamMembers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + testFormValues(t, r, values{"role": "member", "page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &OrganizationListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}} + members, _, err := client.Organizations.ListTeamMembers(1, opt) + if err != nil { + t.Errorf("Organizations.ListTeamMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListTeamMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsTeamMember_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err != nil { + t.Errorf("Organizations.IsTeamMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsTeamMember_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err != nil { + t.Errorf("Organizations.IsTeamMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsTeamMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsTeamMember_invalidUser(t *testing.T) { + _, _, err := client.Organizations.IsTeamMember(1, "%") + testURLParseError(t, err) +} + +func TestOrganizationsService_PublicizeMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.PublicizeMembership("o", "u") + if err != nil { + t.Errorf("Organizations.PublicizeMembership returned error: %v", err) + } +} + +func TestOrganizationsService_PublicizeMembership_invalidOrg(t *testing.T) { + _, err := client.Organizations.PublicizeMembership("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ConcealMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.ConcealMembership("o", "u") + if err != nil { + t.Errorf("Organizations.ConcealMembership returned error: %v", err) + } +} + +func TestOrganizationsService_ConcealMembership_invalidOrg(t *testing.T) { + _, err := client.Organizations.ConcealMembership("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ListTeamRepos(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + members, _, err := client.Organizations.ListTeamRepos(1, opt) + if err != nil { + t.Errorf("Organizations.ListTeamRepos returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListTeamRepos returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsTeamRepo_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeOrgPermissionRepoPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.IsTeamRepo returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", repo, want) + } +} + +func TestOrganizationsService_IsTeamRepo_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 404 response") + } + if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { + t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestOrganizationsService_IsTeamRepo_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want) + } + if repo != nil { + t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo) + } +} + +func TestOrganizationsService_IsTeamRepo_invalidOwner(t *testing.T) { + _, _, err := client.Organizations.IsTeamRepo(1, "%", "r") + testURLParseError(t, err) +} + +func TestOrganizationsService_AddTeamRepo(t *testing.T) { + setup() + defer teardown() + + opt := &OrganizationAddTeamRepoOptions{Permission: "admin"} + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(OrganizationAddTeamRepoOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.AddTeamRepo(1, "o", "r", opt) + if err != nil { + t.Errorf("Organizations.AddTeamRepo returned error: %v", err) + } +} + +func TestOrganizationsService_AddTeamRepo_noAccess(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(422) + }) + + _, err := client.Organizations.AddTeamRepo(1, "o", "r", nil) + if err == nil { + t.Errorf("Expcted error to be returned") + } +} + +func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) { + _, err := client.Organizations.AddTeamRepo(1, "%", "r", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveTeamRepo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.RemoveTeamRepo returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveTeamRepo_invalidOwner(t *testing.T) { + _, err := client.Organizations.RemoveTeamRepo(1, "%", "r") + testURLParseError(t, err) +} + +func TestOrganizationsService_GetTeamMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u", "state":"active"}`) + }) + + membership, _, err := client.Organizations.GetTeamMembership(1, "u") + if err != nil { + t.Errorf("Organizations.GetTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("active")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_AddTeamMembership(t *testing.T) { + setup() + defer teardown() + + opt := &OrganizationAddTeamMembershipOptions{Role: "maintainer"} + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + v := new(OrganizationAddTeamMembershipOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + fmt.Fprint(w, `{"url":"u", "state":"pending"}`) + }) + + membership, _, err := client.Organizations.AddTeamMembership(1, "u", opt) + if err != nil { + t.Errorf("Organizations.AddTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("pending")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.AddTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_RemoveTeamMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveTeamMembership(1, "u") + if err != nil { + t.Errorf("Organizations.RemoveTeamMembership returned error: %v", err) + } +} + +func TestOrganizationsService_ListUserTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1} + teams, _, err := client.Organizations.ListUserTeams(opt) + if err != nil { + t.Errorf("Organizations.ListUserTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Organizations.ListUserTeams returned %+v, want %+v", teams, want) + } +} diff --git a/vendor/github.com/google/go-github/github/orgs_test.go b/vendor/github.com/google/go-github/github/orgs_test.go new file mode 100644 index 0000000..84ebc54 --- /dev/null +++ b/vendor/github.com/google/go-github/github/orgs_test.go @@ -0,0 +1,120 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestOrganizationsService_List_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + orgs, _, err := client.Organizations.List("", nil) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []Organization{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + orgs, _, err := client.Organizations.List("u", opt) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []Organization{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_invalidUser(t *testing.T) { + _, _, err := client.Organizations.List("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "login":"l", "url":"u", "avatar_url": "a", "location":"l"}`) + }) + + org, _, err := client.Organizations.Get("o") + if err != nil { + t.Errorf("Organizations.Get returned error: %v", err) + } + + want := &Organization{ID: Int(1), Login: String("l"), URL: String("u"), AvatarURL: String("a"), Location: String("l")} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Get returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Get_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.Get("%") + testURLParseError(t, err) +} + +func TestOrganizationsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &Organization{Login: String("l")} + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + v := new(Organization) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + org, _, err := client.Organizations.Edit("o", input) + if err != nil { + t.Errorf("Organizations.Edit returned error: %v", err) + } + + want := &Organization{ID: Int(1)} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Edit returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Edit_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.Edit("%", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/pulls.go b/vendor/github.com/google/go-github/github/pulls.go new file mode 100644 index 0000000..71cf2e2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls.go @@ -0,0 +1,275 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// PullRequestsService handles communication with the pull request related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/pulls/ +type PullRequestsService struct { + client *Client +} + +// PullRequest represents a GitHub pull request on a repository. +type PullRequest struct { + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + MergedAt *time.Time `json:"merged_at,omitempty"` + User *User `json:"user,omitempty"` + Merged *bool `json:"merged,omitempty"` + Mergeable *bool `json:"mergeable,omitempty"` + MergedBy *User `json:"merged_by,omitempty"` + Comments *int `json:"comments,omitempty"` + Commits *int `json:"commits,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + ChangedFiles *int `json:"changed_files,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` + + Head *PullRequestBranch `json:"head,omitempty"` + Base *PullRequestBranch `json:"base,omitempty"` +} + +func (p PullRequest) String() string { + return Stringify(p) +} + +// PullRequestBranch represents a base or head branch in a GitHub pull request. +type PullRequestBranch struct { + Label *string `json:"label,omitempty"` + Ref *string `json:"ref,omitempty"` + SHA *string `json:"sha,omitempty"` + Repo *Repository `json:"repo,omitempty"` + User *User `json:"user,omitempty"` +} + +// PullRequestListOptions specifies the optional parameters to the +// PullRequestsService.List method. +type PullRequestListOptions struct { + // State filters pull requests based on their state. Possible values are: + // open, closed. Default is "open". + State string `url:"state,omitempty"` + + // Head filters pull requests by head user and branch name in the format of: + // "user:ref-name". + Head string `url:"head,omitempty"` + + // Base filters pull requests by base branch name. + Base string `url:"base,omitempty"` + + // Sort specifies how to sort pull requests. Possible values are: created, + // updated, popularity, long-running. Default is "created". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort pull requests. Possible values are: asc, desc. + // If Sort is "created" or not specified, Default is "desc", otherwise Default + // is "asc" + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// List the pull requests for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/pulls/#list-pull-requests +func (s *PullRequestsService) List(owner string, repo string, opt *PullRequestListOptions) ([]PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + pulls := new([]PullRequest) + resp, err := s.client.Do(req, pulls) + if err != nil { + return nil, resp, err + } + + return *pulls, resp, err +} + +// Get a single pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request +func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + pull := new(PullRequest) + resp, err := s.client.Do(req, pull) + if err != nil { + return nil, resp, err + } + + return pull, resp, err +} + +// NewPullRequest represents a new pull request to be created. +type NewPullRequest struct { + Title *string `json:"title,omitempty"` + Head *string `json:"head,omitempty"` + Base *string `json:"base,omitempty"` + Body *string `json:"body,omitempty"` + Issue *int `json:"issue,omitempty"` +} + +// Create a new pull request on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request +func (s *PullRequestsService) Create(owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + req, err := s.client.NewRequest("POST", u, pull) + if err != nil { + return nil, nil, err + } + + p := new(PullRequest) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// Edit a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request +func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, pull) + if err != nil { + return nil, nil, err + } + + p := new(PullRequest) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ListCommits lists the commits in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request +func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) ([]RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commits := new([]RepositoryCommit) + resp, err := s.client.Do(req, commits) + if err != nil { + return nil, resp, err + } + + return *commits, resp, err +} + +// ListFiles lists the files in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files +func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) ([]CommitFile, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commitFiles := new([]CommitFile) + resp, err := s.client.Do(req, commitFiles) + if err != nil { + return nil, resp, err + } + + return *commitFiles, resp, err +} + +// IsMerged checks if a pull request has been merged. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged +func (s *PullRequestsService) IsMerged(owner string, repo string, number int) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + merged, err := parseBoolResponse(err) + return merged, resp, err +} + +// PullRequestMergeResult represents the result of merging a pull request. +type PullRequestMergeResult struct { + SHA *string `json:"sha,omitempty"` + Merged *bool `json:"merged,omitempty"` + Message *string `json:"message,omitempty"` +} + +type pullRequestMergeRequest struct { + CommitMessage *string `json:"commit_message"` +} + +// Merge a pull request (Merge Button™). +// +// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade +func (s *PullRequestsService) Merge(owner string, repo string, number int, commitMessage string) (*PullRequestMergeResult, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + + req, err := s.client.NewRequest("PUT", u, &pullRequestMergeRequest{ + CommitMessage: &commitMessage, + }) + + if err != nil { + return nil, nil, err + } + + mergeResult := new(PullRequestMergeResult) + resp, err := s.client.Do(req, mergeResult) + if err != nil { + return nil, resp, err + } + + return mergeResult, resp, err +} diff --git a/vendor/github.com/google/go-github/github/pulls_comments.go b/vendor/github.com/google/go-github/github/pulls_comments.go new file mode 100644 index 0000000..f165d5f --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_comments.go @@ -0,0 +1,145 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// PullRequestComment represents a comment left on a pull request. +type PullRequestComment struct { + ID *int `json:"id,omitempty"` + Body *string `json:"body,omitempty"` + Path *string `json:"path,omitempty"` + DiffHunk *string `json:"diff_hunk,omitempty"` + Position *int `json:"position,omitempty"` + OriginalPosition *int `json:"original_position,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + OriginalCommitID *string `json:"original_commit_id,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (p PullRequestComment) String() string { + return Stringify(p) +} + +// PullRequestListCommentsOptions specifies the optional parameters to the +// PullRequestsService.ListComments method. +type PullRequestListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified pull request. Specifying a +// pull request number of 0 will return all comments on all pull requests for +// the repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request +func (s *PullRequestsService) ListComments(owner string, repo string, number int, opt *PullRequestListCommentsOptions) ([]PullRequestComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/pulls/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comments := new([]PullRequestComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment fetches the specified pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment +func (s *PullRequestsService) GetComment(owner string, repo string, number int) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comment := new(PullRequestComment) + resp, err := s.client.Do(req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, err +} + +// CreateComment creates a new comment on the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment +func (s *PullRequestsService) CreateComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment updates a pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#edit-a-comment +func (s *PullRequestsService) EditComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#delete-a-comment +func (s *PullRequestsService) DeleteComment(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/pulls_comments_test.go b/vendor/github.com/google/go-github/github/pulls_comments_test.go new file mode 100644 index 0000000..7885ab1 --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_comments_test.go @@ -0,0 +1,189 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestPullRequestsService_ListComments_allPulls(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &PullRequestListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + pulls, _, err := client.PullRequests.ListComments("o", "r", 0, opt) + + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []PullRequestComment{{ID: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_specificPull(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + pulls, _, err := client.PullRequests.ListComments("o", "r", 1, nil) + + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []PullRequestComment{{ID: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.ListComments("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.GetComment("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.GetComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_GetComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.GetComment("%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.CreateComment("o", "r", 1, input) + + if err != nil { + t.Errorf("PullRequests.CreateComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_CreateComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.CreateComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.EditComment("o", "r", 1, input) + + if err != nil { + t.Errorf("PullRequests.EditComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_EditComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.EditComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.PullRequests.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("PullRequests.DeleteComment returned error: %v", err) + } +} + +func TestPullRequestsService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.PullRequests.DeleteComment("%", "r", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/pulls_test.go b/vendor/github.com/google/go-github/github/pulls_test.go new file mode 100644 index 0000000..6ac0ddb --- /dev/null +++ b/vendor/github.com/google/go-github/github/pulls_test.go @@ -0,0 +1,365 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestPullRequestsService_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "closed", + "head": "h", + "base": "b", + "sort": "created", + "direction": "desc", + "page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &PullRequestListOptions{"closed", "h", "b", "created", "desc", ListOptions{Page: 2}} + pulls, _, err := client.PullRequests.List("o", "r", opt) + + if err != nil { + t.Errorf("PullRequests.List returned error: %v", err) + } + + want := []PullRequest{{Number: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.List returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_List_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.List("%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Get("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Get_headAndBase(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1,"head":{"ref":"r2","repo":{"id":2}},"base":{"ref":"r1","repo":{"id":1}}}`) + }) + + pull, _, err := client.PullRequests.Get("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{ + Number: Int(1), + Head: &PullRequestBranch{ + Ref: String("r2"), + Repo: &Repository{ID: Int(2)}, + }, + Base: &PullRequestBranch{ + Ref: String("r1"), + Repo: &Repository{ID: Int(1)}, + }, + } + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestService_Get_DiffURLAndPatchURL(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1, + "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", + "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"}`) + }) + + pull, _, err := client.PullRequests.Get("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{Number: Int(1), DiffURL: String("https://github.com/octocat/Hello-World/pull/1347.diff"), PatchURL: String("https://github.com/octocat/Hello-World/pull/1347.patch")} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Get_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.Get("%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_Create(t *testing.T) { + setup() + defer teardown() + + input := &NewPullRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + v := new(NewPullRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Create("o", "r", input) + if err != nil { + t.Errorf("PullRequests.Create returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Create returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Create_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.Create("%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &PullRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Edit("o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.Edit returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Edit returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.Edit("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_ListCommits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "3", + "parents": [ + { + "sha": "2" + } + ] + }, + { + "sha": "2", + "parents": [ + { + "sha": "1" + } + ] + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commits, _, err := client.PullRequests.ListCommits("o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListCommits returned error: %v", err) + } + + want := []RepositoryCommit{ + { + SHA: String("3"), + Parents: []Commit{ + { + SHA: String("2"), + }, + }, + }, + { + SHA: String("2"), + Parents: []Commit{ + { + SHA: String("1"), + }, + }, + }, + } + if !reflect.DeepEqual(commits, want) { + t.Errorf("PullRequests.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestPullRequestsService_ListFiles(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/files", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "filename": "file1.txt", + "status": "added", + "additions": 103, + "deletions": 21, + "changes": 124, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + }, + { + "sha": "f61aebed695e2e4193db5e6dcb09b5b57875f334", + "filename": "file2.txt", + "status": "modified", + "additions": 5, + "deletions": 3, + "changes": 103, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commitFiles, _, err := client.PullRequests.ListFiles("o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListFiles returned error: %v", err) + } + + want := []CommitFile{ + { + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Filename: String("file1.txt"), + Additions: Int(103), + Deletions: Int(21), + Changes: Int(124), + Status: String("added"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + { + SHA: String("f61aebed695e2e4193db5e6dcb09b5b57875f334"), + Filename: String("file2.txt"), + Additions: Int(5), + Deletions: Int(3), + Changes: Int(103), + Status: String("modified"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + } + + if !reflect.DeepEqual(commitFiles, want) { + t.Errorf("PullRequests.ListFiles returned %+v, want %+v", commitFiles, want) + } +} + +func TestPullRequestsService_IsMerged(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isMerged, _, err := client.PullRequests.IsMerged("o", "r", 1) + if err != nil { + t.Errorf("PullRequests.IsMerged returned error: %v", err) + } + + want := true + if !reflect.DeepEqual(isMerged, want) { + t.Errorf("PullRequests.IsMerged returned %+v, want %+v", isMerged, want) + } +} + +func TestPullRequestsService_Merge(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, ` + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "merged": true, + "message": "Pull Request successfully merged" + }`) + }) + + merge, _, err := client.PullRequests.Merge("o", "r", 1, "merging pull request") + if err != nil { + t.Errorf("PullRequests.Merge returned error: %v", err) + } + + want := &PullRequestMergeResult{ + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Merged: Bool(true), + Message: String("Pull Request successfully merged"), + } + if !reflect.DeepEqual(merge, want) { + t.Errorf("PullRequests.Merge returned %+v, want %+v", merge, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos.go b/vendor/github.com/google/go-github/github/repos.go new file mode 100644 index 0000000..2e9b29e --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos.go @@ -0,0 +1,496 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// RepositoriesService handles communication with the repository related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/repos/ +type RepositoriesService struct { + client *Client +} + +// Repository represents a GitHub repository. +type Repository struct { + ID *int `json:"id,omitempty"` + Owner *User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Description *string `json:"description,omitempty"` + Homepage *string `json:"homepage,omitempty"` + DefaultBranch *string `json:"default_branch,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PushedAt *Timestamp `json:"pushed_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CloneURL *string `json:"clone_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + MirrorURL *string `json:"mirror_url,omitempty"` + SSHURL *string `json:"ssh_url,omitempty"` + SVNURL *string `json:"svn_url,omitempty"` + Language *string `json:"language,omitempty"` + Fork *bool `json:"fork"` + ForksCount *int `json:"forks_count,omitempty"` + NetworkCount *int `json:"network_count,omitempty"` + OpenIssuesCount *int `json:"open_issues_count,omitempty"` + StargazersCount *int `json:"stargazers_count,omitempty"` + SubscribersCount *int `json:"subscribers_count,omitempty"` + WatchersCount *int `json:"watchers_count,omitempty"` + Size *int `json:"size,omitempty"` + AutoInit *bool `json:"auto_init,omitempty"` + Parent *Repository `json:"parent,omitempty"` + Source *Repository `json:"source,omitempty"` + Organization *Organization `json:"organization,omitempty"` + Permissions *map[string]bool `json:"permissions,omitempty"` + + // Only provided when using RepositoriesService.Get while in preview + License *License `json:"license,omitempty"` + + // Additional mutable fields when creating and editing a repository + Private *bool `json:"private"` + HasIssues *bool `json:"has_issues"` + HasWiki *bool `json:"has_wiki"` + HasDownloads *bool `json:"has_downloads"` + // Creating an organization repository. Required for non-owners. + TeamID *int `json:"team_id"` + + // API URLs + URL *string `json:"url,omitempty"` + ArchiveURL *string `json:"archive_url,omitempty"` + AssigneesURL *string `json:"assignees_url,omitempty"` + BlobsURL *string `json:"blobs_url,omitempty"` + BranchesURL *string `json:"branches_url,omitempty"` + CollaboratorsURL *string `json:"collaborators_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + CommitsURL *string `json:"commits_url,omitempty"` + CompareURL *string `json:"compare_url,omitempty"` + ContentsURL *string `json:"contents_url,omitempty"` + ContributorsURL *string `json:"contributors_url,omitempty"` + DownloadsURL *string `json:"downloads_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ForksURL *string `json:"forks_url,omitempty"` + GitCommitsURL *string `json:"git_commits_url,omitempty"` + GitRefsURL *string `json:"git_refs_url,omitempty"` + GitTagsURL *string `json:"git_tags_url,omitempty"` + HooksURL *string `json:"hooks_url,omitempty"` + IssueCommentURL *string `json:"issue_comment_url,omitempty"` + IssueEventsURL *string `json:"issue_events_url,omitempty"` + IssuesURL *string `json:"issues_url,omitempty"` + KeysURL *string `json:"keys_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + LanguagesURL *string `json:"languages_url,omitempty"` + MergesURL *string `json:"merges_url,omitempty"` + MilestonesURL *string `json:"milestones_url,omitempty"` + NotificationsURL *string `json:"notifications_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + ReleasesURL *string `json:"releases_url,omitempty"` + StargazersURL *string `json:"stargazers_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + SubscribersURL *string `json:"subscribers_url,omitempty"` + SubscriptionURL *string `json:"subscription_url,omitempty"` + TagsURL *string `json:"tags_url,omitempty"` + TreesURL *string `json:"trees_url,omitempty"` + TeamsURL *string `json:"teams_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (r Repository) String() string { + return Stringify(r) +} + +// RepositoryListOptions specifies the optional parameters to the +// RepositoriesService.List method. +type RepositoryListOptions struct { + // Type of repositories to list. Possible values are: all, owner, public, + // private, member. Default is "all". + Type string `url:"type,omitempty"` + + // How to sort the repository list. Possible values are: created, updated, + // pushed, full_name. Default is "full_name". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Possible values are: asc, desc. + // Default is "asc" when sort is "full_name", otherwise default is "desc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// List the repositories for a user. Passing the empty string will list +// repositories for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-user-repositories +func (s *RepositoriesService) List(user string, opt *RepositoryListOptions) ([]Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/repos", user) + } else { + u = "user/repos" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when license support fully launches + req.Header.Set("Accept", mediaTypeLicensesPreview) + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryListByOrgOptions specifies the optional parameters to the +// RepositoriesService.ListByOrg method. +type RepositoryListByOrgOptions struct { + // Type of repositories to list. Possible values are: all, public, private, + // forks, sources, member. Default is "all". + Type string `url:"type,omitempty"` + + ListOptions +} + +// ListByOrg lists the repositories for an organization. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-organization-repositories +func (s *RepositoriesService) ListByOrg(org string, opt *RepositoryListByOrgOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("orgs/%v/repos", org) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when license support fully launches + req.Header.Set("Accept", mediaTypeLicensesPreview) + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryListAllOptions specifies the optional parameters to the +// RepositoriesService.ListAll method. +type RepositoryListAllOptions struct { + // ID of the last repository seen + Since int `url:"since,omitempty"` + + ListOptions +} + +// ListAll lists all GitHub repositories in the order that they were created. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-all-public-repositories +func (s *RepositoriesService) ListAll(opt *RepositoryListAllOptions) ([]Repository, *Response, error) { + u, err := addOptions("repositories", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// Create a new repository. If an organization is specified, the new +// repository will be created under that org. If the empty string is +// specified, it will be created for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/repos/#create +func (s *RepositoriesService) Create(org string, repo *Repository) (*Repository, *Response, error) { + var u string + if org != "" { + u = fmt.Sprintf("orgs/%v/repos", org) + } else { + u = "user/repos" + } + + req, err := s.client.NewRequest("POST", u, repo) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// Get fetches a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#get +func (s *RepositoriesService) Get(owner, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when the license support fully launches + // https://developer.github.com/v3/licenses/#get-a-repositorys-license + req.Header.Set("Accept", mediaTypeLicensesPreview) + + repository := new(Repository) + resp, err := s.client.Do(req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, err +} + +// Edit updates a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#edit +func (s *RepositoriesService) Edit(owner, repo string, repository *Repository) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("PATCH", u, repository) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// Delete a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#delete-a-repository +func (s *RepositoriesService) Delete(owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Contributor represents a repository contributor +type Contributor struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin"` + Contributions *int `json:"contributions,omitempty"` +} + +// ListContributorsOptions specifies the optional parameters to the +// RepositoriesService.ListContributors method. +type ListContributorsOptions struct { + // Include anonymous contributors in results or not + Anon string `url:"anon,omitempty"` + + ListOptions +} + +// ListContributors lists contributors for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-contributors +func (s *RepositoriesService) ListContributors(owner string, repository string, opt *ListContributorsOptions) ([]Contributor, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/contributors", owner, repository) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + contributor := new([]Contributor) + resp, err := s.client.Do(req, contributor) + if err != nil { + return nil, nil, err + } + + return *contributor, resp, err +} + +// ListLanguages lists languages for the specified repository. The returned map +// specifies the languages and the number of bytes of code written in that +// language. For example: +// +// { +// "C": 78769, +// "Python": 7769 +// } +// +// GitHub API Docs: http://developer.github.com/v3/repos/#list-languages +func (s *RepositoriesService) ListLanguages(owner string, repo string) (map[string]int, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/languages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + languages := make(map[string]int) + resp, err := s.client.Do(req, &languages) + if err != nil { + return nil, resp, err + } + + return languages, resp, err +} + +// ListTeams lists the teams for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-teams +func (s *RepositoriesService) ListTeams(owner string, repo string, opt *ListOptions) ([]Team, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/teams", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// RepositoryTag represents a repository tag. +type RepositoryTag struct { + Name *string `json:"name,omitempty"` + Commit *Commit `json:"commit,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` +} + +// ListTags lists tags for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-tags +func (s *RepositoriesService) ListTags(owner string, repo string, opt *ListOptions) ([]RepositoryTag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + tags := new([]RepositoryTag) + resp, err := s.client.Do(req, tags) + if err != nil { + return nil, resp, err + } + + return *tags, resp, err +} + +// Branch represents a repository branch +type Branch struct { + Name *string `json:"name,omitempty"` + Commit *Commit `json:"commit,omitempty"` +} + +// ListBranches lists branches for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-branches +func (s *RepositoriesService) ListBranches(owner string, repo string, opt *ListOptions) ([]Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + branches := new([]Branch) + resp, err := s.client.Do(req, branches) + if err != nil { + return nil, resp, err + } + + return *branches, resp, err +} + +// GetBranch gets the specified branch for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#get-branch +func (s *RepositoriesService) GetBranch(owner, repo, branch string) (*Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_collaborators.go b/vendor/github.com/google/go-github/github/repos_collaborators.go new file mode 100644 index 0000000..61dc4ef --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_collaborators.go @@ -0,0 +1,95 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// ListCollaborators lists the Github users that have access to the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#list +func (s *RepositoriesService) ListCollaborators(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// IsCollaborator checks whether the specified Github user has collaborator +// access to the given repo. +// Note: This will return false if the user is not a collaborator OR the user +// is not a GitHub user. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#get +func (s *RepositoriesService) IsCollaborator(owner, repo, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + isCollab, err := parseBoolResponse(err) + return isCollab, resp, err +} + +// RepositoryAddCollaboratorOptions specifies the optional parameters to the +// RepositoriesService.AddCollaborator method. +type RepositoryAddCollaboratorOptions struct { + // Permission specifies the permission to grant the user on this repository. + // Possible values are: + // pull - team members can pull, but not push to or administer this repository + // push - team members can pull and push, but not administer this repository + // admin - team members can pull, push and administer this repository + // + // Default value is "pull". This option is only valid for organization-owned repositories. + Permission string `json:"permission,omitempty"` +} + +// AddCollaborator adds the specified Github user as collaborator to the given repo. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator +func (s *RepositoriesService) AddCollaborator(owner, repo, user string, opt *RepositoryAddCollaboratorOptions) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, err + } + + if opt != nil { + req.Header.Set("Accept", mediaTypeOrgPermissionPreview) + } + + return s.client.Do(req, nil) +} + +// RemoveCollaborator removes the specified Github user as collaborator from the given repo. +// Note: Does not return error if a valid user that is not a collaborator is removed. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#remove-collaborator +func (s *RepositoriesService) RemoveCollaborator(owner, repo, user string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_collaborators_test.go b/vendor/github.com/google/go-github/github/repos_collaborators_test.go new file mode 100644 index 0000000..ee6c498 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_collaborators_test.go @@ -0,0 +1,135 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListCollaborators(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + users, _, err := client.Repositories.ListCollaborators("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCollaborators returned error: %v", err) + } + + want := []User{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want) + } +} + +func TestRepositoriesService_ListCollaborators_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListCollaborators("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_IsCollaborator_True(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isCollab, _, err := client.Repositories.IsCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if !isCollab { + t.Errorf("Repositories.IsCollaborator returned false, want true") + } +} + +func TestRepositoriesService_IsCollaborator_False(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + isCollab, _, err := client.Repositories.IsCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if isCollab { + t.Errorf("Repositories.IsCollaborator returned true, want false") + } +} + +func TestRepositoriesService_IsCollaborator_invalidUser(t *testing.T) { + _, _, err := client.Repositories.IsCollaborator("%", "%", "%") + testURLParseError(t, err) +} + +func TestRepositoriesService_AddCollaborator(t *testing.T) { + setup() + defer teardown() + + opt := &RepositoryAddCollaboratorOptions{Permission: "admin"} + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryAddCollaboratorOptions) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview) + if !reflect.DeepEqual(v, opt) { + t.Errorf("Request body = %+v, want %+v", v, opt) + } + + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.AddCollaborator("o", "r", "u", opt) + if err != nil { + t.Errorf("Repositories.AddCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) { + _, err := client.Repositories.AddCollaborator("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_RemoveCollaborator(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemoveCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.RemoveCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_RemoveCollaborator_invalidUser(t *testing.T) { + _, err := client.Repositories.RemoveCollaborator("%", "%", "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_comments.go b/vendor/github.com/google/go-github/github/repos_comments.go new file mode 100644 index 0000000..2d090bb --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_comments.go @@ -0,0 +1,150 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// RepositoryComment represents a comment for a commit, file, or line in a repository. +type RepositoryComment struct { + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + ID *int `json:"id,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + + // User-mutable fields + Body *string `json:"body"` + // User-initialized fields + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` +} + +func (r RepositoryComment) String() string { + return Stringify(r) +} + +// ListComments lists all the comments for the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#list-commit-comments-for-a-repository +func (s *RepositoriesService) ListComments(owner, repo string, opt *ListOptions) ([]RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comments := new([]RepositoryComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// ListCommitComments lists all the comments for a given commit SHA. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit +func (s *RepositoriesService) ListCommitComments(owner, repo, sha string, opt *ListOptions) ([]RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comments := new([]RepositoryComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// CreateComment creates a comment for the given commit. +// Note: GitHub allows for comments to be created for non-existing files and positions. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#create-a-commit-comment +func (s *RepositoriesService) CreateComment(owner, repo, sha string, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// GetComment gets a single comment from a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#get-a-single-commit-comment +func (s *RepositoriesService) GetComment(owner, repo string, id int) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// UpdateComment updates the body of a single comment. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#update-a-commit-comment +func (s *RepositoriesService) UpdateComment(owner, repo string, id int, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a single comment from a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#delete-a-commit-comment +func (s *RepositoriesService) DeleteComment(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_comments_test.go b/vendor/github.com/google/go-github/github/repos_comments_test.go new file mode 100644 index 0000000..b5a8786 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_comments_test.go @@ -0,0 +1,180 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Repositories.ListComments("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListComments returned error: %v", err) + } + + want := []RepositoryComment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListComments("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListCommitComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Repositories.ListCommitComments("o", "r", "s", opt) + if err != nil { + t.Errorf("Repositories.ListCommitComments returned error: %v", err) + } + + want := []RepositoryComment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListCommitComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListCommitComments_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListCommitComments("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/commits/s/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.CreateComment("o", "r", "s", input) + if err != nil { + t.Errorf("Repositories.CreateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_CreateComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateComment("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.GetComment("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_GetComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetComment("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_UpdateComment(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.UpdateComment("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.UpdateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.UpdateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_UpdateComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.UpdateComment("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteComment returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteComment("%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_commits.go b/vendor/github.com/google/go-github/github/repos_commits.go new file mode 100644 index 0000000..6401cb4 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_commits.go @@ -0,0 +1,168 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// RepositoryCommit represents a commit in a repo. +// Note that it's wrapping a Commit, so author/committer information is in two places, +// but contain different details about them: in RepositoryCommit "github details", in Commit - "git details". +type RepositoryCommit struct { + SHA *string `json:"sha,omitempty"` + Commit *Commit `json:"commit,omitempty"` + Author *User `json:"author,omitempty"` + Committer *User `json:"committer,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Message *string `json:"message,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + + // Details about how many changes were made in this commit. Only filled in during GetCommit! + Stats *CommitStats `json:"stats,omitempty"` + // Details about which files, and how this commit touched. Only filled in during GetCommit! + Files []CommitFile `json:"files,omitempty"` +} + +func (r RepositoryCommit) String() string { + return Stringify(r) +} + +// CommitStats represents the number of additions / deletions from a file in a given RepositoryCommit. +type CommitStats struct { + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Total *int `json:"total,omitempty"` +} + +func (c CommitStats) String() string { + return Stringify(c) +} + +// CommitFile represents a file modified in a commit. +type CommitFile struct { + SHA *string `json:"sha,omitempty"` + Filename *string `json:"filename,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Changes *int `json:"changes,omitempty"` + Status *string `json:"status,omitempty"` + Patch *string `json:"patch,omitempty"` +} + +func (c CommitFile) String() string { + return Stringify(c) +} + +// CommitsComparison is the result of comparing two commits. +// See CompareCommits() for details. +type CommitsComparison struct { + BaseCommit *RepositoryCommit `json:"base_commit,omitempty"` + MergeBaseCommit *RepositoryCommit `json:"merge_base_commit,omitempty"` + + // Head can be 'behind' or 'ahead' + Status *string `json:"status,omitempty"` + AheadBy *int `json:"ahead_by,omitempty"` + BehindBy *int `json:"behind_by,omitempty"` + TotalCommits *int `json:"total_commits,omitempty"` + + Commits []RepositoryCommit `json:"commits,omitempty"` + + Files []CommitFile `json:"files,omitempty"` +} + +func (c CommitsComparison) String() string { + return Stringify(c) +} + +// CommitsListOptions specifies the optional parameters to the +// RepositoriesService.ListCommits method. +type CommitsListOptions struct { + // SHA or branch to start listing Commits from. + SHA string `url:"sha,omitempty"` + + // Path that should be touched by the returned Commits. + Path string `url:"path,omitempty"` + + // Author of by which to filter Commits. + Author string `url:"author,omitempty"` + + // Since when should Commits be included in the response. + Since time.Time `url:"since,omitempty"` + + // Until when should Commits be included in the response. + Until time.Time `url:"until,omitempty"` + + ListOptions +} + +// ListCommits lists the commits of a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#list +func (s *RepositoriesService) ListCommits(owner, repo string, opt *CommitsListOptions) ([]RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commits := new([]RepositoryCommit) + resp, err := s.client.Do(req, commits) + if err != nil { + return nil, resp, err + } + + return *commits, resp, err +} + +// GetCommit fetches the specified commit, including all details about it. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#get-a-single-commit +// See also: http://developer.github.com//v3/git/commits/#get-a-single-commit provides the same functionality +func (s *RepositoriesService) GetCommit(owner, repo, sha string) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, err +} + +// CompareCommits compares a range of commits with each other. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/index.html#compare-two-commits +func (s *RepositoriesService) CompareCommits(owner, repo string, base, head string) (*CommitsComparison, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/compare/%v...%v", owner, repo, base, head) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comp := new(CommitsComparison) + resp, err := s.client.Do(req, comp) + if err != nil { + return nil, resp, err + } + + return comp, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_commits_test.go b/vendor/github.com/google/go-github/github/repos_commits_test.go new file mode 100644 index 0000000..56ba8a5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_commits_test.go @@ -0,0 +1,191 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_ListCommits(t *testing.T) { + setup() + defer teardown() + + // given + mux.HandleFunc("/repos/o/r/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, + values{ + "sha": "s", + "path": "p", + "author": "a", + "since": "2013-08-01T00:00:00Z", + "until": "2013-09-03T00:00:00Z", + }) + fmt.Fprintf(w, `[{"sha": "s"}]`) + }) + + opt := &CommitsListOptions{ + SHA: "s", + Path: "p", + Author: "a", + Since: time.Date(2013, time.August, 1, 0, 0, 0, 0, time.UTC), + Until: time.Date(2013, time.September, 3, 0, 0, 0, 0, time.UTC), + } + commits, _, err := client.Repositories.ListCommits("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCommits returned error: %v", err) + } + + want := []RepositoryCommit{{SHA: String("s")}} + if !reflect.DeepEqual(commits, want) { + t.Errorf("Repositories.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestRepositoriesService_GetCommit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "sha": "s", + "commit": { "message": "m" }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ], + "stats": { "additions": 104, "deletions": 4, "total": 108 }, + "files": [ + { + "filename": "f", + "additions": 10, + "deletions": 2, + "changes": 12, + "status": "s", + "raw_url": "r", + "blob_url": "b", + "patch": "p" + } + ] + }`) + }) + + commit, _, err := client.Repositories.GetCommit("o", "r", "s") + if err != nil { + t.Errorf("Repositories.GetCommit returned error: %v", err) + } + + want := &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + Author: &User{ + Login: String("l"), + }, + Committer: &User{ + Login: String("l"), + }, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + Stats: &CommitStats{ + Additions: Int(104), + Deletions: Int(4), + Total: Int(108), + }, + Files: []CommitFile{ + { + Filename: String("f"), + Additions: Int(10), + Deletions: Int(2), + Changes: Int(12), + Status: String("s"), + Patch: String("p"), + }, + }, + } + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.GetCommit returned \n%+v, want \n%+v", commit, want) + } +} + +func TestRepositoriesService_CompareCommits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/compare/b...h", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "base_commit": { + "sha": "s", + "commit": { + "author": { "name": "n" }, + "committer": { "name": "n" }, + "message": "m", + "tree": { "sha": "t" } + }, + "author": { "login": "n" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + }, + "status": "s", + "ahead_by": 1, + "behind_by": 2, + "total_commits": 1, + "commits": [ + { + "sha": "s", + "commit": { "author": { "name": "n" } }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + } + ], + "files": [ { "filename": "f" } ] + }`) + }) + + got, _, err := client.Repositories.CompareCommits("o", "r", "b", "h") + if err != nil { + t.Errorf("Repositories.CompareCommits returned error: %v", err) + } + + want := &CommitsComparison{ + Status: String("s"), + AheadBy: Int(1), + BehindBy: Int(2), + TotalCommits: Int(1), + BaseCommit: &RepositoryCommit{ + Commit: &Commit{ + Author: &CommitAuthor{Name: String("n")}, + }, + Author: &User{Login: String("l")}, + Committer: &User{Login: String("l")}, + Message: String("m"), + }, + Commits: []RepositoryCommit{ + { + SHA: String("s"), + }, + }, + Files: []CommitFile{ + { + Filename: String("f"), + }, + }, + } + + if reflect.DeepEqual(got, want) { + t.Errorf("Repositories.CompareCommits returned \n%+v, want \n%+v", got, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_contents.go b/vendor/github.com/google/go-github/github/repos_contents.go new file mode 100644 index 0000000..80776f2 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_contents.go @@ -0,0 +1,248 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Repository contents API methods. +// http://developer.github.com/v3/repos/contents/ + +package github + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "path" +) + +// RepositoryContent represents a file or directory in a github repository. +type RepositoryContent struct { + Type *string `json:"type,omitempty"` + Encoding *string `json:"encoding,omitempty"` + Size *int `json:"size,omitempty"` + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + Content *string `json:"content,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DownloadURL *string `json:"download_url,omitempty"` +} + +// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentResponse struct { + Content *RepositoryContent `json:"content,omitempty"` + Commit `json:"commit,omitempty"` +} + +// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentFileOptions struct { + Message *string `json:"message,omitempty"` + Content []byte `json:"content,omitempty"` + SHA *string `json:"sha,omitempty"` + Branch *string `json:"branch,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` +} + +// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA, +// branch, or tag +type RepositoryContentGetOptions struct { + Ref string `url:"ref,omitempty"` +} + +func (r RepositoryContent) String() string { + return Stringify(r) +} + +// Decode decodes the file content if it is base64 encoded. +func (r *RepositoryContent) Decode() ([]byte, error) { + if *r.Encoding != "base64" { + return nil, errors.New("cannot decode non-base64") + } + o, err := base64.StdEncoding.DecodeString(*r.Content) + if err != nil { + return nil, err + } + return o, nil +} + +// GetReadme gets the Readme file for the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme +func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/readme", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + readme := new(RepositoryContent) + resp, err := s.client.Do(req, readme) + if err != nil { + return nil, resp, err + } + return readme, resp, err +} + +// DownloadContents returns an io.ReadCloser that reads the contents of the +// specified file. This function will work with files of any size, as opposed +// to GetContents which is limited to 1 Mb files. It is the caller's +// responsibility to close the ReadCloser. +func (s *RepositoriesService) DownloadContents(owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) { + dir := path.Dir(filepath) + filename := path.Base(filepath) + _, dirContents, _, err := s.GetContents(owner, repo, dir, opt) + if err != nil { + return nil, err + } + for _, contents := range dirContents { + if *contents.Name == filename { + if contents.DownloadURL == nil || *contents.DownloadURL == "" { + return nil, fmt.Errorf("No download link found for %s", filepath) + } + resp, err := s.client.client.Get(*contents.DownloadURL) + if err != nil { + return nil, err + } + return resp.Body, nil + } + } + return nil, fmt.Errorf("No file named %s found in %s", filename, dir) +} + +// GetContents can return either the metadata and content of a single file +// (when path references a file) or the metadata of all the files and/or +// subdirectories of a directory (when path references a directory). To make it +// easy to distinguish between both result types and to mimic the API as much +// as possible, both result types will be returned but only one will contain a +// value and the other will be nil. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents +func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent, + directoryContent []*RepositoryContent, resp *Response, err error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + u, err = addOptions(u, opt) + if err != nil { + return nil, nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, nil, err + } + var rawJSON json.RawMessage + resp, err = s.client.Do(req, &rawJSON) + if err != nil { + return nil, nil, resp, err + } + fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent) + if fileUnmarshalError == nil { + return fileContent, nil, resp, fileUnmarshalError + } + directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent) + if directoryUnmarshalError == nil { + return nil, directoryContent, resp, directoryUnmarshalError + } + return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError) +} + +// CreateFile creates a new file in a repository at the given path and returns +// the commit and file metadata. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file +func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + createResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, createResponse) + if err != nil { + return nil, resp, err + } + return createResponse, resp, err +} + +// UpdateFile updates a file in a repository at the given path and returns the +// commit and file metadata. Requires the blob SHA of the file being updated. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file +func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + updateResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, updateResponse) + if err != nil { + return nil, resp, err + } + return updateResponse, resp, err +} + +// DeleteFile deletes a file from a repository and returns the commit. +// Requires the blob SHA of the file to be deleted. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file +func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("DELETE", u, opt) + if err != nil { + return nil, nil, err + } + deleteResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, deleteResponse) + if err != nil { + return nil, resp, err + } + return deleteResponse, resp, err +} + +// archiveFormat is used to define the archive type when calling GetArchiveLink. +type archiveFormat string + +const ( + // Tarball specifies an archive in gzipped tar format. + Tarball archiveFormat = "tarball" + + // Zipball specifies an archive in zip format. + Zipball archiveFormat = "zipball" +) + +// GetArchiveLink returns an URL to download a tarball or zipball archive for a +// repository. The archiveFormat can be specified by either the github.Tarball +// or github.Zipball constant. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link +func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat) + if opt != nil && opt.Ref != "" { + u += fmt.Sprintf("/%s", opt.Ref) + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var resp *http.Response + // Use http.DefaultTransport if no custom Transport is configured + if s.client.client.Transport == nil { + resp, err = http.DefaultTransport.RoundTrip(req) + } else { + resp, err = s.client.client.Transport.RoundTrip(req) + } + if err != nil || resp.StatusCode != http.StatusFound { + return nil, newResponse(resp), err + } + parsedURL, err := url.Parse(resp.Header.Get("Location")) + return parsedURL, newResponse(resp), err +} diff --git a/vendor/github.com/google/go-github/github/repos_contents_test.go b/vendor/github.com/google/go-github/github/repos_contents_test.go new file mode 100644 index 0000000..8ab3ecd --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_contents_test.go @@ -0,0 +1,304 @@ +package github + +import ( + "fmt" + "io/ioutil" + "net/http" + "reflect" + "testing" +) + +func TestDecode(t *testing.T) { + setup() + defer teardown() + r := RepositoryContent{Encoding: String("base64"), Content: String("aGVsbG8=")} + o, err := r.Decode() + if err != nil { + t.Errorf("Failed to decode content.") + } + want := "hello" + if string(o) != want { + t.Errorf("RepositoryContent.Decode returned %+v, want %+v", string(o), want) + } +} + +func TestDecodeBadEncoding(t *testing.T) { + setup() + defer teardown() + r := RepositoryContent{Encoding: String("bad")} + _, err := r.Decode() + if err == nil { + t.Errorf("Should fail to decode non-base64") + } +} + +func TestRepositoriesService_GetReadme(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/readme", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 5362, + "name": "README.md", + "path": "README.md" + }`) + }) + readme, _, err := client.Repositories.GetReadme("o", "r", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetReadme returned error: %v", err) + } + want := &RepositoryContent{Type: String("file"), Name: String("README.md"), Size: Int(5362), Encoding: String("base64"), Path: String("README.md")} + if !reflect.DeepEqual(readme, want) { + t.Errorf("Repositories.GetReadme returned %+v, want %+v", readme, want) + } +} + +func TestRepositoriesService_DownloadContents_Success(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "file", + "name": "f", + "download_url": "`+server.URL+`/download/f" + }]`) + }) + mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, "foo") + }) + + r, err := client.Repositories.DownloadContents("o", "r", "d/f", nil) + if err != nil { + t.Errorf("Repositories.DownloadContents returned error: %v", err) + } + + bytes, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("Error reading response body: %v", err) + } + r.Close() + + if got, want := string(bytes), "foo"; got != want { + t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want) + } +} + +func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "file", + "name": "f", + }]`) + }) + + _, err := client.Repositories.DownloadContents("o", "r", "d/f", nil) + if err == nil { + t.Errorf("Repositories.DownloadContents did not return expected error") + } +} + +func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[]`) + }) + + _, err := client.Repositories.DownloadContents("o", "r", "d/f", nil) + if err == nil { + t.Errorf("Repositories.DownloadContents did not return expected error") + } +} + +func TestRepositoriesService_GetContents_File(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }`) + }) + fileContents, _, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetContents returned error: %v", err) + } + want := &RepositoryContent{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Encoding: String("base64"), Path: String("LICENSE")} + if !reflect.DeepEqual(fileContents, want) { + t.Errorf("Repositories.GetContents returned %+v, want %+v", fileContents, want) + } +} + +func TestRepositoriesService_GetContents_Directory(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "dir", + "name": "lib", + "path": "lib" + }, + { + "type": "file", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }]`) + }) + _, directoryContents, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetContents returned error: %v", err) + } + want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")}, + {Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}} + if !reflect.DeepEqual(directoryContents, want) { + t.Errorf("Repositories.GetContents_Directory returned %+v, want %+v", directoryContents, want) + } +} + +func TestRepositoriesService_CreateFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + createResponse, _, err := client.Repositories.CreateFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.CreateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(createResponse, want) { + t.Errorf("Repositories.CreateFile returned %+v, want %+v", createResponse, want) + } +} + +func TestRepositoriesService_UpdateFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + updateResponse, _, err := client.Repositories.UpdateFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.UpdateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(updateResponse, want) { + t.Errorf("Repositories.UpdateFile returned %+v, want %+v", updateResponse, want) + } +} + +func TestRepositoriesService_DeleteFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, `{ + "content": null, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + deleteResponse, _, err := client.Repositories.DeleteFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.DeleteFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: nil, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(deleteResponse, want) { + t.Errorf("Repositories.DeleteFile returned %+v, want %+v", deleteResponse, want) + } +} + +func TestRepositoriesService_GetArchiveLink(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Redirect(w, r, "http://github.com/a", http.StatusFound) + }) + url, resp, err := client.Repositories.GetArchiveLink("o", "r", Tarball, &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetArchiveLink returned error: %v", err) + } + if resp.StatusCode != http.StatusFound { + t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound) + } + want := "http://github.com/a" + if url.String() != want { + t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_deployments.go b/vendor/github.com/google/go-github/github/repos_deployments.go new file mode 100644 index 0000000..77c7949 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_deployments.go @@ -0,0 +1,162 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" +) + +// Deployment represents a deployment in a repo +type Deployment struct { + URL *string `json:"url,omitempty"` + ID *int `json:"id,omitempty"` + SHA *string `json:"sha,omitempty"` + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + Payload json.RawMessage `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"pushed_at,omitempty"` +} + +// DeploymentRequest represents a deployment request +type DeploymentRequest struct { + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + AutoMerge *bool `json:"auto_merge,omitempty"` + RequiredContexts *[]string `json:"required_contexts,omitempty"` + Payload *string `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` +} + +// DeploymentsListOptions specifies the optional parameters to the +// RepositoriesService.ListDeployments method. +type DeploymentsListOptions struct { + // SHA of the Deployment. + SHA string `url:"sha,omitempty"` + + // List deployments for a given ref. + Ref string `url:"ref,omitempty"` + + // List deployments for a given task. + Task string `url:"task,omitempty"` + + // List deployments for a given environment. + Environment string `url:"environment,omitempty"` + + ListOptions +} + +// ListDeployments lists the deployments of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployments +func (s *RepositoriesService) ListDeployments(owner, repo string, opt *DeploymentsListOptions) ([]Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + deployments := new([]Deployment) + resp, err := s.client.Do(req, deployments) + if err != nil { + return nil, resp, err + } + + return *deployments, resp, err +} + +// CreateDeployment creates a new deployment for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment +func (s *RepositoriesService) CreateDeployment(owner, repo string, request *DeploymentRequest) (*Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + d := new(Deployment) + resp, err := s.client.Do(req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} + +// DeploymentStatus represents the status of a +// particular deployment. +type DeploymentStatus struct { + ID *int `json:"id,omitempty"` + State *string `json:"state,omitempty"` + Creator *User `json:"creator,omitempty"` + Description *string `json:"description,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"pushed_at,omitempty"` +} + +// DeploymentStatusRequest represents a deployment request +type DeploymentStatusRequest struct { + State *string `json:"state,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ListDeploymentStatuses lists the statuses of a given deployment of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployment-statuses +func (s *RepositoriesService) ListDeploymentStatuses(owner, repo string, deployment int, opt *ListOptions) ([]DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + statuses := new([]DeploymentStatus) + resp, err := s.client.Do(req, statuses) + if err != nil { + return nil, resp, err + } + + return *statuses, resp, err +} + +// CreateDeploymentStatus creates a new status for a deployment. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment-status +func (s *RepositoriesService) CreateDeploymentStatus(owner, repo string, deployment int, request *DeploymentStatusRequest) (*DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + d := new(DeploymentStatus) + resp, err := s.client.Do(req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_deployments_test.go b/vendor/github.com/google/go-github/github/repos_deployments_test.go new file mode 100644 index 0000000..161a07c --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_deployments_test.go @@ -0,0 +1,87 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListDeployments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"environment": "test"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &DeploymentsListOptions{Environment: "test"} + deployments, _, err := client.Repositories.ListDeployments("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListDeployments returned error: %v", err) + } + + want := []Deployment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(deployments, want) { + t.Errorf("Repositories.ListDeployments returned %+v, want %+v", deployments, want) + } +} + +func TestRepositoriesService_CreateDeployment(t *testing.T) { + setup() + defer teardown() + + input := &DeploymentRequest{Ref: String("1111"), Task: String("deploy")} + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + v := new(DeploymentRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ref": "1111", "task": "deploy"}`) + }) + + deployment, _, err := client.Repositories.CreateDeployment("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateDeployment returned error: %v", err) + } + + want := &Deployment{Ref: String("1111"), Task: String("deploy")} + if !reflect.DeepEqual(deployment, want) { + t.Errorf("Repositories.CreateDeployment returned %+v, want %+v", deployment, want) + } +} + +func TestRepositoriesService_ListDeploymentStatuses(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments/1/statuses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + statutses, _, err := client.Repositories.ListDeploymentStatuses("o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListDeploymentStatuses returned error: %v", err) + } + + want := []DeploymentStatus{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(statutses, want) { + t.Errorf("Repositories.ListDeploymentStatuses returned %+v, want %+v", statutses, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_forks.go b/vendor/github.com/google/go-github/github/repos_forks.go new file mode 100644 index 0000000..1fec829 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_forks.go @@ -0,0 +1,73 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// RepositoryListForksOptions specifies the optional parameters to the +// RepositoriesService.ListForks method. +type RepositoryListForksOptions struct { + // How to sort the forks list. Possible values are: newest, oldest, + // watchers. Default is "newest". + Sort string `url:"sort,omitempty"` + + ListOptions +} + +// ListForks lists the forks of the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/forks/#list-forks +func (s *RepositoriesService) ListForks(owner, repo string, opt *RepositoryListForksOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryCreateForkOptions specifies the optional parameters to the +// RepositoriesService.CreateFork method. +type RepositoryCreateForkOptions struct { + // The organization to fork the repository into. + Organization string `url:"organization,omitempty"` +} + +// CreateFork creates a fork of the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/forks/#list-forks +func (s *RepositoriesService) CreateFork(owner, repo string, opt *RepositoryCreateForkOptions) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + fork := new(Repository) + resp, err := s.client.Do(req, fork) + if err != nil { + return nil, resp, err + } + + return fork, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_forks_test.go b/vendor/github.com/google/go-github/github/repos_forks_test.go new file mode 100644 index 0000000..965a066 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_forks_test.go @@ -0,0 +1,73 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListForks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "newest", + "page": "3", + }) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &RepositoryListForksOptions{ + Sort: "newest", + ListOptions: ListOptions{Page: 3}, + } + repos, _, err := client.Repositories.ListForks("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListForks returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListForks returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListForks_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListForks("%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateFork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testFormValues(t, r, values{"organization": "o"}) + fmt.Fprint(w, `{"id":1}`) + }) + + opt := &RepositoryCreateForkOptions{Organization: "o"} + repo, _, err := client.Repositories.CreateFork("o", "r", opt) + if err != nil { + t.Errorf("Repositories.CreateFork returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.CreateFork returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_CreateFork_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateFork("%", "r", nil) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_hooks.go b/vendor/github.com/google/go-github/github/repos_hooks.go new file mode 100644 index 0000000..bc4c8c5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_hooks.go @@ -0,0 +1,194 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// WebHookPayload represents the data that is received from GitHub when a push +// event hook is triggered. The format of these payloads pre-date most of the +// GitHub v3 API, so there are lots of minor incompatibilities with the types +// defined in the rest of the API. Therefore, several types are duplicated +// here to account for these differences. +// +// GitHub API docs: https://help.github.com/articles/post-receive-hooks +type WebHookPayload struct { + After *string `json:"after,omitempty"` + Before *string `json:"before,omitempty"` + Commits []WebHookCommit `json:"commits,omitempty"` + Compare *string `json:"compare,omitempty"` + Created *bool `json:"created,omitempty"` + Deleted *bool `json:"deleted,omitempty"` + Forced *bool `json:"forced,omitempty"` + HeadCommit *WebHookCommit `json:"head_commit,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Ref *string `json:"ref,omitempty"` + Repo *Repository `json:"repository,omitempty"` +} + +func (w WebHookPayload) String() string { + return Stringify(w) +} + +// WebHookCommit represents the commit variant we receive from GitHub in a +// WebHookPayload. +type WebHookCommit struct { + Added []string `json:"added,omitempty"` + Author *WebHookAuthor `json:"author,omitempty"` + Committer *WebHookAuthor `json:"committer,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + ID *string `json:"id,omitempty"` + Message *string `json:"message,omitempty"` + Modified []string `json:"modified,omitempty"` + Removed []string `json:"removed,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` +} + +func (w WebHookCommit) String() string { + return Stringify(w) +} + +// WebHookAuthor represents the author or committer of a commit, as specified +// in a WebHookCommit. The commit author may not correspond to a GitHub User. +type WebHookAuthor struct { + Email *string `json:"email,omitempty"` + Name *string `json:"name,omitempty"` + Username *string `json:"username,omitempty"` +} + +func (w WebHookAuthor) String() string { + return Stringify(w) +} + +// Hook represents a GitHub (web and service) hook for a repository. +type Hook struct { + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name *string `json:"name,omitempty"` + Events []string `json:"events,omitempty"` + Active *bool `json:"active,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` + ID *int `json:"id,omitempty"` +} + +func (h Hook) String() string { + return Stringify(h) +} + +// CreateHook creates a Hook for the specified repository. +// Name and Config are required fields. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#create-a-hook +func (s *RepositoriesService) CreateHook(owner, repo string, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + req, err := s.client.NewRequest("POST", u, hook) + if err != nil { + return nil, nil, err + } + + h := new(Hook) + resp, err := s.client.Do(req, h) + if err != nil { + return nil, resp, err + } + + return h, resp, err +} + +// ListHooks lists all Hooks for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#list +func (s *RepositoriesService) ListHooks(owner, repo string, opt *ListOptions) ([]Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + hooks := new([]Hook) + resp, err := s.client.Do(req, hooks) + if err != nil { + return nil, resp, err + } + + return *hooks, resp, err +} + +// GetHook returns a single specified Hook. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#get-single-hook +func (s *RepositoriesService) GetHook(owner, repo string, id int) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + hook := new(Hook) + resp, err := s.client.Do(req, hook) + return hook, resp, err +} + +// EditHook updates a specified Hook. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#edit-a-hook +func (s *RepositoriesService) EditHook(owner, repo string, id int, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, hook) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := s.client.Do(req, h) + return h, resp, err +} + +// DeleteHook deletes a specified Hook. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#delete-a-hook +func (s *RepositoriesService) DeleteHook(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// PingHook triggers a 'ping' event to be sent to the Hook. +// +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#ping-a-hook +func (s *RepositoriesService) PingHook(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d/pings", owner, repo, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// TestHook triggers a test Hook by github. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#test-a-push-hook +func (s *RepositoriesService) TestHook(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d/tests", owner, repo, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListServiceHooks is deprecated. Use Client.ListServiceHooks instead. +func (s *RepositoriesService) ListServiceHooks() ([]ServiceHook, *Response, error) { + return s.client.ListServiceHooks() +} diff --git a/vendor/github.com/google/go-github/github/repos_hooks_test.go b/vendor/github.com/google/go-github/github/repos_hooks_test.go new file mode 100644 index 0000000..c163a26 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_hooks_test.go @@ -0,0 +1,187 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_CreateHook(t *testing.T) { + setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.CreateHook("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.CreateHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_CreateHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateHook("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListHooks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + + hooks, _, err := client.Repositories.ListHooks("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListHooks returned error: %v", err) + } + + want := []Hook{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Repositories.ListHooks returned %+v, want %+v", hooks, want) + } +} + +func TestRepositoriesService_ListHooks_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListHooks("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.GetHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.GetHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_GetHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetHook("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditHook(t *testing.T) { + setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.EditHook("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.EditHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_EditHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.EditHook("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteHook returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteHook("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_PingHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Repositories.PingHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.PingHook returned error: %v", err) + } +} + +func TestRepositoriesService_TestHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1/tests", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Repositories.TestHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.TestHook returned error: %v", err) + } +} + +func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) { + _, err := client.Repositories.TestHook("%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_keys.go b/vendor/github.com/google/go-github/github/repos_keys.go new file mode 100644 index 0000000..0d12ec9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_keys.go @@ -0,0 +1,108 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// The Key type is defined in users_keys.go + +// ListKeys lists the deploy keys for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#list +func (s *RepositoriesService) ListKeys(owner string, repo string, opt *ListOptions) ([]Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + keys := new([]Key) + resp, err := s.client.Do(req, keys) + if err != nil { + return nil, resp, err + } + + return *keys, resp, err +} + +// GetKey fetches a single deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#get +func (s *RepositoriesService) GetKey(owner string, repo string, id int) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, err +} + +// CreateKey adds a deploy key for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#create +func (s *RepositoriesService) CreateKey(owner string, repo string, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// EditKey edits a deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#edit +func (s *RepositoriesService) EditKey(owner string, repo string, id int, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteKey deletes a deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#delete +func (s *RepositoriesService) DeleteKey(owner string, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/repos_keys_test.go b/vendor/github.com/google/go-github/github/repos_keys_test.go new file mode 100644 index 0000000..dcf6c55 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_keys_test.go @@ -0,0 +1,153 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListKeys(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Repositories.ListKeys("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Repositories.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestRepositoriesService_ListKeys_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListKeys("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.GetKey("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_GetKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetKey("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.CreateKey("o", "r", input) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_CreateKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateKey("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.EditKey("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.EditKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_EditKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.EditKey("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteKey("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteKey returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteKey_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteKey("%", "%", 1) + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/repos_merging.go b/vendor/github.com/google/go-github/github/repos_merging.go new file mode 100644 index 0000000..31f8313 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_merging.go @@ -0,0 +1,37 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" +) + +// RepositoryMergeRequest represents a request to merge a branch in a +// repository. +type RepositoryMergeRequest struct { + Base *string `json:"base,omitempty"` + Head *string `json:"head,omitempty"` + CommitMessage *string `json:"commit_message,omitempty"` +} + +// Merge a branch in the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/merging/#perform-a-merge +func (s *RepositoriesService) Merge(owner, repo string, request *RepositoryMergeRequest) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/merges", owner, repo) + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_merging_test.go b/vendor/github.com/google/go-github/github/repos_merging_test.go new file mode 100644 index 0000000..166c5e5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_merging_test.go @@ -0,0 +1,47 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_Merge(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryMergeRequest{ + Base: String("b"), + Head: String("h"), + CommitMessage: String("c"), + } + + mux.HandleFunc("/repos/o/r/merges", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryMergeRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"sha":"s"}`) + }) + + commit, _, err := client.Repositories.Merge("o", "r", input) + if err != nil { + t.Errorf("Repositories.Merge returned error: %v", err) + } + + want := &RepositoryCommit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.Merge returned %+v, want %+v", commit, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_pages.go b/vendor/github.com/google/go-github/github/repos_pages.go new file mode 100644 index 0000000..2384eaf --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_pages.go @@ -0,0 +1,90 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Pages represents a GitHub Pages site configuration. +type Pages struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + CNAME *string `json:"cname,omitempty"` + Custom404 *bool `json:"custom_404,omitempty"` +} + +// PagesError represents a build error for a GitHub Pages site. +type PagesError struct { + Message *string `json:"message,omitempty"` +} + +// PagesBuild represents the build information for a GitHub Pages site. +type PagesBuild struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + Error *PagesError `json:"error,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Commit *string `json:"commit,omitempty"` + Duration *int `json:"duration,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"created_at,omitempty"` +} + +// GetPagesInfo fetches information about a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#get-information-about-a-pages-site +func (s *RepositoriesService) GetPagesInfo(owner string, repo string) (*Pages, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + site := new(Pages) + resp, err := s.client.Do(req, site) + if err != nil { + return nil, resp, err + } + + return site, resp, err +} + +// ListPagesBuilds lists the builds for a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-pages-builds +func (s *RepositoriesService) ListPagesBuilds(owner string, repo string) ([]PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pages []PagesBuild + resp, err := s.client.Do(req, &pages) + if err != nil { + return nil, resp, err + } + + return pages, resp, err +} + +// GetLatestPagesBuild fetches the latest build information for a GitHub pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-latest-pages-build +func (s *RepositoriesService) GetLatestPagesBuild(owner string, repo string) (*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds/latest", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + build := new(PagesBuild) + resp, err := s.client.Do(req, build) + if err != nil { + return nil, resp, err + } + + return build, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_pages_test.go b/vendor/github.com/google/go-github/github/repos_pages_test.go new file mode 100644 index 0000000..4cbc43a --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_pages_test.go @@ -0,0 +1,73 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_GetPagesInfo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","cname":"c","custom_404":false}`) + }) + + page, _, err := client.Repositories.GetPagesInfo("o", "r") + if err != nil { + t.Errorf("Repositories.GetPagesInfo returned error: %v", err) + } + + want := &Pages{URL: String("u"), Status: String("s"), CNAME: String("c"), Custom404: Bool(false)} + if !reflect.DeepEqual(page, want) { + t.Errorf("Repositories.GetPagesInfo returned %+v, want %+v", page, want) + } +} + +func TestRepositoriesService_ListPagesBuilds(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"url":"u","status":"s","commit":"c"}]`) + }) + + pages, _, err := client.Repositories.ListPagesBuilds("o", "r") + if err != nil { + t.Errorf("Repositories.ListPagesBuilds returned error: %v", err) + } + + want := []PagesBuild{{URL: String("u"), Status: String("s"), Commit: String("c")}} + if !reflect.DeepEqual(pages, want) { + t.Errorf("Repositories.ListPagesBuilds returned %+v, want %+v", pages, want) + } +} + +func TestRepositoriesService_GetLatestPagesBuild(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds/latest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","commit":"c"}`) + }) + + build, _, err := client.Repositories.GetLatestPagesBuild("o", "r") + if err != nil { + t.Errorf("Repositories.GetLatestPagesBuild returned error: %v", err) + } + + want := &PagesBuild{URL: String("u"), Status: String("s"), Commit: String("c")} + if !reflect.DeepEqual(build, want) { + t.Errorf("Repositories.GetLatestPagesBuild returned %+v, want %+v", build, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_releases.go b/vendor/github.com/google/go-github/github/repos_releases.go new file mode 100644 index 0000000..4e4e2ab --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_releases.go @@ -0,0 +1,301 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "errors" + "fmt" + "io" + "mime" + "os" + "path/filepath" +) + +// RepositoryRelease represents a GitHub release in a repository. +type RepositoryRelease struct { + ID *int `json:"id,omitempty"` + TagName *string `json:"tag_name,omitempty"` + TargetCommitish *string `json:"target_commitish,omitempty"` + Name *string `json:"name,omitempty"` + Body *string `json:"body,omitempty"` + Draft *bool `json:"draft,omitempty"` + Prerelease *bool `json:"prerelease,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PublishedAt *Timestamp `json:"published_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + AssetsURL *string `json:"assets_url,omitempty"` + Assets []ReleaseAsset `json:"assets,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` +} + +func (r RepositoryRelease) String() string { + return Stringify(r) +} + +// ReleaseAsset represents a Github release asset in a repository. +type ReleaseAsset struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Label *string `json:"label,omitempty"` + State *string `json:"state,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size *int `json:"size,omitempty"` + DownloadCount *int `json:"download_count,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + BrowserDownloadURL *string `json:"browser_download_url,omitempty"` + Uploader *User `json:"uploader,omitempty"` +} + +func (r ReleaseAsset) String() string { + return Stringify(r) +} + +// ListReleases lists the releases for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/releases/#list-releases-for-a-repository +func (s *RepositoriesService) ListReleases(owner, repo string, opt *ListOptions) ([]RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + releases := new([]RepositoryRelease) + resp, err := s.client.Do(req, releases) + if err != nil { + return nil, resp, err + } + return *releases, resp, err +} + +// GetRelease fetches a single release. +// +// GitHub API docs: http://developer.github.com/v3/repos/releases/#get-a-single-release +func (s *RepositoriesService) GetRelease(owner, repo string, id int) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + return s.getSingleRelease(u) +} + +// GetLatestRelease fetches the latest published release for the repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release +func (s *RepositoriesService) GetLatestRelease(owner, repo string) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) + return s.getSingleRelease(u) +} + +// GetReleaseByTag fetches a release with the specified tag. +// +// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name +func (s *RepositoriesService) GetReleaseByTag(owner, repo, tag string) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) + return s.getSingleRelease(u) +} + +func (s *RepositoriesService) getSingleRelease(url string) (*RepositoryRelease, *Response, error) { + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + release := new(RepositoryRelease) + resp, err := s.client.Do(req, release) + if err != nil { + return nil, resp, err + } + return release, resp, err +} + +// CreateRelease adds a new release for a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#create-a-release +func (s *RepositoriesService) CreateRelease(owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + + req, err := s.client.NewRequest("POST", u, release) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + return r, resp, err +} + +// EditRelease edits a repository release. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release +func (s *RepositoriesService) EditRelease(owner, repo string, id int, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + return r, resp, err +} + +// DeleteRelease delete a single release from a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#delete-a-release +func (s *RepositoriesService) DeleteRelease(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListReleaseAssets lists the release's assets. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#list-assets-for-a-release +func (s *RepositoriesService) ListReleaseAssets(owner, repo string, id int, opt *ListOptions) ([]ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + assets := new([]ReleaseAsset) + resp, err := s.client.Do(req, assets) + if err != nil { + return nil, resp, nil + } + return *assets, resp, err +} + +// GetReleaseAsset fetches a single release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) GetReleaseAsset(owner, repo string, id int) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, nil + } + return asset, resp, err +} + +// DownloadReleaseAsset downloads a release asset. +// +// DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the +// specified release asset. It is the caller's responsibility to close the ReadCloser. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) DownloadReleaseAsset(owner, repo string, id int) (io.ReadCloser, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", defaultMediaType) + + resp, err := s.client.client.Do(req) + if err != nil { + return nil, err + } + + return resp.Body, nil +} + +// EditReleaseAsset edits a repository release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset +func (s *RepositoriesService) EditReleaseAsset(owner, repo string, id int, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} + +// DeleteReleaseAsset delete a single release asset from a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#delete-a-release-asset +func (s *RepositoriesService) DeleteReleaseAsset(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// UploadReleaseAsset creates an asset by uploading a file into a release repository. +// To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#upload-a-release-asset +func (s *RepositoriesService) UploadReleaseAsset(owner, repo string, id int, opt *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + stat, err := file.Stat() + if err != nil { + return nil, nil, err + } + if stat.IsDir() { + return nil, nil, errors.New("the asset to upload can't be a directory") + } + + mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) + req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_releases_test.go b/vendor/github.com/google/go-github/github/repos_releases_test.go new file mode 100644 index 0000000..5b0f094 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_releases_test.go @@ -0,0 +1,337 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "reflect" + "testing" +) + +func TestRepositoriesService_ListReleases(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + releases, _, err := client.Repositories.ListReleases("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListReleases returned error: %v", err) + } + want := []RepositoryRelease{{ID: Int(1)}} + if !reflect.DeepEqual(releases, want) { + t.Errorf("Repositories.ListReleases returned %+v, want %+v", releases, want) + } +} + +func TestRepositoriesService_GetRelease(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + release, resp, err := client.Repositories.GetRelease("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_GetLatestRelease(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":3}`) + }) + + release, resp, err := client.Repositories.GetLatestRelease("o", "r") + if err != nil { + t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int(3)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_GetReleaseByTag(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":13}`) + }) + + release, resp, err := client.Repositories.GetReleaseByTag("o", "r", "foo") + if err != nil { + t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int(13)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_CreateRelease(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryRelease{Name: String("v1.0")} + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryRelease) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + release, _, err := client.Repositories.CreateRelease("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateRelease returned error: %v", err) + } + + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.CreateRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_EditRelease(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryRelease{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryRelease) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + release, _, err := client.Repositories.EditRelease("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditRelease returned error: %v", err) + } + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.EditRelease returned = %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_DeleteRelease(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteRelease("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteRelease returned error: %v", err) + } +} + +func TestRepositoriesService_ListReleaseAssets(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assets, _, err := client.Repositories.ListReleaseAssets("o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListReleaseAssets returned error: %v", err) + } + want := []ReleaseAsset{{ID: Int(1)}} + if !reflect.DeepEqual(assets, want) { + t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want) + } +} + +func TestRepositoriesService_GetReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.GetReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", defaultMediaType) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt") + fmt.Fprint(w, "Hello World") + }) + + reader, err := client.Repositories.DownloadReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err) + } + want := []byte("Hello World") + content, err := ioutil.ReadAll(reader) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned bad reader: %v", err) + } + if !bytes.Equal(want, content) { + t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want) + } +} + +func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", defaultMediaType) + w.Header().Set("Location", server.URL+"/github-cloud/releases/1/hello-world.txt") + w.WriteHeader(http.StatusFound) + }) + + mux.HandleFunc("/github-cloud/releases/1/hello-world.txt", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt") + fmt.Fprint(w, "Hello World") + }) + + reader, err := client.Repositories.DownloadReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned error: %v", err) + } + want := []byte("Hello World") + content, err := ioutil.ReadAll(reader) + if err != nil { + t.Errorf("Repositories.DownloadReleaseAsset returned bad reader: %v", err) + } + if !bytes.Equal(want, content) { + t.Errorf("Repositories.DownloadReleaseAsset returned %+v, want %+v", content, want) + } +} + +func TestRepositoriesService_EditReleaseAsset(t *testing.T) { + setup() + defer teardown() + + input := &ReleaseAsset{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + v := new(ReleaseAsset) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.EditReleaseAsset("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err) + } +} + +func TestRepositoriesService_UploadReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Content-Type", "text/plain; charset=utf-8") + testHeader(t, r, "Content-Length", "12") + testFormValues(t, r, values{"name": "n"}) + testBody(t, r, "Upload me !\n") + + fmt.Fprintf(w, `{"id":1}`) + }) + + file, dir, err := openTestFile("upload.txt", "Upload me !\n") + if err != nil { + t.Fatalf("Unable to create temp file: %v", err) + } + defer os.RemoveAll(dir) + + opt := &UploadOptions{Name: "n"} + asset, _, err := client.Repositories.UploadReleaseAsset("o", "r", 1, opt, file) + if err != nil { + t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_stats.go b/vendor/github.com/google/go-github/github/repos_stats.go new file mode 100644 index 0000000..7c1de09 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_stats.go @@ -0,0 +1,214 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// ContributorStats represents a contributor to a repository and their +// weekly contributions to a given repo. +type ContributorStats struct { + Author *Contributor `json:"author,omitempty"` + Total *int `json:"total,omitempty"` + Weeks []WeeklyStats `json:"weeks,omitempty"` +} + +func (c ContributorStats) String() string { + return Stringify(c) +} + +// WeeklyStats represents the number of additions, deletions and commits +// a Contributor made in a given week. +type WeeklyStats struct { + Week *Timestamp `json:"w,omitempty"` + Additions *int `json:"a,omitempty"` + Deletions *int `json:"d,omitempty"` + Commits *int `json:"c,omitempty"` +} + +func (w WeeklyStats) String() string { + return Stringify(w) +} + +// ListContributorsStats gets a repo's contributor list with additions, +// deletions and commit counts. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code of +// 202. This is because this is the status that github returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#contributors +func (s *RepositoriesService) ListContributorsStats(owner, repo string) ([]ContributorStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/contributors", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var contributorStats []ContributorStats + resp, err := s.client.Do(req, &contributorStats) + if err != nil { + return nil, resp, err + } + + return contributorStats, resp, err +} + +// WeeklyCommitActivity represents the weekly commit activity for a repository. +// The days array is a group of commits per day, starting on Sunday. +type WeeklyCommitActivity struct { + Days []int `json:"days,omitempty"` + Total *int `json:"total,omitempty"` + Week *Timestamp `json:"week,omitempty"` +} + +func (w WeeklyCommitActivity) String() string { + return Stringify(w) +} + +// ListCommitActivity returns the last year of commit activity +// grouped by week. The days array is a group of commits per day, +// starting on Sunday. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code of +// 202. This is because this is the status that github returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#commit-activity +func (s *RepositoriesService) ListCommitActivity(owner, repo string) ([]WeeklyCommitActivity, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/commit_activity", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeklyCommitActivity []WeeklyCommitActivity + resp, err := s.client.Do(req, &weeklyCommitActivity) + if err != nil { + return nil, resp, err + } + + return weeklyCommitActivity, resp, err +} + +// ListCodeFrequency returns a weekly aggregate of the number of additions and +// deletions pushed to a repository. Returned WeeklyStats will contain +// additiona and deletions, but not total commits. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#code-frequency +func (s *RepositoriesService) ListCodeFrequency(owner, repo string) ([]WeeklyStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/code_frequency", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeks [][]int + resp, err := s.client.Do(req, &weeks) + + // convert int slices into WeeklyStats + var stats []WeeklyStats + for _, week := range weeks { + if len(week) != 3 { + continue + } + stat := WeeklyStats{ + Week: &Timestamp{time.Unix(int64(week[0]), 0)}, + Additions: Int(week[1]), + Deletions: Int(week[2]), + } + stats = append(stats, stat) + } + + return stats, resp, err +} + +// RepositoryParticipation is the number of commits by everyone +// who has contributed to the repository (including the owner) +// as well as the number of commits by the owner themself. +type RepositoryParticipation struct { + All []int `json:"all,omitempty"` + Owner []int `json:"owner,omitempty"` +} + +func (r RepositoryParticipation) String() string { + return Stringify(r) +} + +// ListParticipation returns the total commit counts for the 'owner' +// and total commit counts in 'all'. 'all' is everyone combined, +// including the 'owner' in the last 52 weeks. If you’d like to get +// the commit counts for non-owners, you can subtract 'all' from 'owner'. +// +// The array order is oldest week (index 0) to most recent week. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code +// of 202. This is because this is the status that github returns to +// signify that it is now computing the requested statistics. A follow +// up request, after a delay of a second or so, should result in a +// successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#participation +func (s *RepositoriesService) ListParticipation(owner, repo string) (*RepositoryParticipation, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/participation", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + participation := new(RepositoryParticipation) + resp, err := s.client.Do(req, participation) + if err != nil { + return nil, resp, err + } + + return participation, resp, err +} + +// PunchCard respresents the number of commits made during a given hour of a +// day of thew eek. +type PunchCard struct { + Day *int // Day of the week (0-6: =Sunday - Saturday). + Hour *int // Hour of day (0-23). + Commits *int // Number of commits. +} + +// ListPunchCard returns the number of commits per hour in each day. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#punch-card +func (s *RepositoriesService) ListPunchCard(owner, repo string) ([]PunchCard, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/punch_card", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var results [][]int + resp, err := s.client.Do(req, &results) + + // convert int slices into Punchcards + var cards []PunchCard + for _, result := range results { + if len(result) != 3 { + continue + } + card := PunchCard{ + Day: Int(result[0]), + Hour: Int(result[1]), + Commits: Int(result[2]), + } + cards = append(cards, card) + } + + return cards, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_stats_test.go b/vendor/github.com/google/go-github/github/repos_stats_test.go new file mode 100644 index 0000000..3f9fab5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_stats_test.go @@ -0,0 +1,210 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestRepositoriesService_ListContributorsStats(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "author": { + "id": 1 + }, + "total": 135, + "weeks": [ + { + "w": 1367712000, + "a": 6898, + "d": 77, + "c": 10 + } + ] + } +] +`) + }) + + stats, _, err := client.Repositories.ListContributorsStats("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListContributorsStats returned error: %v", err) + } + + want := []ContributorStats{ + { + Author: &Contributor{ + ID: Int(1), + }, + Total: Int(135), + Weeks: []WeeklyStats{ + { + Week: &Timestamp{time.Date(2013, 05, 05, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(6898), + Deletions: Int(77), + Commits: Int(10), + }, + }, + }, + } + + if !reflect.DeepEqual(stats, want) { + t.Errorf("RepositoriesService.ListContributorsStats returned %+v, want %+v", stats, want) + } +} + +func TestRepositoriesService_ListCommitActivity(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/commit_activity", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "days": [0, 3, 26, 20, 39, 1, 0], + "total": 89, + "week": 1336280400 + } +] +`) + }) + + activity, _, err := client.Repositories.ListCommitActivity("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCommitActivity returned error: %v", err) + } + + want := []WeeklyCommitActivity{ + { + Days: []int{0, 3, 26, 20, 39, 1, 0}, + Total: Int(89), + Week: &Timestamp{time.Date(2012, 05, 06, 05, 00, 00, 0, time.UTC).Local()}, + }, + } + + if !reflect.DeepEqual(activity, want) { + t.Errorf("RepositoriesService.ListCommitActivity returned %+v, want %+v", activity, want) + } +} + +func TestRepositoriesService_ListCodeFrequency(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/code_frequency", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[[1302998400, 1124, -435]]`) + }) + + code, _, err := client.Repositories.ListCodeFrequency("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCodeFrequency returned error: %v", err) + } + + want := []WeeklyStats{{ + Week: &Timestamp{time.Date(2011, 04, 17, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(1124), + Deletions: Int(-435), + }} + + if !reflect.DeepEqual(code, want) { + t.Errorf("RepositoriesService.ListCodeFrequency returned %+v, want %+v", code, want) + } +} + +func TestRepositoriesService_Participation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/participation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +{ + "all": [ + 11,21,15,2,8,1,8,23,17,21,11,10,33, + 91,38,34,22,23,32,3,43,87,71,18,13,5, + 13,16,66,27,12,45,110,117,13,8,18,9,19, + 26,39,12,20,31,46,91,45,10,24,9,29,7 + ], + "owner": [ + 3,2,3,0,2,0,5,14,7,9,1,5,0, + 48,19,2,0,1,10,2,23,40,35,8,8,2, + 10,6,30,0,2,9,53,104,3,3,10,4,7, + 11,21,4,4,22,26,63,11,2,14,1,10,3 + ] +} +`) + }) + + participation, _, err := client.Repositories.ListParticipation("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListParticipation returned error: %v", err) + } + + want := &RepositoryParticipation{ + All: []int{ + 11, 21, 15, 2, 8, 1, 8, 23, 17, 21, 11, 10, 33, + 91, 38, 34, 22, 23, 32, 3, 43, 87, 71, 18, 13, 5, + 13, 16, 66, 27, 12, 45, 110, 117, 13, 8, 18, 9, 19, + 26, 39, 12, 20, 31, 46, 91, 45, 10, 24, 9, 29, 7, + }, + Owner: []int{ + 3, 2, 3, 0, 2, 0, 5, 14, 7, 9, 1, 5, 0, + 48, 19, 2, 0, 1, 10, 2, 23, 40, 35, 8, 8, 2, + 10, 6, 30, 0, 2, 9, 53, 104, 3, 3, 10, 4, 7, + 11, 21, 4, 4, 22, 26, 63, 11, 2, 14, 1, 10, 3, + }, + } + + if !reflect.DeepEqual(participation, want) { + t.Errorf("RepositoriesService.ListParticipation returned %+v, want %+v", participation, want) + } +} + +func TestRepositoriesService_ListPunchCard(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/punch_card", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[ + [0, 0, 5], + [0, 1, 43], + [0, 2, 21] + ]`) + }) + + card, _, err := client.Repositories.ListPunchCard("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListPunchCard returned error: %v", err) + } + + want := []PunchCard{ + {Day: Int(0), Hour: Int(0), Commits: Int(5)}, + {Day: Int(0), Hour: Int(1), Commits: Int(43)}, + {Day: Int(0), Hour: Int(2), Commits: Int(21)}, + } + + if !reflect.DeepEqual(card, want) { + t.Errorf("RepositoriesService.ListPunchCard returned %+v, want %+v", card, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_statuses.go b/vendor/github.com/google/go-github/github/repos_statuses.go new file mode 100644 index 0000000..0379a2b --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_statuses.go @@ -0,0 +1,128 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "time" +) + +// RepoStatus represents the status of a repository at a particular reference. +type RepoStatus struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // State is the current state of the repository. Possible values are: + // pending, success, error, or failure. + State *string `json:"state,omitempty"` + + // TargetURL is the URL of the page representing this status. It will be + // linked from the GitHub UI to allow users to see the source of the status. + TargetURL *string `json:"target_url,omitempty"` + + // Description is a short high level summary of the status. + Description *string `json:"description,omitempty"` + + // A string label to differentiate this status from the statuses of other systems. + Context *string `json:"context,omitempty"` + + Creator *User `json:"creator,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (r RepoStatus) String() string { + return Stringify(r) +} + +// ListStatuses lists the statuses of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: http://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref +func (s *RepositoriesService) ListStatuses(owner, repo, ref string, opt *ListOptions) ([]RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/statuses", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + statuses := new([]RepoStatus) + resp, err := s.client.Do(req, statuses) + if err != nil { + return nil, resp, err + } + + return *statuses, resp, err +} + +// CreateStatus creates a new status for a repository at the specified +// reference. Ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: http://developer.github.com/v3/repos/statuses/#create-a-status +func (s *RepositoriesService) CreateStatus(owner, repo, ref string, status *RepoStatus) (*RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/statuses/%v", owner, repo, ref) + req, err := s.client.NewRequest("POST", u, status) + if err != nil { + return nil, nil, err + } + + repoStatus := new(RepoStatus) + resp, err := s.client.Do(req, repoStatus) + if err != nil { + return nil, resp, err + } + + return repoStatus, resp, err +} + +// CombinedStatus represents the combined status of a repository at a particular reference. +type CombinedStatus struct { + // State is the combined state of the repository. Possible values are: + // failture, pending, or success. + State *string `json:"state,omitempty"` + + Name *string `json:"name,omitempty"` + SHA *string `json:"sha,omitempty"` + TotalCount *int `json:"total_count,omitempty"` + Statuses []RepoStatus `json:"statuses,omitempty"` + + CommitURL *string `json:"commit_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` +} + +func (s CombinedStatus) String() string { + return Stringify(s) +} + +// GetCombinedStatus returns the combined status of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref +func (s *RepositoriesService) GetCombinedStatus(owner, repo, ref string, opt *ListOptions) (*CombinedStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/status", owner, repo, ref) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + status := new(CombinedStatus) + resp, err := s.client.Do(req, status) + if err != nil { + return nil, resp, err + } + + return status, resp, err +} diff --git a/vendor/github.com/google/go-github/github/repos_statuses_test.go b/vendor/github.com/google/go-github/github/repos_statuses_test.go new file mode 100644 index 0000000..8b23052 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_statuses_test.go @@ -0,0 +1,96 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_ListStatuses(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/statuses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + statuses, _, err := client.Repositories.ListStatuses("o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.ListStatuses returned error: %v", err) + } + + want := []RepoStatus{{ID: Int(1)}} + if !reflect.DeepEqual(statuses, want) { + t.Errorf("Repositories.ListStatuses returned %+v, want %+v", statuses, want) + } +} + +func TestRepositoriesService_ListStatuses_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListStatuses("%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateStatus(t *testing.T) { + setup() + defer teardown() + + input := &RepoStatus{State: String("s"), TargetURL: String("t"), Description: String("d")} + + mux.HandleFunc("/repos/o/r/statuses/r", func(w http.ResponseWriter, r *http.Request) { + v := new(RepoStatus) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + status, _, err := client.Repositories.CreateStatus("o", "r", "r", input) + if err != nil { + t.Errorf("Repositories.CreateStatus returned error: %v", err) + } + + want := &RepoStatus{ID: Int(1)} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.CreateStatus returned %+v, want %+v", status, want) + } +} + +func TestRepositoriesService_CreateStatus_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateStatus("%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetCombinedStatus(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/status", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `{"state":"success", "statuses":[{"id":1}]}`) + }) + + opt := &ListOptions{Page: 2} + status, _, err := client.Repositories.GetCombinedStatus("o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.GetCombinedStatus returned error: %v", err) + } + + want := &CombinedStatus{State: String("success"), Statuses: []RepoStatus{{ID: Int(1)}}} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.GetCombinedStatus returned %+v, want %+v", status, want) + } +} diff --git a/vendor/github.com/google/go-github/github/repos_test.go b/vendor/github.com/google/go-github/github/repos_test.go new file mode 100644 index 0000000..6e98e28 --- /dev/null +++ b/vendor/github.com/google/go-github/github/repos_test.go @@ -0,0 +1,409 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestRepositoriesService_List_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + repos, _, err := client.Repositories.List("", nil) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + testFormValues(t, r, values{ + "type": "owner", + "sort": "created", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListOptions{"owner", "created", "asc", ListOptions{Page: 2}} + repos, _, err := client.Repositories.List("u", opt) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_invalidUser(t *testing.T) { + _, _, err := client.Repositories.List("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListByOrg(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + testFormValues(t, r, values{ + "type": "forks", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListByOrgOptions{"forks", ListOptions{Page: 2}} + repos, _, err := client.Repositories.ListByOrg("o", opt) + if err != nil { + t.Errorf("Repositories.ListByOrg returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListByOrg returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListByOrg_invalidOrg(t *testing.T) { + _, _, err := client.Repositories.ListByOrg("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListAll(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": "1", + "page": "2", + "per_page": "3", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListAllOptions{1, ListOptions{2, 3}} + repos, _, err := client.Repositories.ListAll(opt) + if err != nil { + t.Errorf("Repositories.ListAll returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListAll returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_Create_user(t *testing.T) { + setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Create("", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_org(t *testing.T) { + setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Create("o", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_invalidOrg(t *testing.T) { + _, _, err := client.Repositories.Create("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeLicensesPreview) + fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`) + }) + + repo, _, err := client.Repositories.Get("o", "r") + if err != nil { + t.Errorf("Repositories.Get returned error: %v", err) + } + + want := &Repository{ID: Int(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Get returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Edit(t *testing.T) { + setup() + defer teardown() + + i := true + input := &Repository{HasIssues: &i} + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + fmt.Fprint(w, `{"id":1}`) + }) + + repo, _, err := client.Repositories.Edit("o", "r", input) + if err != nil { + t.Errorf("Repositories.Edit returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Edit returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.Delete("o", "r") + if err != nil { + t.Errorf("Repositories.Delete returned error: %v", err) + } +} + +func TestRepositoriesService_Get_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.Get("%", "r") + testURLParseError(t, err) +} + +func TestRepositoriesService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.Edit("%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListContributors(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "anon": "true", + "page": "2", + }) + fmt.Fprint(w, `[{"contributions":42}]`) + }) + + opts := &ListContributorsOptions{Anon: "true", ListOptions: ListOptions{Page: 2}} + contributors, _, err := client.Repositories.ListContributors("o", "r", opts) + + if err != nil { + t.Errorf("Repositories.ListContributors returned error: %v", err) + } + + want := []Contributor{{Contributions: Int(42)}} + if !reflect.DeepEqual(contributors, want) { + t.Errorf("Repositories.ListContributors returned %+v, want %+v", contributors, want) + } +} + +func TestRepositoriesService_ListLanguages(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/languages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"go":1}`) + }) + + languages, _, err := client.Repositories.ListLanguages("o", "r") + if err != nil { + t.Errorf("Repositories.ListLanguages returned error: %v", err) + } + + want := map[string]int{"go": 1} + if !reflect.DeepEqual(languages, want) { + t.Errorf("Repositories.ListLanguages returned %+v, want %+v", languages, want) + } +} + +func TestRepositoriesService_ListTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Repositories.ListTeams("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Repositories.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestRepositoriesService_ListTags(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/tags", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"n", "commit" : {"sha" : "s", "url" : "u"}, "zipball_url": "z", "tarball_url": "t"}]`) + }) + + opt := &ListOptions{Page: 2} + tags, _, err := client.Repositories.ListTags("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTags returned error: %v", err) + } + + want := []RepositoryTag{ + { + Name: String("n"), + Commit: &Commit{ + SHA: String("s"), + URL: String("u"), + }, + ZipballURL: String("z"), + TarballURL: String("t"), + }, + } + if !reflect.DeepEqual(tags, want) { + t.Errorf("Repositories.ListTags returned %+v, want %+v", tags, want) + } +} + +func TestRepositoriesService_ListBranches(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"master", "commit" : {"sha" : "a57781", "url" : "https://api.github.com/repos/o/r/commits/a57781"}}]`) + }) + + opt := &ListOptions{Page: 2} + branches, _, err := client.Repositories.ListBranches("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListBranches returned error: %v", err) + } + + want := []Branch{{Name: String("master"), Commit: &Commit{SHA: String("a57781"), URL: String("https://api.github.com/repos/o/r/commits/a57781")}}} + if !reflect.DeepEqual(branches, want) { + t.Errorf("Repositories.ListBranches returned %+v, want %+v", branches, want) + } +} + +func TestRepositoriesService_GetBranch(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s"}}`) + }) + + branch, _, err := client.Repositories.GetBranch("o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetBranch returned error: %v", err) + } + + want := &Branch{Name: String("n"), Commit: &Commit{SHA: String("s")}} + if !reflect.DeepEqual(branch, want) { + t.Errorf("Repositories.GetBranch returned %+v, want %+v", branch, want) + } +} + +func TestRepositoriesService_ListLanguages_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListLanguages("%", "%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/search.go b/vendor/github.com/google/go-github/github/search.go new file mode 100644 index 0000000..d9e9b41 --- /dev/null +++ b/vendor/github.com/google/go-github/github/search.go @@ -0,0 +1,158 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + + qs "github.com/google/go-querystring/query" +) + +// SearchService provides access to the search related functions +// in the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/search/ +type SearchService struct { + client *Client +} + +// SearchOptions specifies optional parameters to the SearchService methods. +type SearchOptions struct { + // How to sort the search results. Possible values are: + // - for repositories: stars, fork, updated + // - for code: indexed + // - for issues: comments, created, updated + // - for users: followers, repositories, joined + // + // Default is to sort by best match. + Sort string `url:"sort,omitempty"` + + // Sort order if sort parameter is provided. Possible values are: asc, + // desc. Default is desc. + Order string `url:"order,omitempty"` + + // Whether to retrieve text match metadata with a query + TextMatch bool `url:"-"` + + ListOptions +} + +// RepositoriesSearchResult represents the result of a repositories search. +type RepositoriesSearchResult struct { + Total *int `json:"total_count,omitempty"` + Repositories []Repository `json:"items,omitempty"` +} + +// Repositories searches repositories via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-repositories +func (s *SearchService) Repositories(query string, opt *SearchOptions) (*RepositoriesSearchResult, *Response, error) { + result := new(RepositoriesSearchResult) + resp, err := s.search("repositories", query, opt, result) + return result, resp, err +} + +// IssuesSearchResult represents the result of an issues search. +type IssuesSearchResult struct { + Total *int `json:"total_count,omitempty"` + Issues []Issue `json:"items,omitempty"` +} + +// Issues searches issues via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-issues +func (s *SearchService) Issues(query string, opt *SearchOptions) (*IssuesSearchResult, *Response, error) { + result := new(IssuesSearchResult) + resp, err := s.search("issues", query, opt, result) + return result, resp, err +} + +// UsersSearchResult represents the result of an issues search. +type UsersSearchResult struct { + Total *int `json:"total_count,omitempty"` + Users []User `json:"items,omitempty"` +} + +// Users searches users via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-users +func (s *SearchService) Users(query string, opt *SearchOptions) (*UsersSearchResult, *Response, error) { + result := new(UsersSearchResult) + resp, err := s.search("users", query, opt, result) + return result, resp, err +} + +// Match represents a single text match. +type Match struct { + Text *string `json:"text,omitempty"` + Indices []int `json:"indices,omitempty"` +} + +// TextMatch represents a text match for a SearchResult +type TextMatch struct { + ObjectURL *string `json:"object_url,omitempty"` + ObjectType *string `json:"object_type,omitempty"` + Property *string `json:"property,omitempty"` + Fragment *string `json:"fragment,omitempty"` + Matches []Match `json:"matches,omitempty"` +} + +func (tm TextMatch) String() string { + return Stringify(tm) +} + +// CodeSearchResult represents the result of an code search. +type CodeSearchResult struct { + Total *int `json:"total_count,omitempty"` + CodeResults []CodeResult `json:"items,omitempty"` +} + +// CodeResult represents a single search result. +type CodeResult struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + SHA *string `json:"sha,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Repository *Repository `json:"repository,omitempty"` + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (c CodeResult) String() string { + return Stringify(c) +} + +// Code searches code via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-code +func (s *SearchService) Code(query string, opt *SearchOptions) (*CodeSearchResult, *Response, error) { + result := new(CodeSearchResult) + resp, err := s.search("code", query, opt, result) + return result, resp, err +} + +// Helper function that executes search queries against different +// GitHub search types (repositories, code, issues, users) +func (s *SearchService) search(searchType string, query string, opt *SearchOptions, result interface{}) (*Response, error) { + params, err := qs.Values(opt) + if err != nil { + return nil, err + } + params.Add("q", query) + u := fmt.Sprintf("search/%s?%s", searchType, params.Encode()) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + if opt.TextMatch { + // Accept header defaults to "application/vnd.github.v3+json" + // We change it here to fetch back text-match metadata + req.Header.Set("Accept", "application/vnd.github.v3.text-match+json") + } + + return s.client.Do(req, result) +} diff --git a/vendor/github.com/google/go-github/github/search_test.go b/vendor/github.com/google/go-github/github/search_test.go new file mode 100644 index 0000000..3cfd162 --- /dev/null +++ b/vendor/github.com/google/go-github/github/search_test.go @@ -0,0 +1,196 @@ +package github + +import ( + "fmt" + "net/http" + "reflect" + + "testing" +) + +func TestSearchService_Repositories(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Repositories("blah", opts) + if err != nil { + t.Errorf("Search.Repositories returned error: %v", err) + } + + want := &RepositoriesSearchResult{ + Total: Int(4), + Repositories: []Repository{{ID: Int(1)}, {ID: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Repositories returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Issues(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"number":1},{"number":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Issues("blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &IssuesSearchResult{ + Total: Int(4), + Issues: []Issue{{Number: Int(1)}, {Number: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Issues returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Users(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Users("blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &UsersSearchResult{ + Total: Int(4), + Users: []User{{ID: Int(1)}, {ID: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Users returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Code(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"name":"1"},{"name":"2"}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Code("blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + want := &CodeSearchResult{ + Total: Int(4), + CodeResults: []CodeResult{{Name: String("1")}, {Name: String("2")}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} + +func TestSearchService_CodeTextMatch(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + textMatchResponse := ` + { + "total_count": 1, + "items": [ + { + "name":"gopher1", + "text_matches": [ + { + "fragment": "I'm afraid my friend what you have found\nIs a gopher who lives to feed", + "matches": [ + { + "text": "gopher", + "indices": [ + 14, + 21 + ] + } + ] + } + ] + } + ] + } + ` + + fmt.Fprint(w, textMatchResponse) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}, TextMatch: true} + result, _, err := client.Search.Code("blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + wantedCodeResult := CodeResult{ + Name: String("gopher1"), + TextMatches: []TextMatch{{ + Fragment: String("I'm afraid my friend what you have found\nIs a gopher who lives to feed"), + Matches: []Match{{Text: String("gopher"), Indices: []int{14, 21}}}, + }, + }, + } + + want := &CodeSearchResult{ + Total: Int(1), + CodeResults: []CodeResult{wantedCodeResult}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} diff --git a/vendor/github.com/google/go-github/github/strings.go b/vendor/github.com/google/go-github/github/strings.go new file mode 100644 index 0000000..3857723 --- /dev/null +++ b/vendor/github.com/google/go-github/github/strings.go @@ -0,0 +1,93 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "bytes" + "fmt" + "io" + + "reflect" +) + +var timestampType = reflect.TypeOf(Timestamp{}) + +// Stringify attempts to create a reasonable string representation of types in +// the GitHub library. It does things like resolve pointers to their values +// and omits struct fields with nil values. +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// stringifyValue was heavily inspired by the goprotobuf library. + +func stringifyValue(w io.Writer, val reflect.Value) { + if val.Kind() == reflect.Ptr && val.IsNil() { + w.Write([]byte("")) + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(w, `"%s"`, v) + case reflect.Slice: + w.Write([]byte{'['}) + for i := 0; i < v.Len(); i++ { + if i > 0 { + w.Write([]byte{' '}) + } + + stringifyValue(w, v.Index(i)) + } + + w.Write([]byte{']'}) + return + case reflect.Struct: + if v.Type().Name() != "" { + w.Write([]byte(v.Type().String())) + } + + // special handling of Timestamp values + if v.Type() == timestampType { + fmt.Fprintf(w, "{%s}", v.Interface()) + return + } + + w.Write([]byte{'{'}) + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + w.Write([]byte(", ")) + } else { + sep = true + } + + w.Write([]byte(v.Type().Field(i).Name)) + w.Write([]byte{':'}) + stringifyValue(w, fv) + } + + w.Write([]byte{'}'}) + default: + if v.CanInterface() { + fmt.Fprint(w, v.Interface()) + } + } +} diff --git a/vendor/github.com/google/go-github/github/strings_test.go b/vendor/github.com/google/go-github/github/strings_test.go new file mode 100644 index 0000000..a393eb6 --- /dev/null +++ b/vendor/github.com/google/go-github/github/strings_test.go @@ -0,0 +1,137 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "testing" + "time" +) + +func TestStringify(t *testing.T) { + var nilPointer *string + + var tests = []struct { + in interface{} + out string + }{ + // basic types + {"foo", `"foo"`}, + {123, `123`}, + {1.5, `1.5`}, + {false, `false`}, + { + []string{"a", "b"}, + `["a" "b"]`, + }, + { + struct { + A []string + }{nil}, + // nil slice is skipped + `{}`, + }, + { + struct { + A string + }{"foo"}, + // structs not of a named type get no prefix + `{A:"foo"}`, + }, + + // pointers + {nilPointer, ``}, + {String("foo"), `"foo"`}, + {Int(123), `123`}, + {Bool(false), `false`}, + { + []*string{String("a"), String("b")}, + `["a" "b"]`, + }, + + // actual GitHub structs + { + Timestamp{time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + &Timestamp{time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + User{ID: Int(123), Name: String("n")}, + `github.User{ID:123, Name:"n"}`, + }, + { + Repository{Owner: &User{ID: Int(123)}}, + `github.Repository{Owner:github.User{ID:123}}`, + }, + } + + for i, tt := range tests { + s := Stringify(tt.in) + if s != tt.out { + t.Errorf("%d. Stringify(%q) => %q, want %q", i, tt.in, s, tt.out) + } + } +} + +// Directly test the String() methods on various GitHub types. We don't do an +// exaustive test of all the various field types, since TestStringify() above +// takes care of that. Rather, we just make sure that Stringify() is being +// used to build the strings, which we do by verifying that pointers are +// stringified as their underlying value. +func TestString(t *testing.T) { + var tests = []struct { + in interface{} + out string + }{ + {CodeResult{Name: String("n")}, `github.CodeResult{Name:"n"}`}, + {CommitAuthor{Name: String("n")}, `github.CommitAuthor{Name:"n"}`}, + {CommitFile{SHA: String("s")}, `github.CommitFile{SHA:"s"}`}, + {CommitStats{Total: Int(1)}, `github.CommitStats{Total:1}`}, + {CommitsComparison{TotalCommits: Int(1)}, `github.CommitsComparison{TotalCommits:1}`}, + {Commit{SHA: String("s")}, `github.Commit{SHA:"s"}`}, + {Event{ID: String("1")}, `github.Event{ID:"1"}`}, + {GistComment{ID: Int(1)}, `github.GistComment{ID:1}`}, + {GistFile{Size: Int(1)}, `github.GistFile{Size:1}`}, + {Gist{ID: String("1")}, `github.Gist{ID:"1", Files:map[]}`}, + {GitObject{SHA: String("s")}, `github.GitObject{SHA:"s"}`}, + {Gitignore{Name: String("n")}, `github.Gitignore{Name:"n"}`}, + {Hook{ID: Int(1)}, `github.Hook{Config:map[], ID:1}`}, + {IssueComment{ID: Int(1)}, `github.IssueComment{ID:1}`}, + {Issue{Number: Int(1)}, `github.Issue{Number:1}`}, + {Key{ID: Int(1)}, `github.Key{ID:1}`}, + {Label{Name: String("l")}, "l"}, + {Organization{ID: Int(1)}, `github.Organization{ID:1}`}, + {PullRequestComment{ID: Int(1)}, `github.PullRequestComment{ID:1}`}, + {PullRequest{Number: Int(1)}, `github.PullRequest{Number:1}`}, + {PushEventCommit{SHA: String("s")}, `github.PushEventCommit{SHA:"s"}`}, + {PushEvent{PushID: Int(1)}, `github.PushEvent{PushID:1}`}, + {Reference{Ref: String("r")}, `github.Reference{Ref:"r"}`}, + {ReleaseAsset{ID: Int(1)}, `github.ReleaseAsset{ID:1}`}, + {RepoStatus{ID: Int(1)}, `github.RepoStatus{ID:1}`}, + {RepositoryComment{ID: Int(1)}, `github.RepositoryComment{ID:1}`}, + {RepositoryCommit{SHA: String("s")}, `github.RepositoryCommit{SHA:"s"}`}, + {RepositoryContent{Name: String("n")}, `github.RepositoryContent{Name:"n"}`}, + {RepositoryRelease{ID: Int(1)}, `github.RepositoryRelease{ID:1}`}, + {Repository{ID: Int(1)}, `github.Repository{ID:1}`}, + {Team{ID: Int(1)}, `github.Team{ID:1}`}, + {TreeEntry{SHA: String("s")}, `github.TreeEntry{SHA:"s"}`}, + {Tree{SHA: String("s")}, `github.Tree{SHA:"s"}`}, + {User{ID: Int(1)}, `github.User{ID:1}`}, + {WebHookAuthor{Name: String("n")}, `github.WebHookAuthor{Name:"n"}`}, + {WebHookCommit{ID: String("1")}, `github.WebHookCommit{ID:"1"}`}, + {WebHookPayload{Ref: String("r")}, `github.WebHookPayload{Ref:"r"}`}, + } + + for i, tt := range tests { + s := tt.in.(fmt.Stringer).String() + if s != tt.out { + t.Errorf("%d. String() => %q, want %q", i, tt.in, tt.out) + } + } +} diff --git a/vendor/github.com/google/go-github/github/timestamp.go b/vendor/github.com/google/go-github/github/timestamp.go new file mode 100644 index 0000000..a1c1554 --- /dev/null +++ b/vendor/github.com/google/go-github/github/timestamp.go @@ -0,0 +1,41 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "strconv" + "time" +) + +// Timestamp represents a time that can be unmarshalled from a JSON string +// formatted as either an RFC3339 or Unix timestamp. This is necessary for some +// fields since the GitHub API is inconsistent in how it represents times. All +// exported methods of time.Time can be called on Timestamp. +type Timestamp struct { + time.Time +} + +func (t Timestamp) String() string { + return t.Time.String() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// Time is expected in RFC3339 or Unix format. +func (t *Timestamp) UnmarshalJSON(data []byte) (err error) { + str := string(data) + i, err := strconv.ParseInt(str, 10, 64) + if err == nil { + (*t).Time = time.Unix(i, 0) + } else { + (*t).Time, err = time.Parse(`"`+time.RFC3339+`"`, str) + } + return +} + +// Equal reports whether t and u are equal based on time.Equal +func (t Timestamp) Equal(u Timestamp) bool { + return t.Time.Equal(u.Time) +} diff --git a/vendor/github.com/google/go-github/github/timestamp_test.go b/vendor/github.com/google/go-github/github/timestamp_test.go new file mode 100644 index 0000000..12376c5 --- /dev/null +++ b/vendor/github.com/google/go-github/github/timestamp_test.go @@ -0,0 +1,181 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "testing" + "time" +) + +const ( + emptyTimeStr = `"0001-01-01T00:00:00Z"` + referenceTimeStr = `"2006-01-02T15:04:05Z"` + referenceUnixTimeStr = `1136214245` +) + +var ( + referenceTime = time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC) + unixOrigin = time.Unix(0, 0).In(time.UTC) +) + +func TestTimestamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + want string + wantErr bool + equal bool + }{ + {"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true}, + {"Empty", Timestamp{}, emptyTimeStr, false, true}, + {"Mismatch", Timestamp{}, referenceTimeStr, false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if (got == tc.want) != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimestamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want Timestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true}, + {"ReferenceUnix", `1136214245`, Timestamp{referenceTime}, false, true}, + {"Empty", emptyTimeStr, Timestamp{}, false, true}, + {"UnixStart", `0`, Timestamp{unixOrigin}, false, true}, + {"Mismatch", referenceTimeStr, Timestamp{}, false, false}, + {"MismatchUnix", `0`, Timestamp{}, false, false}, + {"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Equal(tc.want) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + }{ + {"Reference", Timestamp{referenceTime}}, + {"Empty", Timestamp{}}, + } + for _, tc := range testCases { + data, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got Timestamp + err = json.Unmarshal(data, &got) + if !got.Equal(tc.data) { + t.Errorf("%s: %+v != %+v", tc.desc, got, data) + } + } +} + +type WrappedTimestamp struct { + A int + Time Timestamp +} + +func TestWrappedTimstamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + want string + wantErr bool + equal bool + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true}, + {"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true}, + {"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if equal != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want WrappedTimestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true}, + {"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true}, + {"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Time.Equal(tc.want.Time.Time) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}}, + {"Empty", WrappedTimestamp{0, Timestamp{}}}, + } + for _, tc := range testCases { + bytes, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got WrappedTimestamp + err = json.Unmarshal(bytes, &got) + if !got.Time.Equal(tc.data.Time) { + t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data) + } + } +} diff --git a/vendor/github.com/google/go-github/github/users.go b/vendor/github.com/google/go-github/github/users.go new file mode 100644 index 0000000..a041bbf --- /dev/null +++ b/vendor/github.com/google/go-github/github/users.go @@ -0,0 +1,144 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// UsersService handles communication with the user related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/users/ +type UsersService struct { + client *Client +} + +// User represents a GitHub user. +type User struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + Hireable *bool `json:"hireable,omitempty"` + Bio *string `json:"bio,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + Plan *Plan `json:"plan,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` + + // Permissions identifies the permissions that a user has on a given + // repository. This is only populated when calling Repositories.ListCollaborators. + Permissions *map[string]bool `json:"permissions,omitempty"` +} + +func (u User) String() string { + return Stringify(u) +} + +// Get fetches a user. Passing the empty string will fetch the authenticated +// user. +// +// GitHub API docs: http://developer.github.com/v3/users/#get-a-single-user +func (s *UsersService) Get(user string) (*User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v", user) + } else { + u = "user" + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, err +} + +// Edit the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/#update-the-authenticated-user +func (s *UsersService) Edit(user *User) (*User, *Response, error) { + u := "user" + req, err := s.client.NewRequest("PATCH", u, user) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, err +} + +// UserListOptions specifies optional parameters to the UsersService.ListAll +// method. +type UserListOptions struct { + // ID of the last user seen + Since int `url:"since,omitempty"` +} + +// ListAll lists all GitHub users. +// +// GitHub API docs: http://developer.github.com/v3/users/#get-all-users +func (s *UsersService) ListAll(opt *UserListOptions) ([]User, *Response, error) { + u, err := addOptions("users", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} diff --git a/vendor/github.com/google/go-github/github/users_administration.go b/vendor/github.com/google/go-github/github/users_administration.go new file mode 100644 index 0000000..dc1dcb8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_administration.go @@ -0,0 +1,64 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// PromoteSiteAdmin promotes a user to a site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#promote-an-ordinary-user-to-a-site-administrator +func (s *UsersService) PromoteSiteAdmin(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DemoteSiteAdmin demotes a user from site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#demote-a-site-administrator-to-an-ordinary-user +func (s *UsersService) DemoteSiteAdmin(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Suspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#suspend-a-user +func (s *UsersService) Suspend(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Unsuspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#unsuspend-a-user +func (s *UsersService) Unsuspend(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_administration_test.go b/vendor/github.com/google/go-github/github/users_administration_test.go new file mode 100644 index 0000000..d415f4d --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_administration_test.go @@ -0,0 +1,71 @@ +// Copyright 2014 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "net/http" + "testing" +) + +func TestUsersService_PromoteSiteAdmin(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.PromoteSiteAdmin("u") + if err != nil { + t.Errorf("Users.PromoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_DemoteSiteAdmin(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.DemoteSiteAdmin("u") + if err != nil { + t.Errorf("Users.DemoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_Suspend(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Suspend("u") + if err != nil { + t.Errorf("Users.Suspend returned error: %v", err) + } +} + +func TestUsersService_Unsuspend(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Unsuspend("u") + if err != nil { + t.Errorf("Users.Unsuspend returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_emails.go b/vendor/github.com/google/go-github/github/users_emails.go new file mode 100644 index 0000000..7553191 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_emails.go @@ -0,0 +1,69 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +// UserEmail represents user's email address +type UserEmail struct { + Email *string `json:"email,omitempty"` + Primary *bool `json:"primary,omitempty"` + Verified *bool `json:"verified,omitempty"` +} + +// ListEmails lists all email addresses for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user +func (s *UsersService) ListEmails(opt *ListOptions) ([]UserEmail, *Response, error) { + u := "user/emails" + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + emails := new([]UserEmail) + resp, err := s.client.Do(req, emails) + if err != nil { + return nil, resp, err + } + + return *emails, resp, err +} + +// AddEmails adds email addresses of the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#add-email-addresses +func (s *UsersService) AddEmails(emails []string) ([]UserEmail, *Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("POST", u, emails) + if err != nil { + return nil, nil, err + } + + e := new([]UserEmail) + resp, err := s.client.Do(req, e) + if err != nil { + return nil, resp, err + } + + return *e, resp, err +} + +// DeleteEmails deletes email addresses from authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#delete-email-addresses +func (s *UsersService) DeleteEmails(emails []string) (*Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("DELETE", u, emails) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_emails_test.go b/vendor/github.com/google/go-github/github/users_emails_test.go new file mode 100644 index 0000000..7eb6508 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_emails_test.go @@ -0,0 +1,94 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListEmails(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{ + "email": "user@example.com", + "verified": false, + "primary": true + }]`) + }) + + opt := &ListOptions{Page: 2} + emails, _, err := client.Users.ListEmails(opt) + if err != nil { + t.Errorf("Users.ListEmails returned error: %v", err) + } + + want := []UserEmail{{Email: String("user@example.com"), Verified: Bool(false), Primary: Bool(true)}} + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.ListEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_AddEmails(t *testing.T) { + setup() + defer teardown() + + input := []string{"new@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", *v, input) + } + + fmt.Fprint(w, `[{"email":"old@example.com"}, {"email":"new@example.com"}]`) + }) + + emails, _, err := client.Users.AddEmails(input) + if err != nil { + t.Errorf("Users.AddEmails returned error: %v", err) + } + + want := []UserEmail{ + {Email: String("old@example.com")}, + {Email: String("new@example.com")}, + } + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.AddEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_DeleteEmails(t *testing.T) { + setup() + defer teardown() + + input := []string{"user@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", *v, input) + } + }) + + _, err := client.Users.DeleteEmails(input) + if err != nil { + t.Errorf("Users.DeleteEmails returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_followers.go b/vendor/github.com/google/go-github/github/users_followers.go new file mode 100644 index 0000000..7ecbed9 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_followers.go @@ -0,0 +1,116 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// ListFollowers lists the followers for a user. Passing the empty string will +// fetch followers for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#list-followers-of-a-user +func (s *UsersService) ListFollowers(user string, opt *ListOptions) ([]User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/followers", user) + } else { + u = "user/followers" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// ListFollowing lists the people that a user is following. Passing the empty +// string will list people the authenticated user is following. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#list-users-followed-by-another-user +func (s *UsersService) ListFollowing(user string, opt *ListOptions) ([]User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following", user) + } else { + u = "user/following" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// IsFollowing checks if "user" is following "target". Passing the empty +// string for "user" will check if the authenticated user is following "target". +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user +func (s *UsersService) IsFollowing(user, target string) (bool, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following/%v", user, target) + } else { + u = fmt.Sprintf("user/following/%v", target) + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + following, err := parseBoolResponse(err) + return following, resp, err +} + +// Follow will cause the authenticated user to follow the specified user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#follow-a-user +func (s *UsersService) Follow(user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Unfollow will cause the authenticated user to unfollow the specified user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#unfollow-a-user +func (s *UsersService) Unfollow(user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_followers_test.go b/vendor/github.com/google/go-github/github/users_followers_test.go new file mode 100644 index 0000000..f4d2457 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_followers_test.go @@ -0,0 +1,222 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListFollowers_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowers("", opt) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowers("u", nil) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_invalidUser(t *testing.T) { + _, _, err := client.Users.ListFollowers("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_ListFollowing_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opts := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowing("", opts) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowing("u", nil) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_invalidUser(t *testing.T) { + _, _, err := client.Users.ListFollowing("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_IsFollowing_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing("", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_invalidUser(t *testing.T) { + _, _, err := client.Users.IsFollowing("%", "%") + testURLParseError(t, err) +} + +func TestUsersService_Follow(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Users.Follow("u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Follow_invalidUser(t *testing.T) { + _, err := client.Users.Follow("%") + testURLParseError(t, err) +} + +func TestUsersService_Unfollow(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.Unfollow("u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Unfollow_invalidUser(t *testing.T) { + _, err := client.Users.Unfollow("%") + testURLParseError(t, err) +} diff --git a/vendor/github.com/google/go-github/github/users_keys.go b/vendor/github.com/google/go-github/github/users_keys.go new file mode 100644 index 0000000..dcbd773 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_keys.go @@ -0,0 +1,104 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import "fmt" + +// Key represents a public SSH key used to authenticate a user or deploy script. +type Key struct { + ID *int `json:"id,omitempty"` + Key *string `json:"key,omitempty"` + URL *string `json:"url,omitempty"` + Title *string `json:"title,omitempty"` +} + +func (k Key) String() string { + return Stringify(k) +} + +// ListKeys lists the verified public keys for a user. Passing the empty +// string will fetch keys for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#list-public-keys-for-a-user +func (s *UsersService) ListKeys(user string, opt *ListOptions) ([]Key, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/keys", user) + } else { + u = "user/keys" + } + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + keys := new([]Key) + resp, err := s.client.Do(req, keys) + if err != nil { + return nil, resp, err + } + + return *keys, resp, err +} + +// GetKey fetches a single public key. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#get-a-single-public-key +func (s *UsersService) GetKey(id int) (*Key, *Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, err +} + +// CreateKey adds a public key for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#create-a-public-key +func (s *UsersService) CreateKey(key *Key) (*Key, *Response, error) { + u := "user/keys" + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteKey deletes a public key. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#delete-a-public-key +func (s *UsersService) DeleteKey(id int) (*Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/google/go-github/github/users_keys_test.go b/vendor/github.com/google/go-github/github/users_keys_test.go new file mode 100644 index 0000000..e47afd7 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_keys_test.go @@ -0,0 +1,124 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUsersService_ListKeys_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Users.ListKeys("", opt) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + keys, _, err := client.Users.ListKeys("u", nil) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_invalidUser(t *testing.T) { + _, _, err := client.Users.ListKeys("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_GetKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.GetKey(1) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_CreateKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.CreateKey(input) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_DeleteKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.DeleteKey(1) + if err != nil { + t.Errorf("Users.DeleteKey returned error: %v", err) + } +} diff --git a/vendor/github.com/google/go-github/github/users_test.go b/vendor/github.com/google/go-github/github/users_test.go new file mode 100644 index 0000000..15ea3e8 --- /dev/null +++ b/vendor/github.com/google/go-github/github/users_test.go @@ -0,0 +1,150 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestUser_marshall(t *testing.T) { + testJSONMarshal(t, &User{}, "{}") + + u := &User{ + Login: String("l"), + ID: Int(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + } + want := `{ + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "url": "u" + }` + testJSONMarshal(t, u, want) +} + +func TestUsersService_Get_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get("") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get("u") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_invalidUser(t *testing.T) { + _, _, err := client.Users.Get("%") + testURLParseError(t, err) +} + +func TestUsersService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &User{Name: String("n")} + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + v := new(User) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Edit(input) + if err != nil { + t.Errorf("Users.Edit returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Edit returned %+v, want %+v", user, want) + } +} + +func TestUsersService_ListAll(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"since": "1"}) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &UserListOptions{1} + users, _, err := client.Users.ListAll(opt) + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := []User{{ID: Int(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListAll returned %+v, want %+v", users, want) + } +} diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go new file mode 100644 index 0000000..31d36e7 --- /dev/null +++ b/vendor/github.com/google/go-querystring/query/encode.go @@ -0,0 +1,274 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package query implements encoding of structs into URL query parameters. +// +// As a simple example: +// +// type Options struct { +// Query string `url:"q"` +// ShowAll bool `url:"all"` +// Page int `url:"page"` +// } +// +// opt := Options{ "foo", true, 2 } +// v, _ := query.Values(opt) +// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +// +// The exact mapping between Go values and url.Values is described in the +// documentation for the Values() function. +package query + +import ( + "bytes" + "errors" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) + +var encoderType = reflect.TypeOf(new(Encoder)).Elem() + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// Values returns the url.Values encoding of v. +// +// Values expects to be passed a struct, and traverses it recursively using the +// following encoding rules. +// +// Each exported struct field is encoded as a URL parameter unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option +// +// The empty values are false, 0, any nil pointer or interface value, any array +// slice, map, or string of length zero, and any time.Time that returns true +// for IsZero(). +// +// The URL parameter name defaults to the struct field name but can be +// specified in the struct field's tag value. The "url" key in the struct +// field's tag value is the key name, followed by an optional comma and +// options. For example: +// +// // Field is ignored by this package. +// Field int `url:"-"` +// +// // Field appears as URL parameter "myName". +// Field int `url:"myName"` +// +// // Field appears as URL parameter "myName" and the field is omitted if +// // its value is empty +// Field int `url:"myName,omitempty"` +// +// // Field appears as URL parameter "Field" (the default), but the field +// // is skipped if empty. Note the leading comma. +// Field int `url:",omitempty"` +// +// For encoding individual field values, the following type-dependent rules +// apply: +// +// Boolean values default to encoding as the strings "true" or "false". +// Including the "int" option signals that the field should be encoded as the +// strings "1" or "0". +// +// time.Time values default to encoding as RFC3339 timestamps. Including the +// "unix" option signals that the field should be encoded as a Unix time (see +// time.Unix()) +// +// Slice and Array values default to encoding as multiple URL values of the +// same name. Including the "comma" option signals that the field should be +// encoded as a single comma-delimited value. Including the "space" option +// similarly encodes the value as a single space-delimited string. +// +// Anonymous struct fields are usually encoded as if their inner exported +// fields were fields in the outer struct, subject to the standard Go +// visibility rules. An anonymous struct field with a name given in its URL +// tag is treated as having that name, rather than being anonymous. +// +// Non-nil pointer values are encoded as the value pointed to. +// +// All other values are encoded using their default string representation. +// +// Multiple fields that encode to the same URL parameter name will be included +// as multiple URL values of the same name. +func Values(v interface{}) (url.Values, error) { + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, errors.New("query: Values() expects non-nil value") + } + val = val.Elem() + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + values := make(url.Values) + err := reflectValue(values, val) + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value) error { + var embedded []reflect.Value + + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if name == "" { + if sf.Anonymous && sv.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, sv) + continue + } + + name = sf.Name + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(encoderType) { + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + var del byte + if opts.Contains("comma") { + del = ',' + } else if opts.Contains("space") { + del = ' ' + } + + if del != 0 { + s := new(bytes.Buffer) + first := true + for i := 0; i < sv.Len(); i++ { + if first { + first = false + } else { + s.WriteByte(del) + } + s.WriteString(valueString(sv.Index(i), opts)) + } + values.Add(name, s.String()) + } else { + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts)) + } + } + continue + } + + values.Add(name, valueString(sv, opts)) + } + + for _, f := range embedded { + if err := reflectValue(values, f); err != nil { + return err + } + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + if v.Bool() { + return "1" + } + return "0" + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + return t.Format(time.RFC3339) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if v.Type() == timeType { + return v.Interface().(time.Time).IsZero() + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/vendor/github.com/google/go-querystring/query/encode_test.go b/vendor/github.com/google/go-querystring/query/encode_test.go new file mode 100644 index 0000000..8afbd0b --- /dev/null +++ b/vendor/github.com/google/go-querystring/query/encode_test.go @@ -0,0 +1,238 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package query + +import ( + "fmt" + "net/url" + "reflect" + "testing" + "time" +) + +func TestValues_types(t *testing.T) { + str := "string" + strPtr := &str + + tests := []struct { + in interface{} + want url.Values + }{ + { + // basic primitives + struct { + A string + B int + C uint + D float32 + E bool + }{}, + url.Values{ + "A": {""}, + "B": {"0"}, + "C": {"0"}, + "D": {"0"}, + "E": {"false"}, + }, + }, + { + // pointers + struct { + A *string + B *int + C **string + }{A: strPtr, C: &strPtr}, + url.Values{ + "A": {str}, + "B": {""}, + "C": {str}, + }, + }, + { + // slices and arrays + struct { + A []string + B []string `url:",comma"` + C []string `url:",space"` + D [2]string + E [2]string `url:",comma"` + F [2]string `url:",space"` + G []*string `url:",space"` + H []bool `url:",int,space"` + }{ + A: []string{"a", "b"}, + B: []string{"a", "b"}, + C: []string{"a", "b"}, + D: [2]string{"a", "b"}, + E: [2]string{"a", "b"}, + F: [2]string{"a", "b"}, + G: []*string{&str, &str}, + H: []bool{true, false}, + }, + url.Values{ + "A": {"a", "b"}, + "B": {"a,b"}, + "C": {"a b"}, + "D": {"a", "b"}, + "E": {"a,b"}, + "F": {"a b"}, + "G": {"string string"}, + "H": {"1 0"}, + }, + }, + { + // other types + struct { + A time.Time + B time.Time `url:",unix"` + C bool `url:",int"` + D bool `url:",int"` + }{ + A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + C: true, + D: false, + }, + url.Values{ + "A": {"2000-01-01T12:34:56Z"}, + "B": {"946730096"}, + "C": {"1"}, + "D": {"0"}, + }, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_omitEmpty(t *testing.T) { + str := "" + s := struct { + a string + A string + B string `url:",omitempty"` + C string `url:"-"` + D string `url:"omitempty"` // actually named omitempty, not an option + E *string `url:",omitempty"` + }{E: &str} + + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "A": {""}, + "omitempty": {""}, + "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +type A struct { + B +} + +type B struct { + C string +} + +type D struct { + B + C string +} + +func TestValues_embeddedStructs(t *testing.T) { + tests := []struct { + in interface{} + want url.Values + }{ + { + A{B{C: "foo"}}, + url.Values{"C": {"foo"}}, + }, + { + D{B: B{C: "bar"}, C: "foo"}, + url.Values{"C": {"foo", "bar"}}, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_invalidInput(t *testing.T) { + _, err := Values("") + if err == nil { + t.Errorf("expected Values() to return an error on invalid input") + } +} + +type EncodedArgs []string + +func (m EncodedArgs) EncodeValues(key string, v *url.Values) error { + for i, arg := range m { + v.Set(fmt.Sprintf("%s.%d", key, i), arg) + } + return nil +} + +func TestValues_Marshaler(t *testing.T) { + s := struct { + Args EncodedArgs `url:"arg"` + }{[]string{"a", "b", "c"}} + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "arg.0": {"a"}, + "arg.1": {"b"}, + "arg.2": {"c"}, + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +func TestTagParsing(t *testing.T) { + name, opts := parseTag("field,foobar,foo") + if name != "field" { + t.Fatalf("name = %q, want field", name) + } + for _, tt := range []struct { + opt string + want bool + }{ + {"foobar", true}, + {"foo", true}, + {"bar", false}, + {"field", false}, + } { + if opts.Contains(tt.opt) != tt.want { + t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + } + } +} diff --git a/vendor/github.com/lgtmco/lgtm-go/lgtm/client.go b/vendor/github.com/lgtmco/lgtm-go/lgtm/client.go new file mode 100644 index 0000000..f6f91a3 --- /dev/null +++ b/vendor/github.com/lgtmco/lgtm-go/lgtm/client.go @@ -0,0 +1,198 @@ +package lgtm + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + + "golang.org/x/oauth2" +) + +const ( + pathLogin = "%s/login?access_token=%s" + pathUser = "%s/api/user" + pathRepos = "%s/api/user/repos" + pathRepo = "%s/api/repos/%s" + pathConf = "%s/api/repos/%s/maintainers" + pathOrgConf = "%s/api/repos/%s/maintainers/%s" +) + +type client struct { + client *http.Client + base string // base url +} + +// NewClient returns a client at the specified url. +func NewClient(uri string) Client { + return &client{http.DefaultClient, uri} +} + +// NewClientToken returns a client at the specified url that +// authenticates all outbound requests with the given token. +func NewClientToken(uri, token string) Client { + config := new(oauth2.Config) + auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token}) + return &client{auther, uri} +} + +// SetClient sets the default http client. This should be +// used in conjunction with golang.org/x/oauth2 to +// authenticate requests to the server. +func (c *client) SetClient(client *http.Client) { + c.client = client +} + +// Token returns a repository MAINTAINERS configuration. +func (c *client) Token(token string) (*Token, error) { + out := new(Token) + uri := fmt.Sprintf(pathLogin, c.base, token) + err := c.post(uri, nil, out) + return out, err +} + +// User returns the currently authenticated user. +func (c *client) User() (*User, error) { + out := new(User) + uri := fmt.Sprintf(pathUser, c.base) + err := c.get(uri, out) + return out, err +} + +// Repo returns a repository by name. +func (c *client) Repo(repo string) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepo, c.base, repo) + err := c.get(uri, out) + return out, err +} + +// Repos returns a list of all repositories to which +// the user has explicit access in the host system. +func (c *client) Repos() ([]*Repo, error) { + out := make([]*Repo, 0) + uri := fmt.Sprintf(pathRepos, c.base) + err := c.get(uri, &out) + return out, err +} + +// Activate activates a repository. +func (c *client) Activate(repo string) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepo, c.base, repo) + err := c.post(uri, nil, out) + return out, err +} + +// Deactivate de-activates a repository. +func (c *client) Deactivate(repo string) error { + uri := fmt.Sprintf(pathRepo, c.base, repo) + err := c.delete(uri) + return err +} + +// Maintainer returns a repository MAINTAINERS configuration. +func (c *client) Maintainer(repo string) (*Maintainer, error) { + out := new(Maintainer) + uri := fmt.Sprintf(pathConf, c.base, repo) + err := c.get(uri, out) + return out, err +} + +// MaintainerOrg returns a repository MAINTAINERS configuration file +// for a subset of people in the sepcified organization. +func (c *client) MaintainerOrg(repo, org string) (*Maintainer, error) { + out := new(Maintainer) + uri := fmt.Sprintf(pathOrgConf, c.base, repo, org) + err := c.get(uri, out) + return out, err +} + +// +// http request helper functions +// + +// helper function for making an http GET request. +func (c *client) get(rawurl string, out interface{}) error { + return c.do(rawurl, "GET", nil, out) +} + +// helper function for making an http POST request. +func (c *client) post(rawurl string, in, out interface{}) error { + return c.do(rawurl, "POST", in, out) +} + +// helper function for making an http PUT request. +func (c *client) put(rawurl string, in, out interface{}) error { + return c.do(rawurl, "PUT", in, out) +} + +// helper function for making an http PATCH request. +func (c *client) patch(rawurl string, in, out interface{}) error { + return c.do(rawurl, "PATCH", in, out) +} + +// helper function for making an http DELETE request. +func (c *client) delete(rawurl string) error { + return c.do(rawurl, "DELETE", nil, nil) +} + +// helper function to make an http request +func (c *client) do(rawurl, method string, in, out interface{}) error { + // executes the http request and returns the body as + // and io.ReadCloser + body, err := c.stream(rawurl, method, in, out) + if err != nil { + return err + } + defer body.Close() + + // if a json response is expected, parse and return + // the json response. + if out != nil { + return json.NewDecoder(body).Decode(out) + } + return nil +} + +// helper function to stream an http request +func (c *client) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) { + uri, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + // if we are posting or putting data, we need to + // write it to the body of the request. + var buf io.ReadWriter + if in != nil { + buf = new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(in) + if err != nil { + return nil, err + } + } + + // creates a new http request to bitbucket. + req, err := http.NewRequest(method, uri.String(), buf) + if err != nil { + return nil, err + } + if in != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode > http.StatusPartialContent { + defer resp.Body.Close() + out, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf(string(out)) + } + return resp.Body, nil +} diff --git a/vendor/github.com/lgtmco/lgtm-go/lgtm/interface.go b/vendor/github.com/lgtmco/lgtm-go/lgtm/interface.go new file mode 100644 index 0000000..3063f47 --- /dev/null +++ b/vendor/github.com/lgtmco/lgtm-go/lgtm/interface.go @@ -0,0 +1,13 @@ +package lgtm + +// Client to access the LGTM remote APIs. +type Client interface { + User() (*User, error) + Token(string) (*Token, error) + Repo(string) (*Repo, error) + Repos() ([]*Repo, error) + Activate(string) (*Repo, error) + Deactivate(string) error + Maintainer(string) (*Maintainer, error) + MaintainerOrg(string, string) (*Maintainer, error) +} diff --git a/vendor/github.com/lgtmco/lgtm-go/lgtm/types.go b/vendor/github.com/lgtmco/lgtm-go/lgtm/types.go new file mode 100644 index 0000000..1cbae4c --- /dev/null +++ b/vendor/github.com/lgtmco/lgtm-go/lgtm/types.go @@ -0,0 +1,46 @@ +package lgtm + +// Person represets an individual in the MAINTAINERS file. +type Person struct { + Name string `json:"name" toml:"name"` + Email string `json:"email" toml:"email"` + Login string `json:"login" toml:"login"` +} + +// Org represents a group, team or subset of users. +type Org struct { + People []string `json:"people" toml:"people"` +} + +// Maintainer represents a MAINTAINERS file. +type Maintainer struct { + People map[string]*Person `json:"people" toml:"people"` + Org map[string]*Org `json:"org" toml:"org"` + Approvals int `json:"approvals" toml:"approvals"` +} + +// User represents a github user. +type User struct { + ID int64 `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + Avatar string `json:"avatar"` +} + +// Repo represents a github repository. +type Repo struct { + ID int64 `json:"id,omitempty"` + Owner string `json:"owner"` + Name string `json:"name"` + Slug string `json:"slug"` + Private bool `json:"private"` + Link string `json:"link_url"` +} + +// Token represents an Access Token used to authenticate +// to the remote server. +type Token struct { + Access string `json:"access_token"` + Refresh string `json:"refresh_token"` + Expires int64 `json:"expires_in"` +} diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go new file mode 100644 index 0000000..e7ee376 --- /dev/null +++ b/vendor/golang.org/x/net/context/context.go @@ -0,0 +1,447 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it's is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, &c) + return &c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) cancelCtx { + return cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return &c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/vendor/golang.org/x/net/context/context_test.go b/vendor/golang.org/x/net/context/context_test.go new file mode 100644 index 0000000..05345fc --- /dev/null +++ b/vendor/golang.org/x/net/context/context_test.go @@ -0,0 +1,575 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "fmt" + "math/rand" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +func TestBackground(t *testing.T) { + c := Background() + if c == nil { + t.Fatalf("Background returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.Background"; got != want { + t.Errorf("Background().String() = %q want %q", got, want) + } +} + +func TestTODO(t *testing.T) { + c := TODO() + if c == nil { + t.Fatalf("TODO returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.TODO"; got != want { + t.Errorf("TODO().String() = %q want %q", got, want) + } +} + +func TestWithCancel(t *testing.T) { + c1, cancel := WithCancel(Background()) + + if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { + t.Errorf("c1.String() = %q want %q", got, want) + } + + o := otherContext{c1} + c2, _ := WithCancel(o) + contexts := []Context{c1, o, c2} + + for i, c := range contexts { + if d := c.Done(); d == nil { + t.Errorf("c[%d].Done() == %v want non-nil", i, d) + } + if e := c.Err(); e != nil { + t.Errorf("c[%d].Err() == %v want nil", i, e) + } + + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + } + + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + + for i, c := range contexts { + select { + case <-c.Done(): + default: + t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) + } + if e := c.Err(); e != Canceled { + t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) + } + } +} + +func TestParentFinishesChild(t *testing.T) { + // Context tree: + // parent -> cancelChild + // parent -> valueChild -> timerChild + parent, cancel := WithCancel(Background()) + cancelChild, stop := WithCancel(parent) + defer stop() + valueChild := WithValue(parent, "key", "value") + timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + defer stop() + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-cancelChild.Done(): + t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) + case x := <-timerChild.Done(): + t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) + case x := <-valueChild.Done(): + t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) + default: + } + + // The parent's children should contain the two cancelable children. + pc := parent.(*cancelCtx) + cc := cancelChild.(*cancelCtx) + tc := timerChild.(*timerCtx) + pc.mu.Lock() + if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { + t.Errorf("bad linkage: pc.children = %v, want %v and %v", + pc.children, cc, tc) + } + pc.mu.Unlock() + + if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) + } + if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) + } + + cancel() + + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) + } + pc.mu.Unlock() + + // parent and children should all be finished. + check := func(ctx Context, name string) { + select { + case <-ctx.Done(): + default: + t.Errorf("<-%s.Done() blocked, but shouldn't have", name) + } + if e := ctx.Err(); e != Canceled { + t.Errorf("%s.Err() == %v want %v", name, e, Canceled) + } + } + check(parent, "parent") + check(cancelChild, "cancelChild") + check(valueChild, "valueChild") + check(timerChild, "timerChild") + + // WithCancel should return a canceled context on a canceled parent. + precanceledChild := WithValue(parent, "key", "value") + select { + case <-precanceledChild.Done(): + default: + t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") + } + if e := precanceledChild.Err(); e != Canceled { + t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) + } +} + +func TestChildFinishesFirst(t *testing.T) { + cancelable, stop := WithCancel(Background()) + defer stop() + for _, parent := range []Context{Background(), cancelable} { + child, cancel := WithCancel(parent) + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-child.Done(): + t.Errorf("<-child.Done() == %v want nothing (it should block)", x) + default: + } + + cc := child.(*cancelCtx) + pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() + if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { + t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) + } + + if pcok { + pc.mu.Lock() + if len(pc.children) != 1 || !pc.children[cc] { + t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) + } + pc.mu.Unlock() + } + + cancel() + + if pcok { + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) + } + pc.mu.Unlock() + } + + // child should be finished. + select { + case <-child.Done(): + default: + t.Errorf("<-child.Done() blocked, but shouldn't have") + } + if e := child.Err(); e != Canceled { + t.Errorf("child.Err() == %v want %v", e, Canceled) + } + + // parent should not be finished. + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + default: + } + if e := parent.Err(); e != nil { + t.Errorf("parent.Err() == %v want nil", e) + } + } +} + +func testDeadline(c Context, wait time.Duration, t *testing.T) { + select { + case <-time.After(wait): + t.Fatalf("context should have timed out") + case <-c.Done(): + } + if e := c.Err(); e != DeadlineExceeded { + t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) + } +} + +func TestDeadline(t *testing.T) { + c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o = otherContext{c} + c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 100*time.Millisecond) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o = otherContext{c} + c, _ = WithTimeout(o, 300*time.Millisecond) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestCanceledTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 200*time.Millisecond) + o := otherContext{c} + c, cancel := WithTimeout(o, 400*time.Millisecond) + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + select { + case <-c.Done(): + default: + t.Errorf("<-c.Done() blocked, but shouldn't have") + } + if e := c.Err(); e != Canceled { + t.Errorf("c.Err() == %v want %v", e, Canceled) + } +} + +type key1 int +type key2 int + +var k1 = key1(1) +var k2 = key2(1) // same int as k1, different type +var k3 = key2(3) // same type as k2, different int + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Background() + check(c0, "c0", "", "", "") + + c1 := WithValue(Background(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + } + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Background()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Background(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} + +func TestAllocs(t *testing.T) { + bg := Background() + for _, test := range []struct { + desc string + f func() + limit float64 + gccgoLimit float64 + }{ + { + desc: "Background()", + f: func() { Background() }, + limit: 0, + gccgoLimit: 0, + }, + { + desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), + f: func() { + c := WithValue(bg, k1, nil) + c.Value(k1) + }, + limit: 3, + gccgoLimit: 3, + }, + { + desc: "WithTimeout(bg, 15*time.Millisecond)", + f: func() { + c, _ := WithTimeout(bg, 15*time.Millisecond) + <-c.Done() + }, + limit: 8, + gccgoLimit: 15, + }, + { + desc: "WithCancel(bg)", + f: func() { + c, cancel := WithCancel(bg) + cancel() + <-c.Done() + }, + limit: 5, + gccgoLimit: 8, + }, + { + desc: "WithTimeout(bg, 100*time.Millisecond)", + f: func() { + c, cancel := WithTimeout(bg, 100*time.Millisecond) + cancel() + <-c.Done() + }, + limit: 8, + gccgoLimit: 25, + }, + } { + limit := test.limit + if runtime.Compiler == "gccgo" { + // gccgo does not yet do escape analysis. + // TOOD(iant): Remove this when gccgo does do escape analysis. + limit = test.gccgoLimit + } + if n := testing.AllocsPerRun(100, test.f); n > limit { + t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) + } + } +} + +func TestSimultaneousCancels(t *testing.T) { + root, cancel := WithCancel(Background()) + m := map[Context]CancelFunc{root: cancel} + q := []Context{root} + // Create a tree of contexts. + for len(q) != 0 && len(m) < 100 { + parent := q[0] + q = q[1:] + for i := 0; i < 4; i++ { + ctx, cancel := WithCancel(parent) + m[ctx] = cancel + q = append(q, ctx) + } + } + // Start all the cancels in a random order. + var wg sync.WaitGroup + wg.Add(len(m)) + for _, cancel := range m { + go func(cancel CancelFunc) { + cancel() + wg.Done() + }(cancel) + } + // Wait on all the contexts in a random order. + for ctx := range m { + select { + case <-ctx.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + } + } + // Wait for all the cancel functions to return. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + } +} + +func TestInterlockedCancels(t *testing.T) { + parent, cancelParent := WithCancel(Background()) + child, cancelChild := WithCancel(parent) + go func() { + parent.Done() + cancelChild() + }() + cancelParent() + select { + case <-child.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + } +} + +func TestLayersCancel(t *testing.T) { + testLayers(t, time.Now().UnixNano(), false) +} + +func TestLayersTimeout(t *testing.T) { + testLayers(t, time.Now().UnixNano(), true) +} + +func testLayers(t *testing.T, seed int64, testTimeout bool) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + minLayers = 30 + ) + type value int + var ( + vals []*value + cancels []CancelFunc + numTimers int + ctx = Background() + ) + for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { + switch rand.Intn(3) { + case 0: + v := new(value) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + ctx, cancel = WithTimeout(ctx, timeout) + cancels = append(cancels, cancel) + numTimers++ + } + } + checkValues := func(when string) { + for _, key := range vals { + if val := ctx.Value(key).(*value); key != val { + errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { + t.Errorf("ctx.String() = %q want prefix %q", s, prefix) + } + t.Log(ctx) + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + 100*time.Millisecond): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +} + +func TestCancelRemoves(t *testing.T) { + checkChildren := func(when string, ctx Context, want int) { + if got := len(ctx.(*cancelCtx).children); got != want { + t.Errorf("%s: context has %d children, want %d", when, got, want) + } + } + + ctx, _ := WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel := WithCancel(ctx) + checkChildren("with WithCancel child ", ctx, 1) + cancel() + checkChildren("after cancelling WithCancel child", ctx, 0) + + ctx, _ = WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel = WithTimeout(ctx, 60*time.Minute) + checkChildren("with WithTimeout child ", ctx, 1) + cancel() + checkChildren("after cancelling WithTimeout child", ctx, 0) +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/cancelreq.go b/vendor/golang.org/x/net/context/ctxhttp/cancelreq.go new file mode 100644 index 0000000..48610e3 --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/cancelreq.go @@ -0,0 +1,18 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.5 + +package ctxhttp + +import "net/http" + +func canceler(client *http.Client, req *http.Request) func() { + ch := make(chan struct{}) + req.Cancel = ch + + return func() { + close(ch) + } +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go b/vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go new file mode 100644 index 0000000..56bcbad --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/cancelreq_go14.go @@ -0,0 +1,23 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.5 + +package ctxhttp + +import "net/http" + +type requestCanceler interface { + CancelRequest(*http.Request) +} + +func canceler(client *http.Client, req *http.Request) func() { + rc, ok := client.Transport.(requestCanceler) + if !ok { + return func() {} + } + return func() { + rc.CancelRequest(req) + } +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go new file mode 100644 index 0000000..504dd63 --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go @@ -0,0 +1,79 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ctxhttp provides helper functions for performing context-aware HTTP requests. +package ctxhttp // import "golang.org/x/net/context/ctxhttp" + +import ( + "io" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" +) + +// Do sends an HTTP request with the provided http.Client and returns an HTTP response. +// If the client is nil, http.DefaultClient is used. +// If the context is canceled or times out, ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + + // Request cancelation changed in Go 1.5, see cancelreq.go and cancelreq_go14.go. + cancel := canceler(client, req) + + type responseAndError struct { + resp *http.Response + err error + } + result := make(chan responseAndError, 1) + + go func() { + resp, err := client.Do(req) + result <- responseAndError{resp, err} + }() + + select { + case <-ctx.Done(): + cancel() + return nil, ctx.Err() + case r := <-result: + return r.resp, r.err + } +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go new file mode 100644 index 0000000..47b53d7 --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go @@ -0,0 +1,72 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ctxhttp + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "golang.org/x/net/context" +) + +const ( + requestDuration = 100 * time.Millisecond + requestBody = "ok" +) + +func TestNoTimeout(t *testing.T) { + ctx := context.Background() + resp, err := doRequest(ctx) + + if resp == nil || err != nil { + t.Fatalf("error received from client: %v %v", err, resp) + } +} +func TestCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(requestDuration / 2) + cancel() + }() + + resp, err := doRequest(ctx) + + if resp != nil || err == nil { + t.Fatalf("expected error, didn't get one. resp: %v", resp) + } + if err != ctx.Err() { + t.Fatalf("expected error from context but got: %v", err) + } +} + +func TestCancelAfterRequest(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + resp, err := doRequest(ctx) + + // Cancel before reading the body. + // Request.Body should still be readable after the context is canceled. + cancel() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil || string(b) != requestBody { + t.Fatalf("could not read body: %q %v", b, err) + } +} + +func doRequest(ctx context.Context) (*http.Response, error) { + var okHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(requestDuration) + w.Write([]byte(requestBody)) + }) + + serv := httptest.NewServer(okHandler) + defer serv.Close() + + return Get(ctx, nil, serv.URL) +} diff --git a/vendor/golang.org/x/net/context/withtimeout_test.go b/vendor/golang.org/x/net/context/withtimeout_test.go new file mode 100644 index 0000000..a6754dc --- /dev/null +++ b/vendor/golang.org/x/net/context/withtimeout_test.go @@ -0,0 +1,26 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context_test + +import ( + "fmt" + "time" + + "golang.org/x/net/context" +) + +func ExampleWithTimeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) + select { + case <-time.After(200 * time.Millisecond): + fmt.Println("overslept") + case <-ctx.Done(): + fmt.Println(ctx.Err()) // prints "context deadline exceeded" + } + // Output: + // context deadline exceeded +} diff --git a/vendor/golang.org/x/oauth2/AUTHORS b/vendor/golang.org/x/oauth2/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/oauth2/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/oauth2/CONTRIBUTING.md b/vendor/golang.org/x/oauth2/CONTRIBUTING.md new file mode 100644 index 0000000..46aa2b1 --- /dev/null +++ b/vendor/golang.org/x/oauth2/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to Go + +Go is an open source project. + +It is the work of hundreds of contributors. We appreciate your help! + + +## Filing issues + +When [filing an issue](https://github.com/golang/oauth2/issues), make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. +The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) +before sending patches. + +**We do not accept GitHub pull requests** +(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review). + +Unless otherwise noted, the Go source files are distributed under +the BSD-style license found in the LICENSE file. + diff --git a/vendor/golang.org/x/oauth2/CONTRIBUTORS b/vendor/golang.org/x/oauth2/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/oauth2/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/oauth2/LICENSE b/vendor/golang.org/x/oauth2/LICENSE new file mode 100644 index 0000000..d02f24f --- /dev/null +++ b/vendor/golang.org/x/oauth2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The oauth2 Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/oauth2/README.md b/vendor/golang.org/x/oauth2/README.md new file mode 100644 index 0000000..0d51417 --- /dev/null +++ b/vendor/golang.org/x/oauth2/README.md @@ -0,0 +1,64 @@ +# OAuth2 for Go + +[![Build Status](https://travis-ci.org/golang/oauth2.svg?branch=master)](https://travis-ci.org/golang/oauth2) + +oauth2 package contains a client implementation for OAuth 2.0 spec. + +## Installation + +~~~~ +go get golang.org/x/oauth2 +~~~~ + +See godoc for further documentation and examples. + +* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2) +* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google) + + +## App Engine + +In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor +of the [`context.Context`](https://golang.org/x/net/context#Context) type from +the `golang.org/x/net/context` package + +This means its no longer possible to use the "Classic App Engine" +`appengine.Context` type with the `oauth2` package. (You're using +Classic App Engine if you import the package `"appengine"`.) + +To work around this, you may use the new `"google.golang.org/appengine"` +package. This package has almost the same API as the `"appengine"` package, +but it can be fetched with `go get` and used on "Managed VMs" and well as +Classic App Engine. + +See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app) +for information on updating your app. + +If you don't want to update your entire app to use the new App Engine packages, +you may use both sets of packages in parallel, using only the new packages +with the `oauth2` package. + + import ( + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + newappengine "google.golang.org/appengine" + newurlfetch "google.golang.org/appengine/urlfetch" + + "appengine" + ) + + func handler(w http.ResponseWriter, r *http.Request) { + var c appengine.Context = appengine.NewContext(r) + c.Infof("Logging a message with the old package") + + var ctx context.Context = newappengine.NewContext(r) + client := &http.Client{ + Transport: &oauth2.Transport{ + Source: google.AppEngineTokenSource(ctx, "scope"), + Base: &newurlfetch.Transport{Context: ctx}, + }, + } + client.Get("...") + } + diff --git a/vendor/golang.org/x/oauth2/bitbucket/bitbucket.go b/vendor/golang.org/x/oauth2/bitbucket/bitbucket.go new file mode 100644 index 0000000..44af1f1 --- /dev/null +++ b/vendor/golang.org/x/oauth2/bitbucket/bitbucket.go @@ -0,0 +1,16 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bitbucket provides constants for using OAuth2 to access Bitbucket. +package bitbucket + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is Bitbucket's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://bitbucket.org/site/oauth2/authorize", + TokenURL: "https://bitbucket.org/site/oauth2/access_token", +} diff --git a/vendor/golang.org/x/oauth2/client_appengine.go b/vendor/golang.org/x/oauth2/client_appengine.go new file mode 100644 index 0000000..52f8d1d --- /dev/null +++ b/vendor/golang.org/x/oauth2/client_appengine.go @@ -0,0 +1,25 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +// App Engine hooks. + +package oauth2 + +import ( + "net/http" + + "golang.org/x/net/context" + "golang.org/x/oauth2/internal" + "google.golang.org/appengine/urlfetch" +) + +func init() { + internal.RegisterContextClientFunc(contextClientAppEngine) +} + +func contextClientAppEngine(ctx context.Context) (*http.Client, error) { + return urlfetch.Client(ctx), nil +} diff --git a/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go new file mode 100644 index 0000000..baebced --- /dev/null +++ b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go @@ -0,0 +1,112 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package clientcredentials implements the OAuth2.0 "client credentials" token flow, +// also known as the "two-legged OAuth 2.0". +// +// This should be used when the client is acting on its own behalf or when the client +// is the resource owner. It may also be used when requesting access to protected +// resources based on an authorization previously arranged with the authorization +// server. +// +// See http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.4 +package clientcredentials // import "golang.org/x/oauth2/clientcredentials" + +import ( + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" +) + +// tokenFromInternal maps an *internal.Token struct into +// an *oauth2.Token struct. +func tokenFromInternal(t *internal.Token) *oauth2.Token { + if t == nil { + return nil + } + tk := &oauth2.Token{ + AccessToken: t.AccessToken, + TokenType: t.TokenType, + RefreshToken: t.RefreshToken, + Expiry: t.Expiry, + } + return tk.WithExtra(t.Raw) +} + +// retrieveToken takes a *Config and uses that to retrieve an *internal.Token. +// This token is then mapped from *internal.Token into an *oauth2.Token which is +// returned along with an error. +func retrieveToken(ctx context.Context, c *Config, v url.Values) (*oauth2.Token, error) { + tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.TokenURL, v) + if err != nil { + return nil, err + } + return tokenFromInternal(tk), nil +} + +// Client Credentials Config describes a 2-legged OAuth2 flow, with both the +// client application information and the server's endpoint URLs. +type Config struct { + // ClientID is the application's ID. + ClientID string + + // ClientSecret is the application's secret. + ClientSecret string + + // TokenURL is the resource server's token endpoint + // URL. This is a constant specific to each server. + TokenURL string + + // Scope specifies optional requested permissions. + Scopes []string +} + +// Token uses client credentials to retreive a token. +// The HTTP client to use is derived from the context. +// If nil, http.DefaultClient is used. +func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) { + return retrieveToken(ctx, c, url.Values{ + "grant_type": {"client_credentials"}, + "scope": internal.CondVal(strings.Join(c.Scopes, " ")), + }) +} + +// Client returns an HTTP client using the provided token. +// The token will auto-refresh as necessary. The underlying +// HTTP transport will be obtained using the provided context. +// The returned client and its Transport should not be modified. +func (c *Config) Client(ctx context.Context) *http.Client { + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + +// TokenSource returns a TokenSource that returns t until t expires, +// automatically refreshing it as necessary using the provided context and the +// client ID and client secret. +// +// Most users will use Config.Client instead. +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { + source := &tokenSource{ + ctx: ctx, + conf: c, + } + return oauth2.ReuseTokenSource(nil, source) +} + +type tokenSource struct { + ctx context.Context + conf *Config +} + +// Token refreshes the token by using a new client credentials request. +// tokens received this way do not include a refresh token +func (c *tokenSource) Token() (*oauth2.Token, error) { + return retrieveToken(c.ctx, c.conf, url.Values{ + "grant_type": {"client_credentials"}, + "scope": internal.CondVal(strings.Join(c.conf.Scopes, " ")), + }) +} diff --git a/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go new file mode 100644 index 0000000..ab319e0 --- /dev/null +++ b/vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go @@ -0,0 +1,96 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package clientcredentials + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "golang.org/x/oauth2" +) + +func newConf(url string) *Config { + return &Config{ + ClientID: "CLIENT_ID", + ClientSecret: "CLIENT_SECRET", + Scopes: []string{"scope1", "scope2"}, + TokenURL: url + "/token", + } +} + +type mockTransport struct { + rt func(req *http.Request) (resp *http.Response, err error) +} + +func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { + return t.rt(req) +} + +func TestTokenRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() != "/token" { + t.Errorf("authenticate client request URL = %q; want %q", r.URL, "/token") + } + headerAuth := r.Header.Get("Authorization") + if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { + t.Errorf("Unexpected authorization header, %v is found.", headerAuth) + } + if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want { + t.Errorf("Content-Type header = %q; want %q", got, want) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + r.Body.Close() + } + if err != nil { + t.Errorf("failed reading request body: %s.", err) + } + if string(body) != "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2" { + t.Errorf("payload = %q; want %q", string(body), "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2") + } + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&token_type=bearer")) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.Token(oauth2.NoContext) + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("token invalid. got: %#v", tok) + } + if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { + t.Errorf("Access token = %q; want %q", tok.AccessToken, "90d64460d14870c08c81352a05dedd3465940a7c") + } + if tok.TokenType != "bearer" { + t.Errorf("token type = %q; want %q", tok.TokenType, "bearer") + } +} + +func TestTokenRefreshRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() == "/somethingelse" { + return + } + if r.URL.String() != "/token" { + t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL) + } + headerContentType := r.Header.Get("Content-Type") + if headerContentType != "application/x-www-form-urlencoded" { + t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) + } + body, _ := ioutil.ReadAll(r.Body) + if string(body) != "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2" { + t.Errorf("Unexpected refresh token payload, %v is found.", string(body)) + } + })) + defer ts.Close() + conf := newConf(ts.URL) + c := conf.Client(oauth2.NoContext) + c.Get(ts.URL + "/somethingelse") +} diff --git a/vendor/golang.org/x/oauth2/example_test.go b/vendor/golang.org/x/oauth2/example_test.go new file mode 100644 index 0000000..8be2788 --- /dev/null +++ b/vendor/golang.org/x/oauth2/example_test.go @@ -0,0 +1,45 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2_test + +import ( + "fmt" + "log" + + "golang.org/x/oauth2" +) + +func ExampleConfig() { + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + Scopes: []string{"SCOPE1", "SCOPE2"}, + Endpoint: oauth2.Endpoint{ + AuthURL: "https://provider.com/o/oauth2/auth", + TokenURL: "https://provider.com/o/oauth2/token", + }, + } + + // Redirect user to consent page to ask for permission + // for the scopes specified above. + url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline) + fmt.Printf("Visit the URL for the auth dialog: %v", url) + + // Use the authorization code that is pushed to the redirect URL. + // NewTransportWithCode will do the handshake to retrieve + // an access token and initiate a Transport that is + // authorized and authenticated by the retrieved token. + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatal(err) + } + tok, err := conf.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatal(err) + } + + client := conf.Client(oauth2.NoContext, tok) + client.Get("...") +} diff --git a/vendor/golang.org/x/oauth2/facebook/facebook.go b/vendor/golang.org/x/oauth2/facebook/facebook.go new file mode 100644 index 0000000..962e86b --- /dev/null +++ b/vendor/golang.org/x/oauth2/facebook/facebook.go @@ -0,0 +1,16 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package facebook provides constants for using OAuth2 to access Facebook. +package facebook // import "golang.org/x/oauth2/facebook" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is Facebook's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://www.facebook.com/dialog/oauth", + TokenURL: "https://graph.facebook.com/oauth/access_token", +} diff --git a/vendor/golang.org/x/oauth2/github/github.go b/vendor/golang.org/x/oauth2/github/github.go new file mode 100644 index 0000000..1648cb5 --- /dev/null +++ b/vendor/golang.org/x/oauth2/github/github.go @@ -0,0 +1,16 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package github provides constants for using OAuth2 to access Github. +package github // import "golang.org/x/oauth2/github" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is Github's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://github.com/login/oauth/authorize", + TokenURL: "https://github.com/login/oauth/access_token", +} diff --git a/vendor/golang.org/x/oauth2/google/appengine.go b/vendor/golang.org/x/oauth2/google/appengine.go new file mode 100644 index 0000000..8554221 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appengine.go @@ -0,0 +1,86 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "sort" + "strings" + "sync" + "time" + + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +// Set at init time by appenginevm_hook.go. If true, we are on App Engine Managed VMs. +var appengineVM bool + +// Set at init time by appengine_hook.go. If nil, we're not on App Engine. +var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) + +// AppEngineTokenSource returns a token source that fetches tokens +// issued to the current App Engine application's service account. +// If you are implementing a 3-legged OAuth 2.0 flow on App Engine +// that involves user accounts, see oauth2.Config instead. +// +// The provided context must have come from appengine.NewContext. +func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { + if appengineTokenFunc == nil { + panic("google: AppEngineTokenSource can only be used on App Engine.") + } + scopes := append([]string{}, scope...) + sort.Strings(scopes) + return &appEngineTokenSource{ + ctx: ctx, + scopes: scopes, + key: strings.Join(scopes, " "), + } +} + +// aeTokens helps the fetched tokens to be reused until their expiration. +var ( + aeTokensMu sync.Mutex + aeTokens = make(map[string]*tokenLock) // key is space-separated scopes +) + +type tokenLock struct { + mu sync.Mutex // guards t; held while fetching or updating t + t *oauth2.Token +} + +type appEngineTokenSource struct { + ctx context.Context + scopes []string + key string // to aeTokens map; space-separated scopes +} + +func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) { + if appengineTokenFunc == nil { + panic("google: AppEngineTokenSource can only be used on App Engine.") + } + + aeTokensMu.Lock() + tok, ok := aeTokens[ts.key] + if !ok { + tok = &tokenLock{} + aeTokens[ts.key] = tok + } + aeTokensMu.Unlock() + + tok.mu.Lock() + defer tok.mu.Unlock() + if tok.t.Valid() { + return tok.t, nil + } + access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...) + if err != nil { + return nil, err + } + tok.t = &oauth2.Token{ + AccessToken: access, + Expiry: exp, + } + return tok.t, nil +} diff --git a/vendor/golang.org/x/oauth2/google/appengine_hook.go b/vendor/golang.org/x/oauth2/google/appengine_hook.go new file mode 100644 index 0000000..362766f --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appengine_hook.go @@ -0,0 +1,13 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +package google + +import "google.golang.org/appengine" + +func init() { + appengineTokenFunc = appengine.AccessToken +} diff --git a/vendor/golang.org/x/oauth2/google/appenginevm_hook.go b/vendor/golang.org/x/oauth2/google/appenginevm_hook.go new file mode 100644 index 0000000..633611c --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appenginevm_hook.go @@ -0,0 +1,14 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appenginevm + +package google + +import "google.golang.org/appengine" + +func init() { + appengineVM = true + appengineTokenFunc = appengine.AccessToken +} diff --git a/vendor/golang.org/x/oauth2/google/default.go b/vendor/golang.org/x/oauth2/google/default.go new file mode 100644 index 0000000..66daeef --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/default.go @@ -0,0 +1,155 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/jwt" + "google.golang.org/cloud/compute/metadata" +) + +// DefaultClient returns an HTTP Client that uses the +// DefaultTokenSource to obtain authentication credentials. +// +// This client should be used when developing services +// that run on Google App Engine or Google Compute Engine +// and use "Application Default Credentials." +// +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +// +func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { + ts, err := DefaultTokenSource(ctx, scope...) + if err != nil { + return nil, err + } + return oauth2.NewClient(ctx, ts), nil +} + +// DefaultTokenSource is a token source that uses +// "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine it uses the appengine.AccessToken function. +// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches +// credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +// +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +// +func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { + // First, try the environment variable. + const envVar = "GOOGLE_APPLICATION_CREDENTIALS" + if filename := os.Getenv(envVar); filename != "" { + ts, err := tokenSourceFromFile(ctx, filename, scope) + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) + } + return ts, nil + } + + // Second, try a well-known file. + filename := wellKnownFile() + _, err := os.Stat(filename) + if err == nil { + ts, err2 := tokenSourceFromFile(ctx, filename, scope) + if err2 == nil { + return ts, nil + } + err = err2 + } else if os.IsNotExist(err) { + err = nil // ignore this error + } + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) + } + + // Third, if we're on Google App Engine use those credentials. + if appengineTokenFunc != nil && !appengineVM { + return AppEngineTokenSource(ctx, scope...), nil + } + + // Fourth, if we're on Google Compute Engine use the metadata server. + if metadata.OnGCE() { + return ComputeTokenSource(""), nil + } + + // None are found; return helpful error. + const url = "https://developers.google.com/accounts/docs/application-default-credentials" + return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) +} + +func wellKnownFile() string { + const f = "application_default_credentials.json" + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) + } + return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) +} + +func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var d struct { + // Common fields + Type string + ClientID string `json:"client_id"` + + // User Credential fields + ClientSecret string `json:"client_secret"` + RefreshToken string `json:"refresh_token"` + + // Service Account fields + ClientEmail string `json:"client_email"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + } + if err := json.Unmarshal(b, &d); err != nil { + return nil, err + } + switch d.Type { + case "authorized_user": + cfg := &oauth2.Config{ + ClientID: d.ClientID, + ClientSecret: d.ClientSecret, + Scopes: append([]string{}, scopes...), // copy + Endpoint: Endpoint, + } + tok := &oauth2.Token{RefreshToken: d.RefreshToken} + return cfg.TokenSource(ctx, tok), nil + case "service_account": + cfg := &jwt.Config{ + Email: d.ClientEmail, + PrivateKey: []byte(d.PrivateKey), + Scopes: append([]string{}, scopes...), // copy + TokenURL: JWTTokenURL, + } + return cfg.TokenSource(ctx), nil + case "": + return nil, errors.New("missing 'type' field in credentials") + default: + return nil, fmt.Errorf("unknown credential type: %q", d.Type) + } +} diff --git a/vendor/golang.org/x/oauth2/google/example_test.go b/vendor/golang.org/x/oauth2/google/example_test.go new file mode 100644 index 0000000..1726280 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/example_test.go @@ -0,0 +1,150 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appenginevm !appengine + +package google_test + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "golang.org/x/oauth2/jwt" + "google.golang.org/appengine" + "google.golang.org/appengine/urlfetch" +) + +func ExampleDefaultClient() { + client, err := google.DefaultClient(oauth2.NoContext, + "https://www.googleapis.com/auth/devstorage.full_control") + if err != nil { + log.Fatal(err) + } + client.Get("...") +} + +func Example_webServer() { + // Your credentials should be obtained from the Google + // Developer Console (https://console.developers.google.com). + conf := &oauth2.Config{ + ClientID: "YOUR_CLIENT_ID", + ClientSecret: "YOUR_CLIENT_SECRET", + RedirectURL: "YOUR_REDIRECT_URL", + Scopes: []string{ + "https://www.googleapis.com/auth/bigquery", + "https://www.googleapis.com/auth/blogger", + }, + Endpoint: google.Endpoint, + } + // Redirect user to Google's consent page to ask for permission + // for the scopes specified above. + url := conf.AuthCodeURL("state") + fmt.Printf("Visit the URL for the auth dialog: %v", url) + + // Handle the exchange code to initiate a transport. + tok, err := conf.Exchange(oauth2.NoContext, "authorization-code") + if err != nil { + log.Fatal(err) + } + client := conf.Client(oauth2.NoContext, tok) + client.Get("...") +} + +func ExampleJWTConfigFromJSON() { + // Your credentials should be obtained from the Google + // Developer Console (https://console.developers.google.com). + // Navigate to your project, then see the "Credentials" page + // under "APIs & Auth". + // To create a service account client, click "Create new Client ID", + // select "Service Account", and click "Create Client ID". A JSON + // key file will then be downloaded to your computer. + data, err := ioutil.ReadFile("/path/to/your-project-key.json") + if err != nil { + log.Fatal(err) + } + conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/bigquery") + if err != nil { + log.Fatal(err) + } + // Initiate an http.Client. The following GET request will be + // authorized and authenticated on the behalf of + // your service account. + client := conf.Client(oauth2.NoContext) + client.Get("...") +} + +func ExampleSDKConfig() { + // The credentials will be obtained from the first account that + // has been authorized with `gcloud auth login`. + conf, err := google.NewSDKConfig("") + if err != nil { + log.Fatal(err) + } + // Initiate an http.Client. The following GET request will be + // authorized and authenticated on the behalf of the SDK user. + client := conf.Client(oauth2.NoContext) + client.Get("...") +} + +func Example_serviceAccount() { + // Your credentials should be obtained from the Google + // Developer Console (https://console.developers.google.com). + conf := &jwt.Config{ + Email: "xxx@developer.gserviceaccount.com", + // The contents of your RSA private key or your PEM file + // that contains a private key. + // If you have a p12 file instead, you + // can use `openssl` to export the private key into a pem file. + // + // $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes + // + // The field only supports PEM containers with no passphrase. + // The openssl command will convert p12 keys to passphrase-less PEM containers. + PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."), + Scopes: []string{ + "https://www.googleapis.com/auth/bigquery", + "https://www.googleapis.com/auth/blogger", + }, + TokenURL: google.JWTTokenURL, + // If you would like to impersonate a user, you can + // create a transport with a subject. The following GET + // request will be made on the behalf of user@example.com. + // Optional. + Subject: "user@example.com", + } + // Initiate an http.Client, the following GET request will be + // authorized and authenticated on the behalf of user@example.com. + client := conf.Client(oauth2.NoContext) + client.Get("...") +} + +func ExampleAppEngineTokenSource() { + var req *http.Request // from the ServeHTTP handler + ctx := appengine.NewContext(req) + client := &http.Client{ + Transport: &oauth2.Transport{ + Source: google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/bigquery"), + Base: &urlfetch.Transport{ + Context: ctx, + }, + }, + } + client.Get("...") +} + +func ExampleComputeTokenSource() { + client := &http.Client{ + Transport: &oauth2.Transport{ + // Fetch from Google Compute Engine's metadata server to retrieve + // an access token for the provided account. + // If no account is specified, "default" is used. + Source: google.ComputeTokenSource(""), + }, + } + client.Get("...") +} diff --git a/vendor/golang.org/x/oauth2/google/google.go b/vendor/golang.org/x/oauth2/google/google.go new file mode 100644 index 0000000..74aa7d9 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/google.go @@ -0,0 +1,145 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package google provides support for making OAuth2 authorized and +// authenticated HTTP requests to Google APIs. +// It supports the Web server flow, client-side credentials, service accounts, +// Google Compute Engine service accounts, and Google App Engine service +// accounts. +// +// For more information, please read +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/docs/application-default-credentials. +package google // import "golang.org/x/oauth2/google" + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/jwt" + "google.golang.org/cloud/compute/metadata" +) + +// Endpoint is Google's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://accounts.google.com/o/oauth2/token", +} + +// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow. +const JWTTokenURL = "https://accounts.google.com/o/oauth2/token" + +// ConfigFromJSON uses a Google Developers Console client_credentials.json +// file to construct a config. +// client_credentials.json can be downloadable from https://console.developers.google.com, +// under "APIs & Auth" > "Credentials". Download the Web application credentials in the +// JSON format and provide the contents of the file as jsonKey. +func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { + type cred struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + } + var j struct { + Web *cred `json:"web"` + Installed *cred `json:"installed"` + } + if err := json.Unmarshal(jsonKey, &j); err != nil { + return nil, err + } + var c *cred + switch { + case j.Web != nil: + c = j.Web + case j.Installed != nil: + c = j.Installed + default: + return nil, fmt.Errorf("oauth2/google: no credentials found") + } + if len(c.RedirectURIs) < 1 { + return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json") + } + return &oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + RedirectURL: c.RedirectURIs[0], + Scopes: scope, + Endpoint: oauth2.Endpoint{ + AuthURL: c.AuthURI, + TokenURL: c.TokenURI, + }, + }, nil +} + +// JWTConfigFromJSON uses a Google Developers service account JSON key file to read +// the credentials that authorize and authenticate the requests. +// Create a service account on "Credentials" page under "APIs & Auth" for your +// project at https://console.developers.google.com to download a JSON key file. +func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) { + var key struct { + Email string `json:"client_email"` + PrivateKey string `json:"private_key"` + } + if err := json.Unmarshal(jsonKey, &key); err != nil { + return nil, err + } + return &jwt.Config{ + Email: key.Email, + PrivateKey: []byte(key.PrivateKey), + Scopes: scope, + TokenURL: JWTTokenURL, + }, nil +} + +// ComputeTokenSource returns a token source that fetches access tokens +// from Google Compute Engine (GCE)'s metadata server. It's only valid to use +// this token source if your program is running on a GCE instance. +// If no account is specified, "default" is used. +// Further information about retrieving access tokens from the GCE metadata +// server can be found at https://cloud.google.com/compute/docs/authentication. +func ComputeTokenSource(account string) oauth2.TokenSource { + return oauth2.ReuseTokenSource(nil, computeSource{account: account}) +} + +type computeSource struct { + account string +} + +func (cs computeSource) Token() (*oauth2.Token, error) { + if !metadata.OnGCE() { + return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE") + } + acct := cs.account + if acct == "" { + acct = "default" + } + tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token") + if err != nil { + return nil, err + } + var res struct { + AccessToken string `json:"access_token"` + ExpiresInSec int `json:"expires_in"` + TokenType string `json:"token_type"` + } + err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res) + if err != nil { + return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err) + } + if res.ExpiresInSec == 0 || res.AccessToken == "" { + return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata") + } + return &oauth2.Token{ + AccessToken: res.AccessToken, + TokenType: res.TokenType, + Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second), + }, nil +} diff --git a/vendor/golang.org/x/oauth2/google/google_test.go b/vendor/golang.org/x/oauth2/google/google_test.go new file mode 100644 index 0000000..4cc0188 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/google_test.go @@ -0,0 +1,67 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "strings" + "testing" +) + +var webJSONKey = []byte(` +{ + "web": { + "auth_uri": "https://google.com/o/oauth2/auth", + "client_secret": "3Oknc4jS_wA2r9i", + "token_uri": "https://google.com/o/oauth2/token", + "client_email": "222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com", + "redirect_uris": ["https://www.example.com/oauth2callback"], + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com", + "client_id": "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "javascript_origins": ["https://www.example.com"] + } +}`) + +var installedJSONKey = []byte(`{ + "installed": { + "client_id": "222-installed.apps.googleusercontent.com", + "redirect_uris": ["https://www.example.com/oauth2callback"] + } +}`) + +func TestConfigFromJSON(t *testing.T) { + conf, err := ConfigFromJSON(webJSONKey, "scope1", "scope2") + if err != nil { + t.Error(err) + } + if got, want := conf.ClientID, "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com"; got != want { + t.Errorf("ClientID = %q; want %q", got, want) + } + if got, want := conf.ClientSecret, "3Oknc4jS_wA2r9i"; got != want { + t.Errorf("ClientSecret = %q; want %q", got, want) + } + if got, want := conf.RedirectURL, "https://www.example.com/oauth2callback"; got != want { + t.Errorf("RedictURL = %q; want %q", got, want) + } + if got, want := strings.Join(conf.Scopes, ","), "scope1,scope2"; got != want { + t.Errorf("Scopes = %q; want %q", got, want) + } + if got, want := conf.Endpoint.AuthURL, "https://google.com/o/oauth2/auth"; got != want { + t.Errorf("AuthURL = %q; want %q", got, want) + } + if got, want := conf.Endpoint.TokenURL, "https://google.com/o/oauth2/token"; got != want { + t.Errorf("TokenURL = %q; want %q", got, want) + } +} + +func TestConfigFromJSON_Installed(t *testing.T) { + conf, err := ConfigFromJSON(installedJSONKey) + if err != nil { + t.Error(err) + } + if got, want := conf.ClientID, "222-installed.apps.googleusercontent.com"; got != want { + t.Errorf("ClientID = %q; want %q", got, want) + } +} diff --git a/vendor/golang.org/x/oauth2/google/jwt.go b/vendor/golang.org/x/oauth2/google/jwt.go new file mode 100644 index 0000000..6f7ec3a --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/jwt.go @@ -0,0 +1,71 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "crypto/rsa" + "fmt" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" + "golang.org/x/oauth2/jws" +) + +// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON +// key file to read the credentials that authorize and authenticate the +// requests, and returns a TokenSource that does not use any OAuth2 flow but +// instead creates a JWT and sends that as the access token. +// The audience is typically a URL that specifies the scope of the credentials. +// +// Note that this is not a standard OAuth flow, but rather an +// optimization supported by a few Google services. +// Unless you know otherwise, you should use JWTConfigFromJSON instead. +func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) { + cfg, err := JWTConfigFromJSON(jsonKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse JSON key: %v", err) + } + pk, err := internal.ParseKey(cfg.PrivateKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse key: %v", err) + } + ts := &jwtAccessTokenSource{ + email: cfg.Email, + audience: audience, + pk: pk, + } + tok, err := ts.Token() + if err != nil { + return nil, err + } + return oauth2.ReuseTokenSource(tok, ts), nil +} + +type jwtAccessTokenSource struct { + email, audience string + pk *rsa.PrivateKey +} + +func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) { + iat := time.Now() + exp := iat.Add(time.Hour) + cs := &jws.ClaimSet{ + Iss: ts.email, + Sub: ts.email, + Aud: ts.audience, + Iat: iat.Unix(), + Exp: exp.Unix(), + } + hdr := &jws.Header{ + Algorithm: "RS256", + Typ: "JWT", + } + msg, err := jws.Encode(hdr, cs, ts.pk) + if err != nil { + return nil, fmt.Errorf("google: could not encode JWT: %v", err) + } + return &oauth2.Token{AccessToken: msg, TokenType: "Bearer", Expiry: exp}, nil +} diff --git a/vendor/golang.org/x/oauth2/google/sdk.go b/vendor/golang.org/x/oauth2/google/sdk.go new file mode 100644 index 0000000..01ba0ec --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/sdk.go @@ -0,0 +1,168 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + "time" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" +) + +type sdkCredentials struct { + Data []struct { + Credential struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenExpiry *time.Time `json:"token_expiry"` + } `json:"credential"` + Key struct { + Account string `json:"account"` + Scope string `json:"scope"` + } `json:"key"` + } +} + +// An SDKConfig provides access to tokens from an account already +// authorized via the Google Cloud SDK. +type SDKConfig struct { + conf oauth2.Config + initialToken *oauth2.Token +} + +// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK +// account. If account is empty, the account currently active in +// Google Cloud SDK properties is used. +// Google Cloud SDK credentials must be created by running `gcloud auth` +// before using this function. +// The Google Cloud SDK is available at https://cloud.google.com/sdk/. +func NewSDKConfig(account string) (*SDKConfig, error) { + configPath, err := sdkConfigPath() + if err != nil { + return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err) + } + credentialsPath := filepath.Join(configPath, "credentials") + f, err := os.Open(credentialsPath) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err) + } + defer f.Close() + + var c sdkCredentials + if err := json.NewDecoder(f).Decode(&c); err != nil { + return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err) + } + if len(c.Data) == 0 { + return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath) + } + if account == "" { + propertiesPath := filepath.Join(configPath, "properties") + f, err := os.Open(propertiesPath) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err) + } + defer f.Close() + ini, err := internal.ParseINI(f) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err) + } + core, ok := ini["core"] + if !ok { + return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini) + } + active, ok := core["account"] + if !ok { + return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core) + } + account = active + } + + for _, d := range c.Data { + if account == "" || d.Key.Account == account { + if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" { + return nil, fmt.Errorf("oauth2/google: no token available for account %q", account) + } + var expiry time.Time + if d.Credential.TokenExpiry != nil { + expiry = *d.Credential.TokenExpiry + } + return &SDKConfig{ + conf: oauth2.Config{ + ClientID: d.Credential.ClientID, + ClientSecret: d.Credential.ClientSecret, + Scopes: strings.Split(d.Key.Scope, " "), + Endpoint: Endpoint, + RedirectURL: "oob", + }, + initialToken: &oauth2.Token{ + AccessToken: d.Credential.AccessToken, + RefreshToken: d.Credential.RefreshToken, + Expiry: expiry, + }, + }, nil + } + } + return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account) +} + +// Client returns an HTTP client using Google Cloud SDK credentials to +// authorize requests. The token will auto-refresh as necessary. The +// underlying http.RoundTripper will be obtained using the provided +// context. The returned client and its Transport should not be +// modified. +func (c *SDKConfig) Client(ctx context.Context) *http.Client { + return &http.Client{ + Transport: &oauth2.Transport{ + Source: c.TokenSource(ctx), + }, + } +} + +// TokenSource returns an oauth2.TokenSource that retrieve tokens from +// Google Cloud SDK credentials using the provided context. +// It will returns the current access token stored in the credentials, +// and refresh it when it expires, but it won't update the credentials +// with the new access token. +func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource { + return c.conf.TokenSource(ctx, c.initialToken) +} + +// Scopes are the OAuth 2.0 scopes the current account is authorized for. +func (c *SDKConfig) Scopes() []string { + return c.conf.Scopes +} + +// sdkConfigPath tries to guess where the gcloud config is located. +// It can be overridden during tests. +var sdkConfigPath = func() (string, error) { + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil + } + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") + } + return filepath.Join(homeDir, ".config", "gcloud"), nil +} + +func guessUnixHomeDir() string { + usr, err := user.Current() + if err == nil { + return usr.HomeDir + } + return os.Getenv("HOME") +} diff --git a/vendor/golang.org/x/oauth2/google/sdk_test.go b/vendor/golang.org/x/oauth2/google/sdk_test.go new file mode 100644 index 0000000..79df889 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/sdk_test.go @@ -0,0 +1,46 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import "testing" + +func TestSDKConfig(t *testing.T) { + sdkConfigPath = func() (string, error) { + return "testdata/gcloud", nil + } + + tests := []struct { + account string + accessToken string + err bool + }{ + {"", "bar_access_token", false}, + {"foo@example.com", "foo_access_token", false}, + {"bar@example.com", "bar_access_token", false}, + {"baz@serviceaccount.example.com", "", true}, + } + for _, tt := range tests { + c, err := NewSDKConfig(tt.account) + if got, want := err != nil, tt.err; got != want { + if !tt.err { + t.Errorf("expected no error, got error: %v", tt.err, err) + } else { + t.Errorf("expected error, got none") + } + continue + } + if err != nil { + continue + } + tok := c.initialToken + if tok == nil { + t.Errorf("expected token %q, got: nil", tt.accessToken) + continue + } + if tok.AccessToken != tt.accessToken { + t.Errorf("expected token %q, got: %q", tt.accessToken, tok.AccessToken) + } + } +} diff --git a/vendor/golang.org/x/oauth2/internal/oauth2.go b/vendor/golang.org/x/oauth2/internal/oauth2.go new file mode 100644 index 0000000..dc8ebfc --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/oauth2.go @@ -0,0 +1,76 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal + +import ( + "bufio" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "strings" +) + +// ParseKey converts the binary contents of a private key file +// to an *rsa.PrivateKey. It detects whether the private key is in a +// PEM container or not. If so, it extracts the the private key +// from PEM container before conversion. It only supports PEM +// containers with no passphrase. +func ParseKey(key []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(key) + if block != nil { + key = block.Bytes + } + parsedKey, err := x509.ParsePKCS8PrivateKey(key) + if err != nil { + parsedKey, err = x509.ParsePKCS1PrivateKey(key) + if err != nil { + return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err) + } + } + parsed, ok := parsedKey.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("private key is invalid") + } + return parsed, nil +} + +func ParseINI(ini io.Reader) (map[string]map[string]string, error) { + result := map[string]map[string]string{ + "": map[string]string{}, // root section + } + scanner := bufio.NewScanner(ini) + currentSection := "" + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, ";") { + // comment. + continue + } + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentSection = strings.TrimSpace(line[1 : len(line)-1]) + result[currentSection] = map[string]string{} + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 && parts[0] != "" { + result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error scanning ini: %v", err) + } + return result, nil +} + +func CondVal(v string) []string { + if v == "" { + return nil + } + return []string{v} +} diff --git a/vendor/golang.org/x/oauth2/internal/oauth2_test.go b/vendor/golang.org/x/oauth2/internal/oauth2_test.go new file mode 100644 index 0000000..014a351 --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/oauth2_test.go @@ -0,0 +1,62 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal + +import ( + "reflect" + "strings" + "testing" +) + +func TestParseINI(t *testing.T) { + tests := []struct { + ini string + want map[string]map[string]string + }{ + { + `root = toor +[foo] +bar = hop +ini = nin +`, + map[string]map[string]string{ + "": map[string]string{"root": "toor"}, + "foo": map[string]string{"bar": "hop", "ini": "nin"}, + }, + }, + { + `[empty] +[section] +empty= +`, + map[string]map[string]string{ + "": map[string]string{}, + "empty": map[string]string{}, + "section": map[string]string{"empty": ""}, + }, + }, + { + `ignore +[invalid +=stuff +;comment=true +`, + map[string]map[string]string{ + "": map[string]string{}, + }, + }, + } + for _, tt := range tests { + result, err := ParseINI(strings.NewReader(tt.ini)) + if err != nil { + t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err) + continue + } + if !reflect.DeepEqual(result, tt.want) { + t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want) + } + } +} diff --git a/vendor/golang.org/x/oauth2/internal/token.go b/vendor/golang.org/x/oauth2/internal/token.go new file mode 100644 index 0000000..db209d8 --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/token.go @@ -0,0 +1,214 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "golang.org/x/net/context" +) + +// Token represents the crendentials used to authorize +// the requests to access protected resources on the OAuth 2.0 +// provider's backend. +// +// This type is a mirror of oauth2.Token and exists to break +// an otherwise-circular dependency. Other internal packages +// should convert this Token into an oauth2.Token before use. +type Token struct { + // AccessToken is the token that authorizes and authenticates + // the requests. + AccessToken string + + // TokenType is the type of token. + // The Type method returns either this or "Bearer", the default. + TokenType string + + // RefreshToken is a token that's used by the application + // (as opposed to the user) to refresh the access token + // if it expires. + RefreshToken string + + // Expiry is the optional expiration time of the access token. + // + // If zero, TokenSource implementations will reuse the same + // token forever and RefreshToken or equivalent + // mechanisms for that TokenSource will not be used. + Expiry time.Time + + // Raw optionally contains extra metadata from the server + // when updating a token. + Raw interface{} +} + +// tokenJSON is the struct representing the HTTP response from OAuth2 +// providers returning a token in JSON form. +type tokenJSON struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number + Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in +} + +func (e *tokenJSON) expiry() (t time.Time) { + if v := e.ExpiresIn; v != 0 { + return time.Now().Add(time.Duration(v) * time.Second) + } + if v := e.Expires; v != 0 { + return time.Now().Add(time.Duration(v) * time.Second) + } + return +} + +type expirationTime int32 + +func (e *expirationTime) UnmarshalJSON(b []byte) error { + var n json.Number + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + i, err := n.Int64() + if err != nil { + return err + } + *e = expirationTime(i) + return nil +} + +var brokenAuthHeaderProviders = []string{ + "https://accounts.google.com/", + "https://www.googleapis.com/", + "https://api.instagram.com/", + "https://www.douban.com/", + "https://api.dropbox.com/", + "https://api.soundcloud.com/", + "https://www.linkedin.com/", + "https://api.twitch.tv/", + "https://oauth.vk.com/", + "https://api.odnoklassniki.ru/", + "https://connect.stripe.com/", + "https://api.pushbullet.com/", + "https://oauth.sandbox.trainingpeaks.com/", + "https://oauth.trainingpeaks.com/", + "https://www.strava.com/oauth/", + "https://app.box.com/", + "https://test-sandbox.auth.corp.google.com", + "https://user.gini.net/", + "https://api.netatmo.net/", + "https://slack.com/", +} + +// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL +// implements the OAuth2 spec correctly +// See https://code.google.com/p/goauth2/issues/detail?id=31 for background. +// In summary: +// - Reddit only accepts client secret in the Authorization header +// - Dropbox accepts either it in URL param or Auth header, but not both. +// - Google only accepts URL param (not spec compliant?), not Auth header +// - Stripe only accepts client secret in Auth header with Bearer method, not Basic +func providerAuthHeaderWorks(tokenURL string) bool { + for _, s := range brokenAuthHeaderProviders { + if strings.HasPrefix(tokenURL, s) { + // Some sites fail to implement the OAuth2 spec fully. + return false + } + } + + // Assume the provider implements the spec properly + // otherwise. We can add more exceptions as they're + // discovered. We will _not_ be adding configurable hooks + // to this package to let users select server bugs. + return true +} + +func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) { + hc, err := ContextClient(ctx) + if err != nil { + return nil, err + } + v.Set("client_id", ClientID) + bustedAuth := !providerAuthHeaderWorks(TokenURL) + if bustedAuth && ClientSecret != "" { + v.Set("client_secret", ClientSecret) + } + req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if !bustedAuth { + req.SetBasicAuth(ClientID, ClientSecret) + } + r, err := hc.Do(req) + if err != nil { + return nil, err + } + defer r.Body.Close() + body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20)) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + if code := r.StatusCode; code < 200 || code > 299 { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) + } + + var token *Token + content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) + switch content { + case "application/x-www-form-urlencoded", "text/plain": + vals, err := url.ParseQuery(string(body)) + if err != nil { + return nil, err + } + token = &Token{ + AccessToken: vals.Get("access_token"), + TokenType: vals.Get("token_type"), + RefreshToken: vals.Get("refresh_token"), + Raw: vals, + } + e := vals.Get("expires_in") + if e == "" { + // TODO(jbd): Facebook's OAuth2 implementation is broken and + // returns expires_in field in expires. Remove the fallback to expires, + // when Facebook fixes their implementation. + e = vals.Get("expires") + } + expires, _ := strconv.Atoi(e) + if expires != 0 { + token.Expiry = time.Now().Add(time.Duration(expires) * time.Second) + } + default: + var tj tokenJSON + if err = json.Unmarshal(body, &tj); err != nil { + return nil, err + } + token = &Token{ + AccessToken: tj.AccessToken, + TokenType: tj.TokenType, + RefreshToken: tj.RefreshToken, + Expiry: tj.expiry(), + Raw: make(map[string]interface{}), + } + json.Unmarshal(body, &token.Raw) // no error checks for optional fields + } + // Don't overwrite `RefreshToken` with an empty value + // if this was a token refreshing request. + if token.RefreshToken == "" { + token.RefreshToken = v.Get("refresh_token") + } + return token, nil +} diff --git a/vendor/golang.org/x/oauth2/internal/token_test.go b/vendor/golang.org/x/oauth2/internal/token_test.go new file mode 100644 index 0000000..864f6fa --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/token_test.go @@ -0,0 +1,28 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal + +import ( + "fmt" + "testing" +) + +func Test_providerAuthHeaderWorks(t *testing.T) { + for _, p := range brokenAuthHeaderProviders { + if providerAuthHeaderWorks(p) { + t.Errorf("URL: %s not found in list", p) + } + p := fmt.Sprintf("%ssomesuffix", p) + if providerAuthHeaderWorks(p) { + t.Errorf("URL: %s not found in list", p) + } + } + p := "https://api.not-in-the-list-example.com/" + if !providerAuthHeaderWorks(p) { + t.Errorf("URL: %s found in list", p) + } + +} diff --git a/vendor/golang.org/x/oauth2/internal/transport.go b/vendor/golang.org/x/oauth2/internal/transport.go new file mode 100644 index 0000000..521e7b4 --- /dev/null +++ b/vendor/golang.org/x/oauth2/internal/transport.go @@ -0,0 +1,67 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package internal contains support packages for oauth2 package. +package internal + +import ( + "net/http" + + "golang.org/x/net/context" +) + +// HTTPClient is the context key to use with golang.org/x/net/context's +// WithValue function to associate an *http.Client value with a context. +var HTTPClient ContextKey + +// ContextKey is just an empty struct. It exists so HTTPClient can be +// an immutable public variable with a unique type. It's immutable +// because nobody else can create a ContextKey, being unexported. +type ContextKey struct{} + +// ContextClientFunc is a func which tries to return an *http.Client +// given a Context value. If it returns an error, the search stops +// with that error. If it returns (nil, nil), the search continues +// down the list of registered funcs. +type ContextClientFunc func(context.Context) (*http.Client, error) + +var contextClientFuncs []ContextClientFunc + +func RegisterContextClientFunc(fn ContextClientFunc) { + contextClientFuncs = append(contextClientFuncs, fn) +} + +func ContextClient(ctx context.Context) (*http.Client, error) { + for _, fn := range contextClientFuncs { + c, err := fn(ctx) + if err != nil { + return nil, err + } + if c != nil { + return c, nil + } + } + if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { + return hc, nil + } + return http.DefaultClient, nil +} + +func ContextTransport(ctx context.Context) http.RoundTripper { + hc, err := ContextClient(ctx) + // This is a rare error case (somebody using nil on App Engine). + if err != nil { + return ErrorTransport{err} + } + return hc.Transport +} + +// ErrorTransport returns the specified error on RoundTrip. +// This RoundTripper should be used in rare error cases where +// error handling can be postponed to response handling time. +type ErrorTransport struct{ Err error } + +func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) { + return nil, t.Err +} diff --git a/vendor/golang.org/x/oauth2/jws/jws.go b/vendor/golang.org/x/oauth2/jws/jws.go new file mode 100644 index 0000000..bc7df09 --- /dev/null +++ b/vendor/golang.org/x/oauth2/jws/jws.go @@ -0,0 +1,159 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package jws provides encoding and decoding utilities for +// signed JWS messages. +package jws // import "golang.org/x/oauth2/jws" + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + "time" +) + +// ClaimSet contains information about the JWT signature including the +// permissions being requested (scopes), the target of the token, the issuer, +// the time the token was issued, and the lifetime of the token. +type ClaimSet struct { + Iss string `json:"iss"` // email address of the client_id of the application making the access token request + Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests + Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional). + Exp int64 `json:"exp"` // the expiration time of the assertion (seconds since Unix epoch) + Iat int64 `json:"iat"` // the time the assertion was issued (seconds since Unix epoch) + Typ string `json:"typ,omitempty"` // token type (Optional). + + // Email for which the application is requesting delegated access (Optional). + Sub string `json:"sub,omitempty"` + + // The old name of Sub. Client keeps setting Prn to be + // complaint with legacy OAuth 2.0 providers. (Optional) + Prn string `json:"prn,omitempty"` + + // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 + // This array is marshalled using custom code (see (c *ClaimSet) encode()). + PrivateClaims map[string]interface{} `json:"-"` +} + +func (c *ClaimSet) encode() (string, error) { + // Reverting time back for machines whose time is not perfectly in sync. + // If client machine's time is in the future according + // to Google servers, an access token will not be issued. + now := time.Now().Add(-10 * time.Second) + if c.Iat == 0 { + c.Iat = now.Unix() + } + if c.Exp == 0 { + c.Exp = now.Add(time.Hour).Unix() + } + if c.Exp < c.Iat { + return "", fmt.Errorf("jws: invalid Exp = %v; must be later than Iat = %v", c.Exp, c.Iat) + } + + b, err := json.Marshal(c) + if err != nil { + return "", err + } + + if len(c.PrivateClaims) == 0 { + return base64Encode(b), nil + } + + // Marshal private claim set and then append it to b. + prv, err := json.Marshal(c.PrivateClaims) + if err != nil { + return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims) + } + + // Concatenate public and private claim JSON objects. + if !bytes.HasSuffix(b, []byte{'}'}) { + return "", fmt.Errorf("jws: invalid JSON %s", b) + } + if !bytes.HasPrefix(prv, []byte{'{'}) { + return "", fmt.Errorf("jws: invalid JSON %s", prv) + } + b[len(b)-1] = ',' // Replace closing curly brace with a comma. + b = append(b, prv[1:]...) // Append private claims. + return base64Encode(b), nil +} + +// Header represents the header for the signed JWS payloads. +type Header struct { + // The algorithm used for signature. + Algorithm string `json:"alg"` + + // Represents the token type. + Typ string `json:"typ"` +} + +func (h *Header) encode() (string, error) { + b, err := json.Marshal(h) + if err != nil { + return "", err + } + return base64Encode(b), nil +} + +// Decode decodes a claim set from a JWS payload. +func Decode(payload string) (*ClaimSet, error) { + // decode returned id token to get expiry + s := strings.Split(payload, ".") + if len(s) < 2 { + // TODO(jbd): Provide more context about the error. + return nil, errors.New("jws: invalid token received") + } + decoded, err := base64Decode(s[1]) + if err != nil { + return nil, err + } + c := &ClaimSet{} + err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c) + return c, err +} + +// Encode encodes a signed JWS with provided header and claim set. +func Encode(header *Header, c *ClaimSet, signature *rsa.PrivateKey) (string, error) { + head, err := header.encode() + if err != nil { + return "", err + } + cs, err := c.encode() + if err != nil { + return "", err + } + ss := fmt.Sprintf("%s.%s", head, cs) + h := sha256.New() + h.Write([]byte(ss)) + b, err := rsa.SignPKCS1v15(rand.Reader, signature, crypto.SHA256, h.Sum(nil)) + if err != nil { + return "", err + } + sig := base64Encode(b) + return fmt.Sprintf("%s.%s", ss, sig), nil +} + +// base64Encode returns and Base64url encoded version of the input string with any +// trailing "=" stripped. +func base64Encode(b []byte) string { + return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") +} + +// base64Decode decodes the Base64url encoded string +func base64Decode(s string) ([]byte, error) { + // add back missing padding + switch len(s) % 4 { + case 2: + s += "==" + case 3: + s += "=" + } + return base64.URLEncoding.DecodeString(s) +} diff --git a/vendor/golang.org/x/oauth2/jwt/example_test.go b/vendor/golang.org/x/oauth2/jwt/example_test.go new file mode 100644 index 0000000..6d61883 --- /dev/null +++ b/vendor/golang.org/x/oauth2/jwt/example_test.go @@ -0,0 +1,31 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jwt_test + +import ( + "golang.org/x/oauth2" + "golang.org/x/oauth2/jwt" +) + +func ExampleJWTConfig() { + conf := &jwt.Config{ + Email: "xxx@developer.com", + // The contents of your RSA private key or your PEM file + // that contains a private key. + // If you have a p12 file instead, you + // can use `openssl` to export the private key into a pem file. + // + // $ openssl pkcs12 -in key.p12 -out key.pem -nodes + // + // It only supports PEM containers with no passphrase. + PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."), + Subject: "user@example.com", + TokenURL: "https://provider.com/o/oauth2/token", + } + // Initiate an http.Client, the following GET request will be + // authorized and authenticated on the behalf of user@example.com. + client := conf.Client(oauth2.NoContext) + client.Get("...") +} diff --git a/vendor/golang.org/x/oauth2/jwt/jwt.go b/vendor/golang.org/x/oauth2/jwt/jwt.go new file mode 100644 index 0000000..11a2687 --- /dev/null +++ b/vendor/golang.org/x/oauth2/jwt/jwt.go @@ -0,0 +1,153 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly +// known as "two-legged OAuth 2.0". +// +// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12 +package jwt + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" + "golang.org/x/oauth2/jws" +) + +var ( + defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" + defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"} +) + +// Config is the configuration for using JWT to fetch tokens, +// commonly known as "two-legged OAuth 2.0". +type Config struct { + // Email is the OAuth client identifier used when communicating with + // the configured OAuth provider. + Email string + + // PrivateKey contains the contents of an RSA private key or the + // contents of a PEM file that contains a private key. The provided + // private key is used to sign JWT payloads. + // PEM containers with a passphrase are not supported. + // Use the following command to convert a PKCS 12 file into a PEM. + // + // $ openssl pkcs12 -in key.p12 -out key.pem -nodes + // + PrivateKey []byte + + // Subject is the optional user to impersonate. + Subject string + + // Scopes optionally specifies a list of requested permission scopes. + Scopes []string + + // TokenURL is the endpoint required to complete the 2-legged JWT flow. + TokenURL string + + // Expires optionally specifies how long the token is valid for. + Expires time.Duration +} + +// TokenSource returns a JWT TokenSource using the configuration +// in c and the HTTP client from the provided context. +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { + return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c}) +} + +// Client returns an HTTP client wrapping the context's +// HTTP transport and adding Authorization headers with tokens +// obtained from c. +// +// The returned client and its Transport should not be modified. +func (c *Config) Client(ctx context.Context) *http.Client { + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + +// jwtSource is a source that always does a signed JWT request for a token. +// It should typically be wrapped with a reuseTokenSource. +type jwtSource struct { + ctx context.Context + conf *Config +} + +func (js jwtSource) Token() (*oauth2.Token, error) { + pk, err := internal.ParseKey(js.conf.PrivateKey) + if err != nil { + return nil, err + } + hc := oauth2.NewClient(js.ctx, nil) + claimSet := &jws.ClaimSet{ + Iss: js.conf.Email, + Scope: strings.Join(js.conf.Scopes, " "), + Aud: js.conf.TokenURL, + } + if subject := js.conf.Subject; subject != "" { + claimSet.Sub = subject + // prn is the old name of sub. Keep setting it + // to be compatible with legacy OAuth 2.0 providers. + claimSet.Prn = subject + } + if t := js.conf.Expires; t > 0 { + claimSet.Exp = time.Now().Add(t).Unix() + } + payload, err := jws.Encode(defaultHeader, claimSet, pk) + if err != nil { + return nil, err + } + v := url.Values{} + v.Set("grant_type", defaultGrantType) + v.Set("assertion", payload) + resp, err := hc.PostForm(js.conf.TokenURL, v) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + if c := resp.StatusCode; c < 200 || c > 299 { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body) + } + // tokenRes is the JSON response body. + var tokenRes struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + IDToken string `json:"id_token"` + ExpiresIn int64 `json:"expires_in"` // relative seconds from now + } + if err := json.Unmarshal(body, &tokenRes); err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + token := &oauth2.Token{ + AccessToken: tokenRes.AccessToken, + TokenType: tokenRes.TokenType, + } + raw := make(map[string]interface{}) + json.Unmarshal(body, &raw) // no error checks for optional fields + token = token.WithExtra(raw) + + if secs := tokenRes.ExpiresIn; secs > 0 { + token.Expiry = time.Now().Add(time.Duration(secs) * time.Second) + } + if v := tokenRes.IDToken; v != "" { + // decode returned id token to get expiry + claimSet, err := jws.Decode(v) + if err != nil { + return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err) + } + token.Expiry = time.Unix(claimSet.Exp, 0) + } + return token, nil +} diff --git a/vendor/golang.org/x/oauth2/jwt/jwt_test.go b/vendor/golang.org/x/oauth2/jwt/jwt_test.go new file mode 100644 index 0000000..da922c3 --- /dev/null +++ b/vendor/golang.org/x/oauth2/jwt/jwt_test.go @@ -0,0 +1,134 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jwt + +import ( + "net/http" + "net/http/httptest" + "testing" + + "golang.org/x/oauth2" +) + +var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE +DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY +fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK +1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr +k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9 +/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt +3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn +2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3 +nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK +6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf +5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e +DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1 +M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g +z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y +1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK +J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U +f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx +QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA +cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr +Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw +5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg +KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84 +OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd +mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ +5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg== +-----END RSA PRIVATE KEY-----`) + +func TestJWTFetch_JSONResponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{ + "access_token": "90d64460d14870c08c81352a05dedd3465940a7c", + "scope": "user", + "token_type": "bearer", + "expires_in": 3600 + }`)) + })) + defer ts.Close() + + conf := &Config{ + Email: "aaa@xxx.com", + PrivateKey: dummyPrivateKey, + TokenURL: ts.URL, + } + tok, err := conf.TokenSource(oauth2.NoContext).Token() + if err != nil { + t.Fatal(err) + } + if !tok.Valid() { + t.Errorf("Token invalid") + } + if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { + t.Errorf("Unexpected access token, %#v", tok.AccessToken) + } + if tok.TokenType != "bearer" { + t.Errorf("Unexpected token type, %#v", tok.TokenType) + } + if tok.Expiry.IsZero() { + t.Errorf("Unexpected token expiry, %#v", tok.Expiry) + } + scope := tok.Extra("scope") + if scope != "user" { + t.Errorf("Unexpected value for scope: %v", scope) + } +} + +func TestJWTFetch_BadResponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`)) + })) + defer ts.Close() + + conf := &Config{ + Email: "aaa@xxx.com", + PrivateKey: dummyPrivateKey, + TokenURL: ts.URL, + } + tok, err := conf.TokenSource(oauth2.NoContext).Token() + if err != nil { + t.Fatal(err) + } + if tok == nil { + t.Fatalf("token is nil") + } + if tok.Valid() { + t.Errorf("token is valid. want invalid.") + } + if tok.AccessToken != "" { + t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken) + } + if want := "bearer"; tok.TokenType != want { + t.Errorf("TokenType = %q; want %q", tok.TokenType, want) + } + scope := tok.Extra("scope") + if want := "user"; scope != want { + t.Errorf("token scope = %q; want %q", scope, want) + } +} + +func TestJWTFetch_BadResponseType(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`)) + })) + defer ts.Close() + conf := &Config{ + Email: "aaa@xxx.com", + PrivateKey: dummyPrivateKey, + TokenURL: ts.URL, + } + tok, err := conf.TokenSource(oauth2.NoContext).Token() + if err == nil { + t.Error("got a token; expected error") + if tok.AccessToken != "" { + t.Errorf("Unexpected access token, %#v.", tok.AccessToken) + } + } +} diff --git a/vendor/golang.org/x/oauth2/linkedin/linkedin.go b/vendor/golang.org/x/oauth2/linkedin/linkedin.go new file mode 100644 index 0000000..de91d5b --- /dev/null +++ b/vendor/golang.org/x/oauth2/linkedin/linkedin.go @@ -0,0 +1,16 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package linkedin provides constants for using OAuth2 to access LinkedIn. +package linkedin // import "golang.org/x/oauth2/linkedin" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is LinkedIn's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://www.linkedin.com/uas/oauth2/authorization", + TokenURL: "https://www.linkedin.com/uas/oauth2/accessToken", +} diff --git a/vendor/golang.org/x/oauth2/oauth2.go b/vendor/golang.org/x/oauth2/oauth2.go new file mode 100644 index 0000000..cca8b18 --- /dev/null +++ b/vendor/golang.org/x/oauth2/oauth2.go @@ -0,0 +1,325 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package oauth2 provides support for making +// OAuth2 authorized and authenticated HTTP requests. +// It can additionally grant authorization with Bearer JWT. +package oauth2 // import "golang.org/x/oauth2" + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "strings" + "sync" + + "golang.org/x/net/context" + "golang.org/x/oauth2/internal" +) + +// NoContext is the default context you should supply if not using +// your own context.Context (see https://golang.org/x/net/context). +var NoContext = context.TODO() + +// Config describes a typical 3-legged OAuth2 flow, with both the +// client application information and the server's endpoint URLs. +type Config struct { + // ClientID is the application's ID. + ClientID string + + // ClientSecret is the application's secret. + ClientSecret string + + // Endpoint contains the resource server's token endpoint + // URLs. These are constants specific to each server and are + // often available via site-specific packages, such as + // google.Endpoint or github.Endpoint. + Endpoint Endpoint + + // RedirectURL is the URL to redirect users going through + // the OAuth flow, after the resource owner's URLs. + RedirectURL string + + // Scope specifies optional requested permissions. + Scopes []string +} + +// A TokenSource is anything that can return a token. +type TokenSource interface { + // Token returns a token or an error. + // Token must be safe for concurrent use by multiple goroutines. + // The returned Token must not be modified. + Token() (*Token, error) +} + +// Endpoint contains the OAuth 2.0 provider's authorization and token +// endpoint URLs. +type Endpoint struct { + AuthURL string + TokenURL string +} + +var ( + // AccessTypeOnline and AccessTypeOffline are options passed + // to the Options.AuthCodeURL method. They modify the + // "access_type" field that gets sent in the URL returned by + // AuthCodeURL. + // + // Online is the default if neither is specified. If your + // application needs to refresh access tokens when the user + // is not present at the browser, then use offline. This will + // result in your application obtaining a refresh token the + // first time your application exchanges an authorization + // code for a user. + AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online") + AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline") + + // ApprovalForce forces the users to view the consent dialog + // and confirm the permissions request at the URL returned + // from AuthCodeURL, even if they've already done so. + ApprovalForce AuthCodeOption = SetAuthURLParam("approval_prompt", "force") +) + +// An AuthCodeOption is passed to Config.AuthCodeURL. +type AuthCodeOption interface { + setValue(url.Values) +} + +type setParam struct{ k, v string } + +func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } + +// SetAuthURLParam builds an AuthCodeOption which passes key/value parameters +// to a provider's authorization endpoint. +func SetAuthURLParam(key, value string) AuthCodeOption { + return setParam{key, value} +} + +// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page +// that asks for permissions for the required scopes explicitly. +// +// State is a token to protect the user from CSRF attacks. You must +// always provide a non-zero string and validate that it matches the +// the state query parameter on your redirect callback. +// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. +// +// Opts may include AccessTypeOnline or AccessTypeOffline, as well +// as ApprovalForce. +func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { + var buf bytes.Buffer + buf.WriteString(c.Endpoint.AuthURL) + v := url.Values{ + "response_type": {"code"}, + "client_id": {c.ClientID}, + "redirect_uri": internal.CondVal(c.RedirectURL), + "scope": internal.CondVal(strings.Join(c.Scopes, " ")), + "state": internal.CondVal(state), + } + for _, opt := range opts { + opt.setValue(v) + } + if strings.Contains(c.Endpoint.AuthURL, "?") { + buf.WriteByte('&') + } else { + buf.WriteByte('?') + } + buf.WriteString(v.Encode()) + return buf.String() +} + +// PasswordCredentialsToken converts a resource owner username and password +// pair into a token. +// +// Per the RFC, this grant type should only be used "when there is a high +// degree of trust between the resource owner and the client (e.g., the client +// is part of the device operating system or a highly privileged application), +// and when other authorization grant types are not available." +// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. +// +// The HTTP client to use is derived from the context. +// If nil, http.DefaultClient is used. +func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { + return retrieveToken(ctx, c, url.Values{ + "grant_type": {"password"}, + "username": {username}, + "password": {password}, + "scope": internal.CondVal(strings.Join(c.Scopes, " ")), + }) +} + +// Exchange converts an authorization code into a token. +// +// It is used after a resource provider redirects the user back +// to the Redirect URI (the URL obtained from AuthCodeURL). +// +// The HTTP client to use is derived from the context. +// If a client is not provided via the context, http.DefaultClient is used. +// +// The code will be in the *http.Request.FormValue("code"). Before +// calling Exchange, be sure to validate FormValue("state"). +func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { + return retrieveToken(ctx, c, url.Values{ + "grant_type": {"authorization_code"}, + "code": {code}, + "redirect_uri": internal.CondVal(c.RedirectURL), + "scope": internal.CondVal(strings.Join(c.Scopes, " ")), + }) +} + +// Client returns an HTTP client using the provided token. +// The token will auto-refresh as necessary. The underlying +// HTTP transport will be obtained using the provided context. +// The returned client and its Transport should not be modified. +func (c *Config) Client(ctx context.Context, t *Token) *http.Client { + return NewClient(ctx, c.TokenSource(ctx, t)) +} + +// TokenSource returns a TokenSource that returns t until t expires, +// automatically refreshing it as necessary using the provided context. +// +// Most users will use Config.Client instead. +func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource { + tkr := &tokenRefresher{ + ctx: ctx, + conf: c, + } + if t != nil { + tkr.refreshToken = t.RefreshToken + } + return &reuseTokenSource{ + t: t, + new: tkr, + } +} + +// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token" +// HTTP requests to renew a token using a RefreshToken. +type tokenRefresher struct { + ctx context.Context // used to get HTTP requests + conf *Config + refreshToken string +} + +// WARNING: Token is not safe for concurrent access, as it +// updates the tokenRefresher's refreshToken field. +// Within this package, it is used by reuseTokenSource which +// synchronizes calls to this method with its own mutex. +func (tf *tokenRefresher) Token() (*Token, error) { + if tf.refreshToken == "" { + return nil, errors.New("oauth2: token expired and refresh token is not set") + } + + tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{ + "grant_type": {"refresh_token"}, + "refresh_token": {tf.refreshToken}, + }) + + if err != nil { + return nil, err + } + if tf.refreshToken != tk.RefreshToken { + tf.refreshToken = tk.RefreshToken + } + return tk, err +} + +// reuseTokenSource is a TokenSource that holds a single token in memory +// and validates its expiry before each call to retrieve it with +// Token. If it's expired, it will be auto-refreshed using the +// new TokenSource. +type reuseTokenSource struct { + new TokenSource // called when t is expired. + + mu sync.Mutex // guards t + t *Token +} + +// Token returns the current token if it's still valid, else will +// refresh the current token (using r.Context for HTTP client +// information) and return the new one. +func (s *reuseTokenSource) Token() (*Token, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.t.Valid() { + return s.t, nil + } + t, err := s.new.Token() + if err != nil { + return nil, err + } + s.t = t + return t, nil +} + +// StaticTokenSource returns a TokenSource that always returns the same token. +// Because the provided token t is never refreshed, StaticTokenSource is only +// useful for tokens that never expire. +func StaticTokenSource(t *Token) TokenSource { + return staticTokenSource{t} +} + +// staticTokenSource is a TokenSource that always returns the same Token. +type staticTokenSource struct { + t *Token +} + +func (s staticTokenSource) Token() (*Token, error) { + return s.t, nil +} + +// HTTPClient is the context key to use with golang.org/x/net/context's +// WithValue function to associate an *http.Client value with a context. +var HTTPClient internal.ContextKey + +// NewClient creates an *http.Client from a Context and TokenSource. +// The returned client is not valid beyond the lifetime of the context. +// +// As a special case, if src is nil, a non-OAuth2 client is returned +// using the provided context. This exists to support related OAuth2 +// packages. +func NewClient(ctx context.Context, src TokenSource) *http.Client { + if src == nil { + c, err := internal.ContextClient(ctx) + if err != nil { + return &http.Client{Transport: internal.ErrorTransport{err}} + } + return c + } + return &http.Client{ + Transport: &Transport{ + Base: internal.ContextTransport(ctx), + Source: ReuseTokenSource(nil, src), + }, + } +} + +// ReuseTokenSource returns a TokenSource which repeatedly returns the +// same token as long as it's valid, starting with t. +// When its cached token is invalid, a new token is obtained from src. +// +// ReuseTokenSource is typically used to reuse tokens from a cache +// (such as a file on disk) between runs of a program, rather than +// obtaining new tokens unnecessarily. +// +// The initial token t may be nil, in which case the TokenSource is +// wrapped in a caching version if it isn't one already. This also +// means it's always safe to wrap ReuseTokenSource around any other +// TokenSource without adverse effects. +func ReuseTokenSource(t *Token, src TokenSource) TokenSource { + // Don't wrap a reuseTokenSource in itself. That would work, + // but cause an unnecessary number of mutex operations. + // Just build the equivalent one. + if rt, ok := src.(*reuseTokenSource); ok { + if t == nil { + // Just use it directly. + return rt + } + src = rt.new + } + return &reuseTokenSource{ + t: t, + new: src, + } +} diff --git a/vendor/golang.org/x/oauth2/oauth2_test.go b/vendor/golang.org/x/oauth2/oauth2_test.go new file mode 100644 index 0000000..a99d7a3 --- /dev/null +++ b/vendor/golang.org/x/oauth2/oauth2_test.go @@ -0,0 +1,471 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strconv" + "testing" + "time" + + "golang.org/x/net/context" +) + +type mockTransport struct { + rt func(req *http.Request) (resp *http.Response, err error) +} + +func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { + return t.rt(req) +} + +type mockCache struct { + token *Token + readErr error +} + +func (c *mockCache) ReadToken() (*Token, error) { + return c.token, c.readErr +} + +func (c *mockCache) WriteToken(*Token) { + // do nothing +} + +func newConf(url string) *Config { + return &Config{ + ClientID: "CLIENT_ID", + ClientSecret: "CLIENT_SECRET", + RedirectURL: "REDIRECT_URL", + Scopes: []string{"scope1", "scope2"}, + Endpoint: Endpoint{ + AuthURL: url + "/auth", + TokenURL: url + "/token", + }, + } +} + +func TestAuthCodeURL(t *testing.T) { + conf := newConf("server") + url := conf.AuthCodeURL("foo", AccessTypeOffline, ApprovalForce) + if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" { + t.Errorf("Auth code URL doesn't match the expected, found: %v", url) + } +} + +func TestAuthCodeURL_CustomParam(t *testing.T) { + conf := newConf("server") + param := SetAuthURLParam("foo", "bar") + url := conf.AuthCodeURL("baz", param) + if url != "server/auth?client_id=CLIENT_ID&foo=bar&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=baz" { + t.Errorf("Auth code URL doesn't match the expected, found: %v", url) + } +} + +func TestAuthCodeURL_Optional(t *testing.T) { + conf := &Config{ + ClientID: "CLIENT_ID", + Endpoint: Endpoint{ + AuthURL: "/auth-url", + TokenURL: "/token-url", + }, + } + url := conf.AuthCodeURL("") + if url != "/auth-url?client_id=CLIENT_ID&response_type=code" { + t.Fatalf("Auth code URL doesn't match the expected, found: %v", url) + } +} + +func TestExchangeRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() != "/token" { + t.Errorf("Unexpected exchange request URL, %v is found.", r.URL) + } + headerAuth := r.Header.Get("Authorization") + if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { + t.Errorf("Unexpected authorization header, %v is found.", headerAuth) + } + headerContentType := r.Header.Get("Content-Type") + if headerContentType != "application/x-www-form-urlencoded" { + t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Failed reading request body: %s.", err) + } + if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" { + t.Errorf("Unexpected exchange payload, %v is found.", string(body)) + } + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.Exchange(NoContext, "exchange-code") + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { + t.Errorf("Unexpected access token, %#v.", tok.AccessToken) + } + if tok.TokenType != "bearer" { + t.Errorf("Unexpected token type, %#v.", tok.TokenType) + } + scope := tok.Extra("scope") + if scope != "user" { + t.Errorf("Unexpected value for scope: %v", scope) + } +} + +func TestExchangeRequest_JSONResponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() != "/token" { + t.Errorf("Unexpected exchange request URL, %v is found.", r.URL) + } + headerAuth := r.Header.Get("Authorization") + if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" { + t.Errorf("Unexpected authorization header, %v is found.", headerAuth) + } + headerContentType := r.Header.Get("Content-Type") + if headerContentType != "application/x-www-form-urlencoded" { + t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Failed reading request body: %s.", err) + } + if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" { + t.Errorf("Unexpected exchange payload, %v is found.", string(body)) + } + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer", "expires_in": 86400}`)) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.Exchange(NoContext, "exchange-code") + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" { + t.Errorf("Unexpected access token, %#v.", tok.AccessToken) + } + if tok.TokenType != "bearer" { + t.Errorf("Unexpected token type, %#v.", tok.TokenType) + } + scope := tok.Extra("scope") + if scope != "user" { + t.Errorf("Unexpected value for scope: %v", scope) + } + expiresIn := tok.Extra("expires_in") + if expiresIn != float64(86400) { + t.Errorf("Unexpected non-numeric value for expires_in: %v", expiresIn) + } +} + +func TestExtraValueRetrieval(t *testing.T) { + values := url.Values{} + + kvmap := map[string]string{ + "scope": "user", "token_type": "bearer", "expires_in": "86400.92", + "server_time": "1443571905.5606415", "referer_ip": "10.0.0.1", + "etag": "\"afZYj912P4alikMz_P11982\"", "request_id": "86400", + "untrimmed": " untrimmed ", + } + + for key, value := range kvmap { + values.Set(key, value) + } + + tok := Token{ + raw: values, + } + + scope := tok.Extra("scope") + if scope != "user" { + t.Errorf("Unexpected scope %v wanted \"user\"", scope) + } + serverTime := tok.Extra("server_time") + if serverTime != 1443571905.5606415 { + t.Errorf("Unexpected non-float64 value for server_time: %v", serverTime) + } + refererIp := tok.Extra("referer_ip") + if refererIp != "10.0.0.1" { + t.Errorf("Unexpected non-string value for referer_ip: %v", refererIp) + } + expires_in := tok.Extra("expires_in") + if expires_in != 86400.92 { + t.Errorf("Unexpected value for expires_in, wanted 86400 got %v", expires_in) + } + requestId := tok.Extra("request_id") + if requestId != int64(86400) { + t.Errorf("Unexpected non-int64 value for request_id: %v", requestId) + } + untrimmed := tok.Extra("untrimmed") + if untrimmed != " untrimmed " { + t.Errorf("Unexpected value for untrimmed, got %q expected \" untrimmed \"", untrimmed) + } +} + +const day = 24 * time.Hour + +func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) { + seconds := int32(day.Seconds()) + jsonNumberType := reflect.TypeOf(json.Number("0")) + for _, c := range []struct { + expires string + expect error + }{ + {fmt.Sprintf(`"expires_in": %d`, seconds), nil}, + {fmt.Sprintf(`"expires_in": "%d"`, seconds), nil}, // PayPal case + {fmt.Sprintf(`"expires": %d`, seconds), nil}, // Facebook case + {`"expires": false`, &json.UnmarshalTypeError{Value: "bool", Type: jsonNumberType}}, // wrong type + {`"expires": {}`, &json.UnmarshalTypeError{Value: "object", Type: jsonNumberType}}, // wrong type + {`"expires": "zzz"`, &strconv.NumError{Func: "ParseInt", Num: "zzz", Err: strconv.ErrSyntax}}, // wrong value + } { + testExchangeRequest_JSONResponse_expiry(t, c.expires, c.expect) + } +} + +func testExchangeRequest_JSONResponse_expiry(t *testing.T, exp string, expect error) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(fmt.Sprintf(`{"access_token": "90d", "scope": "user", "token_type": "bearer", %s}`, exp))) + })) + defer ts.Close() + conf := newConf(ts.URL) + t1 := time.Now().Add(day) + tok, err := conf.Exchange(NoContext, "exchange-code") + t2 := time.Now().Add(day) + // Do a fmt.Sprint comparison so either side can be + // nil. fmt.Sprint just stringifies them to "", and no + // non-nil expected error ever stringifies as "", so this + // isn't terribly disgusting. We do this because Go 1.4 and + // Go 1.5 return a different deep value for + // json.UnmarshalTypeError. In Go 1.5, the + // json.UnmarshalTypeError contains a new field with a new + // non-zero value. Rather than ignore it here with reflect or + // add new files and +build tags, just look at the strings. + if fmt.Sprint(err) != fmt.Sprint(expect) { + t.Errorf("Error = %v; want %v", err, expect) + } + if err != nil { + return + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + expiry := tok.Expiry + if expiry.Before(t1) || expiry.After(t2) { + t.Errorf("Unexpected value for Expiry: %v (shold be between %v and %v)", expiry, t1, t2) + } +} + +func TestExchangeRequest_BadResponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`)) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.Exchange(NoContext, "code") + if err != nil { + t.Fatal(err) + } + if tok.AccessToken != "" { + t.Errorf("Unexpected access token, %#v.", tok.AccessToken) + } +} + +func TestExchangeRequest_BadResponseType(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`)) + })) + defer ts.Close() + conf := newConf(ts.URL) + _, err := conf.Exchange(NoContext, "exchange-code") + if err == nil { + t.Error("expected error from invalid access_token type") + } +} + +func TestExchangeRequest_NonBasicAuth(t *testing.T) { + tr := &mockTransport{ + rt: func(r *http.Request) (w *http.Response, err error) { + headerAuth := r.Header.Get("Authorization") + if headerAuth != "" { + t.Errorf("Unexpected authorization header, %v is found.", headerAuth) + } + return nil, errors.New("no response") + }, + } + c := &http.Client{Transport: tr} + conf := &Config{ + ClientID: "CLIENT_ID", + Endpoint: Endpoint{ + AuthURL: "https://accounts.google.com/auth", + TokenURL: "https://accounts.google.com/token", + }, + } + + ctx := context.WithValue(context.Background(), HTTPClient, c) + conf.Exchange(ctx, "code") +} + +func TestPasswordCredentialsTokenRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + expected := "/token" + if r.URL.String() != expected { + t.Errorf("URL = %q; want %q", r.URL, expected) + } + headerAuth := r.Header.Get("Authorization") + expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" + if headerAuth != expected { + t.Errorf("Authorization header = %q; want %q", headerAuth, expected) + } + headerContentType := r.Header.Get("Content-Type") + expected = "application/x-www-form-urlencoded" + if headerContentType != expected { + t.Errorf("Content-Type header = %q; want %q", headerContentType, expected) + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Failed reading request body: %s.", err) + } + expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1" + if string(body) != expected { + t.Errorf("res.Body = %q; want %q", string(body), expected) + } + w.Header().Set("Content-Type", "application/x-www-form-urlencoded") + w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer")) + })) + defer ts.Close() + conf := newConf(ts.URL) + tok, err := conf.PasswordCredentialsToken(NoContext, "user1", "password1") + if err != nil { + t.Error(err) + } + if !tok.Valid() { + t.Fatalf("Token invalid. Got: %#v", tok) + } + expected := "90d64460d14870c08c81352a05dedd3465940a7c" + if tok.AccessToken != expected { + t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected) + } + expected = "bearer" + if tok.TokenType != expected { + t.Errorf("TokenType = %q; want %q", tok.TokenType, expected) + } +} + +func TestTokenRefreshRequest(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() == "/somethingelse" { + return + } + if r.URL.String() != "/token" { + t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL) + } + headerContentType := r.Header.Get("Content-Type") + if headerContentType != "application/x-www-form-urlencoded" { + t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) + } + body, _ := ioutil.ReadAll(r.Body) + if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" { + t.Errorf("Unexpected refresh token payload, %v is found.", string(body)) + } + })) + defer ts.Close() + conf := newConf(ts.URL) + c := conf.Client(NoContext, &Token{RefreshToken: "REFRESH_TOKEN"}) + c.Get(ts.URL + "/somethingelse") +} + +func TestFetchWithNoRefreshToken(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() == "/somethingelse" { + return + } + if r.URL.String() != "/token" { + t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL) + } + headerContentType := r.Header.Get("Content-Type") + if headerContentType != "application/x-www-form-urlencoded" { + t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType) + } + body, _ := ioutil.ReadAll(r.Body) + if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" { + t.Errorf("Unexpected refresh token payload, %v is found.", string(body)) + } + })) + defer ts.Close() + conf := newConf(ts.URL) + c := conf.Client(NoContext, nil) + _, err := c.Get(ts.URL + "/somethingelse") + if err == nil { + t.Errorf("Fetch should return an error if no refresh token is set") + } +} + +func TestRefreshToken_RefreshTokenReplacement(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`)) + return + })) + defer ts.Close() + conf := newConf(ts.URL) + tkr := tokenRefresher{ + conf: conf, + ctx: NoContext, + refreshToken: "OLD REFRESH TOKEN", + } + tk, err := tkr.Token() + if err != nil { + t.Errorf("Unexpected refreshToken error returned: %v", err) + return + } + if tk.RefreshToken != tkr.refreshToken { + t.Errorf("tokenRefresher.refresh_token = %s; want %s", tkr.refreshToken, tk.RefreshToken) + } +} + +func TestConfigClientWithToken(t *testing.T) { + tok := &Token{ + AccessToken: "abc123", + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Authorization"), fmt.Sprintf("Bearer %s", tok.AccessToken); got != want { + t.Errorf("Authorization header = %q; want %q", got, want) + } + return + })) + defer ts.Close() + conf := newConf(ts.URL) + + c := conf.Client(NoContext, tok) + req, err := http.NewRequest("GET", ts.URL, nil) + if err != nil { + t.Error(err) + } + _, err = c.Do(req) + if err != nil { + t.Error(err) + } +} diff --git a/vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go b/vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go new file mode 100644 index 0000000..2f7a962 --- /dev/null +++ b/vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go @@ -0,0 +1,16 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package odnoklassniki provides constants for using OAuth2 to access Odnoklassniki. +package odnoklassniki // import "golang.org/x/oauth2/odnoklassniki" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is Odnoklassniki's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://www.odnoklassniki.ru/oauth/authorize", + TokenURL: "https://api.odnoklassniki.ru/oauth/token.do", +} diff --git a/vendor/golang.org/x/oauth2/paypal/paypal.go b/vendor/golang.org/x/oauth2/paypal/paypal.go new file mode 100644 index 0000000..baeaa23 --- /dev/null +++ b/vendor/golang.org/x/oauth2/paypal/paypal.go @@ -0,0 +1,22 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package paypal provides constants for using OAuth2 to access PayPal. +package paypal // import "golang.org/x/oauth2/paypal" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is PayPal's OAuth 2.0 endpoint in live (production) environment. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize", + TokenURL: "https://api.paypal.com/v1/identity/openidconnect/tokenservice", +} + +// SandboxEndpoint is PayPal's OAuth 2.0 endpoint in sandbox (testing) environment. +var SandboxEndpoint = oauth2.Endpoint{ + AuthURL: "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize", + TokenURL: "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice", +} diff --git a/vendor/golang.org/x/oauth2/token.go b/vendor/golang.org/x/oauth2/token.go new file mode 100644 index 0000000..4e596f0 --- /dev/null +++ b/vendor/golang.org/x/oauth2/token.go @@ -0,0 +1,158 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "golang.org/x/net/context" + "golang.org/x/oauth2/internal" +) + +// expiryDelta determines how earlier a token should be considered +// expired than its actual expiration time. It is used to avoid late +// expirations due to client-server time mismatches. +const expiryDelta = 10 * time.Second + +// Token represents the crendentials used to authorize +// the requests to access protected resources on the OAuth 2.0 +// provider's backend. +// +// Most users of this package should not access fields of Token +// directly. They're exported mostly for use by related packages +// implementing derivative OAuth2 flows. +type Token struct { + // AccessToken is the token that authorizes and authenticates + // the requests. + AccessToken string `json:"access_token"` + + // TokenType is the type of token. + // The Type method returns either this or "Bearer", the default. + TokenType string `json:"token_type,omitempty"` + + // RefreshToken is a token that's used by the application + // (as opposed to the user) to refresh the access token + // if it expires. + RefreshToken string `json:"refresh_token,omitempty"` + + // Expiry is the optional expiration time of the access token. + // + // If zero, TokenSource implementations will reuse the same + // token forever and RefreshToken or equivalent + // mechanisms for that TokenSource will not be used. + Expiry time.Time `json:"expiry,omitempty"` + + // raw optionally contains extra metadata from the server + // when updating a token. + raw interface{} +} + +// Type returns t.TokenType if non-empty, else "Bearer". +func (t *Token) Type() string { + if strings.EqualFold(t.TokenType, "bearer") { + return "Bearer" + } + if strings.EqualFold(t.TokenType, "mac") { + return "MAC" + } + if strings.EqualFold(t.TokenType, "basic") { + return "Basic" + } + if t.TokenType != "" { + return t.TokenType + } + return "Bearer" +} + +// SetAuthHeader sets the Authorization header to r using the access +// token in t. +// +// This method is unnecessary when using Transport or an HTTP Client +// returned by this package. +func (t *Token) SetAuthHeader(r *http.Request) { + r.Header.Set("Authorization", t.Type()+" "+t.AccessToken) +} + +// WithExtra returns a new Token that's a clone of t, but using the +// provided raw extra map. This is only intended for use by packages +// implementing derivative OAuth2 flows. +func (t *Token) WithExtra(extra interface{}) *Token { + t2 := new(Token) + *t2 = *t + t2.raw = extra + return t2 +} + +// Extra returns an extra field. +// Extra fields are key-value pairs returned by the server as a +// part of the token retrieval response. +func (t *Token) Extra(key string) interface{} { + if raw, ok := t.raw.(map[string]interface{}); ok { + return raw[key] + } + + vals, ok := t.raw.(url.Values) + if !ok { + return nil + } + + v := vals.Get(key) + switch s := strings.TrimSpace(v); strings.Count(s, ".") { + case 0: // Contains no "."; try to parse as int + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return i + } + case 1: // Contains a single "."; try to parse as float + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } + } + + return v +} + +// expired reports whether the token is expired. +// t must be non-nil. +func (t *Token) expired() bool { + if t.Expiry.IsZero() { + return false + } + return t.Expiry.Add(-expiryDelta).Before(time.Now()) +} + +// Valid reports whether t is non-nil, has an AccessToken, and is not expired. +func (t *Token) Valid() bool { + return t != nil && t.AccessToken != "" && !t.expired() +} + +// tokenFromInternal maps an *internal.Token struct into +// a *Token struct. +func tokenFromInternal(t *internal.Token) *Token { + if t == nil { + return nil + } + return &Token{ + AccessToken: t.AccessToken, + TokenType: t.TokenType, + RefreshToken: t.RefreshToken, + Expiry: t.Expiry, + raw: t.Raw, + } +} + +// retrieveToken takes a *Config and uses that to retrieve an *internal.Token. +// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along +// with an error.. +func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { + tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v) + if err != nil { + return nil, err + } + return tokenFromInternal(tk), nil +} diff --git a/vendor/golang.org/x/oauth2/token_test.go b/vendor/golang.org/x/oauth2/token_test.go new file mode 100644 index 0000000..8344329 --- /dev/null +++ b/vendor/golang.org/x/oauth2/token_test.go @@ -0,0 +1,72 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "testing" + "time" +) + +func TestTokenExtra(t *testing.T) { + type testCase struct { + key string + val interface{} + want interface{} + } + const key = "extra-key" + cases := []testCase{ + {key: key, val: "abc", want: "abc"}, + {key: key, val: 123, want: 123}, + {key: key, val: "", want: ""}, + {key: "other-key", val: "def", want: nil}, + } + for _, tc := range cases { + extra := make(map[string]interface{}) + extra[tc.key] = tc.val + tok := &Token{raw: extra} + if got, want := tok.Extra(key), tc.want; got != want { + t.Errorf("Extra(%q) = %q; want %q", key, got, want) + } + } +} + +func TestTokenExpiry(t *testing.T) { + now := time.Now() + cases := []struct { + name string + tok *Token + want bool + }{ + {name: "12 seconds", tok: &Token{Expiry: now.Add(12 * time.Second)}, want: false}, + {name: "10 seconds", tok: &Token{Expiry: now.Add(expiryDelta)}, want: true}, + {name: "-1 hour", tok: &Token{Expiry: now.Add(-1 * time.Hour)}, want: true}, + } + for _, tc := range cases { + if got, want := tc.tok.expired(), tc.want; got != want { + t.Errorf("expired (%q) = %v; want %v", tc.name, got, want) + } + } +} + +func TestTokenTypeMethod(t *testing.T) { + cases := []struct { + name string + tok *Token + want string + }{ + {name: "bearer-mixed_case", tok: &Token{TokenType: "beAREr"}, want: "Bearer"}, + {name: "default-bearer", tok: &Token{}, want: "Bearer"}, + {name: "basic", tok: &Token{TokenType: "basic"}, want: "Basic"}, + {name: "basic-capitalized", tok: &Token{TokenType: "Basic"}, want: "Basic"}, + {name: "mac", tok: &Token{TokenType: "mac"}, want: "MAC"}, + {name: "mac-caps", tok: &Token{TokenType: "MAC"}, want: "MAC"}, + {name: "mac-mixed_case", tok: &Token{TokenType: "mAc"}, want: "MAC"}, + } + for _, tc := range cases { + if got, want := tc.tok.Type(), tc.want; got != want { + t.Errorf("TokenType(%q) = %v; want %v", tc.name, got, want) + } + } +} diff --git a/vendor/golang.org/x/oauth2/transport.go b/vendor/golang.org/x/oauth2/transport.go new file mode 100644 index 0000000..90db088 --- /dev/null +++ b/vendor/golang.org/x/oauth2/transport.go @@ -0,0 +1,132 @@ +// Copyright 2014 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "errors" + "io" + "net/http" + "sync" +) + +// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests, +// wrapping a base RoundTripper and adding an Authorization header +// with a token from the supplied Sources. +// +// Transport is a low-level mechanism. Most code will use the +// higher-level Config.Client method instead. +type Transport struct { + // Source supplies the token to add to outgoing requests' + // Authorization headers. + Source TokenSource + + // Base is the base RoundTripper used to make HTTP requests. + // If nil, http.DefaultTransport is used. + Base http.RoundTripper + + mu sync.Mutex // guards modReq + modReq map[*http.Request]*http.Request // original -> modified +} + +// RoundTrip authorizes and authenticates the request with an +// access token. If no token exists or token is expired, +// tries to refresh/fetch a new token. +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.Source == nil { + return nil, errors.New("oauth2: Transport's Source is nil") + } + token, err := t.Source.Token() + if err != nil { + return nil, err + } + + req2 := cloneRequest(req) // per RoundTripper contract + token.SetAuthHeader(req2) + t.setModReq(req, req2) + res, err := t.base().RoundTrip(req2) + if err != nil { + t.setModReq(req, nil) + return nil, err + } + res.Body = &onEOFReader{ + rc: res.Body, + fn: func() { t.setModReq(req, nil) }, + } + return res, nil +} + +// CancelRequest cancels an in-flight request by closing its connection. +func (t *Transport) CancelRequest(req *http.Request) { + type canceler interface { + CancelRequest(*http.Request) + } + if cr, ok := t.base().(canceler); ok { + t.mu.Lock() + modReq := t.modReq[req] + delete(t.modReq, req) + t.mu.Unlock() + cr.CancelRequest(modReq) + } +} + +func (t *Transport) base() http.RoundTripper { + if t.Base != nil { + return t.Base + } + return http.DefaultTransport +} + +func (t *Transport) setModReq(orig, mod *http.Request) { + t.mu.Lock() + defer t.mu.Unlock() + if t.modReq == nil { + t.modReq = make(map[*http.Request]*http.Request) + } + if mod == nil { + delete(t.modReq, orig) + } else { + t.modReq[orig] = mod + } +} + +// cloneRequest returns a clone of the provided *http.Request. +// The clone is a shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} + +type onEOFReader struct { + rc io.ReadCloser + fn func() +} + +func (r *onEOFReader) Read(p []byte) (n int, err error) { + n, err = r.rc.Read(p) + if err == io.EOF { + r.runFunc() + } + return +} + +func (r *onEOFReader) Close() error { + err := r.rc.Close() + r.runFunc() + return err +} + +func (r *onEOFReader) runFunc() { + if fn := r.fn; fn != nil { + fn() + r.fn = nil + } +} diff --git a/vendor/golang.org/x/oauth2/transport_test.go b/vendor/golang.org/x/oauth2/transport_test.go new file mode 100644 index 0000000..35cb25e --- /dev/null +++ b/vendor/golang.org/x/oauth2/transport_test.go @@ -0,0 +1,86 @@ +package oauth2 + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +type tokenSource struct{ token *Token } + +func (t *tokenSource) Token() (*Token, error) { + return t.token, nil +} + +func TestTransportTokenSource(t *testing.T) { + ts := &tokenSource{ + token: &Token{ + AccessToken: "abc", + }, + } + tr := &Transport{ + Source: ts, + } + server := newMockServer(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer abc" { + t.Errorf("Transport doesn't set the Authorization header from the fetched token") + } + }) + defer server.Close() + client := http.Client{Transport: tr} + client.Get(server.URL) +} + +// Test for case-sensitive token types, per https://github.com/golang/oauth2/issues/113 +func TestTransportTokenSourceTypes(t *testing.T) { + const val = "abc" + tests := []struct { + key string + val string + want string + }{ + {key: "bearer", val: val, want: "Bearer abc"}, + {key: "mac", val: val, want: "MAC abc"}, + {key: "basic", val: val, want: "Basic abc"}, + } + for _, tc := range tests { + ts := &tokenSource{ + token: &Token{ + AccessToken: tc.val, + TokenType: tc.key, + }, + } + tr := &Transport{ + Source: ts, + } + server := newMockServer(func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Authorization"), tc.want; got != want { + t.Errorf("Authorization header (%q) = %q; want %q", val, got, want) + } + }) + defer server.Close() + client := http.Client{Transport: tr} + client.Get(server.URL) + } +} + +func TestTokenValidNoAccessToken(t *testing.T) { + token := &Token{} + if token.Valid() { + t.Errorf("Token should not be valid with no access token") + } +} + +func TestExpiredWithExpiry(t *testing.T) { + token := &Token{ + Expiry: time.Now().Add(-5 * time.Hour), + } + if token.Valid() { + t.Errorf("Token should not be valid if it expired in the past") + } +} + +func newMockServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(handler)) +} diff --git a/vendor/golang.org/x/oauth2/vk/vk.go b/vendor/golang.org/x/oauth2/vk/vk.go new file mode 100644 index 0000000..5acdeb1 --- /dev/null +++ b/vendor/golang.org/x/oauth2/vk/vk.go @@ -0,0 +1,16 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package vk provides constants for using OAuth2 to access VK.com. +package vk // import "golang.org/x/oauth2/vk" + +import ( + "golang.org/x/oauth2" +) + +// Endpoint is VK's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://oauth.vk.com/authorize", + TokenURL: "https://oauth.vk.com/access_token", +}