taskmapper 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +12 -0
  4. data/Gemfile.lock +52 -0
  5. data/LICENSE +20 -0
  6. data/NOTES +18 -0
  7. data/README.md +296 -0
  8. data/Rakefile +40 -0
  9. data/TODO +1 -0
  10. data/VERSION +1 -0
  11. data/bin/tm +7 -0
  12. data/examples/tm_example.rb +11 -0
  13. data/examples/tm_example_2.rb +11 -0
  14. data/examples/tm_example_3.rb +21 -0
  15. data/examples/tm_example_4.rb +17 -0
  16. data/lib/taskmapper.rb +53 -0
  17. data/lib/taskmapper/authenticator.rb +2 -0
  18. data/lib/taskmapper/cli/commands/config.rb +100 -0
  19. data/lib/taskmapper/cli/commands/console.rb +35 -0
  20. data/lib/taskmapper/cli/commands/generate.rb +112 -0
  21. data/lib/taskmapper/cli/commands/generate/provider.rb +5 -0
  22. data/lib/taskmapper/cli/commands/generate/provider/comment.rb +14 -0
  23. data/lib/taskmapper/cli/commands/generate/provider/project.rb +26 -0
  24. data/lib/taskmapper/cli/commands/generate/provider/provider.rb +25 -0
  25. data/lib/taskmapper/cli/commands/generate/provider/ticket.rb +12 -0
  26. data/lib/taskmapper/cli/commands/help.rb +9 -0
  27. data/lib/taskmapper/cli/commands/help/config +27 -0
  28. data/lib/taskmapper/cli/commands/help/console +13 -0
  29. data/lib/taskmapper/cli/commands/help/generate +19 -0
  30. data/lib/taskmapper/cli/commands/help/help +7 -0
  31. data/lib/taskmapper/cli/commands/help/project +13 -0
  32. data/lib/taskmapper/cli/commands/help/ticket +14 -0
  33. data/lib/taskmapper/cli/commands/project.rb +140 -0
  34. data/lib/taskmapper/cli/commands/ticket.rb +145 -0
  35. data/lib/taskmapper/cli/common.rb +28 -0
  36. data/lib/taskmapper/cli/init.rb +77 -0
  37. data/lib/taskmapper/comment.rb +97 -0
  38. data/lib/taskmapper/common.rb +81 -0
  39. data/lib/taskmapper/dummy/comment.rb +27 -0
  40. data/lib/taskmapper/dummy/dummy.rb +28 -0
  41. data/lib/taskmapper/dummy/project.rb +42 -0
  42. data/lib/taskmapper/dummy/ticket.rb +43 -0
  43. data/lib/taskmapper/exception.rb +2 -0
  44. data/lib/taskmapper/helper.rb +72 -0
  45. data/lib/taskmapper/project.rb +145 -0
  46. data/lib/taskmapper/provider.rb +82 -0
  47. data/lib/taskmapper/tester/comment.rb +18 -0
  48. data/lib/taskmapper/tester/project.rb +19 -0
  49. data/lib/taskmapper/tester/tester.rb +28 -0
  50. data/lib/taskmapper/tester/ticket.rb +19 -0
  51. data/lib/taskmapper/ticket.rb +154 -0
  52. data/spec/project_spec.rb +84 -0
  53. data/spec/rcov.opts +1 -0
  54. data/spec/spec.opts +1 -0
  55. data/spec/spec_helper.rb +9 -0
  56. data/spec/taskmapper-cli_spec.rb +60 -0
  57. data/spec/taskmapper-exception_spec.rb +160 -0
  58. data/spec/taskmapper_spec.rb +13 -0
  59. data/spec/ticket_spec.rb +56 -0
  60. data/taskmapper.gemspec +118 -0
  61. metadata +189 -0
