schubert-minglr 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  pkg/*
2
+ coverage/*
3
+ .DS_Store
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
 
4
+ task :default => [:rcov, :features]
5
+
4
6
  begin
5
7
  require 'jeweler'
6
8
  Jeweler::Tasks.new do |gem|
@@ -36,6 +38,7 @@ begin
36
38
  require 'rcov/rcovtask'
37
39
  Rcov::RcovTask.new do |test|
38
40
  test.libs << 'test'
41
+ test.rcov_opts = ['--exclude', 'gems', "--text-report", "--only-uncovered"]
39
42
  test.pattern = 'test/**/*_test.rb'
40
43
  test.verbose = true
41
44
  end
@@ -46,8 +49,6 @@ rescue LoadError
46
49
  end
47
50
 
48
51
 
49
- task :default => :test
50
-
51
52
  require 'rake/rdoctask'
52
53
  Rake::RDocTask.new do |rdoc|
53
54
  if File.exist?('VERSION.yml')
@@ -63,5 +64,17 @@ Rake::RDocTask.new do |rdoc|
63
64
  rdoc.rdoc_files.include('lib/**/*.rb')
64
65
  end
65
66
 
67
+ begin
68
+ require 'cucumber'
69
+ require 'cucumber/rake/task'
70
+
71
+ Cucumber::Rake::Task.new(:features) do |t|
72
+ t.cucumber_opts = "features --format pretty"
73
+ end
74
+ rescue LoadError
75
+ task :features do
76
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
77
+ end
78
+ end
66
79
 
67
- task "dev" => ["test", "gemspec", "build"]
80
+ task "ci" => ["rcov", "features", "gemspec", "build", "install"]
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :minor: 2
3
- :patch: 0
4
2
  :major: 1
3
+ :minor: 3
4
+ :patch: 0
data/bin/mtx CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'minglr'))
4
4
 
5
- uri_options, execution_options = MtxOptionsParser.parse(ARGV, :card, :transition)
5
+ uri_options, execution_options = MTX::OptionsParser.parse(ARGV, :card, :transition)
6
6
 
7
7
  Resources::Base.configure uri_options
8
8
 
9
- if card = Card.find(execution_options[:card])
10
- execution = TransitionExecution.create(execution_options)
9
+ if card = Resources::Card.find(execution_options[:card])
10
+ execution = Resources::TransitionExecution.create(execution_options)
11
11
  raise "Transition aborted. Errors: #{execution.errors.full_messages}" if execution.errors.any?
12
12
  else
13
13
  raise "Card '#{execution_options[:card]}' cannot be found."
