pivotal_shell 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|