wayback 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.yardopts +10 -0
  2. data/CHANGELOG.md +3 -0
  3. data/CONTRIBUTING.md +52 -0
  4. data/LICENSE.md +20 -0
  5. data/README.md +81 -0
  6. data/Rakefile +11 -0
  7. data/lib/wayback.rb +32 -0
  8. data/lib/wayback/api/archive.rb +42 -0
  9. data/lib/wayback/api/utils.rb +27 -0
  10. data/lib/wayback/archive.rb +15 -0
  11. data/lib/wayback/base.rb +127 -0
  12. data/lib/wayback/client.rb +62 -0
  13. data/lib/wayback/configurable.rb +48 -0
  14. data/lib/wayback/default.rb +68 -0
  15. data/lib/wayback/error.rb +31 -0
  16. data/lib/wayback/error/already_favorited.rb +10 -0
  17. data/lib/wayback/error/already_retweeted.rb +10 -0
  18. data/lib/wayback/error/bad_gateway.rb +11 -0
  19. data/lib/wayback/error/bad_request.rb +10 -0
  20. data/lib/wayback/error/client_error.rb +28 -0
  21. data/lib/wayback/error/configuration_error.rb +8 -0
  22. data/lib/wayback/error/decode_error.rb +9 -0
  23. data/lib/wayback/error/forbidden.rb +10 -0
  24. data/lib/wayback/error/gateway_timeout.rb +11 -0
  25. data/lib/wayback/error/identity_map_key_error.rb +9 -0
  26. data/lib/wayback/error/internal_server_error.rb +11 -0
  27. data/lib/wayback/error/not_acceptable.rb +10 -0
  28. data/lib/wayback/error/not_found.rb +10 -0
  29. data/lib/wayback/error/server_error.rb +28 -0
  30. data/lib/wayback/error/service_unavailable.rb +11 -0
  31. data/lib/wayback/error/too_many_requests.rb +12 -0
  32. data/lib/wayback/error/unauthorized.rb +10 -0
  33. data/lib/wayback/error/unprocessable_entity.rb +10 -0
  34. data/lib/wayback/factory.rb +21 -0
  35. data/lib/wayback/identity.rb +50 -0
  36. data/lib/wayback/identity_map.rb +22 -0
  37. data/lib/wayback/page.rb +18 -0
  38. data/lib/wayback/response/parse_memento.rb +61 -0
  39. data/lib/wayback/response/parse_memento_page.rb +23 -0
  40. data/lib/wayback/response/raise_error.rb +31 -0
  41. data/lib/wayback/version.rb +18 -0
  42. data/spec/fixtures/list.timemap +9 -0
  43. data/spec/fixtures/page.html +225 -0
  44. data/spec/helper.rb +65 -0
  45. data/spec/wayback/api/archive_spec.rb +73 -0
  46. data/spec/wayback/archive_spec.rb +23 -0
  47. data/spec/wayback/base_spec.rb +117 -0
  48. data/spec/wayback/client_spec.rb +114 -0
  49. data/spec/wayback/error/client_error_spec.rb +23 -0
  50. data/spec/wayback/error/server_error_spec.rb +20 -0
  51. data/spec/wayback/error_spec.rb +20 -0
  52. data/spec/wayback/identifiable_spec.rb +50 -0
  53. data/spec/wayback/page_spec.rb +36 -0
  54. data/spec/wayback_spec.rb +47 -0
  55. data/wayback.gemspec +26 -0
  56. metadata +175 -0