data/cucumber.yml ADDED
@@ -0,0 +1,2 @@
1
+ default: --format profile features
2
+ html_report: --format pretty features
@@ -0,0 +1,27 @@
1
+ Feature: Cards
2
+
3
+ Scenario: Print list of cards in the project
4
+ Given the project "xp"
5
+ When I issue the "cards" action
6
+ Then the result should have "119 - Task - Build PDF Export Widget on UI" in it
7
+ And the result should have "105 - Epic - Find people, projects and documents" in it
8
+
9
+ Scenario: Print list of cards in the project with a filter
10
+ Given the project "xp"
11
+ And a keyword of "Project"
12
+ When I issue the "cards" action
13
+ Then the result should not have "119 - Task - Build PDF Export Widget on UI" in it
14
+ Then the result should have "113 - Feature - Update Project" in it
15
+ And the result should have "112 - Feature - Create Project" in it
16
+ And the result should have "105 - Epic - Find people, projects and documents" in it
17
+
18
+ Scenario: Print the details of a card in the project
19
+ Given the project "xp"
20
+ And the card number "112"
21
+ When I issue the "card" action
22
+ Then the result should have "Number: 112" in it
23
+ And the result should have "Name: Create Project" in it
24
+ And the result should have "Type: Feature" in it
25
+ And the result should have "Status:" in it
26
+ And the result should have "Description: N/A" in it
27
+ And the result should have "Attachments:" in it
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), "shared_steps")
2
+
3
+ Given /^the project "([^\"]*)"$/ do |project|
4
+ @project = project
5
+ end
6
+
7
+ Given /^the card number "([^\"]*)"$/ do |card_number|
8
+ if @options
9
+ @options << [card_number]
10
+ else
11
+ @options = [card_number]
12
+ end
13
+ end
14
+
15
+ Given /^a keyword of "([^\"]*)"$/ do |keyword|
16
+ if @options
17
+ @options << [keyword]
18
+ else
19
+ @options = [keyword]
20
+ end
21
+ end
22
+
23
+ When /^I issue the "([^\"]*)" action$/ do |action|
24
+ @action = action
25
+ @response = execute_minglr_command(@project, @action, @options)
26
+ end
27
+
28
+ Then /^the result should have "([^\"]*)" in it$/ do |string|
29
+ assert @response.split("\n").join(" ").include?(string), "Expected #{@response.inspect} to contain '#{string}'"
30
+ end
31
+
32
+ Then /^the result should not have "([^\"]*)" in it$/ do |string|
33
+ assert !@response.split("\n").join(" ").include?(string), "Expected #{@response.inspect} to not contain '#{string}'"
34
+ end
@@ -0,0 +1,69 @@
1
+ require "test/unit/assertions"
2
+ require File.join(File.dirname(__FILE__), "..", "..", "lib", "minglr")
3
+ World(Test::Unit::Assertions)
4
+
5
+ module Kernel
6
+
7
+ def capture_stdout
8
+ $stdout = $cucumberout
9
+ yield
10
+ return $cucumberout
11
+ ensure
12
+ $stdout = STDOUT
13
+ end
14
+
15
+ end
16
+
17
+ def rc_config
18
+ {
19
+ :scrum => {
20
+ :url => "http://localhost:9090/projects/scrum",
21
+ :password => "mingle",
22
+ :status_property => "cp_status",
23
+ :username => "schubert"
24
+ },
25
+ :global => {
26
+ :default => "blank",
27
+ :username => "schubert"
28
+ },
29
+ :storytracker => {
30
+ :url => "http://localhost:9090/projects/storytracker",
31
+ :password => "mingle",
32
+ :status_property => "cp_status",
33
+ :username => "schubert"
34
+ },
35
+ :xp => {
36
+ :url => "http://localhost:9090/projects/xp",
37
+ :password => "mingle",
38
+ :username => "schubert"
39
+ },
40
+ :blank => {
41
+ :url => "http://localhost:9090/projects/blank",
42
+ :password => "mingle",
43
+ :username => "schubert"
44
+ },
45
+ :agilehybrid => {
46
+ :url => "http://localhost:9090/projects/agilehybrid",
47
+ :password => "mingle",
48
+ :status_property => "cp_status",
49
+ :username=>"schubert"
50
+ }
51
+ }
52
+ end
53
+
54
+ def execute_minglr_command(project, action, extra_arguments = [])
55
+ $cucumberout = StringIO.new
56
+ extra_arguments = [] if extra_arguments.nil?
57
+ uri_options = rc_config[:global] || {}
58
+ original_arguments = ([project, action] + extra_arguments)
59
+
60
+ uri_options.merge! rc_config[project.to_sym]
61
+ Resources::Base.configure uri_options
62
+ Resources::Attachment.configure
63
+ extra_options = Minglr::OptionsParser.parse(original_arguments)
64
+
65
+ output = capture_stdout do
66
+ Minglr::Action.execute(action, original_arguments, extra_options, rc_config[project.to_sym])
67
+ end
68
+ output.string.strip
69
+ end
@@ -0,0 +1,6 @@
1
+ Feature: Users
2
+
3
+ Scenario: Print list of users on the project
4
+ Given the project "xp"
5
+ When I issue the "users" action
6
+ Then the result should have "schubert - Michael Schubert - michael@schubert.cx" in it
data/lib/minglr.rb CHANGED
@@ -1,3 +1,5 @@
1
+ MINGLR_ENV = "normal" unless defined?(MINGLR_ENV)
2
+
1
3
  require 'rubygems'
