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 CHANGED
@@ -2,3 +2,4 @@ pkg/*
2
2
  *.gem
3
3
  .bundle
4
4
  .pivotalrc
5
+ .pivotal_cache
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pivotal_shell (0.1.0)
5
- pivotal-tracker (= 0.3)
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.0)
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!
@@ -1,6 +1,6 @@
1
1
  # pivotal_shell
2
2
 
3
- A command-line wrapper for [Pivotal Tracker](http://www.pivotaltracker.com)
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
@@ -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"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ pivotal commit $1
@@ -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
@@ -1,4 +1,8 @@
1
1
  class PivotalShell::Command
2
2
  def initialize(options)
3
3
  end
4
+
5
+ def execute
6
+ raise "Override me!"
7
+ end
4
8
  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
@@ -29,6 +29,7 @@ module PivotalShell::Commands
29
29
  puts 'Story is not in a started state: '+@story.name
30
30
  else
31
31
  @story.update(:current_state => 'started')
32
+ PivotalShell::Configuration.cache.refresh
32
33
  end
33
34
  end
34
35
  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
@@ -31,6 +31,7 @@ module PivotalShell::Commands
31
31
  puts 'Story is already finished: '+@story.name
32
32
  else
33
33
  @story.update(:current_state => 'started')
34
+ PivotalShell::Configuration.cache.refresh
34
35
  end
35
36
  end
36
37
  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
- available_statuses.each do |status|
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
- available_types.each do |type|
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[0..-2] # chomp s
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::Configuration.project.stories.all(@options[:params])
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::Configuration.project.stories.find(@story_id)
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, '.pivotalrc')
73
+ File.join(dirs)
62
74
  end
63
75
  end
64
76
  end
@@ -1,3 +1,3 @@
1
1
  module PivotalShell
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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', '=0.3'
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: 27
5
- prerelease: false
4
+ hash: 23
5
+ prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.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: 2010-12-06 00:00:00 +02:00
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.3.7
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