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.
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