@@ -0,0 +1,82 @@
1
+ # This is the TaskMapper::Provider Module
2
+ #
3
+ # All provider classes will extend into this module.
4
+ # See the Dummy provider's code for some specifics on implementing a provider
5
+ #
6
+ # Currently, only Projects and Tickets are standardized in ticket master. Therefore,
7
+ # if your provider has other types--such as People/Members, Messages, Milestones, Notes,
8
+ # Tags, etc--you may implement it at your discretion. We are planning to eventually
9
+ # incorporate and standardize many of these into the overall provider. Keep on the look out for it!
10
+ #
11
+ # We are also planning on standardizing non-standard/provider-specific object models
12
+ module TaskMapper::Provider
13
+ module Base
14
+ PROJECT_API = nil # The Class for the project api interaction
15
+ TICKET_API = nil # The Class for the ticket api interaction
16
+
17
+ include TaskMapper::Provider::Helper
18
+
19
+ # All providers must define this method.
20
+ # It doesn't *have* to do anything, it just has to be there. But since it's here, you don't
21
+ # have to worry about it as long as you "include TaskMapper::Provider::Base"
22
+ #
23
+ # If you need to do some additional things to initialize the instance, here is where you would put it
24
+ def authorize(authentication = {})
25
+ @authentication = TaskMapper::Authenticator.new(authentication)
26
+ end
27
+
28
+ # All providers must define this method.
29
+ # It should implement the code for validating the authentication
30
+ def valid?
31
+ raise TaskMapper::Exception.new("#{Base.name}::#{this_method} method must be implemented by the provider")
32
+ end
33
+
34
+ # Providers should try to define this method
35
+ #
36
+ # It returns the project class for this provider, so that there can be calls such as
37
+ # taskmapper.project.find :all
38
+ # taskmapper.project(:id => 777, :name => 'Proj test')
39
+ #
40
+ # Should try to implement a find :first (or find with singular result) if given parameters
41
+ def project(*options)
42
+ easy_finder(@provider::Project, :first, options)
43
+ end
44
+
45
+ # All providers should try to define this method.
46
+ #
47
+ # It returns all projects in an array
48
+ # Should try to implement a find :all if given parameters
49
+ def projects(*options)
50
+ easy_finder(@provider::Project, :all, options)
51
+ end
52
+
53
+ # Create a project same as project.create()
54
+ def project!(*options)
55
+ project.create(*options)
56
+ end
57
+
58
+ # Providers should try to define this method
59
+ #
60
+ # It returns the ticket class for this provider, so that there can be calls such as
61
+ # taskmapper.ticket.find :all
62
+ # taskmapper.ticket(:id => 102, :title => 'Ticket')
63
+ #
64
+ # Don't confuse this with project.ticket.find(...) since that deals with tickets specific to a
65
+ # project. This is deals with tickets
66
+ #
67
+ # Should try to implement a find :first (or find with singular result) if given parameters
68
+ def ticket(*options)
69
+ easy_finder(@provider::Ticket, :first, options)
70
+ end
71
+
72
+ # All providers should try to define this method
73
+ #
74
+ # It returns all tickets in an array.
75
+ # Should try to implement a find :all if given parameters
76
+ def tickets(*options)
77
+ easy_finder(@provider::Ticket, :all, options)
78
+ end
79
+ end
80
+ end
81
+
82
+
@@ -0,0 +1,18 @@
1
+ module TaskMapper::Provider
2
+ module Tester
3
+ # This is the Comment class for the Tester provider
4
+ class Comment < TaskMapper::Provider::Base::Comment
5
+
6
+ # You don't need to define an initializer, this is only here to initialize tester data
7
+ def initialize(project_id, ticket_id, *options)
8
+ data = {:id => rand(1000), :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)],
9
+ :priority => rand(10), :summary => 'Tickets ticket ticket ticket', :resolution => false,
10
+ :created_at => Time.now, :updated_at => Time.now, :description => 'Ticket ticket ticket ticket laughing',
11
+ :assignee => 'lol-man'}
12
+ @system = :tester
13
+ super(data.merge(options.first || {}))
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module TaskMapper::Provider
2
+ module Tester
3
+ # This is the Project class for the Tester provider
4
+ class Project < TaskMapper::Provider::Base::Project
5
+
6
+ # You should define @system and @system_data here.
7
+ # The data stuff is just to initialize fake data. In a real provider, you would use the API
8
+ # to grab the information and then initialize based on that info.
9
+ # @system_data would hold the API's model/instance for reference
10
+ def initialize(*options)
11
+ data = {:id => rand(1000).to_i, :name => 'Tester', :description => 'Mock!-ing Bird',
12
+ :created_at => Time.now, :updated_at => Time.now}
13
+ @system = :tester
14
+ super(data.merge(options.first || {}))
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ module TaskMapper::Provider
2
+ # This is the Tester Provider
3
+ #
4
+ # It doesn't really do anything, it exists in order to test the basic functionality of taskmapper
5
+ # and to provide an example the basics of what is to be expected from the providers.
6
+ #
7
+ # Note that the initial provider name is a module rather than a class. TaskMapper.new
8
+ # extends on an instance-based fashion. If you would rather initialize using code that is
9
+ # closer to:
10
+ #
11
+ # TaskMapper::Provider::Tester.new(authentication)
12
+ #
13
+ # You will have to do a little magic trick and define new on the provider as a wrapper
14
+ # around the TaskMapper.new call.
15
+ module Tester
16
+ include TaskMapper::Provider::Base
17
+ # An example of what to do if you would like to do TaskMapper::Provider::Tester.new(...)
18
+ # rather than TaskMapper.new(:tester, ...)
19
+ def self.new(authentication = {})
20
+ TaskMapper.new(:tester, authentication)
21
+ # maybe do some other stuff
22
+ end
23
+ end
24
+ end
25
+
26
+ %w| project ticket comment |.each do |f|
27
+ require File.dirname(__FILE__) + '/' + f +'.rb'
28
+ end
@@ -0,0 +1,19 @@
1
+ module TaskMapper::Provider
2
+ module Tester
3
+ # The Tester Provider's Ticket class
4
+ class Ticket < TaskMapper::Provider::Base::Ticket
5
+ @system = :tester
6
+
7
+ # You don't need to define an initializer, this is only here to initialize tester data
8
+ def initialize(project_id, *options)
9
+ data = {:id => rand(1000), :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)],
10
+ :priority => rand(10), :summary => 'Tickets ticket ticket ticket', :resolution => false,
11
+ :created_at => Time.now, :updated_at => Time.now, :description => 'Ticket ticket ticket ticket laughing',
12
+ :assignee => 'lol-man', :project_id => project_id}
13
+ @system = :tester
14
+ super(data.merge(options.first || {}))
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,154 @@
1
+ module TaskMapper::Provider
2
+ module Base
3
+ # The base ticket class for taskmapper
4
+ # All providers should inherit this class
5
+ #
6
+ # The difference between the class methods and instance methods are that the instance
7
+ # methods should be treated as though they are called on a known ticket and the class
8
+ # methods act based on a blank slate (which means the info to find a specific ticket has
9
+ # to be passed in the parameters in the ticket)
10
+ #
11
+ # Methods that the provider should define if feasible:
12
+ #
13
+ # * reload!
14
+ # * initialize
15
+ # * close
16
+ # * save
17
+ # * destroy
18
+ #
19
+ # Methods that would probably be okay if the provider left it alone:
20
+ #
21
+ # * self.create
22
+ # * update
23
+ # * update!
24
+ # * self.find
25
+ #
26
+ # A provider should define as many attributes as feasibly possible. The list below are
27
+ # some guidelines as to what attributes are necessary, if your provider's api does not
28
+ # implement them, point it to an attribute that is close to it. (for example, a title
29
+ # can point to summary. and assignee might point to assigned_to. Remember to alias it in your class!)
30
+ #
31
+ # * id
32
+ # * status
33
+ # * priority
34
+ # * title
35
+ # * resolution
36
+ # * created_at
37
+ # * updated_at
38
+ # * description
39
+ # * assignee
40
+ # * requestor
41
+ # * project_id
42
+ class Ticket < Hashie::Mash
43
+ include TaskMapper::Provider::Common
44
+ include TaskMapper::Provider::Helper
45
+ extend TaskMapper::Provider::Helper
46
+ attr_accessor :system, :system_data
47
+ API = nil # Replace with your ticket API's Class
48
+
49
+ # Find ticket
50
+ # You can also retrieve an array of all tickets by not specifying any query.
51
+ #
52
+ # * find(<project_id>) or find(<project_id>, :all) - Returns an array of all tickets
53
+ # * find(<project_id>, ##) - Returns a ticket based on that id or some other primary (unique) attribute
54
+ # * find(<project_id>, [#, #, #]) - Returns many tickets with these ids
55
+ # * find(<project_id>, :first, :title => 'ticket name') - Returns the first ticket based on the ticket's attribute(s)
56
+ # * find(<project_id>, :last, :title => 'Some ticket') - Returns the last ticket based on the ticket's attribute(s)
57
+ # * find(<project_id>, :all, :title => 'Test ticket') - Returns all tickets based on the given attribute(s)
58
+ def self.find(project_id, *options)
59
+ first, attributes = options
60
+ if first.nil? or (first == :all and attributes.nil?)
61
+ self.find_by_attributes(project_id)
62
+ elsif first.is_a? Array
63
+ first.collect { |id| self.find_by_id(project_id, id) }
64
+ elsif first == :first
65
+ tickets = attributes.nil? ? self.find_by_attributes(project_id) : self.find_by_attributes(project_id, attributes)
66
+ tickets.first
67
+ elsif first == :last
68
+ tickets = attributes.nil? ? self.find_by_attributes(project_id) : self.find_by_attributes(project_id, attributes)
69
+ tickets.last
70
+ elsif first == :all
71
+ self.find_by_attributes(project_id, attributes)
72
+ else
73
+ self.find_by_id(project_id, first)
74
+ end
75
+ end
76
+
77
+ # The first of whatever ticket
78
+ def self.first(project_id, *options)
79
+ self.find(project_id, :first, *options)
80
+ end
81
+
82
+ # The last of whatever ticket
83
+ def self.last(project_id, *options)
84
+ self.find(project_id, :last, *options)
85
+ end
86
+
87
+ # Accepts an integer id and returns the single ticket instance
88
+ # Must be defined by the provider
89
+ def self.find_by_id(project_id, ticket_id)
90
+ if self::API.is_a? Class
91
+ self.new self::API.find(ticket_id, :params => {:project_id => project_id})
92
+ else
93
+ raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider")
94
+ end
95
+ end
96
+
97
+ # Accepts an attributes hash and returns all tickets matching those attributes in an array
98
+ # Should return all tickets if the attributes hash is empty
99
+ # Must be defined by the provider
100
+ def self.find_by_attributes(project_id, attributes = {})
101
+ if self::API.is_a? Class
102
+ self.search(project_id, attributes)
103
+ else
104
+ raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider")
105
+ end
106
+ end
107
+
108
+ # This is a helper method to find
109
+ def self.search(project_id, options = {}, limit = 1000)
110
+ if self::API.is_a? Class
111
+ tickets = self::API.find(:all, :params => {:project_id => project_id}).collect { |ticket| self.new ticket }
112
+ search_by_attribute(tickets, options, limit)
113
+ else
114
+ raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider")
115
+ end
116
+ end
117
+
118
+ # Asks the provider's api for the comment associated with the project,
119
+ # returns an array of Comment objects.
120
+ def comments(*options)
121
+ options.insert 0, project_id
122
+ options.insert 1, id
123
+ easy_finder(provider_parent(self.class)::Comment, :all, options, 2)
124
+ end
125
+
126
+ def comment(*options)
127
+ if options.length > 0
128
+ options.insert(0, project_id)
129
+ options.insert(1, id)
130
+ end
131
+ easy_finder(provider_parent(self.class)::Comment, :first, options, 2)
132
+ end
133
+
134
+ # Create a comment
135
+ def comment!(*options)
136
+ options[0].merge!(:project_id => project_id, :ticket_id => id) if options.first.is_a?(Hash)
137
+ provider_parent(self.class)::Comment.create(*options)
138
+ end
139
+
140
+ # Close this ticket
141
+ #
142
+ # On success it should return true, otherwise false
143
+ def close(*options)
144
+ raise TaskMapper::Exception.new("#{self.class.name}::#{this_method} method must be implemented by the provider")
145
+ end
146
+
147
+ # Reload this ticket
148
+ def reload!(*options)
149
+ raise TaskMapper::Exception.new("#{self.class.name}::#{this_method} method must be implemented by the provider")
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,84 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # This can also act as an example test or test skeleton for your provider.
4
+ # Just replace the Dummy in @project_class and @ticket_class
5
+ # Also, remember to mock or stub any API calls
6
+ describe "Projects" do
7
+ let(:tm) { TaskMapper.new(:dummy, {}) }
8
+ let(:project_class) { TaskMapper::Provider::Dummy::Project }
9
+
10
+ describe "with a connection to a provider" do
11
+ context "when #projects" do
12
+ subject { tm.projects }
13
+ it { should be_an_instance_of Array }
14
+ it { subject.first.should be_an_instance_of project_class }
15
+ it { subject.last.should be_an_instance_of project_class }
16
+ end
17
+
18
+ context "when #project with a hash is call" do
19
+ subject { tm.project(:name => "Whack whack what?") }
20
+ it { should be_an_instance_of project_class }
21
+ it { subject.name.should be_eql("Whack whack what?") }
22
+ end
23
+
24
+ context "when retreiving a project" do
25
+ subject { tm.projects.first }
26
+ it { subject.description.should_not be_nil }
27
+ it { subject.description.should be_eql("Mock!-ing Bird") }
28
+ end
29
+
30
+ context "when #projects is call with an ID" do
31
+ subject { tm.projects(555) }
32
+ it { should be_an_instance_of project_class }
33
+ it { subject.id.should be_eql(555) }
34
+ end
35
+
36
+ context "when #projects is call with an array of IDs" do
37
+ subject { tm.projects([555]) }
38
+ it { should be_an_instance_of Array }
39
+ it { subject.first.should be_an_instance_of project_class }
40
+ it { subject.first.id.should be_eql(555) }
41
+ end
42
+
43
+ context "when #projects is call with a hash" do
44
+ subject { tm.projects(:id => 555) }
45
+ it { should be_an_instance_of Array }
46
+ it { subject.first.should be_an_instance_of project_class }
47
+ it { subject.first.id.should be_eql(555) }
48
+ end
49
+
50
+ context "when #project is call" do
51
+ subject { tm.project }
52
+ it { should be_eql project_class }
53
+ it { subject.first.description be_eql("Mock!-ing Bird") }
54
+ it { subject.last.description be_eql("Mock!-ing Bird") }
55
+ end
56
+
57
+ context "when #project is call with a hash" do
58
+ subject { tm.project.find(:first, :description => "Shocking Dirb") }
59
+ it { should be_an_instance_of project_class }
60
+ it { subject.description.should be_eql("Shocking Dirb") }
61
+ end
62
+ end
63
+
64
+ describe "declaring a new project" do
65
+ context "when calling #new" do
66
+ subject { tm.project.new(default_info) }
67
+ it { should be_an_instance_of project_class }
68
+ it { subject.name.should be_eql("Tiket Name c") }
69
+ it { subject.save.should be_true }
70
+ end
71
+ end
72
+
73
+ describe "creating a new project" do
74
+ context "when calling #create" do
75
+ subject { tm.project.create(default_info) }
76
+ it { should be_an_instance_of project_class }
77
+ it { subject.name.should be_eql("Tiket Name c") }
78
+ end
79
+ end
80
+
81
+ def default_info
82
+ {:id => 777, :name => "Tiket Name c", :description => "that c thinks the k is trying to steal it's identity"}
83
+ end
84
+ end
@@ -0,0 +1 @@
1
+ --exclude "lib/taskmapper/dummy/*,spec/*,gems/*"
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'taskmapper.rb'
4
+ require 'taskmapper/dummy/dummy.rb'
5
+ require 'rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.color_enabled = true
9
+ end
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ require 'fileutils'
6
+
7
+ # Tests for the cli
8
+ # I'm not quite sure what the most effective way to test this is...
9
+ describe "TaskMapper CLI" do
10
+ before(:all) do
11
+ @ticket = File.expand_path(File.dirname(__FILE__) + '/../bin/tm')
12
+ @cli_dir = File.expand_path(File.dirname(__FILE__) + '/../lib/taskmapper/cli')
13
+ end
14
+
15
+ it "should output help if no command given" do
16
+ help = `#{@ticket}`
17
+ $?.should == 0
18
+ help.should include('Usage: tm [options] COMMAND [command_options]')
19
+ end
20
+
21
+ it "should be able to show help pages" do
22
+ `#{@ticket} help`.should include(File.read(@cli_dir + '/commands/help/help'))
23
+ `#{@ticket} help config`.should include(File.read(@cli_dir + '/commands/help/config'))
24
+ `#{@ticket} help console`.should include(File.read(@cli_dir + '/commands/help/console'))
25
+ end
26
+
27
+ it "should be able to add and edit config info" do
28
+ pending
29
+ end
30
+
31
+ it "should be able to open up a console" do
32
+ pending
33
+ end
34
+
35
+ describe :generate do
36
+ it "should generate provider skeleton w/o runtime errors" do
37
+ provider_name = "test-provider"
38
+ expected_name = "taskmapper-#{provider_name}"
39
+ begin
40
+ generate = `#{@ticket} generate #{provider_name}`
41
+ $?.should == 0
42
+ File.exists?(expected_name).should == true
43
+ ensure
44
+ FileUtils.remove_dir(expected_name) if File.exists? expected_name
45
+ end
46
+ end
47
+
48
+ it "should not prefix 'taskmapper' when not asked to" do
49
+ provider_name = "test-provider"
50
+ begin
51
+ generate = `#{@ticket} generate _#{provider_name}`
52
+ $?.should == 0
53
+ File.exists?(provider_name).should == true
54
+ ensure
55
+ FileUtils.remove_dir(provider_name) if File.exists? provider_name
56
+ end
57
+ end
58
+ end
59
+
60
+ end