schubert-minglr 1.2.0 → 1.3.0

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 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")}