2
4
  require 'activesupport'
3
5
  require 'activeresource'
@@ -8,8 +10,10 @@ require File.join(prefix, "action")
8
10
  require File.join(prefix, "options_parser")
9
11
  require File.join(prefix, "config_parser")
10
12
 
11
- require File.join(prefix, "mtx_options_parser")
12
- require File.join(prefix, "input_cache")
13
+ mtx = File.join(prefix, "mtx", "*")
14
+ Dir[mtx].each do |file_name|
15
+ load file_name
16
+ end
13
17
 
14
18
  require File.join(prefix, "resources", "base")
15
19
  resources = File.join(prefix, "resources", "*")
data/lib/minglr/action.rb CHANGED
@@ -12,7 +12,7 @@ module Minglr
12
12
  begin
13
13
  Commands.send(action, options, flag_options, config)
14
14
  rescue ActiveResource::ResourceNotFound => error
15
- puts error.message + " for URL #{Resources::Base.site}..."
15
+ puts error.message + " for URL '#{Resources::Base.site}' ..."
16
16
  end
17
17
  end
18
18
 
@@ -34,7 +34,7 @@ module Minglr
34
34
 
35
35
  def self.card(options, flag_options, config)
36
36
  card_number = options.first
37
- Resources::Card.print_card(options.first, config[:status_property])
37
+ Resources::Card.print_card(card_number, config[:status_property])
38
38
  end
39
39
 
40
40
  def self.cards(options, flag_options, config)
@@ -0,0 +1,24 @@
1
+ module MTX
2
+ class InputCache
3
+ class << self
4
+ def put(key, content)
5
+ File.open(file_pathname(key), File::CREAT | File::WRONLY | File::TRUNC) { |file| file.write content }
6
+ end
7
+
8
+ def get(key)
9
+ if content = File.read(file_pathname(key))
10
+ return nil if content.blank?
11
+ content
12
+ end
13
+ rescue
14
+ nil
15
+ end
16
+
17
+ protected
18
+
19
+ def file_pathname(key)
20
+ File.join(Dir::tmpdir, "#{key.to_s.gsub(/[^\w]/, '')}.entry")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ module MTX
2
+
3
+ class OptionsParser
4
+
5
+ def self.parse(args, *required_by_command)
6
+ uri_options = {}
7
+ command_options = {}
8
+
9
+ parser = OptionParser.new do |opts|
10
+ opts.banner = "Usage: mtx [options]"
11
+ opts.on("--transition TRANSITION", "Transition name.") do |transition|
12
+ command_options[:transition] = transition
13
+ end
14
+
15
+ opts.on("--card CARD", "Card number.") do |card|
16
+ command_options[:card] = card
17
+ end
18
+
19
+ opts.on("--properties ARGS", Array, "User-entered properties and values for the transition in array format. Must be an even number of comma-delimited values, like \"A,B,'C with spaces','D with spaces'\".") do |args|
20
+ command_options[:properties] = args.in_groups_of(2).map { |key, value| {'name' => key, 'value' => value} }
21
+ end
22
+
23
+ opts.on("--comment COMMENT", "Transition comment. This may be required depending on your transition settings.") do |comment|
24
+ command_options[:comment] = comment
25
+ end
26
+
27
+ opts.on("--username USERNAME", "Mingle username.") do |username|
28
+ uri_options[:username] = username
29
+ end
30
+
31
+ opts.on("--password PASSWORD", "Mingle password.") do |password|
32
+ uri_options[:password] = password
33
+ end
34
+
35
+ opts.on("--host_port HOST_PORT", "Host and port.") do |host_and_port|
36
+ uri_options[:host_and_port] = host_and_port
37
+ end
38
+
39
+ opts.on("--project PROJECT", "Project name.") do |project|
40
+ uri_options[:project] = project
41
+ end
42
+ end
43
+
44
+ parser.parse! args
45
+
46
+ ([:project, :host_and_port] | required_by_command).each do |arg|
47
+ unless command_options[arg] || uri_options[arg]
48
+ # TODO: let commands handle their own errors
49
+ $stderr.puts "Missing command-line argument --#{arg.to_s}, use --help for command-line options."
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ [uri_options, command_options]
55
+ end
56
+ end
57
+
58
+ end
@@ -1,3 +1,5 @@
1
+ require "yaml"
2
+
1
3
  module Minglr
