gsquire 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|