pivotal_shell 0.1.0 → 0.2.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.
- data/.gitignore +1 -0
- data/Gemfile.lock +5 -4
- data/README.markdown +21 -2
- data/bin/pivotal +3 -0
- data/examples/commit-msg +3 -0
- data/lib/pivotal_shell.rb +5 -2
- data/lib/pivotal_shell/cache.rb +145 -0
- data/lib/pivotal_shell/cache/story.rb +69 -0
- data/lib/pivotal_shell/cache/user.rb +33 -0
- data/lib/pivotal_shell/command.rb +4 -0
- data/lib/pivotal_shell/commands/commit.rb +16 -0
- data/lib/pivotal_shell/commands/finish.rb +1 -0
- data/lib/pivotal_shell/commands/reload.rb +19 -0
- data/lib/pivotal_shell/commands/start.rb +1 -0
- data/lib/pivotal_shell/commands/stories.rb +6 -11
- data/lib/pivotal_shell/commands/story.rb +2 -1
- data/lib/pivotal_shell/commands/update.rb +19 -0
- data/lib/pivotal_shell/configuration.rb +15 -3
- data/lib/pivotal_shell/version.rb +1 -1
- data/pivotal_shell.gemspec +2 -1
- metadata +28 -7
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pivotal_shell (0.
|
5
|
-
pivotal-tracker (
|
4
|
+
pivotal_shell (0.2.0)
|
5
|
+
pivotal-tracker (> 0.3)
|
6
|
+
sqlite3
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: http://rubygems.org/
|
@@ -13,17 +14,17 @@ GEM
|
|
13
14
|
libxml-ruby (1.1.4)
|
14
15
|
mime-types (1.16)
|
15
16
|
nokogiri (1.4.3.1)
|
16
|
-
pivotal-tracker (0.3.
|
17
|
+
pivotal-tracker (0.3.1)
|
17
18
|
builder
|
18
19
|
happymapper (>= 0.3.2)
|
19
20
|
nokogiri (~> 1.4.3.1)
|
20
21
|
rest-client (~> 1.6.0)
|
21
22
|
rest-client (1.6.1)
|
22
23
|
mime-types (>= 1.16)
|
24
|
+
sqlite3 (1.3.3)
|
23
25
|
|
24
26
|
PLATFORMS
|
25
27
|
ruby
|
26
28
|
|
27
29
|
DEPENDENCIES
|
28
|
-
pivotal-tracker (= 0.3)
|
29
30
|
pivotal_shell!
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# pivotal_shell
|
2
2
|
|
3
|
-
A command-line
|
3
|
+
A command-line client for [Pivotal Tracker](http://www.pivotaltracker.com)
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -58,8 +58,27 @@ Finish story
|
|
58
58
|
|
59
59
|
pivotal finish 123456
|
60
60
|
|
61
|
+
## Caching
|
62
|
+
|
63
|
+
`pivotal-shell` caches story and user information into a local database. This can greatly speed up execution since Pivotal Tracker can be *slooow* sometimes.
|
64
|
+
The database is synchronised with the actual project data each 15 minutes (that is, if you call a `pivotal` command and the database is older that 15 minutes, it's updated).
|
65
|
+
To change this interval, use the `refresh_interval` parameter in the global or project `.pivotalrc`; the value is in minutes; set it to `-1` to completely disable updating, which can be
|
66
|
+
useful if you're going to do it by `cron`.
|
67
|
+
|
68
|
+
There are two commands related to caching:
|
69
|
+
|
70
|
+
pivotal update # update stories from the server
|
71
|
+
|
72
|
+
pivotal reload # completely reinitialize the database; use this in case of bugs
|
73
|
+
|
74
|
+
You can add this to your crontab to enable autoupdate:
|
75
|
+
|
76
|
+
0,15,30,45 * * * * cd /your/project/path && pivotal update
|
77
|
+
|
61
78
|
## TODO
|
62
79
|
|
63
|
-
Commit (with git, all comments after the story id go to git, story id gets appended to comments)
|
80
|
+
* Commit (with git, all comments after the story id go to git, story id gets appended to comments)
|
64
81
|
|
65
82
|
pivotal commit 123456 "some more comments"
|
83
|
+
|
84
|
+
* Refactor caching code
|
data/bin/pivotal
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'pivotal_shell'
|
5
5
|
require 'pivotal_shell/configuration'
|
6
|
+
require 'pivotal_shell/cache'
|
6
7
|
#require 'active_support/core_ext'
|
7
8
|
|
8
9
|
PivotalShell::Configuration.load
|
@@ -12,6 +13,8 @@ commands = [
|
|
12
13
|
['story', 'show information about a specific story'],
|
13
14
|
['start', 'start a story'],
|
14
15
|
['finish', 'finish a story'],
|
16
|
+
['update', 'update stories status from the server'],
|
17
|
+
['reload', 'reload entire project from the server'],
|
15
18
|
]
|
16
19
|
|
17
20
|
banner = "Pivotal command-line client #{PivotalShell::VERSION}\nUsage: pivotal COMMAND PARAMS\n\nAvailable commands:\n\n#{commands.map{|c, d| "#{c} - #{d}"}.join("\n")}\n\nRun pivotal COMMAND --help for more info on a specific command\n\n"
|
data/examples/commit-msg
ADDED
data/lib/pivotal_shell.rb
CHANGED
@@ -4,6 +4,9 @@ module PivotalShell
|
|
4
4
|
class Exception < StandardError; end
|
5
5
|
end
|
6
6
|
|
7
|
+
require 'pivotal_shell/version'
|
8
|
+
require 'pivotal_shell/configuration'
|
9
|
+
require 'pivotal_shell/cache'
|
7
10
|
require 'pivotal_shell/command'
|
8
11
|
|
9
12
|
# fixing pivotal-tracker's options encoding; it did not work with array filters
|
@@ -14,11 +17,11 @@ module PivotalTracker
|
|
14
17
|
options_strings = []
|
15
18
|
# remove options which are not filters, and encode them as such
|
16
19
|
[:limit, :offset].each do |o|
|
17
|
-
options_strings << "#{CGI.escape(o.to_s)}=#{CGI.escape(options.delete(o))}" if options[o]
|
20
|
+
options_strings << "#{CGI.escape(o.to_s)}=#{CGI.escape(options.delete(o).to_s)}" if options[o]
|
18
21
|
end
|
19
22
|
# assume remaining key-value pairs describe filters, and encode them as such.
|
20
23
|
filters_string = options.map do |key, value|
|
21
|
-
"#{CGI.escape(key.to_s)}%3A#{CGI.escape([value].flatten.map{|v| v.include?(' ') ? '"'+v+'"' : v}.join(','))}"
|
24
|
+
"#{CGI.escape(key.to_s)}%3A#{CGI.escape([value].flatten.map{|v| v=v.to_s; v.include?(' ') ? '"'+v+'"' : v}.join(','))}"
|
22
25
|
end
|
23
26
|
options_strings << "filter=#{filters_string.join('+')}" unless filters_string.empty?
|
24
27
|
return "?#{options_strings.join('&')}"
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'pivotal_shell/configuration'
|
3
|
+
require 'pivotal_shell/cache/story'
|
4
|
+
require 'pivotal_shell/cache/user'
|
5
|
+
|
6
|
+
class PivotalShell::Cache
|
7
|
+
STORY_ATTRIBUTES=%w(requested_by_id name id current_state accepted_at labels url estimate description created_at owned_by_id story_type)
|
8
|
+
|
9
|
+
attr_reader :db
|
10
|
+
|
11
|
+
def initialize(filename)
|
12
|
+
@filename = filename
|
13
|
+
@db = SQLite3::Database.new(@filename)
|
14
|
+
@db.results_as_hash = true
|
15
|
+
create_tables
|
16
|
+
refresh_if_needed
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<PivotalShell::Cache #{@filename}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def refresh
|
24
|
+
load_stories(:modified_since => self[:last_updated_at])
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload
|
28
|
+
db.execute('DELETE FROM users')
|
29
|
+
db.execute('DELETE FROM stories')
|
30
|
+
@users = nil
|
31
|
+
load_stories
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
resultset = db.execute('SELECT value FROM settings WHERE name=?', key.to_s)
|
36
|
+
resultset.empty? ? nil : YAML.load(resultset.first['value'])
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(key, value)
|
40
|
+
db.execute('REPLACE INTO settings (name, value) VALUES (?,?)', key.to_s, YAML.dump(value))
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def users
|
48
|
+
@users ||= load_users_from_cache
|
49
|
+
end
|
50
|
+
|
51
|
+
def refresh_if_needed
|
52
|
+
updated_at = self[:last_updated_at]
|
53
|
+
if updated_at.nil?
|
54
|
+
puts 'Retrieving all stories from Pivotal Tracker. This could take a while...'
|
55
|
+
load_stories
|
56
|
+
else
|
57
|
+
refresh if (PivotalShell::Configuration.refresh_interval>0) && (self[:last_updated_at]+PivotalShell::Configuration.refresh_interval*60 < Time.now)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_stories(options={})
|
62
|
+
# Pivotal Tracker API limits one stories call to 3000 results. Thus we need to paginate.
|
63
|
+
all_stories = []
|
64
|
+
limit = 3000
|
65
|
+
offset = 0
|
66
|
+
begin
|
67
|
+
new_stories = PivotalShell::Configuration.project.stories.all(options.merge(:limit => limit, :offset => offset))
|
68
|
+
all_stories += new_stories unless new_stories.empty?
|
69
|
+
offset+=limit
|
70
|
+
end until new_stories.empty?
|
71
|
+
save_stories_to_cache(all_stories)
|
72
|
+
self[:last_updated_at] = Time.now
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_users
|
76
|
+
PivotalShell::Configuration.project.memberships.all.each do |membership|
|
77
|
+
if row = db.execute('SELECT id FROM users WHERE name=?', membership.name).first
|
78
|
+
db.execute('UPDATE USERS SET role=?, initials=?, email=? WHERE id=?', membership.role, membership.initials, membership.email, row['id'])
|
79
|
+
else
|
80
|
+
db.execute('INSERT INTO USERS (name, role, initials, email) VALUES(?,?,?,?)', membership.name, membership.role, membership.initials, membership.email)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@users = load_users_from_cache
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_users_from_cache
|
87
|
+
db.execute('SELECT id,name,role,initials,email FROM users').inject({}) do |hash,row|
|
88
|
+
hash[row['name'].to_s] = row
|
89
|
+
hash
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def user_id_by_name(name)
|
94
|
+
name=name.to_s
|
95
|
+
return nil if name==''
|
96
|
+
user = users[name]
|
97
|
+
if user.nil?
|
98
|
+
load_users
|
99
|
+
user = users[name]
|
100
|
+
if user.nil?
|
101
|
+
db.execute('INSERT INTO USERS (name, role, initials, email) VALUES(?,"","","")', name)
|
102
|
+
load_users_from_cache
|
103
|
+
user=users[name]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
user && user['id']
|
107
|
+
end
|
108
|
+
|
109
|
+
def save_stories_to_cache(stories)
|
110
|
+
insert_query = 'INSERT INTO stories (url, name, description, story_type, estimate, labels, current_state, requested_by_id, owned_by_id, created_at, accepted_at, id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'
|
111
|
+
update_query = 'UPDATE stories SET url=?, name=?, description=?, story_type=?, estimate=?, labels=?, current_state=?, requested_by_id=?, owned_by_id=?, created_at=?, accepted_at=? WHERE id=?'
|
112
|
+
stories.each do |story|
|
113
|
+
requested_by_id = user_id_by_name(story.requested_by)
|
114
|
+
owned_by_id = user_id_by_name(story.owned_by)
|
115
|
+
created_at = story.created_at && story.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
116
|
+
accepted_at = story.accepted_at && story.accepted_at.strftime("%Y-%m-%d %H:%M:%S")
|
117
|
+
query = db.execute('SELECT id FROM stories WHERE id=?', story.id).first ? update_query : insert_query
|
118
|
+
db.execute query, story.url, story.name, story.description, story.story_type, story.estimate, story.labels, story.current_state, requested_by_id, owned_by_id, created_at, accepted_at, story.id
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_tables
|
123
|
+
db.execute('CREATE TABLE IF NOT EXISTS settings (name VARCHAR NOT NULL, value VARCHAR)')
|
124
|
+
db.execute('CREATE UNIQUE INDEX IF NOT EXISTS settings_name ON settings(name)')
|
125
|
+
db.execute('CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, role VARCHAR NOT NULL, name VARCHAR NOT NULL, initials VARCHAR NOT NULL, email VARCHAR NOT NULL)')
|
126
|
+
db.execute('CREATE UNIQUE INDEX IF NOT EXISTS users_name ON users(name)')
|
127
|
+
db.execute(<<SQL
|
128
|
+
CREATE TABLE IF NOT EXISTS stories (
|
129
|
+
id INTEGER NOT NULL PRIMARY KEY,
|
130
|
+
url VARCHAR NOT NULL,
|
131
|
+
name VARCHAR NOT NULL,
|
132
|
+
description TEXT,
|
133
|
+
story_type VARCHAR,
|
134
|
+
estimate INTEGER,
|
135
|
+
labels VARCHAR,
|
136
|
+
current_state VARCHAR,
|
137
|
+
requested_by_id INTEGER,
|
138
|
+
owned_by_id INTEGER,
|
139
|
+
created_at DATETIME,
|
140
|
+
accepted_at DATETIME
|
141
|
+
)
|
142
|
+
SQL
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class PivotalShell::Cache
|
2
|
+
class Story
|
3
|
+
ATTRIBUTES=%w(requested_by_id name id current_state accepted_at labels url estimate description created_at owned_by_id story_type)
|
4
|
+
STATUSES = %w(unscheduled unstarted started finished delivered accepted rejected)
|
5
|
+
TYPES = %w(feature bug chore)
|
6
|
+
|
7
|
+
ATTRIBUTES.each do |attribute|
|
8
|
+
attr_reader attribute
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(source={})
|
12
|
+
if source.is_a? PivotalTracker::Story
|
13
|
+
raise 'TODO'
|
14
|
+
elsif source.is_a? Hash
|
15
|
+
create_from_hash(source)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(id)
|
20
|
+
hash = PivotalShell::Configuration.cache.db.execute("SELECT * FROM stories WHERE id=?", id).first
|
21
|
+
hash && new(hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all(params)
|
25
|
+
conditions = []
|
26
|
+
query_params = []
|
27
|
+
if params[:unowned]
|
28
|
+
conditions << "owned_by_id IS NULL"
|
29
|
+
elsif params[:owner] && (owner = PivotalShell::Cache::User.find(params[:owner]))
|
30
|
+
conditions << "owned_by_id==?"
|
31
|
+
query_params << owner.id
|
32
|
+
end
|
33
|
+
|
34
|
+
if params[:state]
|
35
|
+
params[:state] = [params[:state]].flatten
|
36
|
+
conditions << "current_state IN (#{(["?"]*params[:state].length).join(',')})"
|
37
|
+
query_params << params[:state]
|
38
|
+
end
|
39
|
+
|
40
|
+
if params[:type]
|
41
|
+
params[:type] = [params[:type]].flatten
|
42
|
+
conditions << "story_type IN (#{(["?"]*params[:type].length).join(',')})"
|
43
|
+
query_params << params[:type]
|
44
|
+
end
|
45
|
+
|
46
|
+
query = 'SELECT * FROM stories'
|
47
|
+
query << ' WHERE '+conditions.map{|c| "(#{c})"}.join(' AND ') unless conditions.empty?
|
48
|
+
puts query.inspect
|
49
|
+
PivotalShell::Configuration.cache.db.execute(query, query_params).map {|r| new(r)}
|
50
|
+
end
|
51
|
+
|
52
|
+
def owned_by
|
53
|
+
return nil if owned_by_id.nil?
|
54
|
+
@owned_by ||= PivotalShell::Cache::User.find(owned_by_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def requested_by
|
58
|
+
return nil if requested_by_id.nil?
|
59
|
+
@requested_by ||= PivotalShell::Cache::User.find(requested_by_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
def create_from_hash(hash)
|
64
|
+
PivotalShell::Cache::Story::ATTRIBUTES.each do |attribute|
|
65
|
+
self.instance_variable_set("@#{attribute}", hash[attribute])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class PivotalShell::Cache
|
2
|
+
class User
|
3
|
+
ATTRIBUTES=%w(name id initials email)
|
4
|
+
|
5
|
+
ATTRIBUTES.each do |attribute|
|
6
|
+
attr_reader attribute
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(source={})
|
10
|
+
if source.is_a? PivotalTracker::Membership
|
11
|
+
raise 'TODO'
|
12
|
+
elsif source.is_a? Hash
|
13
|
+
create_from_hash(source)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
name
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find(id)
|
22
|
+
hash = PivotalShell::Configuration.cache.db.execute("SELECT * FROM users WHERE id=? OR initials=? OR name=? OR email=?", id, id, id, id).first
|
23
|
+
hash && new(hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def create_from_hash(hash)
|
28
|
+
PivotalShell::Cache::User::ATTRIBUTES.each do |attribute|
|
29
|
+
self.instance_variable_set("@#{attribute}", hash[attribute])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module PivotalShell::Commands
|
2
|
+
class PivotalShell::Commands::Commit < PivotalShell::Command
|
3
|
+
def initialize(options)
|
4
|
+
@message = options[0]
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
puts 'Doesnt work yet! Sorry.'
|
9
|
+
#exit 0 if (message =~ /\[#\d+\]/) || (message =~ /merge/i) # there is already a task ID in the message, or it is a merge
|
10
|
+
|
11
|
+
#input = File.open('/dev/tty', 'r')
|
12
|
+
|
13
|
+
#PivotalShell::Configuration.cache.update
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PivotalShell::Commands
|
2
|
+
class PivotalShell::Commands::Reload < PivotalShell::Command
|
3
|
+
def initialize(options)
|
4
|
+
opts = OptionParser.new do |opts|
|
5
|
+
opts.banner = "Completely reload the stories database from Pivotal Tracker"
|
6
|
+
|
7
|
+
opts.on_tail('--help', 'Show this help') do
|
8
|
+
puts opts
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
opts.parse!(options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
PivotalShell::Configuration.cache.reload
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,8 +5,6 @@ module PivotalShell::Commands
|
|
5
5
|
def initialize(options)
|
6
6
|
@options = {:params => {}}
|
7
7
|
|
8
|
-
available_statuses = %w(unscheduled unstarted started finished delivered accepted rejected)
|
9
|
-
available_types = %w(features bugs chores)
|
10
8
|
|
11
9
|
opts = OptionParser.new do |opts|
|
12
10
|
opts.banner = "List Pivotal stories\nUsage: pivotal stories [options]\n\nThe default is to show all unfinished stories assigned to yourself\n\nDisplay format:\n [id]\n type: Feature/Bug/Chore\n estimate: * (irrelevant)/0/1/2/3\n state: . (unscheduled)/Unstarted/Started/Finished/Delivered/Accepted/Rejected\n title\n\nOptions:"
|
@@ -16,17 +14,17 @@ module PivotalShell::Commands
|
|
16
14
|
@options[:all] = true
|
17
15
|
end
|
18
16
|
|
19
|
-
|
17
|
+
PivotalShell::Cache::Story::STATUSES.each do |status|
|
20
18
|
opts.on("--#{status}", "Show #{status} stories") do
|
21
19
|
@options[:params][:state] ||= []
|
22
20
|
@options[:params][:state] << status
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
|
-
|
27
|
-
opts.on("--#{type}", "Show #{type}") do
|
24
|
+
PivotalShell::Cache::Story::TYPES.each do |type|
|
25
|
+
opts.on("--#{type}s", "Show #{type}") do
|
28
26
|
@options[:params][:type] ||= []
|
29
|
-
@options[:params][:type] << type
|
27
|
+
@options[:params][:type] << type
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
@@ -35,7 +33,7 @@ module PivotalShell::Commands
|
|
35
33
|
end
|
36
34
|
|
37
35
|
opts.on('--unowned', 'Show tasks not assigned to anyone') do
|
38
|
-
@options[:unowned] = true
|
36
|
+
@options[:params][:unowned] = true
|
39
37
|
end
|
40
38
|
|
41
39
|
opts.on('--anyone', 'Show tasks assigned to anyone') do
|
@@ -58,10 +56,7 @@ module PivotalShell::Commands
|
|
58
56
|
end
|
59
57
|
|
60
58
|
def execute
|
61
|
-
stories = PivotalShell::
|
62
|
-
if @options[:unowned]
|
63
|
-
stories.reject!{|s| !s.owned_by.nil?}
|
64
|
-
end
|
59
|
+
stories = PivotalShell::Cache::Story.all(@options[:params])
|
65
60
|
|
66
61
|
puts stories.empty? ? 'No stories!' : stories.map{|s| "#{("[#{s.id}]").rjust 12} #{PivotalShell::Configuration.icon(s.story_type, s.current_state, s.estimate)} #{s.name.strip}"}.join("\n")
|
67
62
|
end
|
@@ -22,11 +22,12 @@ module PivotalShell::Commands
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def execute
|
25
|
-
@story = PivotalShell::
|
25
|
+
@story = PivotalShell::Cache::Story.find(@story_id)
|
26
26
|
if @story.nil?
|
27
27
|
puts 'Story not found'
|
28
28
|
else
|
29
29
|
puts ["[#{@story.id}] - #{@story.name}",
|
30
|
+
|
30
31
|
"State: #{@story.current_state}",
|
31
32
|
"Owner: #{@story.owned_by}",
|
32
33
|
"Creator: #{@story.requested_by}",
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module PivotalShell::Commands
|
2
|
+
class PivotalShell::Commands::Update < PivotalShell::Command
|
3
|
+
def initialize(options)
|
4
|
+
opts = OptionParser.new do |opts|
|
5
|
+
opts.banner = "Update the stories database from Pivotal Tracker"
|
6
|
+
|
7
|
+
opts.on_tail('--help', 'Show this help') do
|
8
|
+
puts opts
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
opts.parse!(options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
PivotalShell::Configuration.cache.refresh
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,9 +2,13 @@ require 'yaml'
|
|
2
2
|
require 'pivotal_tracker'
|
3
3
|
|
4
4
|
module PivotalShell::Configuration
|
5
|
+
DEFAULTS = {
|
6
|
+
'refresh_interval' => 15
|
7
|
+
}
|
8
|
+
|
5
9
|
def self.load
|
6
10
|
@global_config = YAML.load_file(global_config_path)
|
7
|
-
@project_config = YAML.load_file(project_config_path)
|
11
|
+
@project_config = YAML.load_file(File.join(project_config_path,'.pivotalrc'))
|
8
12
|
PivotalTracker::Client.token = @global_config['api_token']
|
9
13
|
end
|
10
14
|
|
@@ -12,10 +16,18 @@ module PivotalShell::Configuration
|
|
12
16
|
@project ||= PivotalTracker::Project.find(@project_config['project_id'])
|
13
17
|
end
|
14
18
|
|
19
|
+
def self.cache
|
20
|
+
@cache ||= PivotalShell::Cache.new(File.join(project_config_path,'.pivotal_cache'))
|
21
|
+
end
|
22
|
+
|
15
23
|
def self.me
|
16
24
|
@me ||= @project_config['me']
|
17
25
|
end
|
18
26
|
|
27
|
+
def self.refresh_interval
|
28
|
+
@refresh_interval ||= @project_config['refresh_interval'] || @global_config['refresh_interval'] || DEFAULTS['refresh_interval']
|
29
|
+
end
|
30
|
+
|
19
31
|
def self.global_config_path
|
20
32
|
@global_config_path ||= File.expand_path('~/.pivotalrc')
|
21
33
|
end
|
@@ -45,7 +57,7 @@ module PivotalShell::Configuration
|
|
45
57
|
end
|
46
58
|
|
47
59
|
def self.icon(type, status, estimate)
|
48
|
-
type_icon(type) + ' ' + estimate_icon(estimate) + ' ' + status_icon(status)
|
60
|
+
type_icon(type).to_s + ' ' + estimate_icon(estimate).to_s + ' ' + status_icon(status).to_s
|
49
61
|
end
|
50
62
|
|
51
63
|
private
|
@@ -58,7 +70,7 @@ private
|
|
58
70
|
if dirs.empty? || File.join(dirs, '.pivotalrc')==global_config_path
|
59
71
|
raise PivotalShell::Exception.new('No project .pivotalrc found')
|
60
72
|
else
|
61
|
-
File.join(dirs
|
73
|
+
File.join(dirs)
|
62
74
|
end
|
63
75
|
end
|
64
76
|
end
|
data/pivotal_shell.gemspec
CHANGED
@@ -11,7 +11,8 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "https://github.com/leonid-shevtsov/pivotal_shell"
|
12
12
|
s.summary = %q{A command-line client for Pivotal Tracker}
|
13
13
|
|
14
|
-
s.add_dependency 'pivotal-tracker', '
|
14
|
+
s.add_dependency 'pivotal-tracker', '>0.3'
|
15
|
+
s.add_dependency 'sqlite3'
|
15
16
|
|
16
17
|
s.files = `git ls-files`.split("\n")
|
17
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pivotal_shell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Leonid Shevtsov
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-02-12 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - ">"
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
hash: 13
|
30
30
|
segments:
|
@@ -33,6 +33,20 @@ dependencies:
|
|
33
33
|
version: "0.3"
|
34
34
|
type: :runtime
|
35
35
|
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sqlite3
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
36
50
|
description:
|
37
51
|
email:
|
38
52
|
- leonid@shevtsov.me
|
@@ -49,12 +63,19 @@ files:
|
|
49
63
|
- README.markdown
|
50
64
|
- Rakefile
|
51
65
|
- bin/pivotal
|
66
|
+
- examples/commit-msg
|
52
67
|
- lib/pivotal_shell.rb
|
68
|
+
- lib/pivotal_shell/cache.rb
|
69
|
+
- lib/pivotal_shell/cache/story.rb
|
70
|
+
- lib/pivotal_shell/cache/user.rb
|
53
71
|
- lib/pivotal_shell/command.rb
|
72
|
+
- lib/pivotal_shell/commands/commit.rb
|
54
73
|
- lib/pivotal_shell/commands/finish.rb
|
74
|
+
- lib/pivotal_shell/commands/reload.rb
|
55
75
|
- lib/pivotal_shell/commands/start.rb
|
56
76
|
- lib/pivotal_shell/commands/stories.rb
|
57
77
|
- lib/pivotal_shell/commands/story.rb
|
78
|
+
- lib/pivotal_shell/commands/update.rb
|
58
79
|
- lib/pivotal_shell/configuration.rb
|
59
80
|
- lib/pivotal_shell/version.rb
|
60
81
|
- pivotal_shell.gemspec
|
@@ -88,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
109
|
requirements: []
|
89
110
|
|
90
111
|
rubyforge_project:
|
91
|
-
rubygems_version: 1.
|
112
|
+
rubygems_version: 1.5.0
|
92
113
|
signing_key:
|
93
114
|
specification_version: 3
|
94
115
|
summary: A command-line client for Pivotal Tracker
|