2
4
  class OptionsParser
3
5
  def self.parse(args, *required_by_command)
@@ -9,7 +11,7 @@ module Minglr
9
11
  rescue ActiveResource::UnauthorizedAccess => exception
10
12
  puts "Connection #{exception.message} to #{Resources::Base.site.to_s}"
11
13
  puts "Did you set 'basic_authentication_enabled: true' in your auth_config.yml file?"
12
- exit 1
14
+ exit 1 unless MINGLR_ENV == "test"
13
15
  end
14
16
  end
15
17
 
@@ -21,23 +23,23 @@ module Minglr
21
23
  opts.separator "Valid Commands Are: #{Minglr::Action.valid_actions.join(", ")}"
22
24
 
23
25
  opts.on("-n NAME", String, "Short name of card") do |card_name|
24
- command_options[:name] = card_name
26
+ command_options[:name] = card_name.strip
25
27
  end
26
28
 
27
29
  opts.on("-d DESCRIPTION", String, "Description of card") do |card_description|
28
- command_options[:description] = card_description
30
+ command_options[:description] = card_description.strip
29
31
  end
30
32
 
31
33
  opts.on("-t TYPE", String, "Type of card") do |card_type|
32
- command_options[:card_type_name] = card_type
34
+ command_options[:card_type_name] = card_type.strip
33
35
  end
34
36
 
35
37
  opts.on("-c COMMENT", String, "Comment") do |comment|
36
- command_options[:comment] = comment
38
+ command_options[:comment] = comment.strip
37
39
  end
38
40
 
39
41
  opts.on("-f FILE", String, "File to attach") do |file|
40
- command_options[:file_attachment] = file
42
+ command_options[:file_attachment] = file.strip
41
43
  end
42
44
 
43
45
  unless project_options.empty?
@@ -56,17 +58,18 @@ module Minglr
56
58
 
57
59
  opts.on_tail("-h", "--help", "Show this help message.") do |help|
58
60
  puts opts
59
- exit
61
+ exit 0 unless MINGLR_ENV == "test"
60
62
  end
61
63
 
62
64
  opts.on_tail("--version", "Show version") do
63
- puts Minglr::VERSION
64
- exit
65
+ version = YAML.load(File.read(File.join(File.dirname(__FILE__), "..", "..", "VERSION.yml")))
66
+ puts "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
67
+ exit 0 unless MINGLR_ENV == "test"
65
68
  end
66
69
 
67
70
  if args.empty?
68
71
  puts opts
69
- exit
72
+ exit 0 unless MINGLR_ENV == "test"
70
73
  end
71
74
  end
72
75
 
@@ -8,6 +8,10 @@ module Resources
8
8
  self.prefix += "cards/:card_number/"
9
9
  end
10
10
 
11
+ def self.curl(command)
12
+ `#{command}`
13
+ end
14
+
11
15
  def self.fetch(card_number, username, password)
12
16
  if card_to_update = Card.find(card_number)
13
17
  attachments = find(:all, :params => { :card_number => card_number })
@@ -15,7 +19,8 @@ module Resources
15
19
  url = self.site + attachment.url
16
20
  url.userinfo = nil, nil
17
21
  puts "Downloading #{url.to_s}:"
18
- `curl --insecure --progress-bar --output #{attachment.file_name} --user #{username}:#{password} #{url}`
22
+ command = "curl --insecure --progress-bar --output #{attachment.file_name} --user #{username}:#{password} #{url}"
23
+ curl(command)
19
24
  end
20
25
  end
21
26
  end
@@ -7,7 +7,7 @@ module Resources
7
7
  card = self.new(options)
8
8
  if card.save
9
9
  card.reload
10
- puts "Card #{card.number} created"
10
+ puts "Card ##{card.number} created"
11
11
  else
