gsquire 0.0.3 → 0.0.4
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.
- data/bin/gsquire +42 -78
- data/lib/gsquire/accounts/tasks_api_middleware.rb +2 -2
- data/lib/gsquire/application.rb +0 -1
- data/lib/gsquire/client.rb +62 -16
- data/lib/gsquire/models.rb +23 -0
- data/lib/gsquire/models/task.rb +23 -0
- data/lib/gsquire/models/tasklist.rb +16 -0
- data/lib/gsquire/version.rb +1 -1
- metadata +19 -16
data/bin/gsquire
CHANGED
@@ -60,22 +60,10 @@ class App < Thor
|
|
60
60
|
|
61
61
|
output = options.output.gsub /{account}/, account
|
62
62
|
output << ".#{options.format}"
|
63
|
-
|
63
|
+
|
64
64
|
case fmt = options.format.to_sym
|
65
65
|
when :json
|
66
66
|
File.open(output, 'w') {|f| f.write JSON.pretty_generate result }
|
67
|
-
=begin Yeah, I'm committing commented code, I feel bad.
|
68
|
-
when :dot, :png
|
69
|
-
graph = GraphViz.new 'Tasks', :type => :digraph
|
70
|
-
result.each do |tasklist|
|
71
|
-
list_node = graph.add_nodes tasklist['id'], :label => "#{tasklist['name']} (#{tasklist['id']})"
|
72
|
-
tasklist['tasks'].each do |task|
|
73
|
-
task_node = graph.add_nodes task['id'], :label => "#{task['name']} (#{task['id']})"
|
74
|
-
graph.add_edges task_node, list_node
|
75
|
-
end
|
76
|
-
end
|
77
|
-
graph.output(fmt => output)
|
78
|
-
=end
|
79
67
|
end
|
80
68
|
|
81
69
|
task_count = result.inject(0) {|sum, h| sum += h[:tasks].size }
|
@@ -88,32 +76,18 @@ class App < Thor
|
|
88
76
|
method_option :format, :aliases => '-f', :type => :string, :default => 'json', :desc => "Input file format (valid: #{INPUT_FORMATS.join(', ')})"
|
89
77
|
method_option :pretend, :aliases => '-p', :type => :boolean, :default => false, :desc => "Run but do not make any changes"
|
90
78
|
method_option :debug, :aliases => '-d', :type => :boolean, :default => false, :desc => "Turn on debugging"
|
91
|
-
#method_option :graph, :aliases => '-g', :type => :boolean, :default => false, :desc => "Generates a graph image of both the import and result sets"
|
92
79
|
def import(account, input)
|
93
80
|
if not INPUT_FORMATS.include? options.format
|
94
81
|
say_status "export", "invalid format #{options.format}", :red
|
95
82
|
exit 1
|
96
83
|
end
|
97
84
|
|
98
|
-
=begin I'm doing it again, fuck me.
|
99
|
-
if options.graph?
|
100
|
-
begin
|
101
|
-
require 'graphviz'
|
102
|
-
rescue LoadError
|
103
|
-
raise Thor::Error, "'ruby-graphviz' gem is required to generate a graph visualization"
|
104
|
-
end
|
105
|
-
|
106
|
-
graph_src = GraphViz.new 'Import', :type => :digraph
|
107
|
-
graph_dest = GraphViz.new 'Result', :type => :digraph
|
108
|
-
end
|
109
|
-
=end
|
110
|
-
|
111
85
|
tasklists = case options.format.to_sym
|
112
86
|
when :json
|
113
87
|
JSON.parse(File.read input)
|
114
88
|
end
|
115
89
|
|
116
|
-
|
90
|
+
tasklists, parents = prepare_import(tasklists)
|
117
91
|
|
118
92
|
if options.pretend?
|
119
93
|
client = nil
|
@@ -127,6 +101,8 @@ class App < Thor
|
|
127
101
|
end
|
128
102
|
end
|
129
103
|
|
104
|
+
id_mapping = {}
|
105
|
+
|
130
106
|
total_tasks = 0
|
131
107
|
tasklists.each do |tasklist|
|
132
108
|
task_count = 0
|
@@ -138,34 +114,10 @@ class App < Thor
|
|
138
114
|
new_tasklist = client.create_tasklist(tasklist)
|
139
115
|
end
|
140
116
|
|
141
|
-
=begin When you do it twice, you stop caring.
|
142
|
-
if options.graph?
|
143
|
-
graph_src.add_node(tasklist['id'])
|
144
|
-
graph_dest.add_node(new_tasklist['id'])
|
145
|
-
end
|
146
|
-
=end
|
147
|
-
|
148
117
|
debug "tasklist:create '#{new_tasklist['title']}' (#{new_tasklist['id']})"
|
149
118
|
|
150
119
|
tasklist['tasks'].each do |task|
|
151
|
-
|
152
|
-
debug "skip: orphaned task #{task['title']} (#{task['id']})"
|
153
|
-
next
|
154
|
-
end
|
155
|
-
|
156
|
-
if task.has_key? 'parent'
|
157
|
-
if parents.fetch(task['parent'], :placeholder) == :placeholder
|
158
|
-
parent = tasklist['tasks'].find {|t| t['id'] == task['parent']}
|
159
|
-
tasks = [task, parent].map {|t| "#{t['id'].rjust(35)} #{t['position'].to_s.rjust(20)}" }.join("\n")
|
160
|
-
debug "skip: child called not yet initialized parent:\n#{tasks}"
|
161
|
-
next
|
162
|
-
end
|
163
|
-
|
164
|
-
debug "parent:rename #{task['parent']} #{parents[task['parent']]}"
|
165
|
-
task['parent'] = parents[task['parent']]
|
166
|
-
end
|
167
|
-
|
168
|
-
task['title'].strip!
|
120
|
+
task_id = task.delete 'id'
|
169
121
|
|
170
122
|
if options.pretend?
|
171
123
|
new_task = task.dup.update 'id' => "task-#{task_id_seq.next}"
|
@@ -173,14 +125,11 @@ class App < Thor
|
|
173
125
|
new_task = client.create_task(task, new_tasklist['id'])
|
174
126
|
end
|
175
127
|
|
176
|
-
|
128
|
+
id_mapping[task_id] = { 'id' => new_task['id'], 'tasklist' => new_tasklist['id'] }
|
177
129
|
|
178
|
-
|
130
|
+
debug "task:create '#{new_task['title']}' (#{new_task['id']}, #{task_id})#{new_task['parent'] ? " [child of #{new_task['parent']}]" : ""}"
|
179
131
|
|
180
|
-
|
181
|
-
debug "trying to set parent id #{task['id']} twice" if parents[task['id']] != :placeholder
|
182
|
-
parents[task['id']] = new_task['id']
|
183
|
-
end
|
132
|
+
task_count += 1
|
184
133
|
end
|
185
134
|
|
186
135
|
total_tasks += task_count
|
@@ -192,6 +141,21 @@ class App < Thor
|
|
192
141
|
pluralize(task_count, "task"))
|
193
142
|
end
|
194
143
|
|
144
|
+
parents.each do |parent, children|
|
145
|
+
new_parent = id_mapping[parent]['id']
|
146
|
+
|
147
|
+
children.each do |id|
|
148
|
+
new_id = id_mapping[id]['id']
|
149
|
+
list_id = id_mapping[id]['tasklist']
|
150
|
+
|
151
|
+
unless options.pretend?
|
152
|
+
client.move_task new_id, list_id, :parent => new_parent
|
153
|
+
end
|
154
|
+
|
155
|
+
debug "task:move (#{id}, #{new_id}) tasklist (#{list_id}) children of (#{parent}, #{new_parent})"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
195
159
|
say_status "import:#{options.format}",
|
196
160
|
format("%s [%s %s, %s %s]",
|
197
161
|
cyan(account, :bold),
|
@@ -300,30 +264,30 @@ to get one and bring it here to him!
|
|
300
264
|
say_status "debug", msg, :white if options.debug?
|
301
265
|
end
|
302
266
|
|
303
|
-
# Handle parent-child relationships
|
304
|
-
def
|
267
|
+
# Handle parent-child relationships and ordering
|
268
|
+
def prepare_import(tasklists)
|
305
269
|
parents = {}
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
parents[task['parent']] ||= :placeholder
|
270
|
+
tasklists = tasklists.map do |list|
|
271
|
+
list_data = GSquire.resource(list).data
|
272
|
+
list_data.delete 'id'
|
273
|
+
# can't order by task['position'] since it doesn't reflect the actual order
|
274
|
+
# and inserts without the 'previous' parameter puts tasks on top like a stack
|
275
|
+
# so we need to revert the original task array
|
276
|
+
tasks = list['tasks'].reverse
|
277
|
+
list_data['tasks'] = tasks.map do |task|
|
278
|
+
task_data = GSquire.resource(task).data
|
279
|
+
if task_data.include? 'parent'
|
280
|
+
parents[task_data['parent']] ||= []
|
281
|
+
parents[task_data['parent']] << task_data['id']
|
282
|
+
task_data.delete 'parent'
|
320
283
|
end
|
284
|
+
# we don't clear the 'id' attribute to be able to use it with parents hash later
|
285
|
+
task_data
|
321
286
|
end
|
322
|
-
|
323
|
-
tasklist['tasks'].sort_by! {|t| t.include?('parent') ? 1 : 0 }
|
287
|
+
list_data
|
324
288
|
end
|
325
289
|
|
326
|
-
[
|
290
|
+
[tasklists, parents]
|
327
291
|
end
|
328
292
|
|
329
293
|
def id_seq
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'gsquire/models'
|
4
4
|
|
5
5
|
module GSquire
|
6
6
|
class Accounts
|
@@ -23,7 +23,7 @@ module GSquire
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def mash(hash)
|
26
|
-
|
26
|
+
GSquire.resource hash
|
27
27
|
end
|
28
28
|
|
29
29
|
def content_type(env)
|
data/lib/gsquire/application.rb
CHANGED
data/lib/gsquire/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'cgi'
|
3
4
|
require 'json'
|
4
5
|
require 'uri'
|
5
6
|
|
@@ -12,66 +13,97 @@ module GSquire
|
|
12
13
|
|
13
14
|
attr_accessor :oauth_token
|
14
15
|
|
16
|
+
# @param [OAuth2::AccessToken] Authorized oauth token
|
15
17
|
def initialize(token)
|
16
18
|
@oauth_token = token
|
17
19
|
end
|
18
20
|
|
19
|
-
# Pulls all
|
20
|
-
# @return [Array] Array of
|
21
|
+
# Pulls all tasklists for authorized user
|
22
|
+
# @return [Array] Array of tasklist hashes
|
21
23
|
def tasklists
|
22
24
|
get gtasks_tasklists_url
|
23
25
|
end
|
24
26
|
|
25
|
-
# Pulls a
|
26
|
-
# @param [String] tasklist_id ('@default')
|
27
|
-
# @
|
28
|
-
|
29
|
-
|
27
|
+
# Pulls a tasklist
|
28
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
29
|
+
# @option opts [true|false] :pull_tasks (false) Pulls tasklist with all tasks preloaded in the `'tasks'` key
|
30
|
+
# @return [Hash] tasklist
|
31
|
+
def tasklist(tasklist_id = '@default', opts={})
|
32
|
+
tasklist = get gtasks_tasklist_url(tasklist_id)
|
33
|
+
tasklist['tasks'] = tasks tasklist_id if opts[:pull_tasks]
|
34
|
+
tasklist
|
30
35
|
end
|
31
36
|
|
37
|
+
# Creates a tasklist
|
38
|
+
# @param [Hash] tasklist Tasklist data
|
32
39
|
def create_tasklist(tasklist)
|
33
40
|
post gtasks_tasklists_url, strip(:tasklist, :create, tasklist)
|
34
41
|
end
|
35
42
|
|
43
|
+
# Updates a tasklist
|
44
|
+
# @param [Hash] tasklist Tasklist data
|
36
45
|
def update_tasklist(tasklist)
|
37
46
|
put gtasks_tasklist_url(tasklist[:id]), strip(:tasklist, :update, tasklist)
|
38
47
|
end
|
39
48
|
|
49
|
+
# Deletes a tasklist
|
50
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
40
51
|
def delete_tasklist(tasklist_id)
|
41
52
|
delete gtasks_tasklist_url(tasklist_id)
|
42
53
|
end
|
43
54
|
|
44
|
-
# Pulls all tasks of a
|
45
|
-
# @param [String] tasklist_id ('@default')
|
55
|
+
# Pulls all tasks of a tasklist
|
56
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
46
57
|
# @return [Array] Array of task hashes
|
47
58
|
def tasks(tasklist_id = '@default')
|
48
59
|
get gtasks_tasks_url(tasklist_id)
|
49
60
|
end
|
50
61
|
|
51
|
-
# Pulls a task of a
|
52
|
-
# @param [String] task_id Task
|
53
|
-
# @param [String] tasklist_id ('@default')
|
62
|
+
# Pulls a task of a tasklist
|
63
|
+
# @param [String] task_id Task id
|
64
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
54
65
|
# @return [Hash] Task hash
|
55
66
|
def task(task_id, tasklist_id = '@default')
|
56
67
|
get gtasks_task_url(task_id, tasklist_id)
|
57
68
|
end
|
58
69
|
|
70
|
+
# Creates a task in the given tasklist
|
71
|
+
# @param [Hash] task Task data
|
72
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
59
73
|
def create_task(task, tasklist_id = '@default')
|
60
74
|
post gtasks_tasks_url(tasklist_id), strip(:task, :create, task)
|
61
75
|
end
|
62
76
|
|
77
|
+
# Moves a task inside the given tasklist
|
78
|
+
# @param [String] task_id Task id
|
79
|
+
# @param [String] tasklist_id ('@default') Tasklist id
|
80
|
+
# @option params [String] :parent Parent task id. Will move the task under the parent id
|
81
|
+
# @option params [String] :previous Previous task id. Will move the task to after the task with this id
|
82
|
+
def move_task(task_id, tasklist_id = '@default', params={})
|
83
|
+
return if params.empty?
|
84
|
+
post gtasks_task_move_url(task_id, tasklist_id, params)
|
85
|
+
end
|
86
|
+
|
63
87
|
protected
|
64
88
|
|
65
89
|
def get(url)
|
66
90
|
_ oauth_token.get(url)
|
67
91
|
end
|
68
92
|
|
69
|
-
def post(url, content)
|
70
|
-
|
93
|
+
def post(url, content={})
|
94
|
+
opts = {}
|
95
|
+
if not content.empty?
|
96
|
+
opts.merge!(body: content.to_json, headers: {'Content-Type' => 'application/json'})
|
97
|
+
end
|
98
|
+
_ oauth_token.post(url, opts)
|
71
99
|
end
|
72
100
|
|
73
|
-
def put(url, content)
|
74
|
-
|
101
|
+
def put(url, content={})
|
102
|
+
opts = {}
|
103
|
+
if not content.empty?
|
104
|
+
opts.merge!(body: content.to_json, headers: {'Content-Type' => 'application/json'})
|
105
|
+
end
|
106
|
+
_ oauth_token.put(url, opts)
|
75
107
|
end
|
76
108
|
|
77
109
|
def delete(url)
|
@@ -94,6 +126,16 @@ module GSquire
|
|
94
126
|
gtasks_urls(:task, tasklist_id, task_id)
|
95
127
|
end
|
96
128
|
|
129
|
+
def gtasks_task_move_url(task_id, tasklist_id = '@default', params)
|
130
|
+
uri = gtasks_urls(:task_move, tasklist_id, task_id)
|
131
|
+
uri.query = params.collect { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
|
132
|
+
uri
|
133
|
+
end
|
134
|
+
|
135
|
+
def gtasks_tasks_clear_url(tasklist_id = '@default')
|
136
|
+
gtasks_urls(:tasks_clear, tasklist_id)
|
137
|
+
end
|
138
|
+
|
97
139
|
def gtasks_urls(resource, *params)
|
98
140
|
segments = case resource
|
99
141
|
when :tasklists
|
@@ -104,6 +146,10 @@ module GSquire
|
|
104
146
|
"/lists/$/tasks"
|
105
147
|
when :task
|
106
148
|
"/lists/$/tasks/$"
|
149
|
+
when :task_move
|
150
|
+
"/lists/$/tasks/$/move"
|
151
|
+
when :tasks_clear
|
152
|
+
"/lists/$/clear"
|
107
153
|
end.split('/')
|
108
154
|
subpath = segments.map {|seg| seg == '$' ? params.shift : seg }.join('/')
|
109
155
|
GOOGLE_TASKS_API.merge(GOOGLE_TASKS_API.path + subpath)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'gsquire/models/task'
|
4
|
+
require 'gsquire/models/tasklist'
|
5
|
+
|
6
|
+
module GSquire
|
7
|
+
RESOURCE_KINDS = {
|
8
|
+
"tasks#taskList" => Tasklist,
|
9
|
+
"tasks#task" => Task
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def resource(hash)
|
14
|
+
return nil unless hash.include? 'kind'
|
15
|
+
|
16
|
+
unless RESOURCE_KINDS.include? hash['kind']
|
17
|
+
raise ArgumentError, "Unknown resource kind #{hash['kind'].inspect}"
|
18
|
+
end
|
19
|
+
|
20
|
+
RESOURCE_KINDS[hash['kind']].new(hash)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'hashie'
|
4
|
+
|
5
|
+
module GSquire
|
6
|
+
class Task < Hashie::Mash
|
7
|
+
DATA_ATTRIBUTES = %w[
|
8
|
+
id
|
9
|
+
title
|
10
|
+
parent
|
11
|
+
notes
|
12
|
+
status
|
13
|
+
due
|
14
|
+
completed
|
15
|
+
deleted
|
16
|
+
hidden
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def data
|
20
|
+
select {|k,v| DATA_ATTRIBUTES.include? k }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/gsquire/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gsquire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-04 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: oauth2
|
16
|
-
requirement: &
|
16
|
+
requirement: &13973740 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.5.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *13973740
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: thor
|
27
|
-
requirement: &
|
27
|
+
requirement: &13973000 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.14.6
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *13973000
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: hashie
|
38
|
-
requirement: &
|
38
|
+
requirement: &13972540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 1.1.0
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *13972540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rdoc
|
49
|
-
requirement: &
|
49
|
+
requirement: &13972160 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *13972160
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rdiscount
|
60
|
-
requirement: &
|
60
|
+
requirement: &13971680 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *13971680
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: yard
|
71
|
-
requirement: &
|
71
|
+
requirement: &13971240 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *13971240
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: awesome_print
|
82
|
-
requirement: &
|
82
|
+
requirement: &13970820 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *13970820
|
91
91
|
description: ! "Back in the Age of Heroes, GSquire would carry thy armor\n and
|
92
92
|
sword, would get thy lordship dressed and accompany in battle. He would\n fight
|
93
93
|
side by side with those who stood brave against the toughest of the\n foes. These
|
@@ -119,6 +119,9 @@ files:
|
|
119
119
|
- lib/gsquire/application.rb
|
120
120
|
- lib/gsquire/client.rb
|
121
121
|
- lib/gsquire/logging.rb
|
122
|
+
- lib/gsquire/models.rb
|
123
|
+
- lib/gsquire/models/task.rb
|
124
|
+
- lib/gsquire/models/tasklist.rb
|
122
125
|
- lib/gsquire/version.rb
|
123
126
|
homepage: http://commita.com/community
|
124
127
|
licenses: []
|