rallycat 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/TODO +5 -12
- data/lib/rallycat.rb +10 -1
- data/lib/rallycat/cat.rb +0 -1
- data/lib/rallycat/cli.rb +73 -51
- data/lib/rallycat/config.rb +16 -0
- data/lib/rallycat/connection.rb +10 -27
- data/lib/rallycat/help.rb +19 -7
- data/lib/rallycat/list.rb +96 -0
- data/lib/rallycat/update.rb +0 -2
- data/lib/rallycat/version.rb +1 -1
- data/spec/integration_spec.rb +105 -1
- data/spec/{lib/rallycat → rallycat}/cat_spec.rb +1 -1
- data/spec/{lib/rallycat → rallycat}/cli_spec.rb +0 -0
- data/spec/{lib/rallycat → rallycat}/connection_spec.rb +2 -2
- data/spec/{lib/rallycat → rallycat}/html_to_text_converter_spec.rb +0 -0
- data/spec/rallycat/list_spec.rb +170 -0
- data/spec/{lib/rallycat → rallycat}/update_spec.rb +3 -3
- data/spec/support/generic_responder.rb +1 -0
- data/spec/support/rally_auth_responder.rb +11 -0
- data/spec/support/rally_iterations_responder.rb +76 -0
- data/spec/support/rally_stories_responder.rb +100 -0
- metadata +31 -36
data/Gemfile.lock
CHANGED
data/TODO
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Rallycat TODO
|
2
2
|
|
3
|
-
* update help (use option_parser help or custom help)
|
4
3
|
* todo time
|
5
4
|
* set todo time
|
6
5
|
* set notes on a task (-n overwrite, -N to append)
|
@@ -8,26 +7,21 @@
|
|
8
7
|
* add update behavior for defects (maybe)
|
9
8
|
* Slow as balls? Make nice wait messages
|
10
9
|
* Add man page
|
11
|
-
* Ordering of arguments matters? (rallycat update TA1234 -b silently fails)
|
12
10
|
* Create .rallycatrc file on first run?
|
13
11
|
* Listing stories
|
14
12
|
* Per iteration
|
15
13
|
* Per project (project configured in rc or passed in?)
|
16
14
|
* AC-only story view
|
15
|
+
* consolidate spec styles
|
17
16
|
|
18
17
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
19
18
|
|
20
19
|
## Ideas
|
21
20
|
|
22
|
-
|
23
|
-
=================
|
21
|
+
rallycat list -s <search phrase> # do a contains lookup for an iteration
|
24
22
|
|
25
|
-
|
26
|
-
rallycat
|
27
|
-
|
28
|
-
rallycat update -p -o "Emilio Cavazos" TA9832
|
29
|
-
rallycat update -b TA1823 -m "Fix specs."
|
30
|
-
````
|
23
|
+
Keep a log of last (n) stories typed into `cat` so the default action for
|
24
|
+
`rallycat cat` will list the last (n) stories for the user.
|
31
25
|
|
32
26
|
### Notes
|
33
27
|
|
@@ -36,13 +30,12 @@ rallycat update -b TA1823 -m "Fix specs."
|
|
36
30
|
|
37
31
|
|
38
32
|
## Stats
|
33
|
+
|
39
34
|
* Stories in progress
|
40
35
|
* Stories accepted
|
41
36
|
* Stories not accepted
|
42
37
|
* Total To-Do Hours
|
43
38
|
|
44
|
-
|
45
|
-
|
46
39
|
rally.vim -> Making User Stories from Vim
|
47
40
|
|
48
41
|
## Rallycat Futures
|
data/lib/rallycat.rb
CHANGED
@@ -1,11 +1,20 @@
|
|
1
1
|
require 'rallycat/cat'
|
2
2
|
require 'rallycat/cli'
|
3
|
+
require 'rallycat/config'
|
3
4
|
require 'rallycat/connection'
|
4
5
|
require 'rallycat/help'
|
5
6
|
require 'rallycat/html_to_text_converter'
|
7
|
+
require 'rallycat/list'
|
6
8
|
require 'rallycat/update'
|
7
9
|
require 'rallycat/version'
|
8
10
|
|
9
11
|
module Rallycat
|
10
|
-
|
12
|
+
class RallycatError < StandardError; end
|
13
|
+
class StoryNotFound < RallycatError; end
|
14
|
+
class InvalidConfigError < RallycatError; end
|
15
|
+
class InvalidCredentialsError < RallycatError; end
|
16
|
+
class ProjectNotFound < RallycatError; end
|
17
|
+
class IterationNotFound < RallycatError; end
|
18
|
+
class UserNotFound < RallycatError; end
|
19
|
+
class TaskNotFound < RallycatError; end
|
11
20
|
end
|
data/lib/rallycat/cat.rb
CHANGED
data/lib/rallycat/cli.rb
CHANGED
@@ -12,19 +12,66 @@ module Rallycat
|
|
12
12
|
parse_command_options!
|
13
13
|
end
|
14
14
|
|
15
|
+
def run
|
16
|
+
case @command
|
17
|
+
when 'cat'
|
18
|
+
api = Rallycat::Connection.new(options[:username], options[:password]).api
|
19
|
+
|
20
|
+
story_number = @argv.shift
|
21
|
+
|
22
|
+
abort 'The "cat" command requires a story or defect number.' unless story_number
|
23
|
+
|
24
|
+
@stdout.puts Rallycat::Cat.new(api).story(story_number)
|
25
|
+
when 'update'
|
26
|
+
api = Rallycat::Connection.new(options[:username], options[:password]).api
|
27
|
+
|
28
|
+
task_number = @argv.shift
|
29
|
+
|
30
|
+
abort 'The "update" command requires a task number.' unless task_number
|
31
|
+
|
32
|
+
opts = {}
|
33
|
+
opts[:blocked] = true if options[:blocked]
|
34
|
+
opts[:state] = "In-Progress" if options[:in_progress]
|
35
|
+
opts[:state] = "Completed" if options[:completed]
|
36
|
+
opts[:state] = "Defined" if options[:defined]
|
37
|
+
opts[:owner] = options[:owner] if options[:owner]
|
38
|
+
|
39
|
+
@stdout.puts Rallycat::Update.new(api).task(task_number, opts)
|
40
|
+
when 'list'
|
41
|
+
api = Rallycat::Connection.new(options[:username], options[:password]).api
|
42
|
+
project = options[:project]
|
43
|
+
|
44
|
+
if options[:iteration]
|
45
|
+
@stdout.puts Rallycat::List.new(api).stories(project, options[:iteration])
|
46
|
+
else
|
47
|
+
@stdout.puts Rallycat::List.new(api).iterations(project)
|
48
|
+
end
|
49
|
+
when 'help'
|
50
|
+
# `puts` calls `to_s`
|
51
|
+
@stdout.puts Rallycat::Help.new
|
52
|
+
else
|
53
|
+
@stdout.puts "'#{@command}' is not a supported command. See 'rallycat help'."
|
54
|
+
end
|
55
|
+
|
56
|
+
rescue Rallycat::RallycatError, ArgumentError => e
|
57
|
+
abort e.message
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
15
62
|
def parse_global_options!
|
16
63
|
@options ||= {}
|
17
64
|
|
18
65
|
global = OptionParser.new do |opts|
|
19
|
-
opts.on('-u USERNAME', '--username') do |username|
|
66
|
+
opts.on('-u USERNAME', '--username', 'Your Rally username') do |username|
|
20
67
|
@options[:username] = username
|
21
68
|
end
|
22
69
|
|
23
|
-
opts.on('-p PASSWORD', '--password') do |password|
|
70
|
+
opts.on('-p PASSWORD', '--password', 'Your Rally password') do |password|
|
24
71
|
@options[:password] = password
|
25
72
|
end
|
26
73
|
|
27
|
-
opts.on('-h', '--help') do
|
74
|
+
opts.on('-h', '--help', 'Show this message') do
|
28
75
|
@stdout.puts Rallycat::Help.new
|
29
76
|
exit
|
30
77
|
end
|
@@ -37,78 +84,53 @@ module Rallycat
|
|
37
84
|
@options ||= {}
|
38
85
|
|
39
86
|
commands = {
|
40
|
-
'cat' => OptionParser.new
|
87
|
+
'cat' => OptionParser.new do |opts|
|
88
|
+
opts.banner = 'Usage: rallycat story <story number>'
|
89
|
+
end,
|
41
90
|
|
42
91
|
'update' => OptionParser.new do |opts|
|
43
|
-
opts.banner = 'Usage: rallycat update <
|
92
|
+
opts.banner = 'Usage: rallycat update <task number> [options]'
|
44
93
|
|
45
|
-
opts.on('-b', '--blocked') do |blocked|
|
94
|
+
opts.on('-b', '--blocked', 'Block the current state of a task') do |blocked|
|
46
95
|
@options[:blocked] = true
|
47
96
|
end
|
48
97
|
|
49
|
-
opts.on('-p', '--in-progress') do |in_progress|
|
98
|
+
opts.on('-p', '--in-progress', 'Set the state of a task to "In-Progress"') do |in_progress|
|
50
99
|
@options[:in_progress] = true
|
51
100
|
end
|
52
101
|
|
53
|
-
opts.on('-c', '--completed') do |completed|
|
102
|
+
opts.on('-c', '--completed', 'Set the state of a task to "Completed"') do |completed|
|
54
103
|
@options[:completed] = true
|
55
104
|
end
|
56
105
|
|
57
|
-
opts.on('-d', '--defined') do |defined|
|
106
|
+
opts.on('-d', '--defined', 'Set the state of a task to "Defined"') do |defined|
|
58
107
|
@options[:defined] = true
|
59
108
|
end
|
60
109
|
|
61
|
-
opts.on('-o OWNER', '--owner') do |owner|
|
110
|
+
opts.on('-o OWNER', '--owner', 'Set the owner of a task') do |owner|
|
62
111
|
@options[:owner] = owner
|
63
112
|
end
|
64
113
|
end,
|
65
114
|
|
66
|
-
'
|
67
|
-
|
115
|
+
'list' => OptionParser.new do |opts|
|
116
|
+
opts.banner = 'Usage: rallycat list [options]'
|
68
117
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def run
|
74
|
-
case @command
|
75
|
-
when 'cat'
|
76
|
-
api = Rallycat::Connection.new(options[:username], options[:password]).api
|
77
|
-
|
78
|
-
story_number = @argv.shift
|
118
|
+
opts.on('-p [PROJECT]', '--project', 'The project whose iterations you want to list') do |project|
|
119
|
+
@options[:project] = project
|
120
|
+
end
|
79
121
|
|
80
|
-
|
122
|
+
opts.on('-i ITERATION', '--iteration', 'The iteration whose stories you want to list') do |iteration|
|
123
|
+
@options[:iteration] = iteration
|
124
|
+
end
|
125
|
+
end,
|
81
126
|
|
82
|
-
|
83
|
-
|
84
|
-
rescue Rallycat::Cat::StoryNotFound => e
|
85
|
-
abort e.message
|
127
|
+
'help' => OptionParser.new do |opts|
|
128
|
+
opts.banner = 'Usage: rallycat help'
|
86
129
|
end
|
87
|
-
|
88
|
-
api = Rallycat::Connection.new(options[:username], options[:password]).api
|
89
|
-
|
90
|
-
task_number = @argv.shift
|
91
|
-
|
92
|
-
abort 'The "update" command requires a task number.' unless task_number
|
93
|
-
|
94
|
-
opts = {}
|
95
|
-
opts[:blocked] = true if options[:blocked]
|
96
|
-
opts[:state] = "In-Progress" if options[:in_progress]
|
97
|
-
opts[:state] = "Completed" if options[:completed]
|
98
|
-
opts[:state] = "Defined" if options[:defined]
|
99
|
-
opts[:owner] = options[:owner] if options[:owner]
|
130
|
+
}
|
100
131
|
|
101
|
-
|
102
|
-
|
103
|
-
rescue Rallycat::Update::UserNotFound, Rallycat::Update::TaskNotFound => e
|
104
|
-
abort e.message
|
105
|
-
end
|
106
|
-
when 'help'
|
107
|
-
# `puts` calls `to_s`
|
108
|
-
@stdout.puts Rallycat::Help.new
|
109
|
-
else
|
110
|
-
@stdout.puts "'#{@command}' is not a supported command. See 'rallycat help'."
|
111
|
-
end
|
132
|
+
@command = @argv.shift
|
133
|
+
commands[@command].parse! @argv if commands.has_key? @command
|
112
134
|
end
|
113
135
|
end
|
114
136
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Rallycat
|
2
|
+
class Config
|
3
|
+
def initialize
|
4
|
+
begin
|
5
|
+
@config = YAML.load_file File.expand_path('~/.rallycatrc')
|
6
|
+
rescue StandardError
|
7
|
+
message = "Your rallycat config file is missing or invalid. See 'rallycat help'."
|
8
|
+
raise InvalidConfigError.new message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
@config[key]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/rallycat/connection.rb
CHANGED
@@ -2,46 +2,29 @@ require 'yaml'
|
|
2
2
|
require 'rally_rest_api'
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
-
module Rallycat
|
6
|
-
class InvalidConfigError < StandardError; end
|
7
|
-
class InvalidCredentialsError < StandardError; end
|
8
|
-
|
5
|
+
module Rallycat
|
9
6
|
class Connection
|
10
7
|
attr_reader :api
|
11
8
|
|
12
9
|
def initialize(username=nil, password=nil)
|
13
|
-
@
|
14
|
-
@password = password
|
10
|
+
@config = Config.new
|
15
11
|
|
16
|
-
|
12
|
+
username ||= @config['username']
|
13
|
+
password ||= @config['password']
|
17
14
|
|
18
15
|
begin
|
16
|
+
logger = ENV['RALLY_DEBUG'] ? Logger.new(STDOUT) : Logger.new(nil)
|
17
|
+
|
19
18
|
@api = RallyRestAPI.new \
|
20
19
|
base_url: 'https://rally1.rallydev.com/slm',
|
21
|
-
username:
|
22
|
-
password:
|
23
|
-
logger:
|
20
|
+
username: username,
|
21
|
+
password: password,
|
22
|
+
logger: logger
|
24
23
|
|
25
24
|
rescue Rally::NotAuthenticatedError
|
26
25
|
raise InvalidCredentialsError.new('Your Rally credentials are invalid.')
|
27
26
|
end
|
28
27
|
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def parse_config
|
33
|
-
rc_file_path = File.expand_path '~/.rallycatrc'
|
34
|
-
|
35
|
-
if @username || @password
|
36
|
-
{ 'username' => @username, 'password' => @password }
|
37
|
-
else
|
38
|
-
begin
|
39
|
-
YAML.load_file(rc_file_path)
|
40
|
-
rescue StandardError
|
41
|
-
message = "Your rallycat config file is missing or invalid. Please RTFM."
|
42
|
-
raise InvalidConfigError.new message
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
28
|
end
|
47
29
|
end
|
30
|
+
|
data/lib/rallycat/help.rb
CHANGED
@@ -2,27 +2,39 @@ module Rallycat
|
|
2
2
|
class Help
|
3
3
|
def to_s
|
4
4
|
<<-HELP
|
5
|
-
Rallycat is a command
|
5
|
+
Rallycat is a command-line utility for interacting with Rally. It should be
|
6
|
+
used to support a command-line centric workflow. Rallycat has the simple goal
|
7
|
+
of being a lightweight alternative to Rally's web interface. It provides quick
|
8
|
+
access to Rally features that are important to developers in their day-to-day
|
9
|
+
work.
|
6
10
|
|
7
11
|
Configuration:
|
8
12
|
Configuration is available through a ~/.rallycatrc file formatted as YAML.
|
9
13
|
The file should have two keys: `username` and `password` for authenticating
|
10
|
-
with the Rally API.
|
14
|
+
with the Rally API. An additional project attribute can be added to make some
|
15
|
+
commands easiser to use. Example:
|
16
|
+
|
17
|
+
username: email@host.com
|
18
|
+
password: pass1234
|
19
|
+
project: SuperBad
|
11
20
|
|
12
21
|
Additionally, the `-u [USERNAME]` and `-p [PASSWORD]` flags may be provided
|
13
22
|
at runtime and will take higher precedence than the configuration file.
|
14
23
|
|
15
24
|
Global Options:
|
16
|
-
-u [USERNAME] #
|
17
|
-
-p [PASSWORD] #
|
25
|
+
-u [USERNAME] # Your Rally username
|
26
|
+
-p [PASSWORD] # Your Rally password
|
18
27
|
-h, --help # Displays this help text
|
19
28
|
|
20
29
|
Commands:
|
21
|
-
rallycat cat <story number> # Displays
|
22
|
-
rallycat update <task number> #
|
23
|
-
[--blocked | -b] [--in-progress | -
|
30
|
+
rallycat cat <story number> # Displays a user story or defect
|
31
|
+
rallycat update <task number> # Updates the state of a task
|
32
|
+
[--blocked | -b] [--in-progress | -p]
|
24
33
|
[--completed | -c] [--defined | -d]
|
25
34
|
[--owner | -o <fullname>]
|
35
|
+
rallycat list # List iterations and stories
|
36
|
+
[--project | -p <project name>]
|
37
|
+
[--iteration | -i <iteration name>]
|
26
38
|
rallycat help # Displays this help text
|
27
39
|
|
28
40
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Rallycat
|
2
|
+
class List
|
3
|
+
|
4
|
+
def initialize(api)
|
5
|
+
@api = api
|
6
|
+
@config = Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def iterations(project_name)
|
10
|
+
project_name ||= @config['project']
|
11
|
+
|
12
|
+
validate_arg project_name, 'Project name is required.'
|
13
|
+
|
14
|
+
project = find_project(project_name)
|
15
|
+
|
16
|
+
iterations = @api.find_all(:iteration, {
|
17
|
+
project: project,
|
18
|
+
order: 'StartDate desc',
|
19
|
+
pagesize: 10
|
20
|
+
}).results
|
21
|
+
|
22
|
+
if iterations.count == 0
|
23
|
+
return "No iterations could be found for project #{project_name}."
|
24
|
+
end
|
25
|
+
|
26
|
+
iteration_names = iterations.map(&:name).uniq
|
27
|
+
|
28
|
+
<<-LIST
|
29
|
+
# 5 Most recent iterations for "#{project_name}"
|
30
|
+
|
31
|
+
#{iteration_names.join("\n")}
|
32
|
+
|
33
|
+
LIST
|
34
|
+
end
|
35
|
+
|
36
|
+
def stories(project_name, iteration_name)
|
37
|
+
project_name ||= @config['project']
|
38
|
+
|
39
|
+
validate_arg project_name, 'Project name is required.'
|
40
|
+
validate_arg iteration_name, 'Iteration name is required.'
|
41
|
+
|
42
|
+
project = find_project(project_name)
|
43
|
+
iteration = find_iteration(iteration_name, project)
|
44
|
+
|
45
|
+
stories = @api.find(:hierarchical_requirement, {
|
46
|
+
project: project,
|
47
|
+
pagesize: 100,
|
48
|
+
fetch: true
|
49
|
+
}) { equal :iteration, iteration }.results
|
50
|
+
|
51
|
+
stories += @api.find(:defect, {
|
52
|
+
project: project,
|
53
|
+
pagesize: 100,
|
54
|
+
fetch: true
|
55
|
+
}) { equal :iteration, iteration }.results
|
56
|
+
|
57
|
+
if stories.count == 0
|
58
|
+
return %{No stories could be found for iteration "#{iteration_name}".}
|
59
|
+
end
|
60
|
+
|
61
|
+
list = %{# Stories for iteration "#{iteration_name}" - "#{project_name}"\n\n}
|
62
|
+
|
63
|
+
stories.each do |story|
|
64
|
+
state = story.schedule_state == 'In-Progress' ? 'P' : story.schedule_state[0]
|
65
|
+
list += "* [#{story.formatted_i_d}] [#{state}] #{story.name}\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
list += "\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def validate_arg(val, message)
|
74
|
+
raise ArgumentError, message if val.nil? || val.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_project(project_name)
|
78
|
+
project = @api.find(:project) { equal(:name, project_name) }.first
|
79
|
+
|
80
|
+
raise ProjectNotFound, "Project (#{project_name}) does not exist." unless project
|
81
|
+
|
82
|
+
project
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_iteration(iteration_name, project)
|
86
|
+
iteration = @api.find(:iteration, :project => project) {
|
87
|
+
equal(:name, iteration_name)
|
88
|
+
}.first
|
89
|
+
|
90
|
+
raise IterationNotFound, "Iteration (#{iteration_name}) does not exist." unless iteration
|
91
|
+
|
92
|
+
iteration
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
data/lib/rallycat/update.rb
CHANGED
data/lib/rallycat/version.rb
CHANGED
data/spec/integration_spec.rb
CHANGED
@@ -57,7 +57,7 @@ describe 'Rallycat' do
|
|
57
57
|
expected = string_io.read
|
58
58
|
|
59
59
|
expected.should include("rallycat cat <story number>")
|
60
|
-
expected.should include("Displays
|
60
|
+
expected.should include("Displays a user story or defect")
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -183,4 +183,108 @@ describe 'Rallycat' do
|
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
186
|
+
|
187
|
+
context 'list' do
|
188
|
+
|
189
|
+
it 'outputs the last 5 iterations for the users configured project' do
|
190
|
+
sout = StringIO.new
|
191
|
+
|
192
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list }, sout
|
193
|
+
|
194
|
+
Artifice.activate_with RallyIterationsResponder.new do
|
195
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
196
|
+
YAML.stub(:load_file).with(rc_file).and_return({
|
197
|
+
'project' => 'SuperBad'
|
198
|
+
})
|
199
|
+
|
200
|
+
cli.run
|
201
|
+
end
|
202
|
+
|
203
|
+
sout.rewind
|
204
|
+
sout.read.should include('# 5 Most recent iterations for "SuperBad"')
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'outputs the last 5 iterations for the project provided' do
|
208
|
+
sout = StringIO.new
|
209
|
+
|
210
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list -p SuperBad }, sout
|
211
|
+
|
212
|
+
Artifice.activate_with RallyIterationsResponder.new do
|
213
|
+
cli.run
|
214
|
+
end
|
215
|
+
|
216
|
+
sout.rewind
|
217
|
+
sout.read.should include('# 5 Most recent iterations for "SuperBad"')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'outputs the stories for an iteration constrained by the configured project' do
|
221
|
+
sout = StringIO.new
|
222
|
+
|
223
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list -i 25\ (2012-05-01\ to\ 2012-05-05) }, sout
|
224
|
+
|
225
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
226
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
227
|
+
YAML.stub(:load_file).with(rc_file).and_return({
|
228
|
+
'project' => 'SuperBad'
|
229
|
+
})
|
230
|
+
|
231
|
+
cli.run
|
232
|
+
end
|
233
|
+
|
234
|
+
sout.rewind
|
235
|
+
sout.read.should include('# Stories for iteration "25 (2012-05-01 to 2012-05-05)" - "SuperBad"')
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'outputs the stories for an iteration constrained by the project provided' do
|
239
|
+
sout = StringIO.new
|
240
|
+
|
241
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list -p SuperBad -i 25\ (2012-05-01\ to\ 2012-05-05) }, sout
|
242
|
+
|
243
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
244
|
+
cli.run
|
245
|
+
end
|
246
|
+
|
247
|
+
sout.rewind
|
248
|
+
sout.read.should include('# Stories for iteration "25 (2012-05-01 to 2012-05-05)" - "SuperBad"')
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'aborts when project is not found' do
|
252
|
+
sout = StringIO.new
|
253
|
+
|
254
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list -p WebFarts -i 25\ (2012-05-01\ to\ 2012-05-05) }, sout
|
255
|
+
|
256
|
+
lambda {
|
257
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
258
|
+
cli.run
|
259
|
+
end
|
260
|
+
}.should raise_error(SystemExit, 'Project (WebFarts) does not exist.')
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'aborts when iteration is not found' do
|
264
|
+
sout = StringIO.new
|
265
|
+
|
266
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list -p SuperBad -i Sprint\ 0 }, sout
|
267
|
+
|
268
|
+
lambda {
|
269
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
270
|
+
cli.run
|
271
|
+
end
|
272
|
+
}.should raise_error(SystemExit, 'Iteration (Sprint 0) does not exist.')
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'aborts when project is not configured or provided' do
|
276
|
+
sout = StringIO.new
|
277
|
+
|
278
|
+
cli = Rallycat::CLI.new %w{ -u foo.bar@rallycat.com -p password list }, sout
|
279
|
+
|
280
|
+
lambda {
|
281
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
282
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
283
|
+
YAML.stub(:load_file).with(rc_file).and_return({ })
|
284
|
+
|
285
|
+
cli.run
|
286
|
+
end
|
287
|
+
}.should raise_error(SystemExit, 'Project name is required.')
|
288
|
+
end
|
289
|
+
end
|
186
290
|
end
|
File without changes
|
@@ -23,7 +23,7 @@ describe Rallycat::Connection do
|
|
23
23
|
@connection = Rallycat::Connection.new
|
24
24
|
end
|
25
25
|
}.should raise_error Rallycat::InvalidConfigError,
|
26
|
-
|
26
|
+
"Your rallycat config file is missing or invalid. See 'rallycat help'."
|
27
27
|
end
|
28
28
|
|
29
29
|
it "raises when the credentials are invalid" do
|
@@ -88,4 +88,4 @@ describe Rallycat::Connection do
|
|
88
88
|
end
|
89
89
|
|
90
90
|
end
|
91
|
-
end
|
91
|
+
end
|
File without changes
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rallycat::List, '#iterations' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Artifice.activate_with RallyAuthResponder.new do
|
7
|
+
@api = Rallycat::Connection.new('foo.bar@rallycat.com', 'password').api
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns last five iterations for the project provided' do
|
12
|
+
expected = <<-LIST
|
13
|
+
# 5 Most recent iterations for "SuperBad"
|
14
|
+
|
15
|
+
25 (2012-05-01 to 2012-05-05)
|
16
|
+
24 (2012-04-01 to 2012-04-05)
|
17
|
+
23 (2012-03-01 to 2012-03-05)
|
18
|
+
22 (2012-02-01 to 2012-02-05)
|
19
|
+
21 (2012-01-01 to 2012-01-05)
|
20
|
+
|
21
|
+
LIST
|
22
|
+
|
23
|
+
Artifice.activate_with RallyIterationsResponder.new do
|
24
|
+
project = 'SuperBad'
|
25
|
+
list = Rallycat::List.new(@api)
|
26
|
+
list.iterations(project).should == expected
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns last five iterations for the configured project in .rallycatrc' do
|
31
|
+
expected = <<-LIST
|
32
|
+
# 5 Most recent iterations for "SuperBad"
|
33
|
+
|
34
|
+
25 (2012-05-01 to 2012-05-05)
|
35
|
+
24 (2012-04-01 to 2012-04-05)
|
36
|
+
23 (2012-03-01 to 2012-03-05)
|
37
|
+
22 (2012-02-01 to 2012-02-05)
|
38
|
+
21 (2012-01-01 to 2012-01-05)
|
39
|
+
|
40
|
+
LIST
|
41
|
+
|
42
|
+
Artifice.activate_with RallyIterationsResponder.new do
|
43
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
44
|
+
YAML.stub(:load_file).with(rc_file).and_return({
|
45
|
+
'project' => 'SuperBad'
|
46
|
+
})
|
47
|
+
list = Rallycat::List.new(@api)
|
48
|
+
list.iterations(nil).should == expected
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises when project name is empty' do
|
53
|
+
lambda {
|
54
|
+
list = Rallycat::List.new(@api)
|
55
|
+
list.iterations('').should == expected
|
56
|
+
}.should raise_error(ArgumentError, 'Project name is required.')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises when project name is nil' do
|
60
|
+
lambda {
|
61
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
62
|
+
YAML.stub(:load_file).with(rc_file).and_return({})
|
63
|
+
|
64
|
+
list = Rallycat::List.new(@api)
|
65
|
+
list.iterations(nil).should == expected
|
66
|
+
}.should raise_error(ArgumentError, 'Project name is required.')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'raises when project cannot be found' do
|
70
|
+
Artifice.activate_with RallyNoResultsResponder.new do
|
71
|
+
lambda {
|
72
|
+
list = Rallycat::List.new(@api)
|
73
|
+
list.iterations('WebFarts').should == expected
|
74
|
+
}.should raise_error(Rallycat::ProjectNotFound, 'Project (WebFarts) does not exist.')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns a user friendly message when project has no iterations' do
|
79
|
+
Artifice.activate_with RallyIterationsResponder.new do
|
80
|
+
project = 'SuperAwful'
|
81
|
+
list = Rallycat::List.new(@api)
|
82
|
+
list.iterations(project).should == 'No iterations could be found for project SuperAwful.'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe Rallycat::List, '#stories' do
|
88
|
+
before do
|
89
|
+
Artifice.activate_with RallyAuthResponder.new do
|
90
|
+
@api = Rallycat::Connection.new('foo.bar@rallycat.com', 'password').api
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns the stories for an iteration filtered by sprint' do
|
95
|
+
expected = <<-LIST
|
96
|
+
# Stories for iteration "25 (2012-05-01 to 2012-05-05)" - "SuperBad"
|
97
|
+
|
98
|
+
* [US123] [C] This is story number one
|
99
|
+
* [US456] [P] This is story number two
|
100
|
+
* [DE789] [D] This is defect number one
|
101
|
+
|
102
|
+
LIST
|
103
|
+
|
104
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
105
|
+
project = 'SuperBad'
|
106
|
+
iteration = '25 (2012-05-01 to 2012-05-05)'
|
107
|
+
list = Rallycat::List.new(@api)
|
108
|
+
list.stories(project, iteration).should == expected
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises when project name is empty' do
|
113
|
+
lambda {
|
114
|
+
list = Rallycat::List.new(@api)
|
115
|
+
list.stories('', '25 (2012-05-01 to 2012-05-05)').should == expected
|
116
|
+
}.should raise_error(ArgumentError, 'Project name is required.')
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'raises when project name is nil' do
|
120
|
+
lambda {
|
121
|
+
rc_file = File.expand_path("~/.rallycatrc")
|
122
|
+
YAML.stub(:load_file).with(rc_file).and_return({})
|
123
|
+
|
124
|
+
list = Rallycat::List.new(@api)
|
125
|
+
list.stories(nil, '25 (2012-05-01 to 2012-05-05)').should == expected
|
126
|
+
}.should raise_error(ArgumentError, 'Project name is required.')
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'raises when iteration name is empty' do
|
130
|
+
lambda {
|
131
|
+
list = Rallycat::List.new(@api)
|
132
|
+
list.stories('SuperBad', '').should == expected
|
133
|
+
}.should raise_error(ArgumentError, 'Iteration name is required.')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'raises when iteration name is nil' do
|
137
|
+
lambda {
|
138
|
+
list = Rallycat::List.new(@api)
|
139
|
+
list.stories('SuperBad', nil).should == expected
|
140
|
+
}.should raise_error(ArgumentError, 'Iteration name is required.')
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'raises when project cannot be found' do
|
144
|
+
Artifice.activate_with RallyNoResultsResponder.new do
|
145
|
+
lambda {
|
146
|
+
list = Rallycat::List.new(@api)
|
147
|
+
list.stories('WebFarts', '25 (2012-05-01 to 2012-05-05)')
|
148
|
+
}.should raise_error(Rallycat::ProjectNotFound, 'Project (WebFarts) does not exist.')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'raises when iteration cannot be found' do
|
153
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
154
|
+
lambda {
|
155
|
+
list = Rallycat::List.new(@api)
|
156
|
+
list.stories('SuperBad', 'Sprint 0')
|
157
|
+
}.should raise_error(Rallycat::IterationNotFound, 'Iteration (Sprint 0) does not exist.')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns a user friendly message when iteration has no stories' do
|
162
|
+
Artifice.activate_with RallyStoriesResponder.new do
|
163
|
+
project = 'SuperBad'
|
164
|
+
iteration = '26 (2012-06-01 to 2012-06-05)'
|
165
|
+
list = Rallycat::List.new(@api)
|
166
|
+
list.stories(project, iteration).should == 'No stories could be found for iteration "26 (2012-06-01 to 2012-06-05)".'
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
@@ -119,7 +119,7 @@ describe Rallycat::Update do
|
|
119
119
|
|
120
120
|
lambda {
|
121
121
|
update.task(task_num, state: 'Completed', owner: 'Norman Notreal')
|
122
|
-
}.should raise_error(Rallycat::
|
122
|
+
}.should raise_error(Rallycat::UserNotFound, 'User (Norman Notreal) does not exist.')
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
@@ -132,8 +132,8 @@ describe Rallycat::Update do
|
|
132
132
|
|
133
133
|
lambda {
|
134
134
|
update.task(task_num, state: 'Completed')
|
135
|
-
}.should raise_error(Rallycat::
|
135
|
+
}.should raise_error(Rallycat::TaskNotFound, 'Task (TA6666) does not exist.')
|
136
136
|
end
|
137
137
|
|
138
138
|
end
|
139
|
-
end
|
139
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class RallyAuthResponder < GenericResponder
|
2
|
+
|
3
|
+
def call(env)
|
4
|
+
@requests << request = Rack::Request.new(env)
|
5
|
+
if request.url == 'https://rally1.rallydev.com/slm/webservice/current/user'
|
6
|
+
[200, {}, ['<User><DisplayName></DisplayName></User>']]
|
7
|
+
else
|
8
|
+
[500, {}, ['oh noes']]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class RallyIterationsResponder < GenericResponder
|
2
|
+
|
3
|
+
def call(env)
|
4
|
+
@requests << request = Rack::Request.new(env)
|
5
|
+
|
6
|
+
case request.url
|
7
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Project?query=%28Name+%3D+SuperBad%29'
|
8
|
+
[200, {}, [
|
9
|
+
<<-XML
|
10
|
+
<QueryResult>
|
11
|
+
<Results>
|
12
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.36/project/777555" type="Project">
|
13
|
+
<Name>SuperBad</Name>
|
14
|
+
</Object>
|
15
|
+
</Results>
|
16
|
+
</QueryResult>
|
17
|
+
XML
|
18
|
+
]]
|
19
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Iteration?query=%28ObjectID+%3E+0%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.36%2Fproject%2F777555&order=Startdate+desc&pagesize=10'
|
20
|
+
[200, {}, [
|
21
|
+
<<-XML
|
22
|
+
<QueryResult>
|
23
|
+
<Results>
|
24
|
+
<Object type="Iteration">
|
25
|
+
<Name>25 (2012-05-01 to 2012-05-05)</Name>
|
26
|
+
</Object>
|
27
|
+
<Object type="Iteration">
|
28
|
+
<Name>25 (2012-05-01 to 2012-05-05)</Name>
|
29
|
+
</Object>
|
30
|
+
<Object type="Iteration">
|
31
|
+
<Name>24 (2012-04-01 to 2012-04-05)</Name>
|
32
|
+
</Object>
|
33
|
+
<Object type="Iteration">
|
34
|
+
<Name>24 (2012-04-01 to 2012-04-05)</Name>
|
35
|
+
</Object>
|
36
|
+
<Object type="Iteration">
|
37
|
+
<Name>23 (2012-03-01 to 2012-03-05)</Name>
|
38
|
+
</Object>
|
39
|
+
<Object type="Iteration">
|
40
|
+
<Name>23 (2012-03-01 to 2012-03-05)</Name>
|
41
|
+
</Object>
|
42
|
+
<Object type="Iteration">
|
43
|
+
<Name>22 (2012-02-01 to 2012-02-05)</Name>
|
44
|
+
</Object>
|
45
|
+
<Object type="Iteration">
|
46
|
+
<Name>22 (2012-02-01 to 2012-02-05)</Name>
|
47
|
+
</Object>
|
48
|
+
<Object type="Iteration">
|
49
|
+
<Name>21 (2012-01-01 to 2012-01-05)</Name>
|
50
|
+
</Object>
|
51
|
+
<Object type="Iteration">
|
52
|
+
<Name>21 (2012-01-01 to 2012-01-05)</Name>
|
53
|
+
</Object>
|
54
|
+
</Results>
|
55
|
+
</QueryResult>
|
56
|
+
XML
|
57
|
+
]]
|
58
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Project?query=%28Name+%3D+SuperAwful%29'
|
59
|
+
[200, {}, [
|
60
|
+
<<-XML
|
61
|
+
<QueryResult>
|
62
|
+
<Results>
|
63
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.36/project/888444" type="Project">
|
64
|
+
<Name>SuperAwful</Name>
|
65
|
+
</Object>
|
66
|
+
</Results>
|
67
|
+
</QueryResult>
|
68
|
+
XML
|
69
|
+
]]
|
70
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Iteration?query=%28ObjectID+%3E+0%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.36%2Fproject%2F888444&order=Startdate+desc&pagesize=10'
|
71
|
+
RallyNoResultsResponder.new.call(env)
|
72
|
+
else
|
73
|
+
RallyNoResultsResponder.new.call(env)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class RallyStoriesResponder < GenericResponder
|
2
|
+
|
3
|
+
def call(env)
|
4
|
+
@requests << request = Rack::Request.new(env)
|
5
|
+
|
6
|
+
case request.url
|
7
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Project?query=%28Name+%3D+SuperBad%29'
|
8
|
+
[200, {}, [
|
9
|
+
<<-XML
|
10
|
+
<QueryResult>
|
11
|
+
<Results>
|
12
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.17/project/777555" type="Project" />
|
13
|
+
</Results>
|
14
|
+
</QueryResult>
|
15
|
+
XML
|
16
|
+
]]
|
17
|
+
when 'https://rally1.rallydev.com/slm/webservice/1.17/project/777555'
|
18
|
+
[200, {}, [
|
19
|
+
<<-XML
|
20
|
+
<Project ref="https://rally1.rallydev.com/slm/webservice/1.17/project/777555">
|
21
|
+
<Name>SuperBad</Name>
|
22
|
+
</Project>
|
23
|
+
XML
|
24
|
+
]]
|
25
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Iteration?query=%28Name+%3D+%2225+%282012-05-01+to+2012-05-05%29%22%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555'
|
26
|
+
[200, {}, [
|
27
|
+
# NOTE: Always getting two results back from Rally, not sure why.
|
28
|
+
<<-XML
|
29
|
+
<QueryResult>
|
30
|
+
<Results>
|
31
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.17/iteration/1234" type="Iteration" />
|
32
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.17/iteration/4567" type="Iteration" />
|
33
|
+
</Results>
|
34
|
+
</QueryResult>
|
35
|
+
XML
|
36
|
+
]]
|
37
|
+
when 'https://rally1.rallydev.com/slm/webservice/1.17/iteration/1234'
|
38
|
+
[200, {}, [
|
39
|
+
<<-XML
|
40
|
+
<Iteration>
|
41
|
+
<Name>25 (2012-05-01 to 2012-05-05)</Name>
|
42
|
+
</Iteration>
|
43
|
+
XML
|
44
|
+
]]
|
45
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/HierarchicalRequirement?query=%28Iteration+%3D+%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555&pagesize=100&fetch=true'
|
46
|
+
[200, {}, [
|
47
|
+
<<-XML
|
48
|
+
<QueryResult>
|
49
|
+
<Results>
|
50
|
+
<Object type="HierarchicalRequirement">
|
51
|
+
<FormattedID>US123</FormattedID>
|
52
|
+
<Name>This is story number one</Name>
|
53
|
+
<ScheduleState>Completed</ScheduleState>
|
54
|
+
</Object>
|
55
|
+
<Object type="HierarchicalRequirement">
|
56
|
+
<FormattedID>US456</FormattedID>
|
57
|
+
<Name>This is story number two</Name>
|
58
|
+
<ScheduleState>In-Progress</ScheduleState>
|
59
|
+
</Object>
|
60
|
+
</Results>
|
61
|
+
</QueryResult>
|
62
|
+
XML
|
63
|
+
]]
|
64
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Defect?query=%28Iteration+%3D+%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555&pagesize=100&fetch=true'
|
65
|
+
[200, {}, [
|
66
|
+
<<-XML
|
67
|
+
<QueryResult>
|
68
|
+
<Results>
|
69
|
+
<Object type="Defect">
|
70
|
+
<FormattedID>DE789</FormattedID>
|
71
|
+
<Name>This is defect number one</Name>
|
72
|
+
<ScheduleState>Defined</ScheduleState>
|
73
|
+
</Object>
|
74
|
+
</Results>
|
75
|
+
</QueryResult>
|
76
|
+
XML
|
77
|
+
]]
|
78
|
+
|
79
|
+
# These are for testing the not so happy path.
|
80
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Iteration?query=%28Name+%3D+%22Sprint+0%22%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555'
|
81
|
+
RallyNoResultsResponder.new.call(env)
|
82
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/Iteration?query=%28Name+%3D+%2226+%282012-06-01+to+2012-06-05%29%22%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555'
|
83
|
+
[200, {}, [
|
84
|
+
<<-XML
|
85
|
+
<QueryResult>
|
86
|
+
<Results>
|
87
|
+
<Object ref="https://rally1.rallydev.com/slm/webservice/1.17/iteration/7890" type="Iteration" />
|
88
|
+
</Results>
|
89
|
+
</QueryResult>
|
90
|
+
XML
|
91
|
+
]]
|
92
|
+
when 'https://rally1.rallydev.com/slm/webservice/1.17/iteration/7890'
|
93
|
+
# does nothing WTF(but will make tests fail if not here)???
|
94
|
+
when 'https://rally1.rallydev.com/slm/webservice/current/HierarchicalRequirement?query=%28Iteration+%3D+https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fiteration%2F7890%29&project=https%3A%2F%2Frally1.rallydev.com%2Fslm%2Fwebservice%2F1.17%2Fproject%2F777555&pagesize=100&fetch=true'
|
95
|
+
RallyNoResultsResponder.new.call(env)
|
96
|
+
else
|
97
|
+
RallyNoResultsResponder.new.call(env)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rallycat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-07-
|
13
|
+
date: 2012-07-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: builder
|
17
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirement: &70357622298460 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,15 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
26
|
-
none: false
|
27
|
-
requirements:
|
28
|
-
- - ! '>='
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
version: '0'
|
25
|
+
version_requirements: *70357622298460
|
31
26
|
- !ruby/object:Gem::Dependency
|
32
27
|
name: rally_rest_api
|
33
|
-
requirement: !ruby/object:Gem::Requirement
|
28
|
+
requirement: &70357622297960 !ruby/object:Gem::Requirement
|
34
29
|
none: false
|
35
30
|
requirements:
|
36
31
|
- - ! '>='
|
@@ -38,15 +33,10 @@ dependencies:
|
|
38
33
|
version: '0'
|
39
34
|
type: :runtime
|
40
35
|
prerelease: false
|
41
|
-
version_requirements:
|
42
|
-
none: false
|
43
|
-
requirements:
|
44
|
-
- - ! '>='
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
36
|
+
version_requirements: *70357622297960
|
47
37
|
- !ruby/object:Gem::Dependency
|
48
38
|
name: nokogiri
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirement: &70357622297540 !ruby/object:Gem::Requirement
|
50
40
|
none: false
|
51
41
|
requirements:
|
52
42
|
- - ! '>='
|
@@ -54,12 +44,7 @@ dependencies:
|
|
54
44
|
version: '0'
|
55
45
|
type: :runtime
|
56
46
|
prerelease: false
|
57
|
-
version_requirements:
|
58
|
-
none: false
|
59
|
-
requirements:
|
60
|
-
- - ! '>='
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
47
|
+
version_requirements: *70357622297540
|
63
48
|
description: The Rally website sucks. CLI is better.
|
64
49
|
email:
|
65
50
|
- adam@adamtanner.org
|
@@ -81,22 +66,28 @@ files:
|
|
81
66
|
- lib/rallycat.rb
|
82
67
|
- lib/rallycat/cat.rb
|
83
68
|
- lib/rallycat/cli.rb
|
69
|
+
- lib/rallycat/config.rb
|
84
70
|
- lib/rallycat/connection.rb
|
85
71
|
- lib/rallycat/help.rb
|
86
72
|
- lib/rallycat/html_to_text_converter.rb
|
73
|
+
- lib/rallycat/list.rb
|
87
74
|
- lib/rallycat/update.rb
|
88
75
|
- lib/rallycat/version.rb
|
89
76
|
- rallycat.gemspec
|
90
77
|
- spec/integration_spec.rb
|
91
|
-
- spec/
|
92
|
-
- spec/
|
93
|
-
- spec/
|
94
|
-
- spec/
|
95
|
-
- spec/
|
78
|
+
- spec/rallycat/cat_spec.rb
|
79
|
+
- spec/rallycat/cli_spec.rb
|
80
|
+
- spec/rallycat/connection_spec.rb
|
81
|
+
- spec/rallycat/html_to_text_converter_spec.rb
|
82
|
+
- spec/rallycat/list_spec.rb
|
83
|
+
- spec/rallycat/update_spec.rb
|
96
84
|
- spec/spec_helper.rb
|
97
85
|
- spec/support/generic_responder.rb
|
86
|
+
- spec/support/rally_auth_responder.rb
|
98
87
|
- spec/support/rally_defect_responder.rb
|
88
|
+
- spec/support/rally_iterations_responder.rb
|
99
89
|
- spec/support/rally_no_results_responder.rb
|
90
|
+
- spec/support/rally_stories_responder.rb
|
100
91
|
- spec/support/rally_story_responder.rb
|
101
92
|
- spec/support/rally_task_update_responder.rb
|
102
93
|
homepage: https://github.com/adamtanner/rallycat
|
@@ -113,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
104
|
version: '0'
|
114
105
|
segments:
|
115
106
|
- 0
|
116
|
-
hash:
|
107
|
+
hash: -2821747029306890735
|
117
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
109
|
none: false
|
119
110
|
requirements:
|
@@ -122,23 +113,27 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
113
|
version: '0'
|
123
114
|
segments:
|
124
115
|
- 0
|
125
|
-
hash:
|
116
|
+
hash: -2821747029306890735
|
126
117
|
requirements: []
|
127
118
|
rubyforge_project:
|
128
|
-
rubygems_version: 1.8.
|
119
|
+
rubygems_version: 1.8.7
|
129
120
|
signing_key:
|
130
121
|
specification_version: 3
|
131
122
|
summary: The Rally website sucks. CLI is better.
|
132
123
|
test_files:
|
133
124
|
- spec/integration_spec.rb
|
134
|
-
- spec/
|
135
|
-
- spec/
|
136
|
-
- spec/
|
137
|
-
- spec/
|
138
|
-
- spec/
|
125
|
+
- spec/rallycat/cat_spec.rb
|
126
|
+
- spec/rallycat/cli_spec.rb
|
127
|
+
- spec/rallycat/connection_spec.rb
|
128
|
+
- spec/rallycat/html_to_text_converter_spec.rb
|
129
|
+
- spec/rallycat/list_spec.rb
|
130
|
+
- spec/rallycat/update_spec.rb
|
139
131
|
- spec/spec_helper.rb
|
140
132
|
- spec/support/generic_responder.rb
|
133
|
+
- spec/support/rally_auth_responder.rb
|
141
134
|
- spec/support/rally_defect_responder.rb
|
135
|
+
- spec/support/rally_iterations_responder.rb
|
142
136
|
- spec/support/rally_no_results_responder.rb
|
137
|
+
- spec/support/rally_stories_responder.rb
|
143
138
|
- spec/support/rally_story_responder.rb
|
144
139
|
- spec/support/rally_task_update_responder.rb
|