percy-client 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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