@@ -0,0 +1,48 @@
1
+ require 'forwardable'
2
+ require 'wayback/error/configuration_error'
3
+
4
+ module Wayback
5
+ module Configurable
6
+ extend Forwardable
7
+ attr_accessor :endpoint, :connection_options, :identity_map, :middleware
8
+ def_delegator :options, :hash
9
+
10
+ class << self
11
+
12
+ def keys
13
+ @keys ||= [
14
+ :endpoint,
15
+ :connection_options,
16
+ :identity_map,
17
+ :middleware
18
+ ]
19
+ end
20
+
21
+ end
22
+
23
+ # Convenience method to allow configuration options to be set in a block
24
+ #
25
+ # @raise [Wayback::Error::ConfigurationError] Error is raised when supplied
26
+ # wayback credentials are not a String or Symbol.
27
+ def configure
28
+ yield self
29
+ self
30
+ end
31
+
32
+ def reset!
33
+ Wayback::Configurable.keys.each do |key|
34
+ instance_variable_set(:"@#{key}", Wayback::Default.options[key])
35
+ end
36
+ self
37
+ end
38
+ alias setup reset!
39
+
40
+ private
41
+
42
+ # @return [Hash]
43
+ def options
44
+ Hash[Wayback::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,68 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'wayback/configurable'
4
+ require 'wayback/error/client_error'
5
+ require 'wayback/error/server_error'
6
+ require 'wayback/response/raise_error'
7
+ require 'wayback/response/parse_memento'
8
+ require 'wayback/response/parse_memento_page'
9
+ require 'wayback/version'
10
+
11
+ module Wayback
12
+ module Default
13
+ ENDPOINT = 'http://api.wayback.archive.org' unless defined? Wayback::Default::ENDPOINT
14
+ CONNECTION_OPTIONS = {
15
+ :headers => {:user_agent => "Wayback Ruby Gem #{Wayback::Version}"},
16
+ :request => {:open_timeout => 5, :timeout => 10},
17
+ :ssl => {:verify => false},
18
+ } unless defined? Wayback::Default::CONNECTION_OPTIONS
19
+ IDENTITY_MAP = false unless defined? Wayback::Default::IDENTITY_MAP
20
+ MIDDLEWARE = Faraday::Builder.new do |builder|
21
+ # Convert request params to "www-form-urlencoded"
22
+ builder.use Faraday::Request::UrlEncoded
23
+ # Follow redirects
24
+ builder.use FaradayMiddleware::FollowRedirects
25
+ # Handle 4xx server responses
26
+ builder.use Wayback::Response::RaiseError, Wayback::Error::ClientError
27
+ # Handle 5xx server responses
28
+ builder.use Wayback::Response::RaiseError, Wayback::Error::ServerError
29
+ # Parse memento page
30
+ builder.use Wayback::Response::ParseMementoPage
31
+ # Parse link-format with custom memento parser
32
+ builder.use Wayback::Response::ParseMemento
33
+ # Set Faraday's HTTP adapter
34
+ builder.adapter Faraday.default_adapter
35
+ end unless defined? Wayback::Default::MIDDLEWARE
36
+
37
+ class << self
38
+
39
+ # @return [Hash]
40
+ def options
41
+ Hash[Wayback::Configurable.keys.map{|key| [key, send(key)]}]
42
+ end
43
+
44
+ # @note This is configurable in case you want to use a Wayback Machine-compatible endpoint.
45
+ # @return [String]
46
+ def endpoint
47
+ ENDPOINT
48
+ end
49
+
50
+ def connection_options
51
+ CONNECTION_OPTIONS
52
+ end
53
+
54
+ def identity_map
55
+ IDENTITY_MAP
56
+ end
57
+
58
+ # @note Faraday's middleware stack implementation is comparable to that of Rack middleware. The order of middleware is important: the first middleware on the list wraps all others, while the last middleware is the innermost one.
59
+ # @see https://github.com/technoweenie/faraday#advanced-middleware-usage
60
+ # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
61
+ # @return [Faraday::Builder]
62
+ def middleware
63
+ MIDDLEWARE
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,31 @@
1
+ module Wayback
2
+ # Custom error class for rescuing from all Wayback errors
3
+ class Error < StandardError
4
+ attr_reader :wrapped_exception
5
+
6
+ # @return [Hash]
7
+ def self.errors
8
+ @errors ||= Hash[descendants.map{|klass| [klass.const_get(:HTTP_STATUS_CODE), klass]}]
9
+ end
10
+
11
+ # @return [Array]
12
+ def self.descendants
13
+ ObjectSpace.each_object(::Class).select{|klass| klass < self}
14
+ end
15
+
16
+ # Initializes a new Error object
17
+ #
18
+ # @param exception [Exception, String]
19
+ # @param response_headers [Hash]
20
+ # @return [Wayback::Error]
21
+ def initialize(exception=$!, response_headers={})
22
+ @wrapped_exception = exception
23
+ exception.respond_to?(:backtrace) ? super(exception.message) : super(exception.to_s)
24
+ end
25
+
26
+ def backtrace
27
+ @wrapped_exception.respond_to?(:backtrace) ? @wrapped_exception.backtrace : super
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/forbidden'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when a Tweet has already been favorited
6
+ class AlreadyFavorited < Wayback::Error
7
+ MESSAGE = "You have already favorited this status"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/forbidden'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when a Tweet has already been retweeted
6
+ class AlreadyRetweeted < Wayback::Error
7
+ MESSAGE = "sharing is not permissible for this status (Share validations failed)"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'wayback/error/server_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 502
6
+ class BadGateway < Wayback::Error::ServerError
7
+ HTTP_STATUS_CODE = 502
8
+ MESSAGE = "Wayback is down or being upgraded."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 400
6
+ class BadRequest < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 400
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ require 'wayback/error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns a 4xx HTTP status code or there's an error in Faraday
6
+ class ClientError < Wayback::Error
7
+
8
+ # Create a new error from an HTTP environment
9
+ #
10
+ # @param response [Hash]
11
+ # @return [Wayback::Error]
12
+ def self.from_response(response={})
13
+ new(parse_error(response[:body]), response[:response_headers])
14
+ end
15
+
16
+ private
17
+
18
+ def self.parse_error(body)
19
+ if body.nil? || body.is_a?(String)
20
+ ''
21
+ # elsif body[:error]
22
+ # body[:error]
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ require 'wayback/error'
2
+
3
+ module Wayback
4
+ class Error
5
+ class ConfigurationError < ::ArgumentError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ require 'wayback/error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when JSON parsing fails
6
+ class DecodeError < Wayback::Error
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 403
6
+ class Forbidden < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 403
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'wayback/error/server_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 504
6
+ class GatewayTimeout < Wayback::Error::ServerError
7
+ HTTP_STATUS_CODE = 504
8
+ MESSAGE = "The Wayback servers are up, but the request couldn't be serviced due to some failure within our stack. Try again later."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ require 'wayback/error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Inherit from KeyError when Ruby 1.8 compatibility is removed
6
+ class IdentityMapKeyError < ::IndexError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'wayback/error/server_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 500
6
+ class InternalServerError < Wayback::Error::ServerError
7
+ HTTP_STATUS_CODE = 500
8
+ MESSAGE = "Something is technically wrong."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 406
6
+ class NotAcceptable < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 406
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 404
6
+ class NotFound < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 404
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ require 'wayback/error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns a 5xx HTTP status code
6
+ class ServerError < Wayback::Error
7
+ MESSAGE = "Server Error"
8
+
9
+ # Create a new error from an HTTP environment
10
+ #
11
+ # @param response [Hash]
12
+ # @return [Wayback::Error]
13
+ def self.from_response(response={})
14
+ new(nil, response[:response_headers])
15
+ end
16
+
17
+ # Initializes a new ServerError object
18
+ #
19
+ # @param message [String]
20
+ # @param response_headers [Hash]
21
+ # @return [Wayback::Error::ServerError]
22
+ def initialize(message=nil, response_headers={})
23
+ super((message || self.class.const_get(:MESSAGE)), response_headers)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ require 'wayback/error/server_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 503
6
+ class ServiceUnavailable < Wayback::Error::ServerError
7
+ HTTP_STATUS_CODE = 503
8
+ MESSAGE = "(__-){ Wayback is over capacity."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 429
6
+ class TooManyRequests < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 429
8
+ end
9
+ EnhanceYourCalm = TooManyRequests
10
+ RateLimited = TooManyRequests
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 401
6
+ class Unauthorized < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 401
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'wayback/error/client_error'
2
+
3
+ module Wayback
4
+ class Error
5
+ # Raised when Wayback returns the HTTP status code 404
6
+ class UnprocessableEntity < Wayback::Error::ClientError
7
+ HTTP_STATUS_CODE = 422
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Wayback
2
+ class Factory
3
+
4
+ # Instantiates a new action object
5
+ #
6
+ # @param attrs [Hash]
7
+ # @raise [ArgumentError] Error raised when supplied argument is missing an :action key.
8
+ # @return [Wayback::Action::Favorite, Wayback::Action::Follow, Wayback::Action::ListMemberAdded, Wayback::Action::Mention, Wayback::Action::Reply, Wayback::Action::Retweet]
9
+ def self.fetch_or_new(method, klass, attrs={})
10
+ return unless attrs
11
+ type = attrs.delete(method.to_sym)
12
+ if type
13
+ const_name = type.gsub(/\/(.?)/){"::#{$1.upcase}"}.gsub(/(?:^|_)(.)/){$1.upcase}
14
+ klass.const_get(const_name.to_sym).fetch_or_new(attrs)
15
+ else
16
+ raise ArgumentError, "argument must have :#{method} key"
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ require 'wayback/base'
2
+ require 'wayback/error/identity_map_key_error'
3
+
4
+ module Wayback
5
+ class Identity < Wayback::Base
6
+
7
+ def self.fetch(attrs)
8
+ return unless identity_map
9
+
10
+ id = attrs[:id]
11
+ if id && object = identity_map.fetch(id)
12
+ return object.update(attrs)
13
+ end
14
+
15
+ return yield if block_given?
16
+ raise Wayback::Error::IdentityMapKeyError, "key not found"
17
+ end
18
+
19
+ # Stores an object in the identity map.
20
+ #
21
+ # @param object [Object]
22
+ # @return [Wayback::Identity]
23
+ def self.store(object)
24
+ return object unless identity_map
25
+ identity_map.store(object.id, object)
26
+ end
27
+
28
+ # Initializes a new object
29
+ #
30
+ # @param attrs [Hash]
31
+ # @raise [ArgumentError] Error raised when supplied argument is missing an :id key.
32
+ # @return [Wayback::Identity]
33
+ def initialize(attrs={})
34
+ super
35
+ raise ArgumentError, "argument must have an :id key" unless id
36
+ end
37
+
38
+ # @param other [Wayback::Identity]
39
+ # @return [Boolean]
40
+ def ==(other)
41
+ super || attr_equal(:id, other) || attrs_equal(other)
42
+ end
43
+
44
+ # @return [Integer]
45
+ def id
46
+ @attrs[:id]
47
+ end
48
+
49
+ end
50
+ end