12
12
  warn "Unable to create card"
13
13
  end
@@ -17,7 +17,12 @@ module Resources
17
17
  if card_to_move = find(card_number)
18
18
  transition_options = { :card => card_number }
19
19
  transition_options.merge!({ :comment => options[:comment]}) if options[:comment]
20
- current_status = card_to_move.send(config[:status_property]) if config[:status_property]
20
+ if config[:status_property]
21
+ current_status = card_to_move.send(config[:status_property])
22
+ else
23
+ warn "No known status of card ##{card_number}, cannot move!"
24
+ return
25
+ end
21
26
  next_transition = nil
22
27
 
23
28
  card_type = card_to_move.card_type_name.downcase
@@ -35,7 +40,8 @@ module Resources
35
40
  key.to_s =~ /^story_state/
36
41
  end
37
42
  else
38
- puts "No transitions defined for card of type #{card_to_move.card_type_name}"
43
+ warn "No transitions defined for card of type #{card_to_move.card_type_name}"
44
+ return
39
45
  end
40
46
  status_states = status_states.collect {|state| state.last }.collect {|state| state.split(">").collect { |value| value.strip } }
41
47
  next_transition = status_states.select {|state| state.first.downcase == current_status.downcase }.first.last
@@ -47,13 +53,13 @@ module Resources
47
53
  end
48
54
  end
49
55
  else
50
- warn "No card #{card_number} found to move"
56
+ warn "No card ##{card_number} found to move"
51
57
  end
52
58
  end
53
59
 
54
60
  def self.print_all(options = [], status_property = nil)
55
61
  attributes = [:number, :card_type_name, status_property, :name].compact
56
- cards = Resources::Card.find(:all)
62
+ cards = find(:all)
57
63
  cards.send(:extend, Minglr::Extensions::Array)
58
64
  cards = cards.filter(attributes, options)
59
65
  if cards.any?
@@ -64,8 +70,7 @@ module Resources
64
70
  end
65
71
 
66
72
  def self.print_card(card_number, status_property = nil)
67
- attributes = [:number, :card_type_name, status_property, :name, :description].compact
68
- if card = find(card_number)
73
+ if card = find(card_number.to_i)
69
74
  puts card.to_s(status_property)
70
75
  else
71
76
  warn "No card ##{card_number} found"
@@ -78,24 +83,29 @@ module Resources
78
83
  card_to_update.send("#{attribute.to_s}=".to_sym, value)
79
84
  end
80
85
  card_to_update.save
81
- puts "Card #{card_to_update.number} updated\n\n"
82
- puts card.to_s
86
+ puts "Card ##{card_to_update.number} updated\n\n"
87
+ puts card_to_update.to_s
83
88
  else
84
- warn "Unable to update card #{card_number}"
89
+ warn "Unable to update card ##{card_number}"
85
90
  end
86
91
  end
87
92
 
88
93
  def to_s(status_property = nil)
89
- attachments = Resources::Attachment.find(:all, :params => { :card_number => number })
90
- attachments = attachments.collect do |attachment|
91
- "* #{attachment.file_name}: #{Resources::Base.site + attachment.url}"
94
+ attachments = []
95
+ begin
96
+ attachments = Attachment.find(:all, :params => { :card_number => number })
97
+ attachments = attachments.collect do |attachment|
98
+ "* #{attachment.file_name}: #{Resources::Base.site + attachment.url}"
99
+ end
100
+ rescue ActiveResource::ResourceNotFound => error
101
+ attachments = ["N/A"]
92
102
  end
93
103
  output = <<-EOS
94
104
  Number: #{number}
95
105
  Name: #{name}
96
- Type: #{card_type_name}
97
- Status: #{send(status_property) if status_property}
98
- Description: #{description}
106
+ Type: #{card_type_name.nil? ? "N/A" : card_type_name}
107
+ Status: #{send(status_property) if status_property && self.respond_to?(status_property)}
108
+ Description: #{description.nil? ? "N/A" : description}
99
109
 
100
110
  Attachments:
101
111
  #{attachments.join("\n")}