pivotal_shell 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/.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
|
+
|