teambox-things-sync 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/bin/teambox-things-sync +6 -0
- data/lib/teambox-things-sync.rb +15 -0
- data/lib/teambox-things-sync/base.rb +146 -0
- data/lib/teambox-things-sync/command.rb +1 -0
- data/lib/teambox-things-sync/config_store.rb +42 -0
- data/lib/teambox-things-sync/task_list_cache.rb +17 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/teambox-things-sync/base_spec.rb +46 -0
- data/spec/teambox-things-sync/task_list_cache_spec.rb +5 -0
- data/spec/teambox-things-sync_spec.rb +5 -0
- metadata +125 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
crack (0.1.6)
|
5
|
+
diff-lcs (1.1.2)
|
6
|
+
hashie (0.2.2)
|
7
|
+
httparty (0.5.2)
|
8
|
+
crack (= 0.1.6)
|
9
|
+
rb-appscript (0.5.3)
|
10
|
+
rspec (2.0.0.beta.20)
|
11
|
+
rspec-core (= 2.0.0.beta.20)
|
12
|
+
rspec-expectations (= 2.0.0.beta.20)
|
13
|
+
rspec-mocks (= 2.0.0.beta.20)
|
14
|
+
rspec-core (2.0.0.beta.20)
|
15
|
+
rspec-expectations (2.0.0.beta.20)
|
16
|
+
diff-lcs (>= 1.1.2)
|
17
|
+
rspec-mocks (2.0.0.beta.20)
|
18
|
+
teambox-client (0.2.0)
|
19
|
+
hashie (~> 0.2.0)
|
20
|
+
httparty (~> 0.5.0)
|
21
|
+
yajl-ruby (~> 0.7.0)
|
22
|
+
things-client (0.2.4)
|
23
|
+
rb-appscript (>= 0.5.3)
|
24
|
+
yajl-ruby (0.7.7)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
rspec (>= 2.0.0.beta.20)
|
31
|
+
teambox-client (= 0.2.0)
|
32
|
+
things-client (= 0.2.4)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Arkadiusz Holko
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
**teambox-things-sync** lets you sync your Things todos to and from Teambox Collaboration Software.
|
3
|
+
|
4
|
+
It fetches todos from remote teambox app (project, task name, due date, task list name) and updates completed ones in both ways. Project (named like the one in teambox) will be created in Things if necessary. By default only tasks assigned to you are imported.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
-------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
For now you can install only from source:
|
10
|
+
|
11
|
+
git clone git://github.com/fastred/teambox-things-sync.git
|
12
|
+
cd teambox-things-sync
|
13
|
+
bundle install
|
14
|
+
|
15
|
+
Then create a file $HOME/.teambox with these values:
|
16
|
+
|
17
|
+
username: your_teambox_username_or_email
|
18
|
+
password: password
|
19
|
+
site_url: http://your-teambox-app.com (optional, default set to http://teambox.com)
|
20
|
+
|
21
|
+
|
22
|
+
Usage
|
23
|
+
-------------------------------------------------------------------------------
|
24
|
+
|
25
|
+
Now you can run:
|
26
|
+
|
27
|
+
ruby sync.rb
|
28
|
+
|
29
|
+
You should see output with names of imported tasks.
|
30
|
+
|
31
|
+
If it worked you can add it to your crontab:
|
32
|
+
|
33
|
+
1,31 * * * * cd /path/to/teambox-things-sync/ && ruby sync.rb
|
34
|
+
|
35
|
+
Legal
|
36
|
+
-------------------------------------------------------------------------------
|
37
|
+
|
38
|
+
Things is a trademark of Cultured Code GmbH & Co. KG.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'jeweler'
|
4
|
+
Jeweler::Tasks.new do |gemspec|
|
5
|
+
gemspec.name = "teambox-things-sync"
|
6
|
+
gemspec.summary = "Simple ruby app for Teambox and Things.app syncing"
|
7
|
+
gemspec.email = "fastred@fastred.org"
|
8
|
+
gemspec.homepage = "http://github.com/fastred/teambox-things-sync"
|
9
|
+
gemspec.authors = ["Arkadiusz Holko"]
|
10
|
+
gemspec.add_development_dependency "rspec", ">=2.0.0.beta.20"
|
11
|
+
gemspec.add_dependency "teambox-client", "0.2.0"
|
12
|
+
gemspec.add_dependency "things-client", "0.2.4"
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
16
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'teambox-client'
|
2
|
+
require 'things'
|
3
|
+
require 'time'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
module TeamboxThingsSync
|
7
|
+
autoload :Base, File.dirname(__FILE__) + '/teambox-things-sync/base'
|
8
|
+
autoload :ConfigStore, File.dirname(__FILE__) + '/teambox-things-sync/config_store'
|
9
|
+
autoload :TaskListCache, File.dirname(__FILE__) + '/teambox-things-sync/task_list_cache'
|
10
|
+
end
|
11
|
+
|
12
|
+
module Things
|
13
|
+
additional_properties = %{properties :tag_names, :due_date}
|
14
|
+
Todo.module_eval(additional_properties)
|
15
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module TeamboxThingsSync
|
2
|
+
class Base
|
3
|
+
|
4
|
+
attr_reader :config
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
parse_config_file
|
8
|
+
httpauth = Teambox::HTTPAuth.new(@config['username'], @config['password'],
|
9
|
+
:api_endpoint => api_url(@config['teambox_url']))
|
10
|
+
@client = Teambox::Base.new(httpauth)
|
11
|
+
end
|
12
|
+
|
13
|
+
def synchronise
|
14
|
+
Timeout::timeout(@config['timeout_limit']) {
|
15
|
+
begin
|
16
|
+
projects = @client.projects
|
17
|
+
rescue
|
18
|
+
abort "Cannot fetch project list, check your internet connection."
|
19
|
+
end
|
20
|
+
|
21
|
+
projects.each do |project|
|
22
|
+
log "Started working with #{project.name}"
|
23
|
+
begin
|
24
|
+
mark_as_done_at_remote(project)
|
25
|
+
|
26
|
+
person_id = find_person_id(project.permalink)
|
27
|
+
fetch_tasks_from_remote(project, person_id)
|
28
|
+
rescue
|
29
|
+
abort "Something went wrong while updating.\n" + $!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.find_or_create_project_in_things(project_name)
|
37
|
+
Things::Project.find(project_name) or Things::Project.create(:name => project_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.find_or_create_todo_in_things(todo_name)
|
41
|
+
Things::Todo.find(todo_name) or Things::Todo.create(:name => todo_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def task_url(project_permalink, task_list_id, task_id)
|
45
|
+
"#{@config['teambox_url']}/projects/#{project_permalink}/"+
|
46
|
+
"task_lists/#{task_list_id}/tasks/#{task_id}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def api_url(url)
|
50
|
+
url.gsub("http://", "")
|
51
|
+
end
|
52
|
+
|
53
|
+
def is_task_open?(status)
|
54
|
+
[0,1].include? status
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# update tasks marked as done in Things
|
60
|
+
def mark_as_done_at_remote(project)
|
61
|
+
@client.tasks.each do |task|
|
62
|
+
things_todo = Things::Todo.find(task.name)
|
63
|
+
if !things_todo.nil? && things_todo.completed? && is_task_open?(task.status)
|
64
|
+
# API isn't great right now, so we update task as resolved
|
65
|
+
# and add the new comment to it
|
66
|
+
@client.update_project_task(project.permalink, task.id, {:status => 3})
|
67
|
+
@client.create_project_task_comment(project.permalink, task.id,
|
68
|
+
{:status => 3,
|
69
|
+
:body => "Sent from my Things.app"})
|
70
|
+
log "#{task.name} at remote set as done"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# fetches current task data from remote
|
76
|
+
def fetch_tasks_from_remote(project, person_id)
|
77
|
+
task_list_cache = TaskListCache.new(@client, project.permalink)
|
78
|
+
@client.tasks.each do |task|
|
79
|
+
# grab only tasks assigned to current user
|
80
|
+
if task.assigned_id == person_id
|
81
|
+
things_todo = Base.find_or_create_todo_in_things(task.name)
|
82
|
+
# update only those that aren't completed in both local and remote
|
83
|
+
if is_task_open?(task.status) || !things_todo.completed?
|
84
|
+
things_todo.project = Base.find_or_create_project_in_things(project.name)
|
85
|
+
things_todo.notes = "Don't edit this field!\n" +
|
86
|
+
task_url(project.permalink, task.task_list_id, task.id)
|
87
|
+
|
88
|
+
unless task.due_on.nil?
|
89
|
+
things_todo.due_date = Time.parse(task.due_on)
|
90
|
+
end
|
91
|
+
|
92
|
+
# data from remote has higher priority
|
93
|
+
unless is_task_open?(task.status)
|
94
|
+
things_todo.complete
|
95
|
+
else
|
96
|
+
things_todo.open
|
97
|
+
end
|
98
|
+
|
99
|
+
things_todo.tag_names = task_list_cache[task.task_list_id]
|
100
|
+
things_todo.save
|
101
|
+
log "\"#{task.name}\" has been saved in Things.app"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def upload_tasks_to_remote(project)
|
108
|
+
# maybe add task from things to teambox app (in future)
|
109
|
+
# things_project_tasks = things_project.todos
|
110
|
+
# things_project_tasks.each do |todo|
|
111
|
+
# if !all_tasks.any?{|t| t.name == todo.name}
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
end
|
115
|
+
|
116
|
+
# finds person_id in given project
|
117
|
+
def find_person_id(project_permalink)
|
118
|
+
project_people = @client.project_people(project_permalink)
|
119
|
+
project_people.find { |person| person.user_id == @client.account.id }.id
|
120
|
+
end
|
121
|
+
|
122
|
+
def log(text)
|
123
|
+
puts text if @config['output_log']
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def parse_config_file
|
128
|
+
@config = ConfigStore.new("#{ENV['HOME']}/.teambox")
|
129
|
+
@config['teambox_url'] ||= "http://teambox.com"
|
130
|
+
@config['output_log'] ||= true
|
131
|
+
@config['timeout_limit'] ||= 40
|
132
|
+
check_correctness_of_config
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_correctness_of_config
|
136
|
+
if @config['username'].nil? || @config['password'].nil?
|
137
|
+
abort "'username' and/or 'password' not found in " +
|
138
|
+
"#{ENV['HOME']}/.teambox file. See README."
|
139
|
+
end
|
140
|
+
|
141
|
+
unless @config['timeout_limit'].integer?
|
142
|
+
abort "timeout_limit should be a number (integer)"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
TeamboxThingsSync::Base.new.synchronise
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# borrowed from http://github.com/teambox/teambox-ruby-client/blob/master/examples/helpers/config_store.rb
|
2
|
+
module TeamboxThingsSync
|
3
|
+
class ConfigStore
|
4
|
+
|
5
|
+
attr_reader :file
|
6
|
+
|
7
|
+
def initialize(file)
|
8
|
+
@file = file
|
9
|
+
end
|
10
|
+
|
11
|
+
def load
|
12
|
+
@config ||= YAML::load(open(file))
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
load
|
18
|
+
@config[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
@config[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(*keys)
|
26
|
+
keys.each { |key| @config.delete(key) }
|
27
|
+
save
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(c={})
|
32
|
+
@config.merge!(c)
|
33
|
+
save
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
File.open(file, 'w') { |f| f.write(YAML.dump(@config)) }
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TeamboxThingsSync
|
2
|
+
class TaskListCache
|
3
|
+
|
4
|
+
def initialize(client, project_name)
|
5
|
+
@data = {}
|
6
|
+
@client = client
|
7
|
+
@project_name = project_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](task_list_id)
|
11
|
+
if @data[task_list_id].nil?
|
12
|
+
@data[task_list_id] = @client.project_task_list(@project_name, task_list_id).name
|
13
|
+
end
|
14
|
+
@data[task_list_id]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "TeamboxThingsSync::Base" do
|
4
|
+
describe "#task_url" do
|
5
|
+
it "should return correct url to teambox.com' task" do
|
6
|
+
sync = TeamboxThingsSync::Base.new
|
7
|
+
sync.task_url("proj", "1", "2").should ==
|
8
|
+
"http://teambox.com/projects/proj/task_lists/1/tasks/2"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return correct url to task if teambox url changed" do
|
12
|
+
sync = TeamboxThingsSync::Base.new
|
13
|
+
sync.config["teambox_url"] = "http://testteambox.com"
|
14
|
+
sync.task_url("proj", "1", "2").should ==
|
15
|
+
"http://testteambox.com/projects/proj/task_lists/1/tasks/2"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#.find_or_create_project_in_things" do
|
20
|
+
|
21
|
+
it "should find project by name in Things" do
|
22
|
+
Things::Project.should_receive(:find).with("testowy").and_return(true)
|
23
|
+
TeamboxThingsSync::Base.find_or_create_project_in_things("testowy")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should create new project in Things if it doesn't exist" do
|
27
|
+
Things::Project.should_receive(:find).with("testowy").and_return(false)
|
28
|
+
Things::Project.should_receive(:create).with(:name => "testowy").and_return(true)
|
29
|
+
TeamboxThingsSync::Base.find_or_create_project_in_things("testowy")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#.find_or_create_todo_in_things" do
|
34
|
+
|
35
|
+
it "should find todo by name in Things" do
|
36
|
+
Things::Todo.should_receive(:find).with("testowe").and_return(true)
|
37
|
+
TeamboxThingsSync::Base.find_or_create_todo_in_things("testowe")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should create new todo in Things if it doesn't exist" do
|
41
|
+
Things::Todo.should_receive(:find).with("testowe").and_return(false)
|
42
|
+
Things::Todo.should_receive(:create).with(:name => "testowe").and_return(true)
|
43
|
+
TeamboxThingsSync::Base.find_or_create_todo_in_things("testowe")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: teambox-things-sync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Arkadiusz Holko
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-15 00:00:00 +02:00
|
18
|
+
default_executable: teambox-things-sync
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
- beta
|
32
|
+
- 20
|
33
|
+
version: 2.0.0.beta.20
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: teambox-client
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 2
|
46
|
+
- 0
|
47
|
+
version: 0.2.0
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: things-client
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
- 2
|
60
|
+
- 4
|
61
|
+
version: 0.2.4
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
description:
|
65
|
+
email: fastred@fastred.org
|
66
|
+
executables:
|
67
|
+
- teambox-things-sync
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
files:
|
74
|
+
- .gitignore
|
75
|
+
- Gemfile
|
76
|
+
- Gemfile.lock
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- VERSION
|
81
|
+
- bin/teambox-things-sync
|
82
|
+
- lib/teambox-things-sync.rb
|
83
|
+
- lib/teambox-things-sync/base.rb
|
84
|
+
- lib/teambox-things-sync/command.rb
|
85
|
+
- lib/teambox-things-sync/config_store.rb
|
86
|
+
- lib/teambox-things-sync/task_list_cache.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
- spec/teambox-things-sync/base_spec.rb
|
89
|
+
- spec/teambox-things-sync/task_list_cache_spec.rb
|
90
|
+
- spec/teambox-things-sync_spec.rb
|
91
|
+
has_rdoc: true
|
92
|
+
homepage: http://github.com/fastred/teambox-things-sync
|
93
|
+
licenses: []
|
94
|
+
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options:
|
97
|
+
- --charset=UTF-8
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
version: "0"
|
114
|
+
requirements: []
|
115
|
+
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 1.3.6
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: Simple ruby app for Teambox and Things.app syncing
|
121
|
+
test_files:
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/teambox-things-sync/base_spec.rb
|
124
|
+
- spec/teambox-things-sync/task_list_cache_spec.rb
|
125
|
+
- spec/teambox-things-sync_spec.rb
|