percy-client 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -30
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +3 -1
  6. data/Guardfile +14 -0
  7. data/LICENSE +2 -2
  8. data/README.md +34 -2
  9. data/Rakefile +1 -1
  10. data/lib/percy.rb +37 -0
  11. data/lib/percy/client.rb +47 -0
  12. data/lib/percy/client/builds.rb +32 -0
  13. data/lib/percy/client/connection.rb +50 -0
  14. data/lib/percy/client/local_git.rb +50 -0
  15. data/lib/percy/client/resources.rb +54 -0
  16. data/lib/percy/client/snapshots.rb +22 -0
  17. data/lib/percy/client/version.rb +5 -0
  18. data/percy-client.gemspec +29 -0
  19. data/spec/cassettes/Percy_Client_Builds/_create_build/creates_a_build.yml +64 -0
  20. data/spec/cassettes/Percy_Client_Builds/_finalize_build/finalizes_a_build.yml +121 -0
  21. data/spec/cassettes/Percy_Client_Resources/_upload_resource/returns_true_with_success.yml +237 -0
  22. data/spec/cassettes/Percy_Client_Snapshots/_create_snapshot/creates_a_build.yml +121 -0
  23. data/spec/cassettes/Percy_Client_Snapshots/_create_snapshot/fails_if_no_resources_are_given.yml +122 -0
  24. data/spec/lib/percy/client/builds_spec.rb +19 -0
  25. data/spec/lib/percy/client/connection_spec.rb +16 -0
  26. data/spec/lib/percy/client/local_git_spec.rb +21 -0
  27. data/spec/lib/percy/client/resources_spec.rb +40 -0
  28. data/spec/lib/percy/client/snapshots_spec.rb +26 -0
  29. data/spec/lib/percy/client_spec.rb +17 -0
  30. data/spec/spec_helper.rb +34 -0
  31. data/spec/support/test_helpers.rb +7 -0
  32. data/spec/support/vcr_setup.rb +27 -0
  33. metadata +116 -9
  34. data/lib/perceptual.rb +0 -5
  35. data/lib/perceptual/version.rb +0 -3
  36. data/perceptual.gemspec +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8e5850fd7fe46854b7a81f08062535f772ee968
4
- data.tar.gz: e162bc2630ecd6cc3098f12a336dc96e4136b2d9
3
+ metadata.gz: a82de11a13b2a403586e6e50adc8afeda0bc32e1
4
+ data.tar.gz: 79849f84e7c401e6e310f4208a6db745f1f3e1e1
5
5
  SHA512:
6
- metadata.gz: 6d1ca813877e312dd255edba8c8399ccf20450174b031ea83092c7bfbe19be91bf48c1af92a0d507543881cfc5b48c89e3427ad312a13c3fc89666e5ae66a985
7
- data.tar.gz: 38b7ab396a28e824973ba57178b43635e145bab1475afd1e4ea98cfbb81d849d1aa654920c8a35408c91845d5ddc4abd2ad100d9a6a6f667ca2e7021aa80773d
6
+ metadata.gz: 33a045cccc8e025003506b6a0d1a7504bf1bec9d3b718dc77b6fde44eb829a183e77c3471b085796b136eb0a1a9b48c928895ef36ab01430b65e1c7711af4f05
7
+ data.tar.gz: 223a6d07e57d67d95248d1bd3d385b396663d6435e6f8cdfbb025cca4699fa77ad9f016195f21b87f008869f782d53d97161c85a5240e93549af6fca2d81c67a
data/.gitignore CHANGED
@@ -1,35 +1,14 @@
1
- *.gem
2
- *.rbc
3
- /.config
1
+ /.bundle/
2
+ /Gemfile.lock
4
3
  /coverage/
5
- /InstalledFiles
4
+ /doc/
6
5
  /pkg/
7
6
  /spec/reports/
8
- /test/tmp/
9
- /test/version_tmp/
10
7
  /tmp/
