sly 0.0.1
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/README.rdoc +6 -0
- data/bin/sly +125 -0
- data/lib/sly.rb +10 -0
- data/lib/sly/connector.rb +66 -0
- data/lib/sly/gui.rb +42 -0
- data/lib/sly/installer.rb +33 -0
- data/lib/sly/item.rb +28 -0
- data/lib/sly/project.rb +63 -0
- data/lib/sly/project_manager.rb +38 -0
- data/lib/sly/version.rb +3 -0
- data/sly.rdoc +5 -0
- metadata +161 -0
data/README.rdoc
ADDED
data/bin/sly
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gli'
|
3
|
+
require 'sly'
|
4
|
+
begin # XXX: Remove this begin/rescue before distributing your app
|
5
|
+
rescue LoadError
|
6
|
+
STDERR.puts "In development, you need to use `bundle exec bin/todo` to run your app"
|
7
|
+
STDERR.puts "At install-time, RubyGems will make sure lib, etc. are in the load path"
|
8
|
+
STDERR.puts "Feel free to remove this message from bin/sly now"
|
9
|
+
exit 64
|
10
|
+
end
|
11
|
+
|
12
|
+
include GLI::App
|
13
|
+
|
14
|
+
program_desc 'Sly - A Sprint.ly Command Line Interface'
|
15
|
+
|
16
|
+
version Sly::VERSION
|
17
|
+
|
18
|
+
desc 'creates a .slyrc file in your home folder; used by Sprint.ly CLI for authenticating your requests.'
|
19
|
+
skips_pre
|
20
|
+
command :install do |c|
|
21
|
+
c.action do |global_options,options,args|
|
22
|
+
puts "Please enter your Sprint.ly username (email):"
|
23
|
+
username = gets
|
24
|
+
puts "Please enter your Sprint.ly API key:"
|
25
|
+
api_key = gets
|
26
|
+
Sly::Installer.process(username.strip, api_key.strip)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'setup the current folder to be associated with a Sprint.ly project.'
|
31
|
+
command :setup do |c|
|
32
|
+
c.action do |global_options,options,args|
|
33
|
+
manager = Sly::ProjectManager.new
|
34
|
+
$stdout.write "Please write the ID of the project to be associated with this folder:\n"
|
35
|
+
$stdout.write manager.project_listings
|
36
|
+
$stdout.write "\ni.e: 1234"
|
37
|
+
$stdout.write "\n====================================\n"
|
38
|
+
|
39
|
+
Sly::GUI.get_project_id(manager)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'shows an overview of the current state of the project from backlog to complete.'
|
44
|
+
command :dashboard do |c|
|
45
|
+
c.action do |global_options,options,args|
|
46
|
+
project = Sly::Project.load(File.join(".sly", "project"))
|
47
|
+
project.update
|
48
|
+
Sly::GUI.display_dashboard(project)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'shows the current backlog for the project.'
|
53
|
+
command :backlog do |c|
|
54
|
+
c.action do |global_options,options,args|
|
55
|
+
project = Sly::Project.load(File.join(".sly", "project"))
|
56
|
+
project.update
|
57
|
+
Sly::GUI.display_backlog(project)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'shows the current in-progress for the project.'
|
62
|
+
command :current do |c|
|
63
|
+
c.action do |global_options,options,args|
|
64
|
+
project = Sly::Project.load(File.join(".sly", "project"))
|
65
|
+
project.update
|
66
|
+
Sly::GUI.display_current(project)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'shows the current completed items for the project.'
|
71
|
+
command :complete do |c|
|
72
|
+
c.action do |global_options,options,args|
|
73
|
+
project = Sly::Project.load(File.join(".sly", "project"))
|
74
|
+
project.update
|
75
|
+
Sly::GUI.display_complete(project)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'Describe new here'
|
80
|
+
arg_name 'Describe arguments to new here'
|
81
|
+
command :new do |c|
|
82
|
+
c.action do |global_options,options,args|
|
83
|
+
puts "new command ran"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
desc 'Describe move here'
|
88
|
+
arg_name 'Describe arguments to move here'
|
89
|
+
command :move do |c|
|
90
|
+
c.action do |global_options,options,args|
|
91
|
+
puts "move command ran"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
desc 'Describe comment here'
|
96
|
+
arg_name 'Describe arguments to comment here'
|
97
|
+
command :comment do |c|
|
98
|
+
c.action do |global_options,options,args|
|
99
|
+
puts "comment command ran"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
pre do |global,command,options,args|
|
104
|
+
# Pre logic here
|
105
|
+
# Return true to proceed; false to abourt and not call the
|
106
|
+
# chosen command
|
107
|
+
# Use skips_pre before a command to skip this block
|
108
|
+
# on that command only
|
109
|
+
Sly::Installer.validate_install!
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
post do |global,command,options,args|
|
114
|
+
# Post logic here
|
115
|
+
# Use skips_post before a command to skip this
|
116
|
+
# block on that command only
|
117
|
+
end
|
118
|
+
|
119
|
+
on_error do |exception|
|
120
|
+
# Error logic here
|
121
|
+
# return false to skip default error handling
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
exit run(ARGV)
|
data/lib/sly.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'sly/version.rb'
|
2
|
+
require 'sly/gui.rb'
|
3
|
+
require 'sly/connector.rb'
|
4
|
+
require 'sly/installer.rb'
|
5
|
+
require 'sly/project_manager.rb'
|
6
|
+
require 'sly/project.rb'
|
7
|
+
require 'sly/item.rb'
|
8
|
+
|
9
|
+
# Add requires for other files you add to your project here, so
|
10
|
+
# you just need to require this one file in your bin file
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'curb'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Sly::Connector
|
5
|
+
|
6
|
+
attr :api
|
7
|
+
|
8
|
+
def self.connect_with_defaults
|
9
|
+
return self.new(default_details)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(user_details = {}, api_url = "https://sprint.ly/api")
|
13
|
+
@api_url = api_url
|
14
|
+
@user_details = user_details
|
15
|
+
end
|
16
|
+
|
17
|
+
def authenticate!
|
18
|
+
request = authenticated_request("https://sprint.ly/api/products.json")
|
19
|
+
request.perform
|
20
|
+
JSON(request.body_str)
|
21
|
+
end
|
22
|
+
|
23
|
+
def products
|
24
|
+
request = authenticated_request(@api_url+"/products.json")
|
25
|
+
perform_and_capture_response(request)
|
26
|
+
end
|
27
|
+
|
28
|
+
def product_overview(id)
|
29
|
+
request = authenticated_request(@api_url+"/products/#{id}.json")
|
30
|
+
perform_and_capture_response(request)
|
31
|
+
end
|
32
|
+
|
33
|
+
def items_for_product(id)
|
34
|
+
request = authenticated_request(@api_url+"/products/#{id}/items.json")
|
35
|
+
perform_and_capture_response(request)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def authenticated_request(url)
|
41
|
+
request = Curl::Easy.new(url)
|
42
|
+
request.http_auth_types = :basic
|
43
|
+
request.username = @user_details[:email]
|
44
|
+
request.password = @user_details[:api_key]
|
45
|
+
request
|
46
|
+
end
|
47
|
+
|
48
|
+
def perform_and_capture_response(request)
|
49
|
+
request.perform
|
50
|
+
JSON(request.body_str)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def self.default_details
|
56
|
+
details_file = File.join(ENV["HOME"], ".slyrc")
|
57
|
+
if File.exists?(details_file)
|
58
|
+
details = File.read(details_file)
|
59
|
+
email, api_key = details.split(":").map(&:strip)
|
60
|
+
return { email: email, api_key: api_key }
|
61
|
+
else
|
62
|
+
raise "Could not locate installation file at #{details_file}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/sly/gui.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class Sly::GUI
|
2
|
+
def self.get_project_id(manager)
|
3
|
+
project_id = gets
|
4
|
+
begin
|
5
|
+
manager.setup_project(Dir.pwd, project_id)
|
6
|
+
rescue Exception => e
|
7
|
+
$stderr.write e.to_s+"\n"
|
8
|
+
get_project_id(manager)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.display_dashboard(project)
|
13
|
+
#table_columns = [{Backlog: project.backlog},
|
14
|
+
# {Current: project.current},
|
15
|
+
# {Complete: project.complete}]
|
16
|
+
#table_columns.each do |hash|
|
17
|
+
# hash.each do |k,v|
|
18
|
+
# p v.map(&:overview)
|
19
|
+
# end
|
20
|
+
#end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.display_backlog(project)
|
24
|
+
self.display_items("Backlog", project.backlog)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.display_current(project)
|
28
|
+
self.display_items("Current", project.current)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.display_complete(project)
|
32
|
+
self.display_items("Completed", project.complete)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.display_items(title, items)
|
38
|
+
STDOUT.print " ---------------- #{title} ---------------- \n"
|
39
|
+
items.map(&:print)
|
40
|
+
STDOUT.print "\n"
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'sly'
|
2
|
+
|
3
|
+
class Sly::Installer
|
4
|
+
|
5
|
+
def self.process(username, password)
|
6
|
+
connector = Sly::Connector.new({email: username, api_key: password})
|
7
|
+
results = connector.authenticate!
|
8
|
+
|
9
|
+
if success_call?(results)
|
10
|
+
create_file(username, password)
|
11
|
+
STDOUT.write "Thanks! Your details are currently stored in ~/.slyrc to authorise your interactions using the Sprint.ly CLI\n"
|
12
|
+
else
|
13
|
+
raise "The details provided were incorrect, please check your details and try again."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.validate_install!
|
18
|
+
unless File.exist?(ENV["HOME"]+"/.slyrc")
|
19
|
+
raise "You have not setup Sly on your machine yet, please run the sly install command first."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def self.create_file(username, password)
|
26
|
+
file = File.open(ENV["HOME"]+"/.slyrc", "w") { |file| file.puts(username+":"+password) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.success_call?(input)
|
30
|
+
return true if input.class == Array
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/sly/item.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Sly::Item
|
2
|
+
|
3
|
+
attr_accessor :number, :archived, :title, :score, :tags, :status
|
4
|
+
|
5
|
+
def initialize(item_attributes = {})
|
6
|
+
@number = item_attributes["number"]
|
7
|
+
@archived = item_attributes["archived"]
|
8
|
+
@title = item_attributes["title"]
|
9
|
+
@score = item_attributes["score"]
|
10
|
+
@tags = item_attributes["tags"]
|
11
|
+
@status = item_attributes["status"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def overview
|
15
|
+
self.prettify([@title, @number].join("\n#"), 44)+"\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :to_s, :overview
|
19
|
+
|
20
|
+
def print
|
21
|
+
STDOUT.print self.overview
|
22
|
+
end
|
23
|
+
|
24
|
+
def prettify(content, wrap_limit)
|
25
|
+
content.scan(/\S.{0,#{wrap_limit}}\S(?=\s|$)|\S+/).join("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/sly/project.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'sly'
|
2
|
+
require "yaml"
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class Sly::Project
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
attr_accessor :id, :name, :archived, :admin, :items
|
9
|
+
|
10
|
+
def initialize(project_attributes = {})
|
11
|
+
@id = project_attributes["id"]
|
12
|
+
@name = project_attributes["name"]
|
13
|
+
@archived = project_attributes["archived"]
|
14
|
+
@admin = project_attributes["admin"]
|
15
|
+
@items = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def save!
|
19
|
+
# Reserved for updating the API
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
return "#{@id} - #{@name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load(file)
|
27
|
+
begin
|
28
|
+
YAML::load(File.open(file))
|
29
|
+
rescue Exception => e
|
30
|
+
raise "Unable to locate project file #{file}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def update
|
35
|
+
download_child_items
|
36
|
+
save_child_items
|
37
|
+
end
|
38
|
+
|
39
|
+
def backlog
|
40
|
+
@items.select { |item| item.status == "backlog" }
|
41
|
+
end
|
42
|
+
|
43
|
+
def current
|
44
|
+
@items.select { |item| item.status == "in-progress" }
|
45
|
+
end
|
46
|
+
|
47
|
+
def complete
|
48
|
+
@items.select { |item| item.status == "complete" }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def download_child_items
|
54
|
+
items = Sly::Connector.connect_with_defaults.items_for_product(@id)
|
55
|
+
items.each { |item| @items << Sly::Item.new(item) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def save_child_items
|
59
|
+
target_file = File.join(pwd, '.sly', 'items')
|
60
|
+
File.open(target_file, 'w') { |file| file.write @items.to_yaml }
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Sly::ProjectManager
|
5
|
+
include FileUtils
|
6
|
+
|
7
|
+
attr_reader :available_projects
|
8
|
+
attr :connector
|
9
|
+
attr :projects_json
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@connector = Sly::Connector.connect_with_defaults
|
13
|
+
@projects_json = @connector.products
|
14
|
+
@available_projects = []
|
15
|
+
@projects_json.each do |project_json|
|
16
|
+
@available_projects << Sly::Project.new(project_json)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def project_listings
|
21
|
+
return @available_projects.map(&:to_s).join("\n")
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_project(directory, project_id)
|
25
|
+
target_directory = File.join(directory, ".sly")
|
26
|
+
target_file = File.join(target_directory, "project")
|
27
|
+
mkdir_p target_directory
|
28
|
+
touch target_file
|
29
|
+
selected_project = @available_projects.select { |project| project.id == project_id.to_i }.first
|
30
|
+
if selected_project
|
31
|
+
File.open(target_file, 'w') { |file| file.write selected_project.to_yaml }
|
32
|
+
$stdout.write "Thanks! That's Sly all setup for the current project, run `sly help` to see the commands available.\n"
|
33
|
+
else
|
34
|
+
raise "That ID does not match any of the projects available; please try again:"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/sly/version.rb
ADDED
data/sly.rdoc
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Robert White
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: aruba
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: curb
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0.8'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.8'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: json
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.4'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1.4'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: gli
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.5.0
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.5.0
|
110
|
+
description:
|
111
|
+
email: robert@terracoding.com
|
112
|
+
executables:
|
113
|
+
- sly
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files:
|
116
|
+
- README.rdoc
|
117
|
+
- sly.rdoc
|
118
|
+
files:
|
119
|
+
- bin/sly
|
120
|
+
- lib/sly/version.rb
|
121
|
+
- lib/sly.rb
|
122
|
+
- lib/sly/connector.rb
|
123
|
+
- lib/sly/gui.rb
|
124
|
+
- lib/sly/installer.rb
|
125
|
+
- lib/sly/item.rb
|
126
|
+
- lib/sly/project.rb
|
127
|
+
- lib/sly/project_manager.rb
|
128
|
+
- README.rdoc
|
129
|
+
- sly.rdoc
|
130
|
+
homepage: http://tallguyrob.com
|
131
|
+
licenses: []
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options:
|
134
|
+
- --title
|
135
|
+
- sly
|
136
|
+
- --main
|
137
|
+
- README.rdoc
|
138
|
+
- -ri
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 1.8.24
|
157
|
+
signing_key:
|
158
|
+
specification_version: 3
|
159
|
+
summary: A small set of tools for working with Sprint.ly without leaving the command
|
160
|
+
line.
|
161
|
+
test_files: []
|