taskmapper 0.8.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/.document +5 -0
- data/.travis.yml +4 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +20 -0
- data/NOTES +18 -0
- data/README.md +296 -0
- data/Rakefile +40 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/bin/tm +7 -0
- data/examples/tm_example.rb +11 -0
- data/examples/tm_example_2.rb +11 -0
- data/examples/tm_example_3.rb +21 -0
- data/examples/tm_example_4.rb +17 -0
- data/lib/taskmapper.rb +53 -0
- data/lib/taskmapper/authenticator.rb +2 -0
- data/lib/taskmapper/cli/commands/config.rb +100 -0
- data/lib/taskmapper/cli/commands/console.rb +35 -0
- data/lib/taskmapper/cli/commands/generate.rb +112 -0
- data/lib/taskmapper/cli/commands/generate/provider.rb +5 -0
- data/lib/taskmapper/cli/commands/generate/provider/comment.rb +14 -0
- data/lib/taskmapper/cli/commands/generate/provider/project.rb +26 -0
- data/lib/taskmapper/cli/commands/generate/provider/provider.rb +25 -0
- data/lib/taskmapper/cli/commands/generate/provider/ticket.rb +12 -0
- data/lib/taskmapper/cli/commands/help.rb +9 -0
- data/lib/taskmapper/cli/commands/help/config +27 -0
- data/lib/taskmapper/cli/commands/help/console +13 -0
- data/lib/taskmapper/cli/commands/help/generate +19 -0
- data/lib/taskmapper/cli/commands/help/help +7 -0
- data/lib/taskmapper/cli/commands/help/project +13 -0
- data/lib/taskmapper/cli/commands/help/ticket +14 -0
- data/lib/taskmapper/cli/commands/project.rb +140 -0
- data/lib/taskmapper/cli/commands/ticket.rb +145 -0
- data/lib/taskmapper/cli/common.rb +28 -0
- data/lib/taskmapper/cli/init.rb +77 -0
- data/lib/taskmapper/comment.rb +97 -0
- data/lib/taskmapper/common.rb +81 -0
- data/lib/taskmapper/dummy/comment.rb +27 -0
- data/lib/taskmapper/dummy/dummy.rb +28 -0
- data/lib/taskmapper/dummy/project.rb +42 -0
- data/lib/taskmapper/dummy/ticket.rb +43 -0
- data/lib/taskmapper/exception.rb +2 -0
- data/lib/taskmapper/helper.rb +72 -0
- data/lib/taskmapper/project.rb +145 -0
- data/lib/taskmapper/provider.rb +82 -0
- data/lib/taskmapper/tester/comment.rb +18 -0
- data/lib/taskmapper/tester/project.rb +19 -0
- data/lib/taskmapper/tester/tester.rb +28 -0
- data/lib/taskmapper/tester/ticket.rb +19 -0
- data/lib/taskmapper/ticket.rb +154 -0
- data/spec/project_spec.rb +84 -0
- data/spec/rcov.opts +1 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/taskmapper-cli_spec.rb +60 -0
- data/spec/taskmapper-exception_spec.rb +160 -0
- data/spec/taskmapper_spec.rb +13 -0
- data/spec/ticket_spec.rb +56 -0
- data/taskmapper.gemspec +118 -0
- metadata +189 -0
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.8.0
|
data/bin/tm
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'taskmapper'
|
3
|
+
require 'taskmapper-pivotal'
|
4
|
+
|
5
|
+
# display list of tickets for last project
|
6
|
+
tm = TaskMapper.new(:pivotal)
|
7
|
+
project = tm.projects.last
|
8
|
+
project.tickets.each {|ticket|
|
9
|
+
puts "#{ticket.id} - #{ticket.title}"
|
10
|
+
}
|
11
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'taskmapper'
|
3
|
+
require 'taskmapper-pivotal'
|
4
|
+
require 'taskmapper-lighthouse'
|
5
|
+
|
6
|
+
# copy all tickets and comments from pivotal tracker to new lighthouse project (the hard way)
|
7
|
+
pivotal = TaskMapper.new(:pivotal)
|
8
|
+
lighthouse = TaskMapper.new(:lighthouse)
|
9
|
+
|
10
|
+
from = pivotal.project(97107)
|
11
|
+
|
12
|
+
to = lighthouse.project!(:name => "Copy Test on #{Time.now}",
|
13
|
+
:description => "A copy test")
|
14
|
+
|
15
|
+
from.tickets.each do |from_ticket|
|
16
|
+
to_ticket = to.ticket!({:title => pivotal_ticket.title,
|
17
|
+
:description => pivotal_ticket.description})
|
18
|
+
from_ticket.comments.each do |comment|
|
19
|
+
to_ticket.comment!(:body => comment.body)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'taskmapper'
|
3
|
+
require 'taskmapper-pivotal'
|
4
|
+
require 'taskmapper-lighthouse'
|
5
|
+
|
6
|
+
# copy all tickets and comments from pivotal tracker to new lighthouse project (the easy way)
|
7
|
+
pivotal = TaskMapper.new(:pivotal)
|
8
|
+
lighthouse = TaskMapper.new(:lighthouse)
|
9
|
+
|
10
|
+
from = pivotal.project(97107)
|
11
|
+
to = lighthouse.project!(:name => "Copy Test on #{Time.now}",
|
12
|
+
:description => "A copy test")
|
13
|
+
|
14
|
+
to.copy(from)
|
15
|
+
|
16
|
+
puts "Copy finished."
|
17
|
+
|
data/lib/taskmapper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
%w{
|
2
|
+
rubygems
|
3
|
+
hashie
|
4
|
+
active_resource
|
5
|
+
}.each {|lib| require lib }
|
6
|
+
|
7
|
+
class TaskMapper
|
8
|
+
end
|
9
|
+
|
10
|
+
%w{
|
11
|
+
common
|
12
|
+
helper
|
13
|
+
project
|
14
|
+
ticket
|
15
|
+
comment
|
16
|
+
authenticator
|
17
|
+
provider
|
18
|
+
exception
|
19
|
+
dummy/dummy.rb
|
20
|
+
tester/tester.rb
|
21
|
+
}.each {|lib| require File.dirname(__FILE__) + '/taskmapper/' + lib }
|
22
|
+
|
23
|
+
|
24
|
+
# This is the TaskMapper class
|
25
|
+
#
|
26
|
+
class TaskMapper
|
27
|
+
attr_reader :provider, :symbol
|
28
|
+
attr_accessor :default_project
|
29
|
+
|
30
|
+
# This initializes the TaskMapper instance and prepares the provider
|
31
|
+
# If called without any arguments, it conveniently tries searching for the information in
|
32
|
+
# ~/.taskmapper.yml
|
33
|
+
# See the documentation for more information on the format of that file.
|
34
|
+
#
|
35
|
+
# What it DOES NOT do is auto-require the provider...so make sure you have the providers required.
|
36
|
+
def initialize(system = nil, authentication = nil)
|
37
|
+
if system.nil? or authentication.nil?
|
38
|
+
require 'yaml'
|
39
|
+
data = YAML.load_file File.expand_path(ENV['TASKMAPPER_CONFIG'] || '~/.taskmapper.yml')
|
40
|
+
system = system.nil? ? data['default'] || data.first.first : system.to_s
|
41
|
+
authentication = data[system]['authentication'] if authentication.nil? and data[system]['authentication']
|
42
|
+
end
|
43
|
+
self.extend TaskMapper::Provider.const_get(system.to_s.capitalize)
|
44
|
+
authorize authentication
|
45
|
+
@symbol = system.to_sym
|
46
|
+
@provider = TaskMapper::Provider.const_get(system.to_s.capitalize)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Providers should over-write this method
|
50
|
+
def authorize(authentication = {})
|
51
|
+
raise TaskMapper::Exception.new("This method must be reimplemented in the provider")
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# The command method call implementation
|
2
|
+
# This sets the option parser and passes the parsed options to the subcommands
|
3
|
+
def config(options)
|
4
|
+
ARGV << '--help' if ARGV.length == 0
|
5
|
+
begin
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: ticket -p PROVIDER [options] config [config_options]'
|
8
|
+
opts.separator ''
|
9
|
+
opts.separator 'Options:'
|
10
|
+
|
11
|
+
opts.on('-a', '--add', 'Add a new entry to the configuration file based on taskmapper options.') do
|
12
|
+
options[:subcommand] = 'add'
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on('-e', '--edit', 'Edit an existing entry to the configuration file based on taskmapper options') do
|
16
|
+
options[:subcommand] = 'edit'
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-p', '--set-default-provider', 'Set the current provider as the default.', 'Requires provider to be specified, otherwise unsets the default') do
|
20
|
+
options[:subcommand] = 'set_default_provider'
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.separator ''
|
24
|
+
opts.separator 'Other options:'
|
25
|
+
|
26
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end.parse(ARGV)
|
31
|
+
rescue OptionParser::MissingArgument => exception
|
32
|
+
puts "ticket #{options[:original_argv].join(' ')}\n\n"
|
33
|
+
puts "Error: An option was called that requires an argument, but was not given one"
|
34
|
+
puts exception.message
|
35
|
+
end
|
36
|
+
send(options[:subcommand], options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called on --add. It adds a new entry to the config file and will refuse if it already exists
|
40
|
+
def add(options)
|
41
|
+
require_provider unless options[:provider]
|
42
|
+
provider = options[:provider]
|
43
|
+
config_file = File.expand_path(options[:config])
|
44
|
+
config = if File.exists?(config_file)
|
45
|
+
YAML.load_file(config_file)
|
46
|
+
else
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
if config[provider]
|
50
|
+
puts "#{provider} has already been specfied in #{options[:config]}. Refusing to add. Use --edit instead."
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
config[provider] = {}
|
54
|
+
config[provider]['authentication'] = options[:authentication] || {}
|
55
|
+
config[provider]['project'] = options[:project] if options[:project]
|
56
|
+
File.open(config_file, 'w') do |out|
|
57
|
+
YAML.dump(config, out)
|
58
|
+
end
|
59
|
+
puts "Wrote #{provider} to #{config_file}"
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
|
63
|
+
# Called on --edit. It updates and edits an entry. If the entry is non-existent, it will add it.
|
64
|
+
def edit(options)
|
65
|
+
require_provider unless options[:provider]
|
66
|
+
provider = options[:provider]
|
67
|
+
config_file = File.expand_path(options[:config])
|
68
|
+
config = if File.exist?(config_file)
|
69
|
+
YAML.load_file(config_file)
|
70
|
+
else
|
71
|
+
{}
|
72
|
+
end
|
73
|
+
config[provider] ||= {}
|
74
|
+
config[provider]['authentication'] = options[:authentication] || {}
|
75
|
+
config[provider]['project'] = options[:project] if options[:project]
|
76
|
+
File.open(config_file, 'w') do |out|
|
77
|
+
YAML.dump(config, out)
|
78
|
+
end
|
79
|
+
puts "Wrote #{provider} to #{config_file}"
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
# Called on --set-default-provider. It sets the current provider as the default
|
84
|
+
def set_default_provider(options)
|
85
|
+
provider = options[:provider]
|
86
|
+
config = YAML.load_file(config_file = File.expand_path(options[:config]))
|
87
|
+
puts "Warning! #{provider} is not defined in #{config_file}" unless provider.nil? or config[provider]
|
88
|
+
config['default'] = provider
|
89
|
+
File.open(config_file, 'w') do |out|
|
90
|
+
YAML.dump(config, out)
|
91
|
+
end
|
92
|
+
puts "Default provider has been set to '#{provider}'"
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
# Called when a provider is not given.
|
97
|
+
def require_provider
|
98
|
+
puts "Provider must be specified!"
|
99
|
+
exit 1
|
100
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# the console command
|
2
|
+
def console(options)
|
3
|
+
send(:open_irb, options, ARGV)
|
4
|
+
end
|
5
|
+
|
6
|
+
# the actual method to do the irb opening
|
7
|
+
def open_irb(options, argv)
|
8
|
+
tm_lib = File.dirname(__FILE__) + '/../../../taskmapper.rb'
|
9
|
+
irb_name = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
|
10
|
+
requires = "-r rubygems -r #{tm_lib} "
|
11
|
+
cmd = ''
|
12
|
+
if File.exist?(config = File.expand_path(options[:config]))
|
13
|
+
ENV['TASKMAPPER_CONFIG']=config
|
14
|
+
end
|
15
|
+
providers = !options[:provider].nil? ? [options[:provider]] : YAML.load_file(config).keys
|
16
|
+
providers.delete 'default'
|
17
|
+
require 'rubygems'
|
18
|
+
require 'taskmapper'
|
19
|
+
providers.inject(requires) do |mem, p|
|
20
|
+
begin
|
21
|
+
require "taskmapper-#{p}"
|
22
|
+
requires << "-r taskmapper-#{p} "
|
23
|
+
rescue Exception => exception
|
24
|
+
#puts exception
|
25
|
+
begin
|
26
|
+
require "#{p}"
|
27
|
+
requires << "-r #{p} "
|
28
|
+
rescue Exception => exception
|
29
|
+
warn "Could not require the '#{p}' provider. Is it installed?"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
cmd << "#{irb_name} #{requires} --simple-prompt #{ARGV.join(' ')}"
|
34
|
+
exec cmd
|
35
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# The generate command
|
2
|
+
def generate(options)
|
3
|
+
if ARGV.length == 0
|
4
|
+
ARGV << '--help'
|
5
|
+
else
|
6
|
+
provider_name = ARGV.shift
|
7
|
+
if provider_name.start_with? '_'
|
8
|
+
options[:provider] = provider_name[1..-1]
|
9
|
+
options[:provider_dir] = options[:provider]
|
10
|
+
else
|
11
|
+
options[:provider] = provider_name
|
12
|
+
options[:provider_dir] = 'taskmapper-' + options[:provider]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
options[:mkdir] = true
|
16
|
+
begin
|
17
|
+
OptionParser.new do |opts|
|
18
|
+
opts.banner = 'Usage: tm generate PROVIDER_NAME [--lib-directory DIR] [--jeweler [jeweler_options]]'
|
19
|
+
opts.separator ''
|
20
|
+
opts.separator 'Options:'
|
21
|
+
|
22
|
+
opts.on('-J', '--jeweler [JEWELER_OPTIONS]', 'Sets the working ticket') do |option|
|
23
|
+
options[:jeweler] = ARGV
|
24
|
+
options[:mkdir] = false
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-L', '--lib-directory DIR', 'Put the skeleton files inside this directory', ' * This assumes the directory already exists') do |dir|
|
28
|
+
options[:lib] = dir
|
29
|
+
options[:mkdir] = false
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.separator ''
|
33
|
+
opts.separator 'Other options:'
|
34
|
+
|
35
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
36
|
+
puts opts
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
opts.separator ''
|
40
|
+
opts.separator 'NOTE: taskmapper- will be prepended to your provider name'
|
41
|
+
opts.separator 'unless you set the first character as _ (it will be removed)'
|
42
|
+
end.order!
|
43
|
+
rescue OptionParser::MissingArgument => exception
|
44
|
+
puts "tm #{options[:original_argv].join(' ')}\n\n"
|
45
|
+
puts "Error: An option was called that requires an argument, but was not given one"
|
46
|
+
puts exception.message
|
47
|
+
rescue OptionParser::InvalidOption => exception
|
48
|
+
options[:jeweler] = exception.recover(ARGV)
|
49
|
+
options[:mkdir] = false
|
50
|
+
end
|
51
|
+
options[:lib] ||= options[:provider_dir] + '/lib/'
|
52
|
+
create_directories(options)
|
53
|
+
copy_skeleton(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Copy over the skeleton files
|
57
|
+
def copy_skeleton(options)
|
58
|
+
skeleton_path = File.dirname(__FILE__) + '/generate/'
|
59
|
+
provider = File.read(skeleton_path + 'provider.rb').gsub('yoursystem', options[:provider].downcase)
|
60
|
+
create_file(options, options[:provider_dir] + '.rb', provider)
|
61
|
+
skeleton_path << 'provider/'
|
62
|
+
provider = File.read(skeleton_path + 'provider.rb').gsub('Yoursystem', options[:provider].capitalize).gsub('yoursystem', options[:provider].downcase)
|
63
|
+
create_file(options, 'provider/' + options[:provider].downcase + '.rb', provider)
|
64
|
+
%w(project.rb ticket.rb comment.rb).each do |p|
|
65
|
+
provider = File.read(skeleton_path + p).gsub('Yoursystem', options[:provider].capitalize).gsub('yoursystem', options[:provider].downcase)
|
66
|
+
create_file(options, 'provider/' + p, provider)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create the directories so copy_skeleton can do its job
|
71
|
+
def create_directories(options)
|
72
|
+
if options[:jeweler]
|
73
|
+
jeweler_options = options[:jeweler].inject('') do |mem, j|
|
74
|
+
j="'#{j}'" if j.include?(' ')
|
75
|
+
mem + j + ' '
|
76
|
+
end
|
77
|
+
puts "Running jeweler #{jeweler_options} #{options[:provider_dir]}"
|
78
|
+
puts `jeweler #{jeweler_options} #{options[:provider_dir]}`
|
79
|
+
elsif options[:mkdir]
|
80
|
+
begin
|
81
|
+
Dir.mkdir(options[:provider_dir])
|
82
|
+
puts "\tcreate\t#{options[:provider_dir]}"
|
83
|
+
rescue Exception => e
|
84
|
+
puts "\t#{e.message}"
|
85
|
+
end
|
86
|
+
begin
|
87
|
+
Dir.mkdir(options[:lib])
|
88
|
+
puts "\tcreate\t#{options[:lib]}"
|
89
|
+
rescue Exception => e
|
90
|
+
puts "\t#{e.message}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
begin
|
94
|
+
Dir.mkdir(options[:lib] + '/provider')
|
95
|
+
puts "\tcreate\t#{options[:lib] + 'provider'}"
|
96
|
+
rescue Exception => e
|
97
|
+
puts "\t#{e.message}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Create files
|
102
|
+
def create_file(options, filename, data)
|
103
|
+
file_path = options[:lib] + '/' + filename
|
104
|
+
if File.exist?(file_path) and File.size(file_path) > 0
|
105
|
+
puts "\texists with content...skipping\t#{filename}"
|
106
|
+
return false;
|
107
|
+
end
|
108
|
+
puts "\tcreate\t#{filename}"
|
109
|
+
f = File.open(file_path, 'a+')
|
110
|
+
f.write data
|
111
|
+
f.close
|
112
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module TaskMapper::Provider
|
2
|
+
module Yoursystem
|
3
|
+
# The comment class for taskmapper-yoursystem
|
4
|
+
#
|
5
|
+
# Do any mapping between TaskMapper and your system's comment model here
|
6
|
+
# versions of the ticket.
|
7
|
+
#
|
8
|
+
class Comment < TaskMapper::Provider::Base::Comment
|
9
|
+
#API = Yoursystem::Comment # The class to access the api's comments
|
10
|
+
# declare needed overloaded methods here
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TaskMapper::Provider
|
2
|
+
module Yoursystem
|
3
|
+
# Project class for taskmapper-yoursystem
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Project < TaskMapper::Provider::Base::Project
|
7
|
+
#API = Yoursystem::Project # The class to access the api's projects
|
8
|
+
# declare needed overloaded methods here
|
9
|
+
|
10
|
+
|
11
|
+
# copy from this.copy(that) copies that into this
|
12
|
+
def copy(project)
|
13
|
+
project.tickets.each do |ticket|
|
14
|
+
copy_ticket = self.ticket!(:title => ticket.title, :description => ticket.description)
|
15
|
+
ticket.comments.each do |comment|
|
16
|
+
copy_ticket.comment!(:body => comment.body)
|
17
|
+
sleep 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TaskMapper::Provider
|
2
|
+
# This is the Yoursystem Provider for taskmapper
|
3
|
+
module Yoursystem
|
4
|
+
include TaskMapper::Provider::Base
|
5
|
+
#TICKET_API = Yoursystem::Ticket # The class to access the api's tickets
|
6
|
+
#PROJECT_API = Yoursystem::Project # The class to access the api's projects
|
7
|
+
|
8
|
+
# This is for cases when you want to instantiate using TaskMapper::Provider::Yoursystem.new(auth)
|
9
|
+
def self.new(auth = {})
|
10
|
+
TaskMapper.new(:yoursystem, auth)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Providers must define an authorize method. This is used to initialize and set authentication
|
14
|
+
# parameters to access the API
|
15
|
+
def authorize(auth = {})
|
16
|
+
@authentication ||= TaskMapper::Authenticator.new(auth)
|
17
|
+
# Set authentication parameters for whatever you're using to access the API
|
18
|
+
end
|
19
|
+
|
20
|
+
# declare needed overloaded methods here
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|