motion-tickspot 1.0.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.
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: