freckle-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 62d0200c8b4beb472d2099eb324cae42d8f4396f
4
+ data.tar.gz: be9601b09316bc7c84605b974ad1edcffd2493fa
5
+ SHA512:
6
+ metadata.gz: 41425bebaca7754292b57479ca4a99799f7f4f2ffb5742fccad9acae04a92ce5bc2f11dd9e9a550b22b983859c92a9d14515ffd88b39a73d7f51d658d31dbf2f
7
+ data.tar.gz: 9c330b1c4bca4160723ff36bf28b9f5200417062e9f334cb5de485e6ab330dda3717a2ceb83cd74a519a7b2a76d738053796f067fc5dc97d14e0dc9c243523a2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jamie Schembri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # freckle-api
2
+ Freckle API client for v2.
3
+
4
+ This is still in development and not ready to use yet.
5
+
6
+ I intend to implement timing functonality only to begin with,
7
+ adding more management features later on. In order to get it to a
8
+ usable/useful state, the following needs to be done:
9
+
10
+ ## TODO
11
+
12
+ There's a lot to do right now, but to get this API into a useful state
13
+ for users simply logging their time, the following is mandatory:
14
+
15
+ - Implement timer:
16
+ - start
17
+ - pause
18
+ - log
19
+ - discard
20
+
21
+ Longer-term TODO items are, in general order of importance:
22
+
23
+ - Implement entries
24
+ - Error handling, retry on fail.
25
+ - Rate limiting -- Freckle only allows two requests per second per IP, so consider
26
+ other users on the network as well. Goes with error handling.
27
+ - The entire v2 API that is currently available
28
+ - Better testing of requests
29
+ - DRYer tests
30
+ - Whatever other TODOs or FIXMEs in the codebase.
@@ -0,0 +1,61 @@
1
+ require 'net/http'
2
+
3
+ require 'freckle_api/coercions.rb'
4
+ require 'freckle_api/model.rb'
5
+ require 'freckle_api/import.rb'
6
+ require 'freckle_api/group.rb'
7
+ require 'freckle_api/invoice.rb'
8
+ require 'freckle_api/user.rb'
9
+ require 'freckle_api/project.rb'
10
+ require 'freckle_api/timer.rb'
11
+
12
+ class FreckleApi
13
+ BASE_URI = URI('https://api.letsfreckle.com/v2').freeze
14
+ USER_AGENT = 'freckle-api' # TODO: make this flexible?
15
+
16
+ def initialize(api_key)
17
+ @api_key = api_key
18
+ end
19
+
20
+ def project(id)
21
+ Project.new(request :get, uri('projects', id))
22
+ end
23
+
24
+ def projects
25
+ request(:get, uri('projects')).map do |project|
26
+ Project.new(project)
27
+ end
28
+ end
29
+
30
+ def timer(project)
31
+ project_id = project.respond_to?(:id) ? project.id : project
32
+
33
+ Timer.new(request :get, uri('projects', project_id, 'timer'))
34
+ end
35
+
36
+ private
37
+
38
+ def request(method, uri)
39
+ https = Net::HTTP.new(uri.host, uri.port).tap { |h| h.use_ssl = true }
40
+
41
+ response = https.request(http_class(method).new(uri.path, headers))
42
+
43
+ JSON.parse(response.body)
44
+ end
45
+
46
+ def http_class(method)
47
+ "Net::HTTP::#{method.to_s.capitalize}".constantize
48
+ end
49
+
50
+ def uri(*path)
51
+ URI.parse [BASE_URI, *path].join('/')
52
+ end
53
+
54
+ def headers
55
+ {
56
+ 'Content-Type' => 'application/json',
57
+ 'User-Agent' => USER_AGENT,
58
+ 'X-FreckleToken' => @api_key
59
+ }
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ # Coercions should retain a proc, so that we can easily
2
+ # use them with Hashie's coercion methods.
3
+ #
4
+ # E.g.:
5
+ # coerce_key, :the_key, name_of_method_that_returns_proc
6
+ #
7
+ module FreckleApi::Coercions
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ # TODO: timezone based on local machine
14
+ # TODO: beware nil values
15
+ def coerce_to_datetime
16
+ DateTime.method(:parse).to_proc
17
+ end
18
+
19
+ def coerce_to_date
20
+ Date.method(:parse).to_proc
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,2 @@
1
+ class FreckleApi::Group < FreckleApi::Model
2
+ end
@@ -0,0 +1,2 @@
1
+ class FreckleApi::Import < FreckleApi::Model
2
+ end
@@ -0,0 +1,3 @@
1
+ class FreckleApi::Invoice < FreckleApi::Model
2
+ coerce_key :invoice_date, coerce_to_date
3
+ end
@@ -0,0 +1,10 @@
1
+ require 'hashie'
2
+
3
+ class FreckleApi::Model < Hash
4
+ include Hashie::Extensions::Coercion
5
+ include Hashie::Extensions::MergeInitializer
6
+ include Hashie::Extensions::MethodAccess
7
+ include Hashie::Extensions::IndifferentAccess
8
+
9
+ include FreckleApi::Coercions
10
+ end
@@ -0,0 +1,19 @@
1
+ class FreckleApi::Project < FreckleApi::Model
2
+ coerce_key :group, FreckleApi::Group
3
+ coerce_key :import, FreckleApi::Import
4
+ coerce_key :invoices, Array[FreckleApi::Invoice]
5
+ coerce_key :participants, Array[FreckleApi::User]
6
+
7
+ coerce_key :created_at, coerce_to_datetime
8
+ coerce_key :updated_at, coerce_to_datetime
9
+
10
+ # entries is a method of hash, so we're aliasing
11
+ # it for now. Hashie's MethodWithIndirectAccess
12
+ # should take care of this, but it doesn't
13
+ # seem to work in this case, so we do it manually.
14
+ alias_method :__entries, :entries
15
+
16
+ def entries
17
+ self[:entries]
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ class FreckleApi::Timer < FreckleApi::Model
2
+ coerce_key :state, Symbol
3
+ coerce_key :date, coerce_to_date
4
+ coerce_key :user, FreckleApi::User
5
+ coerce_key :project, FreckleApi::Project
6
+ end
@@ -0,0 +1,2 @@
1
+ class FreckleApi::User < FreckleApi::Model
2
+ end
@@ -0,0 +1,3 @@
1
+ class FreckleApi
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'freckle_api'
3
+
4
+ RSpec.describe FreckleApi do
5
+ let(:base_uri) { 'https://api.letsfreckle.com/v2' }
6
+ let(:api) { FreckleApi.new('valid_api_key') }
7
+
8
+ describe '#project' do
9
+ context 'given a valid project id' do
10
+ let(:project) { api.project(37_396) }
11
+
12
+ it 'returns the project' do
13
+ expect(project).to be_a FreckleApi::Project
14
+ end
15
+
16
+ it 'contains the expected simple values' do
17
+ expect(project.id).to eq 37_396
18
+ expect(project.name).to eq 'Gear GmbH'
19
+ expect(project.billing_increment).to eq 10
20
+ expect(project.enabled).to eq true
21
+ expect(project.billable).to eq true
22
+ expect(project.color).to eq '#ff9898'
23
+ expect(project.url).to eq "#{base_uri}/projects/37396"
24
+ expect(project.minutes).to eq 180
25
+ expect(project.billable_minutes).to eq 120
26
+ expect(project.unbillable_minutes).to eq 60
27
+ expect(project.invoiced_minutes).to eq 120
28
+ expect(project.remaining_minutes).to eq 630
29
+ expect(project.budget_minutes).to eq 750
30
+ expect(project.entries).to eq 0
31
+ expect(project.entries_url).to eq "#{base_uri}/projects/37396/entries"
32
+ expect(project.expenses).to eq 0
33
+ expect(project.expenses_url).to eq "#{base_uri}/projects/37396/expenses"
34
+ end
35
+
36
+ it 'contains the correct coerced timestamps' do
37
+ expected_timestamp = DateTime.new(2012, 1, 9, 8, 33, 29)
38
+
39
+ expect(project.created_at).to eq expected_timestamp
40
+ expect(project.updated_at).to eq expected_timestamp
41
+ end
42
+
43
+ it 'contains the expected group' do
44
+ group = project.group
45
+
46
+ expect(group).to be_a FreckleApi::Group
47
+ expect(group.id).to eq 3768
48
+ expect(group.name).to eq 'Sprockets, Inc.'
49
+ expect(group.url).to eq "#{base_uri}/project_groups/3768"
50
+ end
51
+
52
+ it 'contains the expected import' do
53
+ import = project.import
54
+
55
+ expect(import).to be_a FreckleApi::Import
56
+ expect(import.id).to eq 8910
57
+ expect(import.url).to eq "#{base_uri}/imports/8910"
58
+ end
59
+
60
+ it 'contains the expected invoices' do
61
+ expect(project.invoices).to respond_to(:count)
62
+ expect(project.invoices.count).to eq 1
63
+
64
+ invoice = project.invoices.last
65
+
66
+ expect(invoice).to be_a FreckleApi::Invoice
67
+
68
+ expect(invoice.id).to eq 12_345_678
69
+ expect(invoice.reference).to eq 'AA001'
70
+ expect(invoice.invoice_date).to eq Date.new(2013, 7, 9)
71
+ expect(invoice.state).to eq 'unpaid'
72
+ expect(invoice.total_amount).to eq 189.33
73
+ expect(invoice.url).to eq "#{base_uri}/invoices/12345678"
74
+ end
75
+
76
+ it 'contains the expected participants' do
77
+ expect(project.participants).to respond_to(:count)
78
+ expect(project.participants.count).to eq 1
79
+
80
+ participant = project.participants.last
81
+
82
+ expect(participant).to be_a FreckleApi::User
83
+ expect(participant.id).to eq 5538
84
+ expect(participant.email).to eq 'john.test@test.com'
85
+ expect(participant.first_name).to eq 'John'
86
+ expect(participant.last_name).to eq 'Test'
87
+ expect(participant.profile_image_url).to eq(
88
+ 'https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg')
89
+ expect(participant.url).to eq "#{base_uri}/users/5538"
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#projects' do
95
+ # Just smoke test for now. We've tested enough
96
+ # of the individual project above.
97
+ let(:projects) { api.projects }
98
+
99
+ it 'returns a collection of projects' do
100
+ expect(projects).to respond_to(:count)
101
+ expect(projects.count).to eq 1
102
+ expect(projects.last).to be_a FreckleApi::Project
103
+ end
104
+ end
105
+
106
+ describe '#timer' do
107
+ let(:timer) { api.timer(37_396) }
108
+
109
+ it 'returns the timer' do
110
+ expect(timer).to be_a FreckleApi::Timer
111
+ end
112
+
113
+ it 'contains the expected simple values' do
114
+ expect(timer.id).to eq 123_456
115
+ expect(timer.state).to eq :running
116
+ expect(timer.seconds).to eq 180
117
+ expect(timer.description).to eq 'freckle work'
118
+ expect(timer.url).to eq "#{base_uri}/projects/37396/timer"
119
+ expect(timer.start_url).to eq "#{base_uri}/projects/37396/timer/start"
120
+ expect(timer.pause_url).to eq "#{base_uri}/projects/37396/timer/pause"
121
+ expect(timer.log_url).to eq "#{base_uri}/projects/37396/timer/log"
122
+ end
123
+
124
+ it 'contains the coerced date' do
125
+ expect(timer.date).to eq Date.new(2013, 7, 9)
126
+ end
127
+
128
+ it 'contains the expected user' do
129
+ user = timer.user
130
+
131
+ expect(user).to be_a FreckleApi::User
132
+ expect(user.id).to eq 5_538
133
+ expect(user.email).to eq 'john.test@test.com'
134
+ expect(user.first_name).to eq 'John'
135
+ expect(user.last_name).to eq 'Test'
136
+ expect(user.profile_image_url).to eq(
137
+ 'https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg')
138
+ expect(user.url).to eq "#{base_uri}/users/5538"
139
+ end
140
+
141
+ it 'contains the expected project' do
142
+ project = timer.project
143
+
144
+ expect(project).to be_a FreckleApi::Project
145
+ expect(project.id).to eq 37_396
146
+ expect(project.name).to eq 'Gear GmbH'
147
+ expect(project.billing_increment).to eq 10
148
+ expect(project.enabled).to eq true
149
+ expect(project.billable).to eq true
150
+ expect(project.color).to eq '#ff9898'
151
+ expect(project.url).to eq "#{base_uri}/projects/37396"
152
+ end
153
+
154
+ context 'when passing an actual project' do
155
+ let(:timer) { api.timer(api.project(37_396)) }
156
+
157
+ it 'returns the timer' do
158
+ expect(timer).to be_a FreckleApi::Timer
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,80 @@
1
+ require 'rspec'
2
+ require 'webmock/rspec'
3
+ require 'pry'
4
+ require 'pry-byebug'
5
+ require 'support/fake_freckle.rb'
6
+
7
+ RSpec.configure do |config|
8
+ config.expect_with :rspec do |expectations|
9
+ # This option will default to `true` in RSpec 4. It makes the `description`
10
+ # and `failure_message` of custom matchers include text for helper methods
11
+ # defined using `chain`, e.g.:
12
+ # be_bigger_than(2).and_smaller_than(4).description
13
+ # # => "be bigger than 2 and smaller than 4"
14
+ # ...rather than:
15
+ # # => "be bigger than 2"
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ end
18
+
19
+ config.mock_with :rspec do |mocks|
20
+ # Prevents you from mocking or stubbing a method that does not exist on
21
+ # a real object. This is generally recommended, and will default to
22
+ # `true` in RSpec 4.
23
+ mocks.verify_partial_doubles = true
24
+ end
25
+
26
+ # These two settings work together to allow you to limit a spec run
27
+ # to individual examples or groups you care about by tagging them with
28
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
29
+ # get run.
30
+ config.filter_run :focus
31
+ config.run_all_when_everything_filtered = true
32
+
33
+ # Allows RSpec to persist some state between runs in order to support
34
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
35
+ # you configure your source control system to ignore this file.
36
+ config.example_status_persistence_file_path = 'spec/examples.txt'
37
+
38
+ # Limits the available syntax to the non-monkey patched syntax that is
39
+ # recommended. For more details, see:
40
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
41
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
42
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
43
+ config.disable_monkey_patching!
44
+
45
+ # This setting enables warnings. It's recommended, but in some cases may
46
+ # be too noisy due to issues in dependencies.
47
+ config.warnings = false
48
+
49
+ # Many RSpec users commonly either run the entire suite or an individual
50
+ # file, and it's useful to allow more verbose output when running an
51
+ # individual spec file.
52
+ if config.files_to_run.one?
53
+ # Use the documentation formatter for detailed output,
54
+ # unless a formatter has already been configured
55
+ # (e.g. via a command-line flag).
56
+ config.default_formatter = 'doc'
57
+ end
58
+
59
+ # Print the 10 slowest examples and example groups at the
60
+ # end of the spec run, to help surface which specs are running
61
+ # particularly slow.
62
+ config.profile_examples = 10
63
+
64
+ # Run specs in random order to surface order dependencies. If you find an
65
+ # order dependency and want to debug it, you can fix the order by providing
66
+ # the seed, which is printed after each run.
67
+ # --seed 1234
68
+ config.order = :random
69
+
70
+ # Seed global randomization in this process using the `--seed` CLI option.
71
+ # Setting this allows you to use `--seed` to deterministically reproduce
72
+ # test failures related to randomization by passing the same `--seed` value
73
+ # as the one that triggered the failure.
74
+ Kernel.srand config.seed
75
+
76
+ config.before(:each) do
77
+ # Thanks https://robots.thoughtbot.com/how-to-stub-external-services-in-tests
78
+ stub_request(:any, %r{api.letsfreckle.com:443/v2}).to_rack(FakeFreckle)
79
+ end
80
+ end
@@ -0,0 +1,35 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/namespace'
3
+
4
+ class FakeFreckle < Sinatra::Base
5
+ register Sinatra::Namespace
6
+
7
+ set :port, 443
8
+
9
+ namespace '/v2' do
10
+ namespace '/projects' do
11
+ get do
12
+ json_response 200, 'projects'
13
+ end
14
+
15
+ namespace '/:id' do
16
+ get do
17
+ json_response 200, 'projects/37396'
18
+ end
19
+
20
+ get '/timer' do
21
+ json_response 200, 'projects/37396/timer'
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def json_response(response_code, file_name)
30
+ content_type :json
31
+ status response_code
32
+
33
+ File.open("#{File.dirname(__FILE__)}/fixtures/#{file_name}.json", 'rb').read
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: freckle-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamie Schembri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.22'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.22'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sinatra-contrib
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashie
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.4'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.4'
111
+ description:
112
+ email: jamie@schembri.me
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - LICENSE
118
+ - README.md
119
+ - lib/freckle_api.rb
120
+ - lib/freckle_api/coercions.rb
121
+ - lib/freckle_api/group.rb
122
+ - lib/freckle_api/import.rb
123
+ - lib/freckle_api/invoice.rb
124
+ - lib/freckle_api/model.rb
125
+ - lib/freckle_api/project.rb
126
+ - lib/freckle_api/timer.rb
127
+ - lib/freckle_api/user.rb
128
+ - lib/freckle_api/version.rb
129
+ - spec/freckle_api_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/support/fake_freckle.rb
132
+ homepage: http://github.com/shkm/freckle-api
133
+ licenses:
134
+ - MIT
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.4.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Client for Freckle's API V2.
156
+ test_files:
157
+ - spec/freckle_api_spec.rb
158
+ - spec/spec_helper.rb
159
+ - spec/support/fake_freckle.rb