8
+ *.bundle
9
+ *.so
10
+ *.o
11
+ *.a
12
+ *.rbc
13
+ mkmf.log
11
14
  .DS_Store
12
-
13
- ## Specific to RubyMotion:
14
- .dat*
15
- .repl_history
16
- build/
17
-
18
- ## Documentation cache and generated files:
19
- /.yardoc/
20
- /_yardoc/
21
- /doc/
22
- /rdoc/
23
-
24
- ## Environment normalisation:
25
- /.bundle/
26
- /lib/bundler/man/
27
-
28
- # for a library or gem, you might want to ignore these files since the code is
29
- # intended to run in multiple environments; otherwise, check them in:
30
- # Gemfile.lock
31
- # .ruby-version
32
- # .ruby-gemset
33
-
34
- # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
- .rvmrc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format d
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.1
5
+ - 2.2.2
6
+ - ruby-head
7
+ script: bundle exec rspec
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in perceptual.gemspec
3
+ # Specify your gem's dependencies in percy-client.gemspec
4
4
  gemspec
5
+
6
+ gem 'guard-rspec', require: false
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+ end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ Copyright (c) 2015 Perceptual Inc.
2
2
 
3
- Copyright (c) 2015 Mike Fotinakis
3
+ The MIT License (MIT)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,2 +1,34 @@
1
- # perceptual-ruby
2
- Ruby client library for Perceptual CI
1
+ # Percy::Client
2
+
3
+ [![Build Status](https://travis-ci.org/percy/percy-client.svg?branch=master)](https://travis-ci.org/percy/percy-client)
4
+ [![Gem Version](https://badge.fury.io/rb/percy-client.svg)](http://badge.fury.io/rb/percy-client)
5
+
6
+ (in development, coming soon)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'percy-client'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install percy-client
23
+
24
+ ## Usage
25
+
26
+ ...
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it ( https://github.com/percy/percy-client/fork )
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create a new Pull Request
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
 
data/lib/percy.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'percy/client'
2
+
3
+ module Percy
4
+ class << self
5
+ attr_accessor :access_token
6
+ end
7
+
8
+ def self.options
9
+ {
10
+ access_token: access_token,
11
+ }
12
+ end
13
+
14
+ # API client based on configured options.
15
+ #
16
+ # @return [Percy::Client] API client.
17
+ def self.client
18
+ @client = Percy::Client.new(options) unless defined?(@client)
19
+ @client
20
+ end
21
+
22
+ # @private
23
+ def self.respond_to_missing?(method_name, include_private = false)
24
+ client.respond_to?(method_name, include_private)
25
+ end if RUBY_VERSION >= '1.9'
26
+
27
+ # @private
28
+ def self.respond_to?(method_name, include_private = false)
29
+ client.respond_to?(method_name, include_private) || super
30
+ end if RUBY_VERSION < '1.9'
31
+
32
+ def self.method_missing(method_name, *args, &block)
33
+ return super if !client.respond_to?(method_name)
34
+ client.send(method_name, *args, &block)
35
+ end
36
+ private :method_missing
37
+ end
@@ -0,0 +1,47 @@
1
+ require 'json'
2
+ require 'percy/client/connection'
3
+ require 'percy/client/local_git'
4
+ require 'percy/client/version'
5
+ require 'percy/client/builds'
6
+ require 'percy/client/snapshots'
7
+ require 'percy/client/resources'
8
+
9
+ module Percy
10
+ class Client
11
+ include Percy::Client::Connection
12
+ include Percy::Client::LocalGit
13
+ include Percy::Client::Builds
14
+ include Percy::Client::Snapshots
15
+ include Percy::Client::Resources
16
+
17
+ class Error < Exception; end
18
+ class ClientError < Error
19
+ attr_accessor :env
20
+ def initialize(env, *args)
21
+ @env = env
22
+ super(*args)
23
+ end
24
+ end
25
+
26
+ API_BASE_URL = ENV['PERCY_API'] || 'https://percy.io'
27
+ API_VERSION = ENV['PERCY_API_VERSION'] || 'v1'
28
+
29
+ attr_accessor :access_token
30
+
31
+ def initialize(options = {})
32
+ @access_token = options[:access_token] || ENV['PERCY_TOKEN']
33
+ end
34
+
35
+ def base_url
36
+ API_BASE_URL
37
+ end
38
+
39
+ def base_path
40
+ "/api/#{API_VERSION}"
41
+ end
42
+
43
+ def full_base
44
+ "#{base_url}#{base_path}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module Percy
2
+ class Client
3
+ module Builds
4
+ def create_build(repo_slug)
5
+ commit = Percy.current_local_commit
6
+ data = {
7
+ 'data' => {
8
+ 'type' => 'builds',
9
+ 'attributes' => {
10
+ 'commit-sha' => commit[:sha],
11
+ 'commit-branch' => commit[:branch],
12
+ 'commit-committed-at' => commit[:committed_at],
13
+ 'commit-author-name' => commit[:author_name],
14
+ 'commit-author-email' => commit[:author_email],
15
+ 'commit-committer-name' => commit[:committer_name],
16
+ 'commit-committer-email' => commit[:committer_email],
17
+ 'commit-message' => commit[:message],
18
+ 'pull-request-number' => nil,
19
+ },
20
+ }
21
+ }
22
+ post("#{full_base}/repos/#{repo_slug}/builds/", data)
23
+ end
24
+
25
+ def finalize_build(build_id)
26
+ post("#{full_base}/builds/#{build_id}/finalize", {})
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
@@ -0,0 +1,50 @@
1
+ require 'faraday'
2
+
3
+ module Percy
4
+ class Client
5
+ module Connection
6
+ class FaradayNiceErrorMiddleware < Faraday::Response::Middleware
7
+ CLIENT_ERROR_STATUS_RANGE = 400...600
8
+
9
+ def on_complete(env)
10
+ case env[:status]
11
+ when 407
12
+ # Mimic the behavior that we get with proxy requests with HTTPS.
13
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
14
+ when CLIENT_ERROR_STATUS_RANGE
15
+ raise Percy::Client::ClientError.new(
16
+ env, "Got #{env.status} (#{env.method.upcase} #{env.url}):\n#{env.body}")
17
+ end
18
+ end
19
+ end
20
+
21
+ def connection
22
+ return @connection if defined?(@connection)
23
+ @connection = Faraday.new(url: base_url) do |faraday|
24
+ faraday.request :token_auth, @access_token if @access_token
25
+
26
+ faraday.use Faraday::Adapter::HTTPClient
27
+ faraday.use Percy::Client::Connection::FaradayNiceErrorMiddleware
28
+ end
29
+ @connection
30
+ end
31
+
32
+ def get(path)
33
+ response = connection.get do |request|
34
+ request.url(path)
35
+ request.headers['Content-Type'] = 'application/vnd.api+json'
36
+ end
37
+ JSON.parse(response.body)
38
+ end
39
+
40
+ def post(path, data)
41
+ response = connection.post do |request|
42
+ request.url(path)
43
+ request.headers['Content-Type'] = 'application/vnd.api+json'
44
+ request.body = data.to_json
45
+ end
46
+ JSON.parse(response.body)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ module Percy
2
+ class Client
3
+ module LocalGit
4
+ GIT_FORMAT_LINES = [
5
+ 'COMMIT_SHA:%H',
6
+ 'AUTHOR_DATE:%ai',
7
+ 'AUTHOR_NAME:%an',
8
+ 'AUTHOR_EMAIL:%ae',
9
+ 'COMMITTER_NAME:%an',
10
+ 'COMMITTER_EMAIL:%ae',
11
+ 'COMMITTER_DATE:%ai',
12
+ # Note: order is important, this must come last because the regex is a multiline match.
13
+ 'COMMIT_MESSAGE:%B'
14
+ ].freeze
15
+
16
+ class Error < Exception; end
17
+ class NoLocalRepo < Exception; end
18
+
19
+ def current_local_commit
20
+ commit = ENV['PERCY_COMMIT'] || 'HEAD'
21
+ branch = ENV['PERCY_BRANCH'] || `git rev-parse --abbrev-ref HEAD`.strip
22
+ if branch == ''
23
+ raise Percy::Client::LocalGit::NoLocalRepo.new('No local git repository found.')
24
+ end
25
+
26
+ format = GIT_FORMAT_LINES.join('%n') # "git show" format uses %n for newlines.
27
+ output = `git show --quiet #{commit} --format="#{format}"`.strip
28
+ data = {
29
+ sha: output.match(/COMMIT_SHA:(.*)/)[1],
30
+ branch: branch,
31
+ committed_at: output.match(/AUTHOR_DATE:(.*)/)[1],
32
+ author_name: output.match(/AUTHOR_NAME:(.*)/)[1],
33
+ author_email: output.match(/AUTHOR_EMAIL:(.*)/)[1],
34
+ committer_name: output.match(/COMMITTER_NAME:(.*)/)[1],
35
+ committer_email: output.match(/COMMITTER_EMAIL:(.*)/)[1],
36
+ message: output.match(/COMMIT_MESSAGE:(.*)/m)[1],
37
+ }
38
+ end
39
+
40
+ def current_local_repo
41
+ origin_url = `git config --get remote.origin.url`
42
+ if origin_url == ''
43
+ raise Percy::Client::LocalGit::NoLocalRepo.new('No local git repository found.')
44
+ end
45
+ match = origin_url.match(Regexp.new('[:/]([^/]+\/[^/]+)\.git'))
46
+ match[1]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,54 @@
1
+ require 'base64'
2
+ require 'digest'
3
+
4
+ module Percy
5
+ class Client
6
+
7
+ # A simple data container object used to pass data to create_snapshot.
8
+ class Resource
9
+ attr_accessor :sha
10
+ attr_accessor :resource_url
11
+ attr_accessor :is_root
12
+ attr_accessor :mimetype
13
+
14
+ def initialize(sha, resource_url, options = {})
15
+ @sha = sha
16
+ @resource_url = resource_url
17
+ @is_root = options[:is_root]
18
+ @mimetype = options[:mimetype]
19
+ end
20
+
21
+ def serialize
22
+ {
23
+ 'type' => 'resources',
24
+ 'id' => sha,
25
+ 'resource-url' => resource_url,
26
+ 'mimetype' => mimetype,
27
+ 'is-root' => is_root,
28
+ }
29
+ end
30
+ end
31
+
32
+ module Resources
33
+ def upload_resource(build_id, content)
34
+ sha = Digest::SHA256.hexdigest(content)
35
+ data = {
36
+ 'data' => {
37
+ 'type' => 'resources',
38
+ 'attributes' => {
39
+ 'id' => sha,
40
+ 'base64-content' => Base64.strict_encode64(content),
41
+ },
42
+ },
43
+ }
44
+ begin
45
+ post("#{full_base}/builds/#{build_id}/resources/", data)
46
+ rescue Percy::Client::ClientError => e
47
+ raise e if e.env.status != 409
48
+ STDERR.puts "[percy] Warning: unnecessary resource reuploaded with SHA-256: #{sha}"
49
+ end
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ module Percy
2
+ class Client
3
+ module Snapshots
4
+ def create_snapshot(build_id, resources, options = {})
5
+ raise ArgumentError.new('resources must be an iterable') if !resources.respond_to?(:each)
6
+ name = options[:name]
7
+ data = {
8
+ 'data' => {
9
+ 'type' => 'snapshots',
10
+ 'attributes' => {
11
+ 'name' => name,
12
+ },
13
+ 'links' => {
14
+ 'resources' => resources.map { |r| r.serialize },
15
+ },
16
+ },
17
+ }
18
+ post("#{full_base}/builds/#{build_id}/snapshots/", data)
19
+ end
20
+ end
21
+ end
22
+ end