pivotal_shell 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +29 -0
- data/README.markdown +65 -0
- data/Rakefile +2 -0
- data/bin/pivotal +28 -0
- data/lib/pivotal_shell/command.rb +4 -0
- data/lib/pivotal_shell/commands/stories.rb +69 -0
- data/lib/pivotal_shell/commands/story.rb +41 -0
- data/lib/pivotal_shell/configuration.rb +64 -0
- data/lib/pivotal_shell/version.rb +3 -0
- data/lib/pivotal_shell.rb +27 -0
- data/pivotal_shell.gemspec +20 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pivotal_shell (0.0.1)
|
5
|
+
pivotal-tracker (= 0.3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
builder (3.0.0)
|
11
|
+
happymapper (0.3.2)
|
12
|
+
libxml-ruby (~> 1.1.3)
|
13
|
+
libxml-ruby (1.1.4)
|
14
|
+
mime-types (1.16)
|
15
|
+
nokogiri (1.4.3.1)
|
16
|
+
pivotal-tracker (0.3.0)
|
17
|
+
builder
|
18
|
+
happymapper (>= 0.3.2)
|
19
|
+
nokogiri (~> 1.4.3.1)
|
20
|
+
rest-client (~> 1.6.0)
|
21
|
+
rest-client (1.6.1)
|
22
|
+
mime-types (>= 1.16)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
pivotal-tracker (= 0.3)
|
29
|
+
pivotal_shell!
|
data/README.markdown
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# pivotal_shell
|
2
|
+
|
3
|
+
A command-line wrapper for [Pivotal Tracker](http://www.pivotaltracker.com)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install pivotal_shell
|
8
|
+
|
9
|
+
## Configuration
|
10
|
+
|
11
|
+
First, you need to [create an API token for your profile](https://www.pivotaltracker.com/profile) (scroll to the bottom) and put it into `~/.pivotalrc`:
|
12
|
+
|
13
|
+
api_token: abcdef0123456789
|
14
|
+
|
15
|
+
The token is the same for all of your Pivotal Tracker projects.
|
16
|
+
|
17
|
+
Second, you need to create a `.pivotalrc` in your project root and set up projectwide settings:
|
18
|
+
|
19
|
+
# For the https://www.pivotaltracker.com/projects/123456 project, the id would be...
|
20
|
+
project_id: 123456
|
21
|
+
|
22
|
+
# these are your initials used in the project
|
23
|
+
me: LS
|
24
|
+
|
25
|
+
Both `.pivotalrc` files are regular YAML files.
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
pivotal
|
30
|
+
|
31
|
+
## Example
|
32
|
+
|
33
|
+
List all your unfinished stories
|
34
|
+
|
35
|
+
pivotal stories
|
36
|
+
|
37
|
+
List all your stories, regardless of status
|
38
|
+
|
39
|
+
pivotal stories --all --mine
|
40
|
+
|
41
|
+
List all finished stories for everyone
|
42
|
+
|
43
|
+
pivotal stories --all --finished
|
44
|
+
|
45
|
+
List all unassigned bugs
|
46
|
+
|
47
|
+
pivotal stories --unowned --bugs
|
48
|
+
|
49
|
+
Show info on a story
|
50
|
+
|
51
|
+
pivotal story 123456
|
52
|
+
|
53
|
+
## TODO
|
54
|
+
|
55
|
+
Start story
|
56
|
+
|
57
|
+
pivotal start 123456
|
58
|
+
|
59
|
+
Finish story
|
60
|
+
|
61
|
+
pivotal finish 123456
|
62
|
+
|
63
|
+
Commit (with git, all comments after the story id go to git, story id gets appended to comments)
|
64
|
+
|
65
|
+
pivotal commit 123456 "some more comments"
|
data/Rakefile
ADDED
data/bin/pivotal
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pivotal_shell'
|
5
|
+
require 'pivotal_shell/configuration'
|
6
|
+
#require 'active_support/core_ext'
|
7
|
+
|
8
|
+
PivotalShell::Configuration.load
|
9
|
+
|
10
|
+
commands = {
|
11
|
+
'stories' => 'show a list of stories',
|
12
|
+
'story' => 'show information about a specific story',
|
13
|
+
}
|
14
|
+
|
15
|
+
banner = "Pivotal command-line client\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"
|
16
|
+
|
17
|
+
command = ARGV.shift
|
18
|
+
|
19
|
+
unless commands.keys.include?(command)
|
20
|
+
puts banner
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
require "pivotal_shell/commands/#{command}"
|
25
|
+
|
26
|
+
command_class = PivotalShell::Commands.const_get(command[0,1].upcase+command[1..-1])
|
27
|
+
|
28
|
+
command_class.new(ARGV).execute
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#<PivotalTracker::Story:0x90b74f4 @jira_url=nil, @requested_by="Pavel Pavlovsky", @name="Add titles for the pages", @attachments=[], @project_id=110960, @jira_id=nil, @id=5952583, @current_state="accepted", @integration_id=nil, @accepted_at=#<DateTime: 212157861559/86400,1/12,2299161>, @labels="ui", @url="http://www.pivotaltracker.com/story/show/5952583", @estimate=nil, @description="so they are identified correctly by user.\nto clarify", @other_id=nil, @created_at=#<DateTime: 5303878313/2160,1/8,2299161>, @owned_by="Leonid Shevtsov", @story_type="chore">
|
2
|
+
|
3
|
+
module PivotalShell::Commands
|
4
|
+
class PivotalShell::Commands::Stories < PivotalShell::Command
|
5
|
+
def initialize(options)
|
6
|
+
@options = {:params => {}}
|
7
|
+
|
8
|
+
available_statuses = %w(unscheduled unstarted started finished delivered accepted rejected)
|
9
|
+
available_types = %w(features bugs chores)
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
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:"
|
13
|
+
|
14
|
+
|
15
|
+
opts.on('--all', 'Show all tasks (reset default filter on state and owner)') do
|
16
|
+
@options[:all] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
available_statuses.each do |status|
|
20
|
+
opts.on("--#{status}", "Show #{status} stories") do
|
21
|
+
@options[:params][:state] ||= []
|
22
|
+
@options[:params][:state] << status
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
available_types.each do |type|
|
27
|
+
opts.on("--#{type}", "Show #{type}") do
|
28
|
+
@options[:params][:type] ||= []
|
29
|
+
@options[:params][:type] << type[0..-2] # chomp s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('--for [USER]', 'Show tasks assigned to USER; accepts comma-separated list') do |user|
|
34
|
+
@options[:params][:owner] = user
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('--unowned', 'Show tasks not assigned to anyone') do
|
38
|
+
@options[:unowned] = true
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('--anyone', 'Show tasks assigned to anyone') do
|
42
|
+
@options[:anyone] = true
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('--mine', 'Show your tasks') do
|
46
|
+
@options[:params][:owner] = PivotalShell::Configuration.me
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on_tail('--help', 'Show this help') do
|
50
|
+
puts opts
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
opts.parse!
|
55
|
+
|
56
|
+
@options[:params][:owner] ||= PivotalShell::Configuration.me unless @options[:unowned] || @options[:anyone] || @options[:all]
|
57
|
+
@options[:params][:state] ||= %w(unestimated unstarted started) unless @options[:all]
|
58
|
+
end
|
59
|
+
|
60
|
+
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
|
65
|
+
|
66
|
+
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
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#<PivotalTracker::Story:0x90b74f4 @jira_url=nil, @requested_by="Pavel Pavlovsky", @name="Add titles for the pages", @attachments=[], @project_id=110960, @jira_id=nil, @id=5952583, @current_state="accepted", @integration_id=nil, @accepted_at=#<DateTime: 212157861559/86400,1/12,2299161>, @labels="ui", @url="http://www.pivotaltracker.com/story/show/5952583", @estimate=nil, @description="so they are identified correctly by user.\nto clarify", @other_id=nil, @created_at=#<DateTime: 5303878313/2160,1/8,2299161>, @owned_by="Leonid Shevtsov", @story_type="chore">
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module PivotalShell::Commands
|
5
|
+
class PivotalShell::Commands::Story < PivotalShell::Command
|
6
|
+
def initialize(options)
|
7
|
+
opts = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Show information on a Pivotal story\nUsage: pivotal story STORY_ID [options]\n\n"
|
9
|
+
|
10
|
+
opts.on_tail('--help', 'Show this help') do
|
11
|
+
puts opts
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
end
|
15
|
+
opts.parse!(options)
|
16
|
+
if options.empty? || options.length>1
|
17
|
+
puts opts
|
18
|
+
exit
|
19
|
+
else
|
20
|
+
@story_id = options.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute
|
25
|
+
@story = PivotalShell::Configuration.project.stories.find(@story_id)
|
26
|
+
if @story.nil?
|
27
|
+
puts 'Story not found'
|
28
|
+
else
|
29
|
+
puts ["[#{@story.id}] - #{@story.name}",
|
30
|
+
"State: #{@story.current_state}",
|
31
|
+
"Owner: #{@story.owned_by}",
|
32
|
+
"Creator: #{@story.requested_by}",
|
33
|
+
"URL: #{@story.url}",
|
34
|
+
"",
|
35
|
+
"#{@story.description.strip}",
|
36
|
+
"",
|
37
|
+
""].join("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pivotal_tracker'
|
3
|
+
|
4
|
+
module PivotalShell::Configuration
|
5
|
+
def self.load
|
6
|
+
@global_config = YAML.load_file(global_config_path)
|
7
|
+
@project_config = YAML.load_file(project_config_path)
|
8
|
+
PivotalTracker::Client.token = @global_config['api_token']
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.project
|
12
|
+
@project ||= PivotalTracker::Project.find(@project_config['project_id'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.me
|
16
|
+
@me ||= @project_config['me']
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.global_config_path
|
20
|
+
@global_config_path ||= File.expand_path('~/.pivotalrc')
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.project_config_path
|
24
|
+
@project_config_path ||= find_project_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.status_icon(status)
|
28
|
+
{
|
29
|
+
'unscheduled' => ' ',
|
30
|
+
'unstarted' => '.',
|
31
|
+
'started' => 'S',
|
32
|
+
'finished' => 'F',
|
33
|
+
'delivered' => 'D',
|
34
|
+
'accepted' => 'A',
|
35
|
+
'rejected' => 'R'
|
36
|
+
}[status]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.estimate_icon(estimate)
|
40
|
+
estimate.nil? ? '*' : ({-1 => '?', 0 => '0', 1=>'1', 2=>'2', 3 => '3'}[estimate] || "[#{estimate.inspect}]")
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.type_icon(type)
|
44
|
+
{'feature' => 'F', 'chore' => 'C', 'bug' => 'B'}[type]
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.icon(type, status, estimate)
|
48
|
+
type_icon(type) + ' ' + estimate_icon(estimate) + ' ' + status_icon(status)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.find_project_config
|
54
|
+
dirs = File.split(Dir.pwd)
|
55
|
+
until dirs.empty? || File.exists?(File.join(dirs, '.pivotalrc'))
|
56
|
+
dirs.pop
|
57
|
+
end
|
58
|
+
if dirs.empty? || File.join(dirs, '.pivotalrc')==global_config_path
|
59
|
+
raise PivotalShell::Exception.new('No project .pivotalrc found')
|
60
|
+
else
|
61
|
+
File.join(dirs, '.pivotalrc')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'pivotal_tracker'
|
2
|
+
|
3
|
+
module PivotalShell
|
4
|
+
class Exception < StandardError; end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'pivotal_shell/command'
|
8
|
+
|
9
|
+
# fixing pivotal-tracker's options encoding; it did not work with array filters
|
10
|
+
module PivotalTracker
|
11
|
+
class <<self
|
12
|
+
def encode_options(options)
|
13
|
+
return nil if !options.is_a?(Hash) || options.empty?
|
14
|
+
options_strings = []
|
15
|
+
# remove options which are not filters, and encode them as such
|
16
|
+
[:limit, :offset].each do |o|
|
17
|
+
options_strings << "#{CGI.escape(o.to_s)}=#{CGI.escape(options.delete(o))}" if options[o]
|
18
|
+
end
|
19
|
+
# assume remaining key-value pairs describe filters, and encode them as such.
|
20
|
+
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(','))}"
|
22
|
+
end
|
23
|
+
options_strings << "filter=#{filters_string.join('+')}" unless filters_string.empty?
|
24
|
+
return "?#{options_strings.join('&')}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pivotal_shell/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pivotal_shell"
|
7
|
+
s.version = PivotalShell::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Leonid Shevtsov"]
|
10
|
+
s.email = ["leonid@shevtsov.me"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/pivotal_shell"
|
12
|
+
s.summary = %q{A command-line client for Pivotal Tracker}
|
13
|
+
|
14
|
+
s.add_dependency 'pivotal-tracker', '=0.3'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pivotal_shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Leonid Shevtsov
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-12-02 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: pivotal-tracker
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 3
|
33
|
+
version: "0.3"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
description:
|
37
|
+
email:
|
38
|
+
- leonid@shevtsov.me
|
39
|
+
executables:
|
40
|
+
- pivotal
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- .gitignore
|
47
|
+
- Gemfile
|
48
|
+
- Gemfile.lock
|
49
|
+
- README.markdown
|
50
|
+
- Rakefile
|
51
|
+
- bin/pivotal
|
52
|
+
- lib/pivotal_shell.rb
|
53
|
+
- lib/pivotal_shell/command.rb
|
54
|
+
- lib/pivotal_shell/commands/stories.rb
|
55
|
+
- lib/pivotal_shell/commands/story.rb
|
56
|
+
- lib/pivotal_shell/configuration.rb
|
57
|
+
- lib/pivotal_shell/version.rb
|
58
|
+
- pivotal_shell.gemspec
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://rubygems.org/gems/pivotal_shell
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.3.7
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: A command-line client for Pivotal Tracker
|
93
|
+
test_files: []
|
94
|
+
|