ki_pivotal 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,4 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- spec/fixtures
18
+ spec/fixtures
19
+ spec/configs
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 'Setup'
27
- command :setup do |c|
26
+ desc 'Initialise config files'
27
+ command :init do |c|
28
28
  c.action do |global_options,options,args|
29
- print "Enter API key> "
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 bug'
44
- command :story do |c|
45
- c.flag [:project]
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
- unless project_id = options[:project]
50
- projects = KiPivotal::Api.projects
66
+ projects.each { |project| $stderr.puts " #{project['id']} ".green + " #{project['name']}" }
51
67
 
52
- $stderr.puts "\n Project ID must be specified with --project=<ID>"
53
- $stderr.puts "\n Available projects:"
54
-
55
- MultiJson.load(projects).each { |project| $stderr.puts " #{project['id']}".green + " #{project['name']}" }
56
-
57
- $stderr.puts
58
- raise "No project ID given"
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
- if options[:f]
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
 
@@ -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 self.get_token(username, password)
13
+ def get_token(username, password)
8
14
  PivotalTracker::Client.token(username, password)
9
15
  end
10
16
 
11
- def self.projects(opts = {})
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 self.create_story(project_id, json)
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 self.add_story_to_project(project_id, data)
57
- project = PivotalTracker::Project.find(project_id)
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
- created_story = {}
61
- story.instance_variables.each do |instance_var|
62
- string_name = instance_var.to_s.gsub('@','')
63
- created_story[string_name] = story.instance_variable_get(instance_var)
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 self.from_and_to_json(json, pretty=false)
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 self.with_session
75
- session = Session.new(Kiseru::Config[:ki_pivotal])
90
+ def with_session
91
+ session = Session.new(@config)
76
92
  session.init
77
- yield
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
+
@@ -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
- DIR_NAME = '.kiseru'
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 + DIR_NAME
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
- file_path = (root_path + "#{app_name}.yml").to_s
33
- @store = YAML::Store.new(file_path)
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, time)
57
+ def write(key, value)
37
58
  @store.transaction do
38
- @store[key] = time
59
+ @store[key] = value
39
60
  end
40
61
  end
41
62
 
@@ -7,6 +7,7 @@ module KiPivotal
7
7
  end
8
8
 
9
9
  def init
10
+ @config.ensure_present('api_token')
10
11
  PivotalTracker::Client.token = @config.read('api_token')
11
12
  end
12
13
 
@@ -1,3 +1,3 @@
1
1
  module KiPivotal
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/ki_pivotal.rb CHANGED
@@ -9,6 +9,7 @@ autoload :Kiseru, 'ki_pivotal/kiseru'
9
9
  module KiPivotal
10
10
 
11
11
  autoload :Api, 'ki_pivotal/api'
12
+ autoload :Interactive, 'ki_pivotal/interactive'
12
13
  autoload :Session, 'ki_pivotal/session'
13
14
 
14
15
  end
@@ -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
- json = Api.projects
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
- json = Api.create_story(617613, input_json)
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
@@ -12,6 +12,9 @@ require 'vcr'
12
12
 
13
13
  include Construct::Helpers
14
14
 
15
+ SPEC_DIR = File.expand_path(File.dirname(__FILE__))
16
+ SPEC_CONFIG_DIR = 'configs'
17
+
15
18
  if [0, 'false', false, 'f', 'n', 'no', 'off'].include?(ENV['VCR'])
16
19
  puts "VCR off"
17
20
  else
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.2
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: &70225014267120 !ruby/object:Gem::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: *70225014267120
25
+ version_requirements: *70107004966400
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: multi_json
28
- requirement: &70225014266700 !ruby/object:Gem::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: *70225014266700
36
+ version_requirements: *70107004965980
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: pivotal-tracker
39
- requirement: &70225014266280 !ruby/object:Gem::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: *70225014266280
47
+ version_requirements: *70107004965560
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: colored
50
- requirement: &70225014265860 !ruby/object:Gem::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: *70225014265860
58
+ version_requirements: *70107004965140
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: debugger
61
- requirement: &70225014265440 !ruby/object:Gem::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: *70225014265440
69
+ version_requirements: *70107004994920
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rake
72
- requirement: &70225014265020 !ruby/object:Gem::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: *70225014265020
80
+ version_requirements: *70107004994500
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: simplecov
83
- requirement: &70225014264600 !ruby/object:Gem::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: *70225014264600
91
+ version_requirements: *70107004994080
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: rspec
94
- requirement: &70225022254180 !ruby/object:Gem::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: *70225022254180
102
+ version_requirements: *70107004993660
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: test-construct
105
- requirement: &70225022253760 !ruby/object:Gem::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: *70225022253760
113
+ version_requirements: *70107004993240
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: vcr
116
- requirement: &70225022253340 !ruby/object:Gem::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: *70225022253340
124
+ version_requirements: *70107004992820
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: webmock
127
- requirement: &70225022252920 !ruby/object:Gem::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: *70225022252920
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