motion-tickspot 1.0.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: 7c1750234a64316c38c16789b8eb82c7eda4f1c3
4
+ data.tar.gz: c66fb44d97c5b63883542ff6de3128684c748fa4
5
+ SHA512:
6
+ metadata.gz: ff3586ed69f69201d794b7caafc66fbef0b4fa0e6a9df04f29096e05002f514cf0278d0c931e792d25edfa095dbaf6a59e2ca86e44165f8834d1a1bd74bb358e
7
+ data.tar.gz: bece59d96ac4aae2410c8a8124184422c3eecb10350597cfbbd756b383ee352d4e5fe12955b147bfa24d2cce109e81f8bb00a76ee6cd20526c9bb52d767bb54c
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # Motion-Tickspot
2
+
3
+ [![Build Status](https://travis-ci.org/81designs/motion-tickspot.png?branch=master)](https://travis-ci.org/81designs/motion-tickspot)
4
+ [![Code Climate](https://codeclimate.com/github/81designs/motion-tickspot.png)](https://codeclimate.com/github/81designs/motion-tickspot)
5
+
6
+ A [RubyMotion](http://www.rubymotion.com) wrapper for the
7
+ [Tick](http://www.tickspot.com)'s [API](http://www.tickspot.com/api)
8
+ that works on iOS and OS X.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem "motion-tickspot"
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```bash
21
+ $ bundle
22
+ ```
23
+
24
+ Or install it yourself as:
25
+
26
+ ```bash
27
+ $ gem install motion-tickspot
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Authentication
33
+
34
+ Authentication is handled by `Tick::Session` with some convenience methods.
35
+
36
+ [SSKeychain](https://github.com/soffes/sskeychain) is being used to
37
+ save the user's password to the iOS or OS X keychain.
38
+
39
+ ```ruby
40
+ Tick.logged_in? # true/false
41
+
42
+ Tick.log_in("company/subdomain", "email@example.com", "password") do |session|
43
+ if session
44
+ # Success!
45
+ else
46
+ # Failed to log in
47
+ end
48
+ end
49
+
50
+ Tick.log_out
51
+ ```
52
+
53
+ ### Tick::Client
54
+
55
+ ```ruby
56
+ Tick::Client.list do |clients|
57
+ # Returns array of Tick::Clients
58
+ # Raises Tick::AuthenticationError if not logged in
59
+ end
60
+ ```
61
+
62
+ ### Tick::Entry
63
+
64
+ ```ruby
65
+ Tick::Entry.create({
66
+ task_id: 1,
67
+ hours: 2.5,
68
+ date: "2008-03-17",
69
+ notes: "She can't take much more of this Captain"
70
+ }) do |entry|
71
+ # Returns the saved Tick::Entry
72
+ # Raises Tick::AuthenticationError if not logged in
73
+ end
74
+ ```
75
+
76
+ ```ruby
77
+ Tick::Entry.list do |entries|
78
+ # Returns array of Tick::Entries
79
+ # Raises Tick::AuthenticationError if not logged in
80
+ end
81
+ ```
82
+
83
+ ### Tick::Project
84
+
85
+ ```ruby
86
+ Tick::Project.list do |projects|
87
+ # Returns array of Tick::Projects
88
+ # Raises Tick::AuthenticationError if not logged in
89
+ end
90
+ ```
91
+
92
+ ### Tick::Session
93
+
94
+ ```ruby
95
+ Tick::Session.current
96
+ # Returns Tick::Session instance or nil
97
+ ```
98
+
99
+ ### Tick::Task
100
+
101
+ ```ruby
102
+ Tick::Task.list do |tasks|
103
+ # Returns array of Tick::Tasks
104
+ # Raises Tick::AuthenticationError if not logged in
105
+ end
106
+ ```
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create new Pull Request
@@ -0,0 +1,16 @@
1
+ require "motion-cocoapods"
2
+
3
+ unless defined?(Motion::Project::Config)
4
+ raise "This file must be required within a RubyMotion project Rakefile."
5
+ end
6
+
7
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
8
+ Motion::Project::App.setup do |app|
9
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "tick/**/*.rb")))
10
+
11
+ app.pods do
12
+ pod "AFNetworking", "~> 2.0.1"
13
+ pod "GDataXML-HTML", "~> 1.1.0"
14
+ pod "SSKeychain", "~> 1.2.1"
15
+ end
16
+ end
data/lib/tick/base.rb ADDED
@@ -0,0 +1,152 @@
1
+ module Tick
2
+
3
+ DATE_FORMAT = "yyyy-MM-dd"
4
+ DATETIME_FORMAT = "EE, dd MMM yyyy HH:mm:ss ZZZ"
5
+
6
+ class << self
7
+
8
+ def log_in(company, email, password, &block)
9
+ params = {
10
+ company: company,
11
+ email: email,
12
+ password: password
13
+ }
14
+ Session.create(params) do |session|
15
+ block.call(session) if block
16
+ end
17
+ end
18
+ alias_method :login, :log_in
19
+
20
+ def log_out
21
+ Session.current.destroy if Session.current
22
+ end
23
+ alias_method :logout, :log_out
24
+
25
+ def logged_in?
26
+ Session.logged_in?
27
+ end
28
+
29
+ end
30
+
31
+ class Base
32
+ attr_accessor :id, :created_at, :updated_at
33
+ attr_reader :api_name, :api_path
34
+
35
+ def set_properties_from_xml_node(xml_node)
36
+ self.class::XML_PROPERTIES.each do |property|
37
+ xml_elements = xml_node.elementsForName(property)
38
+ value = xml_elements ? get_xml_element_value(xml_elements.first) : nil
39
+ self.send("#{property}=", value)
40
+ end
41
+ end
42
+
43
+ def self.api_name
44
+ self.to_s.split('::').last.downcase
45
+ end
46
+
47
+ def self.api_path
48
+ "/api/#{api_name}s"
49
+ end
50
+
51
+ def self.list(options={}, &block)
52
+ url = "https://#{current_session.company}.tickspot.com#{api_path}"
53
+
54
+ params = authentication_params.merge!(options)
55
+
56
+ request_manager.GET(url, parameters:params, success:lambda{|operation, result|
57
+ objects = []
58
+
59
+ # Parse XML
60
+ error = Pointer.new(:object)
61
+ xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
62
+
63
+ # Create the objects
64
+ error = Pointer.new(:object)
65
+ xml_nodes = xml.nodesForXPath("//#{api_name}", error:error)
66
+
67
+ xml_nodes.each do |xml_node|
68
+ object = new
69
+ object.set_properties_from_xml_node(xml_node)
70
+ objects << object
71
+ end
72
+
73
+ block.call(objects) if block
74
+ }, failure:lambda{|operation, error|
75
+ block.call(error) if block
76
+ })
77
+
78
+ self
79
+ end
80
+
81
+ private
82
+
83
+ def get_xml_element_value(xml_element)
84
+ type = xml_element.attributeForName("type")
85
+ type = type.stringValue if type
86
+ case type
87
+ when "boolean"
88
+ xml_element.stringValue.boolValue
89
+ when "date"
90
+ date_from_string(xml_element.stringValue)
91
+ when "datetime"
92
+ datetime_from_string(xml_element.stringValue)
93
+ when "float"
94
+ xml_element.stringValue.floatValue
95
+ when "integer"
96
+ xml_element.stringValue.intValue
97
+ else
98
+ value = xml_element.stringValue
99
+ if value == "true"
100
+ true
101
+ elsif value == "false"
102
+ false
103
+ else
104
+ value
105
+ end
106
+ end
107
+ end
108
+
109
+ def date_from_string(string)
110
+ dateFormatter = NSDateFormatter.new
111
+ dateFormatter.setDateFormat(DATE_FORMAT)
112
+ dateFormatter.dateFromString(string)
113
+ end
114
+
115
+ def datetime_from_string(string)
116
+ dateFormatter = NSDateFormatter.new
117
+ dateFormatter.setDateFormat(DATETIME_FORMAT)
118
+ dateFormatter.dateFromString(string)
119
+ end
120
+
121
+ def self.authentication_params
122
+ {
123
+ email: current_session.email,
124
+ password: current_session.password
125
+ }
126
+ end
127
+
128
+ def self.current_session
129
+ if Session.current
130
+ Session.current
131
+ else
132
+ raise AuthenticationError.new("User is not logged in.")
133
+ end
134
+ end
135
+
136
+ def self.request_manager
137
+ manager = AFHTTPRequestOperationManager.manager
138
+
139
+ request_serializer = AFHTTPRequestSerializer.serializer
140
+ request_serializer.setValue("application/xml", forHTTPHeaderField:"Content-type")
141
+ manager.requestSerializer = request_serializer
142
+
143
+ response_serializer = AFHTTPResponseSerializer.serializer
144
+ response_serializer.acceptableContentTypes = NSSet.setWithObjects("application/xml", nil)
145
+ manager.responseSerializer = response_serializer
146
+
147
+ manager
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,8 @@
1
+ module Tick
2
+
3
+ class Client < Tick::Base
4
+ attr_accessor :name
5
+ XML_PROPERTIES = %w( id name )
6
+ end
7
+
8
+ end
data/lib/tick/entry.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Tick
2
+
3
+ class Entry < Tick::Base
4
+ attr_accessor :billable, :billed, :budget, :client_name,
5
+ :date, :hours, :notes, :project_name, :sum_hours,
6
+ :task_id, :task_name, :user_email, :user_id
7
+
8
+ XML_PROPERTIES = %w( id billable billed budget client_name created_at date hours notes
9
+ project_name sum_hours task_id task_name updated_at user_email user_id )
10
+
11
+ def self.api_path
12
+ "/api/entries"
13
+ end
14
+
15
+ def self.create(options={}, &block)
16
+ url = "https://#{current_session.company}.tickspot.com/api/create_entry"
17
+
18
+ params = {
19
+ email: current_session.email,
20
+ password: current_session.password
21
+ }.merge!(options)
22
+
23
+ if params[:date].is_a?(NSDate)
24
+ dateFormatter = NSDateFormatter.new
25
+ dateFormatter.setDateFormat(DATE_FORMAT)
26
+ params[:date] = dateFormatter.stringFromDate(params[:date])
27
+ end
28
+
29
+ request_manager.GET(url, parameters:params, success:lambda{|operation, result|
30
+ error = Pointer.new(:object)
31
+ xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
32
+
33
+ # Create the entry object from xml
34
+ error = Pointer.new(:object)
35
+ entry_node = xml.nodesForXPath("//entry", error:error).first
36
+ entry = new
37
+ entry.set_properties_from_xml_node(entry_node)
38
+ block.call(entry) if block
39
+ }, failure:lambda{|operation, error|
40
+ block.call(error) if block
41
+ })
42
+
43
+ self
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,70 @@
1
+ module Tick
2
+
3
+ class Project < Tick::Base
4
+ attr_accessor :budget, :client_id, :client_name, :closed_on,
5
+ :name, :opened_on, :owner_id, :sum_hours, :tasks,
6
+ :user_count
7
+
8
+ XML_PROPERTIES = %w( id budget client_id client_name closed_on created_at
9
+ name opened_on owner_id sum_hours updated_at user_count )
10
+
11
+ def self.list(options={}, &block)
12
+ url = "https://#{current_session.company}.tickspot.com/api/projects"
13
+
14
+ params = authentication_params.merge!(options)
15
+
16
+ request_manager.GET(url, parameters:params, success:lambda{|operation, result|
17
+ projects = []
18
+
19
+ # Parse XML
20
+ error = Pointer.new(:object)
21
+ xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
22
+
23
+ # Create the project objects
24
+ error = Pointer.new(:object)
25
+ project_nodes = xml.nodesForXPath("//project", error:error)
26
+
27
+ project_nodes.each do |project_node|
28
+ project = new
29
+ project.set_properties_from_xml_node(project_node)
30
+ project.tasks = get_tasks_from_xml_node(project_node)
31
+ project.tasks.each do |task|
32
+ task.project = project
33
+ end
34
+ projects << project
35
+ end
36
+
37
+ block.call(projects) if block
38
+ }, failure:lambda{|operation, error|
39
+ block.call(error) if block
40
+ })
41
+
42
+ self
43
+ end
44
+
45
+ private
46
+
47
+ def self.get_tasks_from_xml_node(xml_node)
48
+ tasks = []
49
+
50
+ # Seems to be mixed results when parsing the XML where
51
+ # sometimes the tasks element doesn't exist
52
+ tasks_elements = xml_node.elementsForName("tasks")
53
+ if tasks_elements
54
+ task_nodes = tasks_elements.first.elementsForName("task")
55
+ else
56
+ task_nodes = xml_node.elementsForName("task")
57
+ end
58
+
59
+ task_nodes.each do |task_node|
60
+ task = Task.new
61
+ task.set_properties_from_xml_node(task_node)
62
+ tasks << task
63
+ end
64
+
65
+ tasks
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,111 @@
1
+ module Tick
2
+
3
+ class AuthenticationError < Exception
4
+ end
5
+
6
+ class Session < Tick::Base
7
+ attr_accessor :company, :email, :first_name, :last_name, :password
8
+
9
+ SERVICE_NAME = "Tick Timer"
10
+
11
+ def company
12
+ @company ||= storage.objectForKey("company")
13
+ end
14
+
15
+ def company=(value)
16
+ @company = value
17
+ storage.setObject(value, forKey:"company")
18
+ storage.synchronize
19
+ @company
20
+ end
21
+
22
+ def destroy
23
+ storage.removeObjectForKey("company")
24
+ storage.removeObjectForKey("email")
25
+ SSKeychain.deletePasswordForService(SERVICE_NAME, account:email)
26
+ self.class.current = nil
27
+ end
28
+
29
+ def email
30
+ @email ||= storage.objectForKey("email")
31
+ end
32
+
33
+ def email=(value)
34
+ @email = value
35
+ storage.setObject(value, forKey:"email")
36
+ storage.synchronize
37
+ @email
38
+ end
39
+
40
+ def first_name
41
+ @first_name ||= storage.objectForKey("first_name")
42
+ end
43
+
44
+ def first_name=(value)
45
+ @first_name = value
46
+ storage.setObject(value, forKey:"first_name")
47
+ storage.synchronize
48
+ @first_name
49
+ end
50
+
51
+ def last_name
52
+ @last_name ||= storage.objectForKey("last_name")
53
+ end
54
+
55
+ def last_name=(value)
56
+ @last_name = value
57
+ storage.setObject(value, forKey:"last_name")
58
+ storage.synchronize
59
+ @last_name
60
+ end
61
+
62
+ def password
63
+ @password ||= SSKeychain.passwordForService(SERVICE_NAME, account:email)
64
+ end
65
+
66
+ def password=(value)
67
+ @password = value
68
+ SSKeychain.setPassword(value, forService:SERVICE_NAME, account:email)
69
+ @password
70
+ end
71
+
72
+ def storage
73
+ NSUserDefaults.standardUserDefaults
74
+ end
75
+
76
+ def self.create(params, &block)
77
+ url = "https://#{params[:company]}.tickspot.com/api/users"
78
+
79
+ company = params[:company]
80
+ params.delete(:company)
81
+
82
+ request_manager.GET(url, parameters:params, success:lambda{|operation, result|
83
+ # TODO: Save first and last name
84
+ @current = new
85
+ @current.company = company
86
+ @current.email = params[:email]
87
+ @current.password = params[:password]
88
+ block.call(@current) if block
89
+ }, failure:lambda{|operation, error|
90
+ ap error
91
+ block.call(nil) if block
92
+ })
93
+
94
+ self
95
+ end
96
+
97
+ def self.current
98
+ @current || (logged_in? ? new : nil)
99
+ end
100
+
101
+ def self.current=(value)
102
+ @current = value
103
+ end
104
+
105
+ def self.logged_in?
106
+ (@current && @current.company && @current.email && @current.password) ? true : false
107
+ end
108
+
109
+ end
110
+
111
+ end
data/lib/tick/task.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Tick
2
+
3
+ class Task < Tick::Base
4
+ attr_accessor :billable, :budget, :closed_on, :name, :opened_on,
5
+ :position, :project, :project_id, :sum_hours, :user_count
6
+
7
+ XML_PROPERTIES = %w( id billable budget closed_on name
8
+ opened_on position project_id sum_hours user_count )
9
+ end
10
+
11
+ end
data/lib/tick/timer.rb ADDED
@@ -0,0 +1,135 @@
1
+ module Tick
2
+
3
+ class Timer
4
+ attr_accessor :start_time, :task, :time_spans
5
+
6
+ def clear
7
+ self.start_time = nil
8
+ self.time_spans = []
9
+ self.class.list.delete(self)
10
+ true
11
+ end
12
+
13
+ def displayed_time
14
+ hours = self.time_elapsed_in_hours.to_i
15
+ minutes = (self.time_elapsed_in_seconds / 60).to_i - (hours * 60)
16
+
17
+ hours = hours.to_s
18
+ hours = "0#{hours}" if hours.length == 1
19
+
20
+ minutes = minutes.to_s
21
+ minutes = "0#{minutes}" if minutes.length == 1
22
+
23
+ "#{hours}:#{minutes}"
24
+ end
25
+
26
+ def initialize
27
+ self.start
28
+ self
29
+ end
30
+
31
+ def is_paused
32
+ self.start_time.nil?
33
+ end
34
+ alias_method :paused, :is_paused
35
+ alias_method :is_stopped, :is_paused
36
+ alias_method :stopped, :is_paused
37
+
38
+ def is_running
39
+ !self.start_time.nil?
40
+ end
41
+ alias_method :running, :is_running
42
+ alias_method :is_started, :is_running
43
+ alias_method :started, :is_running
44
+
45
+ def start
46
+ # Stop the current timer if it exists
47
+ current_timer = self.class.current
48
+ current_timer.stop if current_timer
49
+
50
+ # Start the timer and add it to the
51
+ # list of timers if it doesn't exist
52
+ self.start_time = Time.now
53
+ unless self.class.list.include?(self)
54
+ self.class.list << self
55
+ end
56
+
57
+ true
58
+ end
59
+ alias_method :resume, :start
60
+
61
+ def stop
62
+ self.time_spans << Time.now - self.start_time
63
+ self.start_time = nil
64
+ true
65
+ end
66
+ alias_method :pause, :stop
67
+
68
+ def submit!(options={}, &block)
69
+ dateFormatter = NSDateFormatter.new
70
+ dateFormatter.setDateFormat(DATE_FORMAT)
71
+
72
+ params = {
73
+ task_id: self.task.id,
74
+ hours: self.time_elapsed_in_hours,
75
+ date: Time.now
76
+ }.merge!(options)
77
+
78
+ entry = Entry.create(params) do |result|
79
+ self.clear
80
+ block.call(result) if block
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def time_elapsed_in_seconds
87
+ time_elapsed_in_seconds = 0
88
+
89
+ # Add up time spans
90
+ self.time_spans.each do |seconds|
91
+ time_elapsed_in_seconds += seconds
92
+ end
93
+
94
+ # Add the current running time
95
+ if self.start_time
96
+ time_elapsed_in_seconds += Time.now - self.start_time
97
+ end
98
+
99
+ time_elapsed_in_seconds
100
+ end
101
+
102
+ def time_elapsed_in_hours
103
+ self.time_elapsed_in_seconds / 60 / 60
104
+ end
105
+
106
+ def time_spans
107
+ @time_spans ||= []
108
+ end
109
+
110
+ def self.current
111
+ list.select{|timer|
112
+ timer.is_running
113
+ }.first
114
+ end
115
+
116
+ def self.list
117
+ @@list ||= []
118
+ end
119
+
120
+ def self.start_with_task(task)
121
+ timer = list.select{|timer|
122
+ timer.task.id == task.id
123
+ }.first
124
+
125
+ if timer.nil?
126
+ timer = new
127
+ timer.task = task
128
+ end
129
+
130
+ timer
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,3 @@
1
+ module Tick
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-tickspot
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Pattison
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: motion-cocoapods
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: awesome_print_motion
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-motion
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: motion-redgreen
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: RackMotion
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: terminal-notifier-guard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 1.5.3
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 1.5.3
111
+ description: Motion Tickspot is a RubyMotion wrapper for accessing the Tick time tracking
112
+ service using the http://tickspot.com API.
113
+ email:
114
+ - brian@brianpattison.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - README.md
120
+ - lib/motion-tickspot.rb
121
+ - lib/tick/base.rb
122
+ - lib/tick/client.rb
123
+ - lib/tick/entry.rb
124
+ - lib/tick/project.rb
125
+ - lib/tick/session.rb
126
+ - lib/tick/task.rb
127
+ - lib/tick/timer.rb
128
+ - lib/tick/version.rb
129
+ homepage: https://github.com/81designs/motion-tickspot
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.2.2
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: A RubyMotion wrapper for the http://tickspot.com API
153
+ test_files: []
154
+ has_rdoc: