ki_pivotal 0.0.2 → 0.0.3
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 +2 -1
- data/bin/ki_pivotal +49 -24
- data/lib/ki_pivotal/api.rb +36 -14
- data/lib/ki_pivotal/interactive.rb +70 -0
- data/lib/ki_pivotal/kiseru.rb +27 -6
- data/lib/ki_pivotal/session.rb +1 -0
- data/lib/ki_pivotal/version.rb +1 -1
- data/lib/ki_pivotal.rb +1 -0
- data/spec/integration/api_spec.rb +10 -3
- data/spec/spec_helper.rb +3 -0
- metadata +24 -23
data/.gitignore
CHANGED
data/bin/ki_pivotal
CHANGED
@@ -19,16 +19,14 @@ require 'ki_pivotal'
|
|
19
19
|
require 'colored'
|
20
20
|
include GLI
|
21
21
|
|
22
|
-
program_desc 'A command-line interface for Pivotal Tracker'
|
22
|
+
program_desc 'A Kiseru command-line interface for Pivotal Tracker'
|
23
23
|
|
24
24
|
version KiPivotal::VERSION
|
25
25
|
|
26
|
-
desc '
|
27
|
-
command :
|
26
|
+
desc 'Initialise config files'
|
27
|
+
command :init do |c|
|
28
28
|
c.action do |global_options,options,args|
|
29
|
-
|
30
|
-
token = $stdin.gets.chomp
|
31
|
-
Kiseru::Config[:ki_pivotal].write('api_token', token)
|
29
|
+
Kiseru::Config[:ki_pivotal].write('api_token', KiPivotal::Interactive.repeatedly_ask("Please enter your Pivotal Tracker API token.\n This can be found at the bottom of your 'Profile' page.\n Token"))
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
@@ -36,33 +34,60 @@ desc 'Get projects'
|
|
36
34
|
command :projects do |c|
|
37
35
|
c.action do |global_options,options,args|
|
38
36
|
pretty = $stdout.tty?
|
39
|
-
$stdout.puts KiPivotal::Api.projects(:pretty => pretty)
|
37
|
+
$stdout.puts KiPivotal::Api.new.projects(:pretty => pretty)
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
43
|
-
desc 'Create a
|
44
|
-
command :
|
45
|
-
c.
|
41
|
+
desc 'Create a story'
|
42
|
+
command :new_story do |c|
|
43
|
+
c.desc "The project ID"
|
44
|
+
c.flag [:p, :project]
|
45
|
+
c.desc "A file containing the JSON for a story"
|
46
46
|
c.flag [:f, :file]
|
47
|
+
c.desc "Interactive mode"
|
48
|
+
c.switch [:i, :interactive]
|
47
49
|
c.action do |global_options,options,args|
|
50
|
+
# When we have received JSON via a pipe/file
|
51
|
+
# interactive mode should allow a missing
|
52
|
+
# project ID to be specified.
|
53
|
+
if options[:f] || !$stdin.tty?
|
54
|
+
story_json = options[:f] ? File.read(options[:f]) : $stdin.read
|
55
|
+
unless project_id = options[:project]
|
56
|
+
projects = MultiJson.load(KiPivotal::Api.new.projects)
|
57
|
+
if options[:i]
|
58
|
+
$stdin = STDIN.reopen('/dev/tty')
|
59
|
+
project_id = KiPivotal::Interactive.select_project
|
60
|
+
else
|
61
|
+
$stderr.puts "\n A story cannot be created without specifying which project to create it under:"
|
62
|
+
$stderr.puts " * The ID of a chosen project may be specified with --project=<ID>"
|
63
|
+
$stderr.puts " * A project may be chosen interactively by using the switch -i."
|
64
|
+
$stderr.puts "\n Available projects:"
|
48
65
|
|
49
|
-
|
50
|
-
projects = KiPivotal::Api.projects
|
66
|
+
projects.each { |project| $stderr.puts " #{project['id']} ".green + " #{project['name']}" }
|
51
67
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
68
|
+
$stderr.puts
|
69
|
+
raise "No project given"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# When there is no JSON provided
|
73
|
+
# interactive mode should allow the
|
74
|
+
# entire story to be created.
|
75
|
+
else
|
76
|
+
if options[:i]
|
77
|
+
project_id = KiPivotal::Interactive.select_project
|
78
|
+
story_json = KiPivotal::Interactive.new_story
|
79
|
+
else
|
80
|
+
$stderr.puts "\n Input must be given to this tool to create a story."
|
81
|
+
$stderr.puts "\n You must do one of the following:"
|
82
|
+
$stderr.puts " * Provide an input file with --file=<file> or -f=<file>."
|
83
|
+
$stderr.puts " * Pipe compatible JSON representing a story to this tool."
|
84
|
+
$stderr.puts " * Use the interactive switch -i to create a story via the command line."
|
85
|
+
$stderr.puts
|
86
|
+
raise "No input given."
|
87
|
+
end
|
59
88
|
end
|
60
89
|
|
61
|
-
|
62
|
-
story_json = File.read(options[:f])
|
63
|
-
end
|
64
|
-
story_json ||= $stdin.read
|
65
|
-
$stdout.puts KiPivotal::Api.create_story(project_id, story_json)
|
90
|
+
$stdout.puts KiPivotal::Api.new.new_story(project_id, story_json)
|
66
91
|
end
|
67
92
|
end
|
68
93
|
|
data/lib/ki_pivotal/api.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
module KiPivotal
|
2
2
|
|
3
|
+
class ApiError < StandardError; end;
|
4
|
+
|
3
5
|
class Api
|
4
6
|
|
7
|
+
def initialize(config = Kiseru::Config[:ki_pivotal])
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
5
11
|
# TODO - this is only used internally, unlike the other methods
|
6
12
|
# It also doesn't return JSON because it is used internally
|
7
|
-
def
|
13
|
+
def get_token(username, password)
|
8
14
|
PivotalTracker::Client.token(username, password)
|
9
15
|
end
|
10
16
|
|
11
|
-
def
|
17
|
+
def projects(opts = {})
|
12
18
|
with_session do
|
13
19
|
pretty = opts.fetch(:pretty) { true }
|
14
20
|
|
@@ -25,7 +31,7 @@ module KiPivotal
|
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
|
-
def
|
34
|
+
def new_story(project_id, json)
|
29
35
|
from_and_to_json(json, true) do |data|
|
30
36
|
with_session do
|
31
37
|
data.delete('ki_type')
|
@@ -53,28 +59,44 @@ module KiPivotal
|
|
53
59
|
# * interact with PivotalTracker gem
|
54
60
|
# * convert PivotalTracker gem response (object) into data
|
55
61
|
# * convert data back into JSON
|
56
|
-
def
|
57
|
-
|
62
|
+
def add_story_to_project(project_id, data)
|
63
|
+
begin
|
64
|
+
project = PivotalTracker::Project.find(project_id)
|
65
|
+
rescue RestClient::ResourceNotFound => e
|
66
|
+
raise ApiError, "Invalid project ID #{project_id}"
|
67
|
+
end
|
68
|
+
|
58
69
|
story = project.stories.create(data)
|
59
70
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
71
|
+
if story.errors.empty?
|
72
|
+
created_story = {}
|
73
|
+
story.instance_variables.each do |instance_var|
|
74
|
+
string_name = instance_var.to_s.gsub('@','')
|
75
|
+
created_story[string_name] = story.instance_variable_get(instance_var)
|
76
|
+
end
|
77
|
+
created_story
|
78
|
+
else
|
79
|
+
all_errors = story.errors.errors.last
|
80
|
+
raise ApiError, "JSON for story was invalid: #{all_errors}"
|
64
81
|
end
|
65
|
-
created_story
|
66
82
|
end
|
67
83
|
|
68
|
-
def
|
84
|
+
def from_and_to_json(json, pretty=false)
|
69
85
|
data = MultiJson.load(json)
|
70
86
|
new_data = yield data
|
71
87
|
MultiJson.dump(new_data, :pretty => pretty)
|
72
88
|
end
|
73
89
|
|
74
|
-
def
|
75
|
-
session = Session.new(
|
90
|
+
def with_session
|
91
|
+
session = Session.new(@config)
|
76
92
|
session.init
|
77
|
-
|
93
|
+
begin
|
94
|
+
yield
|
95
|
+
rescue PivotalTracker::Client::NoToken => e
|
96
|
+
raise ApiError, "Authentication failed. Check your API token."
|
97
|
+
rescue RestClient::Unauthorized => e
|
98
|
+
raise ApiError, "Not authorized. Check your API token."
|
99
|
+
end
|
78
100
|
end
|
79
101
|
|
80
102
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module KiPivotal
|
2
|
+
|
3
|
+
class Interactive
|
4
|
+
# "Resistance is futile!", "We will get an answer!"
|
5
|
+
def self.repeatedly_ask(question)
|
6
|
+
response = ""
|
7
|
+
while response == ""
|
8
|
+
response = simple_query(question)
|
9
|
+
end
|
10
|
+
response
|
11
|
+
end
|
12
|
+
# "We have ways of making you talk!"
|
13
|
+
def self.repeatedly_choose_from(things, question)
|
14
|
+
index = nil
|
15
|
+
while index.nil?
|
16
|
+
index = choose_from_many(things, question)
|
17
|
+
end
|
18
|
+
index
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.choose_from_many(things, question)
|
22
|
+
$stderr.puts "\n "+question
|
23
|
+
|
24
|
+
things.each_with_index { |thing, index| $stderr.puts " #{index+1} ".green + " #{thing}" }
|
25
|
+
|
26
|
+
index = ( (simple_query( "Enter an index (#{(1..things.size)})" ).to_i) - 1 )
|
27
|
+
index = ( index >= 0 && index < things.size ) ? index : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.simple_query(prompt)
|
31
|
+
if prompt.nil?
|
32
|
+
$stderr.print "\n> "
|
33
|
+
else
|
34
|
+
$stderr.print "\n #{prompt}: "
|
35
|
+
end
|
36
|
+
$stdin.gets.chomp
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.select_project
|
40
|
+
projects = MultiJson.load(KiPivotal::Api.new.projects)
|
41
|
+
|
42
|
+
if projects.size > 1
|
43
|
+
index = repeatedly_choose_from(projects.map { |p| p['name'] }, "Which project should this story be created under?")
|
44
|
+
else
|
45
|
+
index = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
$stderr.puts "\n Story will be created under the project, '#{projects[index]['name']}'"
|
49
|
+
projects[index]['id']
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.new_story
|
53
|
+
name = repeatedly_ask("Name of story")
|
54
|
+
|
55
|
+
description = repeatedly_ask("Description of story")
|
56
|
+
|
57
|
+
types = ["bug","feature","release","chore"]
|
58
|
+
type = types[repeatedly_choose_from(types, "What type of story is this?")]
|
59
|
+
|
60
|
+
{
|
61
|
+
name: name,
|
62
|
+
description: description,
|
63
|
+
story_type: type,
|
64
|
+
requested_by: 'Kiseru',
|
65
|
+
ki_type: 'pivotal_story'
|
66
|
+
}.to_json
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/ki_pivotal/kiseru.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
require 'pathname'
|
2
|
+
require 'fileutils'
|
2
3
|
|
3
4
|
module Kiseru
|
4
5
|
|
6
|
+
class ConfigError < StandardError; end;
|
7
|
+
|
5
8
|
class ConfigDir
|
6
9
|
|
7
|
-
|
10
|
+
NAME = '.kiseru'
|
8
11
|
|
9
12
|
attr_reader :path
|
10
13
|
|
11
14
|
def initialize(opts = {})
|
15
|
+
name = opts.fetch(:name) { NAME }
|
12
16
|
root = Pathname.new(File.expand_path(opts.fetch(:root) { '~' }))
|
13
|
-
@path = root +
|
17
|
+
@path = root + name
|
14
18
|
unless File.directory?(@path)
|
15
19
|
Dir.mkdir(@path)
|
20
|
+
FileUtils.chmod(0700, @path)
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
@@ -24,18 +29,34 @@ module Kiseru
|
|
24
29
|
|
25
30
|
class Config
|
26
31
|
|
32
|
+
attr_reader :path
|
33
|
+
|
27
34
|
def self.[](key)
|
28
35
|
ConfigDir.new.config(key)
|
29
36
|
end
|
30
37
|
|
31
38
|
def initialize(root_path, app_name)
|
32
|
-
|
33
|
-
|
39
|
+
@path = (root_path + "#{app_name}.yml").to_s
|
40
|
+
unless File.exists?(@path)
|
41
|
+
FileUtils.touch(@path)
|
42
|
+
FileUtils.chmod(0600, @path)
|
43
|
+
end
|
44
|
+
@store = YAML::Store.new(@path)
|
45
|
+
@store.transaction do
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def ensure_present(*keys)
|
50
|
+
keys.each do |key|
|
51
|
+
if read(key).nil?
|
52
|
+
raise ConfigError, "'#{key}' is not defined in config"
|
53
|
+
end
|
54
|
+
end
|
34
55
|
end
|
35
56
|
|
36
|
-
def write(key,
|
57
|
+
def write(key, value)
|
37
58
|
@store.transaction do
|
38
|
-
@store[key] =
|
59
|
+
@store[key] = value
|
39
60
|
end
|
40
61
|
end
|
41
62
|
|
data/lib/ki_pivotal/session.rb
CHANGED
data/lib/ki_pivotal/version.rb
CHANGED
data/lib/ki_pivotal.rb
CHANGED
@@ -4,8 +4,15 @@ include KiPivotal
|
|
4
4
|
|
5
5
|
describe KiPivotal::Api do
|
6
6
|
|
7
|
+
let(:config) do
|
8
|
+
Kiseru::ConfigDir.new(:root => SPEC_DIR,
|
9
|
+
:name => SPEC_CONFIG_DIR).config(:ki_pivotal)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
7
13
|
it "returns projects", :vcr do
|
8
|
-
|
14
|
+
api = Api.new(config)
|
15
|
+
json = api.projects
|
9
16
|
projects = MultiJson.load(json)
|
10
17
|
|
11
18
|
projects.each do |project|
|
@@ -15,7 +22,6 @@ describe KiPivotal::Api do
|
|
15
22
|
end
|
16
23
|
|
17
24
|
it "creates bugs", :vcr do
|
18
|
-
# uses open-source kiseru tracker project
|
19
25
|
name = "Something is broken"
|
20
26
|
|
21
27
|
input_json = MultiJson.dump({ 'ki_type' => 'pivotal_story',
|
@@ -23,7 +29,8 @@ describe KiPivotal::Api do
|
|
23
29
|
'requested_by' => 'Kiseru',
|
24
30
|
'story_type' => 'bug'
|
25
31
|
})
|
26
|
-
|
32
|
+
api = Api.new(config)
|
33
|
+
json = api.new_story(617613, input_json)
|
27
34
|
story = MultiJson.load(json)
|
28
35
|
|
29
36
|
story['id'].should_not be_nil
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ki_pivotal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ date: 2012-08-15 00:00:00.000000000Z
|
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: gli
|
17
|
-
requirement: &
|
17
|
+
requirement: &70107004966400 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70107004966400
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: multi_json
|
28
|
-
requirement: &
|
28
|
+
requirement: &70107004965980 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70107004965980
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: pivotal-tracker
|
39
|
-
requirement: &
|
39
|
+
requirement: &70107004965560 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70107004965560
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: colored
|
50
|
-
requirement: &
|
50
|
+
requirement: &70107004965140 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70107004965140
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: debugger
|
61
|
-
requirement: &
|
61
|
+
requirement: &70107004994920 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70107004994920
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rake
|
72
|
-
requirement: &
|
72
|
+
requirement: &70107004994500 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ! '>='
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: '0'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70107004994500
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: simplecov
|
83
|
-
requirement: &
|
83
|
+
requirement: &70107004994080 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ! '>='
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: '0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70107004994080
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: rspec
|
94
|
-
requirement: &
|
94
|
+
requirement: &70107004993660 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,10 +99,10 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *70107004993660
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: test-construct
|
105
|
-
requirement: &
|
105
|
+
requirement: &70107004993240 !ruby/object:Gem::Requirement
|
106
106
|
none: false
|
107
107
|
requirements:
|
108
108
|
- - ! '>='
|
@@ -110,10 +110,10 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
type: :development
|
112
112
|
prerelease: false
|
113
|
-
version_requirements: *
|
113
|
+
version_requirements: *70107004993240
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: vcr
|
116
|
-
requirement: &
|
116
|
+
requirement: &70107004992820 !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
119
119
|
- - ! '>='
|
@@ -121,10 +121,10 @@ dependencies:
|
|
121
121
|
version: '0'
|
122
122
|
type: :development
|
123
123
|
prerelease: false
|
124
|
-
version_requirements: *
|
124
|
+
version_requirements: *70107004992820
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: webmock
|
127
|
-
requirement: &
|
127
|
+
requirement: &70107004992400 !ruby/object:Gem::Requirement
|
128
128
|
none: false
|
129
129
|
requirements:
|
130
130
|
- - ! '>='
|
@@ -132,7 +132,7 @@ dependencies:
|
|
132
132
|
version: '0'
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
|
-
version_requirements: *
|
135
|
+
version_requirements: *70107004992400
|
136
136
|
description: Kiseru Pivotal Tracker client
|
137
137
|
email:
|
138
138
|
- ben@freeagent.com
|
@@ -152,6 +152,7 @@ files:
|
|
152
152
|
- ki_pivotal.gemspec
|
153
153
|
- lib/ki_pivotal.rb
|
154
154
|
- lib/ki_pivotal/api.rb
|
155
|
+
- lib/ki_pivotal/interactive.rb
|
155
156
|
- lib/ki_pivotal/kiseru.rb
|
156
157
|
- lib/ki_pivotal/session.rb
|
157
158
|
- lib/ki_pivotal/version.rb
|