├── .document ├── .gitignore ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── NOTES ├── README.md ├── Rakefile ├── TODO ├── examples ├── tm_example.rb ├── tm_example_2.rb ├── tm_example_3.rb └── tm_example_4.rb ├── lib ├── taskmapper.rb └── taskmapper │ ├── authenticator.rb │ ├── comment.rb │ ├── common.rb │ ├── dummy │ ├── comment.rb │ ├── dummy.rb │ ├── project.rb │ └── ticket.rb │ ├── exception.rb │ ├── helper.rb │ ├── project.rb │ ├── provider.rb │ ├── tester │ ├── comment.rb │ ├── project.rb │ ├── tester.rb │ └── ticket.rb │ ├── ticket.rb │ └── version.rb ├── spec ├── project_spec.rb ├── spec.opts ├── spec_helper.rb ├── taskmapper-exception_spec.rb ├── taskmapper_spec.rb └── ticket_spec.rb └── taskmapper.gemspec /.document: -------------------------------------------------------------------------------- 1 | README.md 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | 21 | ## PROJECT::SPECIFIC 22 | .rvmrc 23 | *.un~ 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.2 4 | - 1.9.3 5 | - 2.0.0 6 | - jruby-18mode 7 | - jruby-19mode 8 | - rbx-18mode 9 | - rbx-19mode 10 | - ree 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | taskmapper (1.0.1) 5 | activeresource (~> 3.2) 6 | activesupport (~> 3.2) 7 | hashie (~> 2.0) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | activemodel (3.2.14) 13 | activesupport (= 3.2.14) 14 | builder (~> 3.0.0) 15 | activeresource (3.2.14) 16 | activemodel (= 3.2.14) 17 | activesupport (= 3.2.14) 18 | activesupport (3.2.14) 19 | i18n (~> 0.6, >= 0.6.4) 20 | multi_json (~> 1.0) 21 | builder (3.0.4) 22 | diff-lcs (1.2.4) 23 | hashie (2.0.5) 24 | i18n (0.6.5) 25 | json (1.8.0) 26 | multi_json (1.8.0) 27 | rake (10.0.4) 28 | rdoc (4.0.1) 29 | json (~> 1.4) 30 | rspec (2.14.1) 31 | rspec-core (~> 2.14.0) 32 | rspec-expectations (~> 2.14.0) 33 | rspec-mocks (~> 2.14.0) 34 | rspec-core (2.14.5) 35 | rspec-expectations (2.14.2) 36 | diff-lcs (>= 1.1.3, < 2.0) 37 | rspec-mocks (2.14.3) 38 | 39 | PLATFORMS 40 | ruby 41 | 42 | DEPENDENCIES 43 | rake 44 | rdoc (~> 4.0) 45 | rspec (~> 2.14.1) 46 | taskmapper! 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 The Hybrid Group 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | This needs a documentation for implementing Providers and some guidelines on Provider stuff. 2 | 3 | Environment variables and config files 4 | ~/.taskmapper.yml 5 | 6 | 7 | .taskmapper.yml 8 | default: lighthouse 9 | lighthouse: 10 | token: 67dc50b88ea1e339eb72ba0e2b90573b6453d805 11 | account: taskmapper 12 | unfuddle: 13 | username: xxx 14 | password: xxx 15 | subdomain: xxx 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taskmapper 2 | 3 | Taskmapper is a Gem which eases communication with various project and ticket management systems by providing a consistent Ruby API. 4 | 5 | Taskmapper let's you "remap" a system into the consistent Taskmapper API, easily. For instance the description of an issue/ticket, might be named **description** in one system, and **problem-description** somewhere else. Via Taskmapper, this would always be called **description**. The Taskmapper remaps makes it easy for you to integrate different kinds of ticket systems, into your own system. You don't have to take care of all the different kinds of systems, and their different APIs. Taskmapper handles all this *for* you, so you can focus on making your application awesome. 6 | 7 | ## Installation 8 | 9 | Taskmapper is a Gem, so we can easily install it by using RubyGems: 10 | 11 | gem install taskmapper 12 | 13 | Taskmapper depends on [Hashie](http://github.com/intridea/hashie), which is an amazing library which makes converting objects to hashes, and the other way around, a joy. It should be installed automatically whenever installing taskmapper. 14 | 15 | ### Finding and installing a provider 16 | 17 | Taskmapper by itself won't do too much. You may want to install a provider, to retrieve a list of available providers issue the following command: 18 | 19 | gem search taskmapper 20 | 21 | You could then install for instance taskmapper-pivotal: 22 | 23 | gem install taskmapper-pivotal 24 | 25 | ## Usage 26 | 27 | **Note:** The API may change, and the following may not be the final. Please keep yourself updated before you upgrade. 28 | 29 | First, we instance a new class with the right set of options. In this example, we are authenticating with Pivotal Tracker. 30 | 31 | pivotal = taskmapper.new(:pivotal, {:username => "john", :password => "seekrit"}) 32 | 33 | ### Grabbing a project 34 | 35 | Now that we've got out Taskmapper instance, let's go ahead and grab "testproject": 36 | 37 | project = pivotal.project["testproject"] 38 | #=> TaskMapper::Project<#name="testproject"..> 39 | 40 | *Project#[]* is an alias to *Project#find*: 41 | 42 | project = pivotal.project.find "testproject" 43 | #=> TaskMapper::Project<#name="testproject"..> 44 | 45 | Which translates into: 46 | 47 | project = pivotal.project.find :name => "testproject" 48 | #=> TaskMapper::Project<#name="testproject"..> 49 | 50 | That means you can actually look up a project by something else than the title, like the owner: 51 | 52 | project = pivotal.project.find :owner => "Sirupsen" 53 | #=> TaskMapper::Project<#owner="sirupsen"..> 54 | 55 | To retrieve all projects, simply pass no argument to find: 56 | 57 | project = pivotal.project.find 58 | #=> [TaskMapper::Project<#..>,TaskMapper::Project<#..>,..] 59 | 60 | ### Creating a ticket 61 | 62 | Now that we grabbed the right project. Let's go ahead and create a ticket at this project: 63 | 64 | project.ticket!(:title => "Test", :description => "Hello World") 65 | 66 | We create our ticket with three properties. 67 | 68 | ### Finding tickets 69 | 70 | Alright, let's play with the projects tickets! Here we grab the ticket with the id of 22: 71 | 72 | ticket = project.tickets(:id => 22) 73 | #=> TaskMapper::Ticket<#id=22..> 74 | 75 | Like with projects, we can also find tickets by other attributes, like title, priority and so on, with tickets we do not use a find method though. Also as with projects, if no argument is passed, all tickets are retrieved: 76 | 77 | tickets = project.tickets 78 | #=> [TaskMapper::Ticket<#..>,TaskMapper::Ticket<#..>,..] 79 | 80 | ### Changing ticket attributes 81 | 82 | Let's say that we're working on this ticket right now, so let's go ahead and change the status to reflect that: 83 | 84 | ticket.status = :in_progress 85 | 86 | Other valid ticket statuses include: 87 | 88 | :closed, :accepted, :resolved 89 | 90 | For the sake of example, we'll change the description as well, and then save the ticket. 91 | 92 | ticket.description = "Changed description to something else!" 93 | ticket.save 94 | 95 | ### Closing a ticket 96 | 97 | The issue was solved, let's make that official by closing the ticket with the appropriate resolution: 98 | 99 | ticket.close(:resolution => "fixed", :description => "Fixed issue by doing x") 100 | 101 | Note that you could close the ticket by changing all the attributes manually, like so: 102 | 103 | ticket.status = :closed 104 | ticket.resolution = "fixed" 105 | ticket.resolution_description = "Fixed issue by doing x" 106 | ticket.save 107 | 108 | However, as closing a ticket with a resolution is such a common task, the other method is included because it may be more convenient. 109 | 110 | ## Support 111 | 112 | Currently Taskmapper supports the following systems: 113 | 114 | ### Pivotal Tracker 115 | 116 | To use Pivotal Tracker with Taskmapper, install it: 117 | gem install taskmapper-pivotal 118 | 119 | Then simply require it, and you are good to use Pivotal Tracker with Taskmapper! 120 | 121 | require 'taskmapper' 122 | require 'taskmapper-pivotal' 123 | pivotal = taskmapper.new(:pivotal, {:username => "..", :password => ".."}) 124 | 125 | The source code is located at [taskmapper-pivotal](http://github.com/hybridgroup/taskmapper-pivotal) 126 | 127 | ### Lighthouse 128 | 129 | To use Lighthouse with Taskmapper, install it: 130 | gem install taskmapper-lighthouse 131 | 132 | Then simply require it, and you are all set to use Lighthouse with Taskmapper! 133 | 134 | require 'taskmapper' 135 | require 'taskmapper-lighthouse' 136 | lighthouse = taskmapper.new(:lighthouse, {:username => "..", :password => ".."}) 137 | 138 | The source code is located at [taskmapper-lighthouse](http://github.com/hybridgroup/taskmapper-lighthouse) 139 | 140 | ### Basecamp 141 | 142 | To use Basecamp with Taskmapper, install it: 143 | gem install taskmapper-basecamp 144 | 145 | Once you require it, then you are ready to use Basecamp with Taskmapper 146 | 147 | require 'taskmapper' 148 | require 'taskmapper-basecamp' 149 | basecamp = taskmapper.new(:basecamp, :domain => 'yourdomain.basecamphq.com', :username => 'you', :password => 'pass') 150 | 151 | The source code is located at [taskmapper-basecamp](http://github.com/hybridgroup/taskmapper-basecamp) 152 | 153 | ### Github 154 | 155 | To use Github Issue tracking with Taskmapper, install it: 156 | gem install taskmapper-github 157 | 158 | Once you require it, then you are ready to use Github and Taskmapper 159 | 160 | require 'taskmapper' 161 | require 'taskmapper-github' 162 | github = taskmapper.new(:github, :username => 'you', :password => 'pass') 163 | 164 | The source code is located at [taskmapper-github](http://github.com/hybridgroup/taskmapper-github) 165 | 166 | ### Unfuddle 167 | 168 | To use Unfuddle with Taskmapper, install it: 169 | gem install taskmapper-unfuddle 170 | 171 | Then simply require it, and you are good to use Unfuddle with Taskmapper! 172 | 173 | require 'taskmapper' 174 | require 'taskmapper-unfuddle' 175 | unfuddle = taskmapper.new(:unfuddle, {:username => "..", :password => "..", :account => ".."}) 176 | 177 | The source code is located at [taskmapper-unfuddle](http://github.com/hybridgroup/taskmapper-unfuddle) 178 | 179 | ### Kanbanpad 180 | 181 | To use Kanbanpad with taskmapper, install it: 182 | gem install taskmapper-kanbanpad 183 | 184 | Once you require it, you can connect to Kanbanpad using Taskmapper! 185 | 186 | require 'taskmapper' 187 | require 'taskmapper-kanbanpad' 188 | kanbanpad = taskmapper.new(:kanbanpad, {:username => "xx", :password => "xx"}) 189 | 190 | The source code is located at [taskmapper-kanbanpad](https://github.com/hybridgroup/taskmapper-kanbanpad) 191 | 192 | ### Redmine 193 | 194 | To use Redmine with Taskmapper, install it: 195 | gem install taskmapper-redmine 196 | 197 | Just require it, and you are ready to use Redmine with Taskmapper! 198 | 199 | require 'taskmapper' 200 | require 'taskmapper-redmine' 201 | redmine = taskmapper.new(:redmine, {:username => "..", :password => "..", :server => ".."}) 202 | 203 | The source code is located at [taskmapper-redmine](http://github.com/hybridgroup/taskmapper-redmine) 204 | 205 | ### Trac 206 | 207 | To use Trac with Taskmapper, install it: 208 | gem install taskmapper-trac 209 | 210 | Require it, and you are happening to call Trac with Taskmapper! 211 | 212 | require 'taskmapper' 213 | require 'taskmapper-trac' 214 | trac = taskmapper.new(:trac, {:username => "..", :password => "..", :url => ".."}) 215 | 216 | The source code is located at [taskmapper-trac](http://github.com/hybridgroup/taskmapper-trac) 217 | 218 | ### Bugzilla 219 | 220 | To use Bugzilla with Taskmapper, install it: 221 | gem install taskmapper-bugzilla 222 | 223 | Require and you can talk to Bugzilla with Taskmapper! 224 | 225 | require 'taskmapper' 226 | require 'taskmapper-bugzilla' 227 | codaset = taskmapper.new(:bugzilla, {:username => "foo", :password => "bar", :url => "https://bugzilla.mozilla.org"}) 228 | 229 | The source code is located at [taskmapper-bugzilla](http://github.com/hybridgroup/taskmapper-bugzilla) 230 | 231 | ## Taskmapper CLI 232 | 233 | For the full documentation on the CLI 234 | [taskmapper-cli](http://github.com/hybridgroup/taskmapper-cli) 235 | 236 | ### Installing the CLI 237 | 238 | gem install taskmapper-cli 239 | 240 | ## Creating a provider 241 | Creating a provider consists of three steps: 242 | 243 | * Run the generator like this: 244 | tm generate --provider-name='myprovider' 245 | * Implement whatever is needed to connect to your desired backend 246 | * Release it to RubyGems 247 | 248 | ### Create the Taskmapper provider 249 | Thanks to a simple generator, it is easy to get started with a new provider. Run this from the command line: 250 | tm generate --provider-name='myprovider' 251 | 252 | This will generate a new skeleton provider called taskmapper-myprovider in the current directory. Create a repo from that directory, and you can start implementing your provider. 253 | 254 | Almost all APIs are different. And so are their Ruby providers. Taskmapper attempts to create an universal API for ticket and project management systems, and thus, we need to map the functionality to the Taskmapper API. This is the providers job. The provider is the glue between Taskmapper, and the task management system's API. 255 | Usually, your provider would rely on another library for the raw HTTP interaction. For instance, [taskmapper-lighthouse](http://github.com/hybridgroup/taskmapper-lighthouse) relies on ActiveResource in order to interact with the Lighthouse API. Look at it like this: 256 | 257 | **Taskmapper** -> **Provider** -> *(Ruby library)* -> **Site's API** 258 | 259 | Provider being the *glue* between the site's API and Taskmapper. The Ruby library is "optional" (though highly recommended as mentioned), therefore it is in parantheses. 260 | 261 | An example of a provider could be [taskmapper-lighthouse](http://github.com/hybridgroup/taskmapper-lighthouse), an example of a Ruby library could be ActiveResource. 262 | 263 | For now, look at [taskmapper-lighthouse](http://github.com/hybridgroup/taskmapper-lighthouse) as an example on how to create a provider. More detailed documentation will be available soon. 264 | 265 | ### Release it 266 | Simply release it to RubyGems.org, the name of the provider Gem should follow this simple naming rule: 267 | 268 | taskmapper- 269 | 270 | For instance if you set for a Github provider, it would be named: 271 | 272 | taskmapper-github 273 | 274 | This makes it easy for people to find providers, simply by issuing: 275 | 276 | gem search -r taskmapper 277 | 278 | They should be presented with a nice list of all available providers. 279 | 280 | ## Note on Patches/Pull Requests 281 | 282 | * Fork the project. 283 | * Make your feature addition or bug fix. 284 | * Add tests for it. This is important so we don't break it in a 285 | future version unintentionally. 286 | * Commit, do not mess with rakefile, version, or history. 287 | (if you want to have your own version, that is fine but bump version in a commit by itself so we can ignore when we pull) 288 | * Send us a pull request. Bonus points for feature branches. 289 | 290 | ## Copyright 291 | 292 | Copyright (c) 2010-2013 [The Hybrid Group](http://hybridgroup.com). See LICENSE for details. 293 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | require 'rdoc/task' 4 | 5 | task :default => :spec 6 | 7 | RSpec::Core::RakeTask.new :spec 8 | 9 | Bundler::GemHelper.install_tasks 10 | 11 | Rake::RDocTask.new do |rdoc| 12 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 13 | 14 | rdoc.rdoc_dir = 'rdoc' 15 | rdoc.title = "taskmapper#{version}" 16 | rdoc.rdoc_files.include('README*') 17 | rdoc.rdoc_files.include('lib/**/*.rb') 18 | end 19 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1) A Cache system for the finders 2 | -------------------------------------------------------------------------------- /examples/tm_example.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'taskmapper' 3 | require 'taskmapper-lighthouse' 4 | 5 | # display list of projects 6 | tm = TaskMapper.new :lighthouse 7 | 8 | tm.projects.each do |project| 9 | puts "#{project.id} - #{project.name}" 10 | end 11 | -------------------------------------------------------------------------------- /examples/tm_example_2.rb: -------------------------------------------------------------------------------- 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 | 8 | project = tm.projects.last 9 | 10 | project.tickets.each do |ticket| 11 | puts "#{ticket.id} - #{ticket.title}" 12 | end 13 | -------------------------------------------------------------------------------- /examples/tm_example_3.rb: -------------------------------------------------------------------------------- 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!( 13 | :name => "Copy Test on #{Time.now}", 14 | :description => "A copy test" 15 | ) 16 | 17 | from.tickets.each do |from_ticket| 18 | to_ticket = to.ticket!({ 19 | :title => pivotal_ticket.title, 20 | :description => pivotal_ticket.description 21 | }) 22 | 23 | from_ticket.comments.each do |comment| 24 | to_ticket.comment! :body => comment.body 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /examples/tm_example_4.rb: -------------------------------------------------------------------------------- 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!( 12 | :name => "Copy Test on #{Time.now}", 13 | :description => "A copy test" 14 | ) 15 | 16 | to.copy from 17 | 18 | puts "Copy finished." 19 | -------------------------------------------------------------------------------- /lib/taskmapper.rb: -------------------------------------------------------------------------------- 1 | %w{ 2 | rubygems 3 | hashie 4 | active_resource 5 | }.each {|lib| require lib } 6 | 7 | %w{ 8 | common 9 | helper 10 | project 11 | ticket 12 | comment 13 | authenticator 14 | provider 15 | exception 16 | dummy/dummy.rb 17 | tester/tester.rb 18 | version 19 | }.each {|lib| require File.dirname(__FILE__) + '/taskmapper/' + lib } 20 | 21 | # This is the TaskMapper class 22 | class TaskMapper 23 | attr_reader :provider, :symbol 24 | attr_accessor :default_project 25 | 26 | # This initializes the TaskMapper instance and prepares the provider 27 | # If called without any arguments, it conveniently tries searching for the information in 28 | # ~/.taskmapper.yml 29 | # See the documentation for more information on the format of that file. 30 | # 31 | # What it DOES NOT do is auto-require the provider...so make sure you have the providers required. 32 | def initialize(system = nil, authentication = nil) 33 | if system.nil? or authentication.nil? 34 | require 'yaml' 35 | data = YAML.load_file File.expand_path(ENV['TASKMAPPER_CONFIG'] || '~/.taskmapper.yml') 36 | system = system.nil? ? data['default'] || data.first.first : system.to_s 37 | authentication = data[system]['authentication'] if authentication.nil? and data[system]['authentication'] 38 | end 39 | self.extend TaskMapper::Provider.const_get(system.to_s.capitalize) 40 | authorize authentication 41 | @symbol = system.to_sym 42 | @provider = TaskMapper::Provider.const_get(system.to_s.capitalize) 43 | end 44 | 45 | # Providers should over-write this method 46 | def authorize(authentication = {}) 47 | raise TaskMapper::Exception.new("This method must be reimplemented in the provider") 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/taskmapper/authenticator.rb: -------------------------------------------------------------------------------- 1 | class TaskMapper::Authenticator < Hashie::Mash 2 | end 3 | -------------------------------------------------------------------------------- /lib/taskmapper/comment.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | module Base 3 | # The comment class 4 | # 5 | # This will probably one of the most troublesome parts of creating a provider for taskmapper 6 | # since there are so many different ways comments are handled by different APIs. 7 | # Keep in mind that if you do need to change/overwrite the methods, you will most likely 8 | # only need to overwrite #find_by_id, #find_by_attributes, #search, and possibly #initialize 9 | # as long as their parameters conform to what the find method expects. 10 | # 11 | # Here are the expected attributes: 12 | # 13 | # * author 14 | # * body 15 | # * id 16 | # * created_at 17 | # * updated_at 18 | # * ticket_id 19 | # * project_id 20 | class Comment < Hashie::Mash 21 | include TaskMapper::Provider::Common 22 | include TaskMapper::Provider::Helper 23 | extend TaskMapper::Provider::Helper 24 | attr_accessor :system, :system_data 25 | API = nil # Replace with your comment's API's Class 26 | 27 | # Find comment 28 | # You can also retrieve an array of all comments by not specifying any query. 29 | # 30 | # * find(, ) or find(, , :all) - Returns an array of all comments 31 | # * find(, , ##) - Returns a comment based on that id or some other primary (unique) attribute 32 | # * find(, , [#, #, #]) - Returns many comments with these ids 33 | # * find(, , :first, :name => 'Project name') - Returns the first comment based on the comment's attribute(s) 34 | # * find(, , :last, :name => 'Some project') - Returns the last comment based on the comment's attribute(s) 35 | # * find(, , :all, :name => 'Test Project') - Returns all comments based on the given attribute(s) 36 | def self.find(project_id, ticket_id, *options) 37 | first, attributes = options 38 | if first.nil? or (first == :all and attributes.nil?) 39 | self.find_by_attributes(project_id, ticket_id) 40 | elsif first.is_a? Array 41 | first.collect { |id| self.find_by_id(project_id, ticket_id, id) } 42 | elsif first == :first 43 | comments = attributes.nil? ? self.find_by_attributes(project_id, ticket_id) : self.find_by_attributes(project_id, ticket_id, attributes) 44 | comments.first 45 | elsif first == :last 46 | comments = attributes.nil? ? self.find_by_attributes(project_id, ticket_id) : self.find_by_attributes(project_id, ticket_id, attributes) 47 | comments.last 48 | elsif first == :all 49 | self.find_by_attributes(project_id, ticket_id, attributes) 50 | else 51 | self.find_by_id(project_id, ticket_id, first) 52 | end 53 | end 54 | 55 | # The first of whatever comment 56 | def self.first(project_id, ticket_id, *options) 57 | self.find(project_id, ticket_id, :first, *options) 58 | end 59 | 60 | # The last of whatever comment 61 | def self.last(project_id, ticket_id, *options) 62 | self.find(project_id, ticket_id, :last, *options) 63 | end 64 | 65 | # Accepts an integer id and returns the single comment instance 66 | # Must be defined by the provider 67 | def self.find_by_id(project_id, ticket_id, id) 68 | if self::API.is_a? Class 69 | self.new self::API.find(id, :params => {:project_id => project_id, :ticket_id => ticket_id}) 70 | else 71 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 72 | end 73 | end 74 | 75 | # Accepts an attributes hash and returns all comments matching those attributes in an array 76 | # Should return all comments if the attributes hash is empty 77 | # Must be defined by the provider 78 | def self.find_by_attributes(project_id, ticket_id, attributes = {}) 79 | if self::API.is_a? Class 80 | self.search(project_id, ticket_id, attributes) 81 | else 82 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 83 | end 84 | end 85 | 86 | # This is a helper method to find 87 | def self.search(project_id, ticket_id, options = {}, limit = 1000) 88 | if self::API.is_a? Class 89 | comments = self::API.find(:all, :params => {:project_id => project_id, :ticket_id => ticket_id}).collect { |comment| self.new comment } 90 | search_by_attribute(comments, options, limit) 91 | else 92 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 93 | end 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/taskmapper/common.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | # Common module 3 | # contains method definitions common to all or most of the models 4 | module Common 5 | module ClassMethods 6 | # Create a something. 7 | # Basically, a .new and .save in the same call. The default method assumes it is passed a 8 | # single hash with attribute information 9 | def create(*options) 10 | if self::API.is_a? Class 11 | something = self::API.new(*options) 12 | something.save 13 | self.new something 14 | else 15 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 16 | end 17 | end 18 | end 19 | 20 | # Automatic extension of class methods on include 21 | def self.included(base) 22 | base.extend ClassMethods 23 | end 24 | 25 | # The initializer 26 | # It tries to do a default system for ActiveResource-based api providers 27 | def initialize(*options) 28 | @system_data ||= {} 29 | @cache ||= {} 30 | first = options.shift 31 | case first 32 | when Hash 33 | super(first.to_hash) 34 | else 35 | @system_data[:client] = first 36 | self.prefix_options ||= @system_data[:client].prefix_options if @system_data[:client].prefix_options 37 | super(first.attributes) 38 | end 39 | end 40 | 41 | # Update the something and save 42 | # As opposed to update which just updates the attributes 43 | def update!(*options) 44 | update(*options) 45 | save 46 | end 47 | 48 | # Save changes to this project 49 | # Returns true (success) or false (failure) or nil (no changes) 50 | def save 51 | if @system_data and (something = @system_data[:client]) and something.respond_to?(:attributes) 52 | changes = 0 53 | something.attributes.each do |k, v| 54 | if self.send(k) != v 55 | something.send(k + '=', self.send(k)) 56 | changes += 1 57 | end 58 | end 59 | something.save if changes > 0 60 | else 61 | raise TaskMapper::Exception.new("#{self.class.name}::#{this_method} method must be implemented by the provider") 62 | end 63 | end 64 | 65 | # Delete this project 66 | # Returns true (success) or false(failure) 67 | def destroy 68 | if @system_data and @system_data[:client] and @system_data[:client].respond_to?(:destroy) 69 | return @system_data[:client].destroy 70 | else 71 | raise TaskMapper::Exception.new("#{self.class.name}::#{this_method} method must be implemented by the provider") 72 | end 73 | end 74 | 75 | def respond_to?(symbol, include_private = false) 76 | result = super(symbol) 77 | return true if result or @system_data.nil? or @system_data[:client].nil? 78 | @system_data[:client].respond_to?(symbol, include_private) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/taskmapper/dummy/comment.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | module Dummy 3 | # This is the Comment class for the Dummy provider 4 | class Comment < TaskMapper::Provider::Base::Comment 5 | def self.find_by_id(id) 6 | self.new({:id => id}) 7 | end 8 | 9 | def self.find_by_attributes(*options) 10 | [self.new(*options)] 11 | end 12 | 13 | # You don't need to define an initializer, this is only here to initialize dummy data 14 | def initialize(project_id, ticket_id, *options) 15 | data = { 16 | :id => rand(1000), 17 | :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)], 18 | :priority => rand(10), 19 | :summary => 'Tickets ticket ticket ticket', 20 | :resolution => false, 21 | :created_at => Time.now, 22 | :updated_at => Time.now, 23 | :description => 'Ticket ticket ticket ticket laughing', 24 | :assignee => 'lol-man' 25 | } 26 | @system = :dummy 27 | super(data.merge(options.first || {})) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/taskmapper/dummy/dummy.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | # This is the Dummy 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::Dummy.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 Dummy 16 | include TaskMapper::Provider::Base 17 | # An example of what to do if you would like to do TaskMapper::Provider::Dummy.new(...) 18 | # rather than TaskMapper.new(:dummy, ...) 19 | def self.new(authentication = {}) 20 | TaskMapper.new(:dummy, 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 29 | -------------------------------------------------------------------------------- /lib/taskmapper/dummy/project.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | module Dummy 3 | # This is the Project class for the Dummy provider 4 | class Project < TaskMapper::Provider::Base::Project 5 | def self.find_by_id(id) 6 | self.new({:id => id}) 7 | end 8 | 9 | def self.find_by_attributes(*options) 10 | [self.new(*options)] 11 | end 12 | 13 | def self.create(*attributes) 14 | self.new(*attributes) 15 | end 16 | 17 | # You should define @system and @system_data here. 18 | # The data stuff is just to initialize fake data. In a real provider, you would use the API 19 | # to grab the information and then initialize based on that info. 20 | # @system_data would hold the API's model/instance for reference 21 | def initialize(*options) 22 | data = { 23 | :id => rand(1000).to_i, 24 | :name => 'Dummy', 25 | :description => 'Mock!-ing Bird', 26 | :created_at => Time.now, 27 | :updated_at => Time.now 28 | } 29 | @system = :dummy 30 | super(data.merge(options.first || {})) 31 | end 32 | 33 | # Nothing to save so we always return true 34 | # ...unless it's an odd numbered second on Friday the 13th. muhaha! 35 | def save 36 | time = Time.now 37 | !(time.wday == 5 and time.day == 13 and time.to_i % 2 == 1) 38 | end 39 | 40 | # Nothing to update, so we always return true 41 | def update!(*options) 42 | return true 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/taskmapper/dummy/ticket.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | module Dummy 3 | # The Dummy Provider's Ticket class 4 | class Ticket < TaskMapper::Provider::Base::Ticket 5 | @system = :dummy 6 | 7 | def self.find_by_id(project_id, ticket_id) 8 | self.new(project_id, {:id => ticket_id}) 9 | end 10 | 11 | def self.find_by_attributes(*ticket_attributes) 12 | [self.new(*ticket_attributes)] 13 | end 14 | 15 | # You don't need to define an initializer, this is only here to initialize dummy data 16 | def initialize(project_id, *options) 17 | data = { 18 | :id => rand(1000), 19 | :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)], 20 | :priority => rand(10), 21 | :summary => 'Tickets ticket ticket ticket', 22 | :resolution => false, 23 | :created_at => Time.now, 24 | :updated_at => Time.now, 25 | :description => 'Ticket ticket ticket ticket laughing', 26 | :assignee => 'lol-man', 27 | :project_id => project_id 28 | } 29 | @system = :dummy 30 | super(data.merge(options.first || {})) 31 | end 32 | 33 | # Nothing to save so we always return true 34 | # ...unless it's the Ides of March and the second is divisible by three. muhaha! 35 | def save 36 | time = Time.now 37 | !(time.wday == 15 and time.day == 3 and time.to_i % 3 == 0) 38 | end 39 | 40 | # Nothing to close, so we always return true 41 | def close 42 | true 43 | end 44 | 45 | # Nothing to destroy so we always return true 46 | def destroy 47 | true 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/taskmapper/exception.rb: -------------------------------------------------------------------------------- 1 | class TaskMapper::Exception < Exception 2 | end 3 | -------------------------------------------------------------------------------- /lib/taskmapper/helper.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | # Contains a series of helper methods 3 | module Helper 4 | 5 | # Current method name reflection 6 | # http://www.ruby-forum.com/topic/75258#895630 7 | # Call when raising 'implemented by the provider' exceptions 8 | def this_method 9 | caller[0][/`([^']*)'/, 1] 10 | end 11 | 12 | # A helper method for easy finding 13 | def easy_finder(api, symbol, options, at_index = 0) 14 | if api.is_a? Class 15 | return api if options.length == 0 and symbol == :first 16 | options.insert(at_index, symbol) if options[at_index].is_a?(Hash) 17 | api.find(*options) 18 | else 19 | raise TaskMapper::Exception.new("#{Helper.name}::#{this_method} method must be implemented by the provider") 20 | end 21 | end 22 | 23 | # This is a search filter that all parameters are passed through 24 | # Redefine this method in your classes if you need specific functionality 25 | def search_filter(k) 26 | k 27 | end 28 | 29 | def provider_parent(klass) 30 | TaskMapper::Provider.const_get(klass.to_s.split('::')[-2]) 31 | end 32 | 33 | # Goes through all the things and returns results that match the options attributes hash 34 | def search_by_attribute(things, options = {}, limit = 1000) 35 | things.find_all do |thing| 36 | options.inject(true) do |memo, kv| 37 | break unless memo 38 | key, value = kv 39 | begin 40 | memo &= thing.send(key) == value 41 | rescue NoMethodError 42 | memo = false 43 | end 44 | memo 45 | end and (limit -= 1) > 0 46 | end 47 | end 48 | 49 | # Returns a filter-like string from a hash 50 | # If array_join is given, arrays are joined rather than having their own separated key:values 51 | # 52 | # ex: filter_string({:name => 'some value', :tags => ['abc', 'def']}) = "name:'some value' tag:abc tag:def" 53 | def filter_string(filter = {}, array_join = nil) 54 | filter.inject('') do |mem, kv| 55 | key, value = kv 56 | if value.is_a?(Array) 57 | if !array_join.nil? 58 | mem += value.inject('') { |m, v| 59 | v = "\"#{v}\"" if v.to_s.include?(' ') 60 | m+= "#{key}:#{v}" 61 | } 62 | return mem 63 | else 64 | value = value.join(array_join) 65 | end 66 | end 67 | value = "\"#{value}\"" if value.to_s.include?(' ') 68 | mem += "#{key}:#{value} " 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/taskmapper/project.rb: -------------------------------------------------------------------------------- 1 | module TaskMapper::Provider 2 | module Base 3 | # This is the base Project class for providers 4 | # 5 | # Providers should inherit this class and redefine the methods 6 | # 7 | # Each provider should have their own @system defined. 8 | # For example, taskmapper-unfuddle's @system is :unfuddle and taskmapper-lighthouse's 9 | # @system is :lighthouse. 10 | # 11 | # Methods that must be implemented by the provider 12 | # 13 | # * self.find_by_id 14 | # * self.find_by_attributes 15 | # 16 | # Methods that might need to be implemented by the provider 17 | # * tickets 18 | # * ticket 19 | # * initialize 20 | # * update 21 | # * destroy 22 | # * self.create 23 | # 24 | # Methods that would probably be okay if the provider left it alone: 25 | # 26 | # * self.find - although you can define your own to optimize it a bit 27 | # * update! 28 | # 29 | # A provider should define as many attributes as feasibly possible. The list below are 30 | # some guidelines as to what attributes are necessary, if your provider's api does not 31 | # implement them, point it to an attribute that is close to it. (for example, a name 32 | # can point to title. Remember to alias it in your class!) 33 | # 34 | # * id 35 | # * name 36 | # * created_at 37 | # * updated_at 38 | # * description 39 | class Project < Hashie::Mash 40 | include TaskMapper::Provider::Common 41 | include TaskMapper::Provider::Helper 42 | extend TaskMapper::Provider::Helper 43 | attr_accessor :system, :system_data 44 | API = nil #Replace with your api digestor's class. 45 | 46 | # Find project 47 | # You can also retrieve an array of all projects by not specifying any query. 48 | # 49 | # * find() or find(:all) - Returns an array of all projects 50 | # * find(##) - Returns a project based on that id or some other primary (unique) attribute 51 | # * find([#, #, #]) - Returns many projects with these ids 52 | # * find(:first, :name => 'Project name') - Returns the first project based on the project's attribute(s) 53 | # * find(:last, :name => 'Some project') - Returns the last project based on the project's attribute(s) 54 | # * find(:all, :name => 'Test Project') - Returns all projects based on the given attribute(s) 55 | def self.find(*options) 56 | first = options.shift 57 | attributes = options.shift 58 | if first.nil? or (first == :all and attributes.nil?) 59 | self.find_by_attributes 60 | elsif first.is_a? Array 61 | first.collect { |id| self.find_by_id(id) } 62 | elsif first == :first 63 | projects = attributes.nil? ? self.find_by_attributes : self.find_by_attributes(attributes) 64 | projects.first 65 | elsif first == :last 66 | projects = attributes.nil? ? self.find_by_attributes : self.find_by_attributes(attributes) 67 | projects.last 68 | elsif first == :all 69 | self.find_by_attributes(attributes) 70 | else 71 | self.find_by_id(first) 72 | end 73 | end 74 | 75 | # The first of whatever project 76 | def self.first(*options) 77 | self.find(:first, *options) 78 | end 79 | 80 | # The last of whatever project 81 | def self.last(*options) 82 | self.find(:last, *options) 83 | end 84 | 85 | # Accepts an integer id and returns the single project instance 86 | # Must be defined by the provider 87 | def self.find_by_id(id) 88 | if self::API.is_a? Class 89 | self.new self::API.find(id) 90 | else 91 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 92 | end 93 | end 94 | 95 | # Accepts an attributes hash and returns all projects matching those attributes in an array 96 | # Should return all projects if the attributes hash is empty 97 | # Must be defined by the provider 98 | def self.find_by_attributes(attributes = {}) 99 | if self::API.is_a? Class 100 | self.search(attributes) 101 | else 102 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 103 | end 104 | end 105 | 106 | # This is a helper method to find 107 | def self.search(options = {}, limit = 1000) 108 | if self::API.is_a? Class 109 | projects = self::API.find(:all).collect { |project| self.new project } 110 | search_by_attribute(projects, options, limit) 111 | else 112 | raise TaskMapper::Exception.new("#{self.name}::#{this_method} method must be implemented by the provider") 113 | end 114 | end 115 | 116 | # Asks the provider's api for the tickets associated with the project, 117 | # returns an array of Ticket objects. 118 | def tickets(*options) 119 | options.insert 0, id 120 | easy_finder(provider_parent(self.class)::Ticket, :all, options, 1) 121 | end 122 | 123 | # Very similar to tickets, and is practically an alias of it 124 | # however this returns the ticket class if no parameter is given 125 | # unlike tickets which returns an array of all tickets when given no parameters 126 | def ticket(*options) 127 | options.insert(0, id) if options.length > 0 128 | easy_finder(provider_parent(self.class)::Ticket, :first, options, 1) 129 | end 130 | 131 | # Create a ticket 132 | def ticket!(*options) 133 | options[0].merge!(:project_id => id) if options.first.is_a?(Hash) 134 | provider_parent(self.class)::Ticket.create(*options) 135 | end 136 | 137 | # Define some provider specific initalizations 138 | def initialize(*options) 139 | # @system_data = {'some' => 'data} 140 | super(*options) 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/taskmapper/provider.rb: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /lib/taskmapper/tester/comment.rb: -------------------------------------------------------------------------------- 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 | # You don't need to define an initializer, this is only here to initialize tester data 6 | def initialize(project_id, ticket_id, *options) 7 | data = { 8 | :id => rand(1000), 9 | :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)], 10 | :priority => rand(10), 11 | :summary => 'Tickets ticket ticket ticket', 12 | :resolution => false, 13 | :created_at => Time.now, 14 | :updated_at => Time.now, 15 | :description => 'Ticket ticket ticket ticket laughing', 16 | :assignee => 'lol-man' 17 | } 18 | @system = :tester 19 | super(data.merge(options.first || {})) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/taskmapper/tester/project.rb: -------------------------------------------------------------------------------- 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 | # You should define @system and @system_data here. 6 | # The data stuff is just to initialize fake data. In a real provider, you would use the API 7 | # to grab the information and then initialize based on that info. 8 | # @system_data would hold the API's model/instance for reference 9 | def initialize(*options) 10 | data = { 11 | :id => rand(1000).to_i, 12 | :name => 'Tester', 13 | :description => 'Mock!-ing Bird', 14 | :created_at => Time.now, 15 | :updated_at => Time.now 16 | } 17 | @system = :tester 18 | super(data.merge(options.first || {})) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/taskmapper/tester/tester.rb: -------------------------------------------------------------------------------- 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 29 | -------------------------------------------------------------------------------- /lib/taskmapper/tester/ticket.rb: -------------------------------------------------------------------------------- 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 = { 10 | :id => rand(1000), 11 | :status => ['lol', 'rofl', 'lmao', 'lamo', 'haha', 'heh'][rand(6)], 12 | :priority => rand(10), 13 | :summary => 'Tickets ticket ticket ticket', 14 | :resolution => false, 15 | :created_at => Time.now, 16 | :updated_at => Time.now, 17 | :description => 'Ticket ticket ticket ticket laughing', 18 | :assignee => 'lol-man', 19 | :project_id => project_id 20 | } 21 | @system = :tester 22 | super(data.merge(options.first || {})) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/taskmapper/ticket.rb: -------------------------------------------------------------------------------- 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() or find(, :all) - Returns an array of all tickets 53 | # * find(, ##) - Returns a ticket based on that id or some other primary (unique) attribute 54 | # * find(, [#, #, #]) - Returns many tickets with these ids 55 | # * find(, :first, :title => 'ticket name') - Returns the first ticket based on the ticket's attribute(s) 56 | # * find(, :last, :title => 'Some ticket') - Returns the last ticket based on the ticket's attribute(s) 57 | # * find(, :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 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/taskmapper/version.rb: -------------------------------------------------------------------------------- 1 | class TaskMapper 2 | VERSION = "1.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/project_spec.rb: -------------------------------------------------------------------------------- 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 | describe "#projects" do 12 | context "without arguments" do 13 | let(:projects) { tm.projects } 14 | 15 | it "is an array" do 16 | expect(projects).to be_an Array 17 | end 18 | 19 | it "contains projects" do 20 | expect(projects.first).to be_a project_class 21 | expect(projects.last).to be_a project_class 22 | end 23 | end 24 | 25 | context "with an ID argument" do 26 | let(:project) { tm.projects(555) } 27 | 28 | it "returns the requested project" do 29 | expect(project).to be_a project_class 30 | expect(project.id).to eq 555 31 | end 32 | end 33 | 34 | context "with an array of IDs" do 35 | let(:projects) { tm.projects([555]) } 36 | 37 | it "returns an array of projects" do 38 | expect(projects).to be_an Array 39 | end 40 | 41 | it "returns the requested projects" do 42 | expect(projects.first).to be_a project_class 43 | expect(projects.first.id).to eq 555 44 | end 45 | end 46 | 47 | context "with a hash" do 48 | let(:projects) { tm.projects(:id => 555) } 49 | 50 | it "returns an array of projects" do 51 | expect(projects).to be_an Array 52 | end 53 | 54 | it "returns the requested projects" do 55 | expect(projects.first).to be_a project_class 56 | expect(projects.first.id).to eq 555 57 | end 58 | end 59 | 60 | describe "#first" do 61 | let(:project) { tm.projects.first } 62 | 63 | it "returns the requested project" do 64 | expect(project.description).to_not be_nil 65 | expect(project.description).to eq "Mock!-ing Bird" 66 | end 67 | end 68 | end 69 | 70 | describe "#project" do 71 | context "without arguments" do 72 | let(:project) { tm.project } 73 | let(:first) { project.first } 74 | let(:last) { project.last } 75 | 76 | it "returns the project class" do 77 | expect(project).to eq project_class 78 | end 79 | 80 | it "contains the default items" do 81 | expect(first.description).to eq "Mock!-ing Bird" 82 | expect(last.description).to eq "Mock!-ing Bird" 83 | end 84 | end 85 | 86 | context "with a hash" do 87 | let(:project) { tm.project(:name => "Whack whack what?") } 88 | 89 | it "returns the requested project" do 90 | expect(project).to be_a project_class 91 | expect(project.name).to eq "Whack whack what?" 92 | end 93 | end 94 | 95 | describe "#find" do 96 | let(:project) { tm.project.find(:first, :description => "Shocking Dirb") } 97 | 98 | it "returns the requested project" do 99 | expect(project).to be_a project_class 100 | expect(project.description).to eq "Shocking Dirb" 101 | end 102 | end 103 | 104 | describe "#new" do 105 | let(:project) { tm.project.new(default_info) } 106 | 107 | it "returns a new project" do 108 | expect(project).to be_a project_class 109 | expect(project.name).to eq "Tiket Name c" 110 | end 111 | 112 | it "persists the new project" do 113 | expect(project.save).to be_true 114 | end 115 | end 116 | 117 | describe "#create" do 118 | let(:project) { tm.project.create(default_info) } 119 | 120 | it "returns a new project" do 121 | expect(project).to be_a project_class 122 | expect(project.name).to eq "Tiket Name c" 123 | end 124 | end 125 | end 126 | end 127 | 128 | def default_info 129 | { 130 | :id => 777, 131 | :name => "Tiket Name c", 132 | :description => "that c thinks the k is trying to steal it's identity" 133 | } 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /spec/taskmapper-exception_spec.rb: -------------------------------------------------------------------------------- 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 | require 'rubygems' 5 | require 'taskmapper' 6 | 7 | describe "TaskMapper Exception Messages" do 8 | let(:exception) { TaskMapper::Exception } 9 | let(:tm) { TaskMapper.new(:tester, {}) } 10 | let(:validation_error) { "TaskMapper::Provider::Base::valid? method must be implemented by the provider" } 11 | let(:easy_finder_error) { "TaskMapper::Provider::Helper::easy_finder method must be implemented by the provider" } 12 | 13 | describe TaskMapper::Provider::Base do 14 | describe "#valid?" do 15 | it "has a custom exception message" do 16 | expect{tm.valid?}.to raise_error(exception, validation_error) 17 | end 18 | end 19 | end 20 | 21 | describe TaskMapper::Provider::Helper do 22 | describe "#easy_finder" do 23 | it "has a custom exception message" do 24 | expect { 25 | tm.easy_finder(1, :test, {}) 26 | }.to raise_error(exception, easy_finder_error) 27 | end 28 | end 29 | end 30 | 31 | describe TaskMapper::Provider::Tester::Project do 32 | let(:find_by_id_error) { "TaskMapper::Provider::Tester::Project::find_by_id method must be implemented by the provider" } 33 | let(:find_by_attributes_error) { "TaskMapper::Provider::Tester::Project::find_by_attributes method must be implemented by the provider" } 34 | let(:search_error) { "TaskMapper::Provider::Tester::Project::search method must be implemented by the provider"} 35 | let(:create_error) { "TaskMapper::Provider::Tester::Project::create method must be implemented by the provider"} 36 | let(:save_error) { "TaskMapper::Provider::Tester::Project::save method must be implemented by the provider"} 37 | let(:destroy_error) { "TaskMapper::Provider::Tester::Project::destroy method must be implemented by the provider" } 38 | 39 | describe "#find_by_id" do 40 | it "has a custom exception message" do 41 | expect{tm.project([1])}.to raise_error(exception, find_by_id_error) 42 | end 43 | end 44 | 45 | describe "#find_by_attributes" do 46 | it "has a custom exception message" do 47 | expect { 48 | tm.project.find :all, :name => 'Test Project' 49 | }.to raise_error(exception, find_by_attributes_error) 50 | end 51 | end 52 | 53 | describe "#search" do 54 | it "has a custom exception message" do 55 | expect { 56 | tm.project.search :tag => 'testing' 57 | }.to raise_error(exception, search_error) 58 | end 59 | end 60 | 61 | describe "#create" do 62 | it "has a custom exception message" do 63 | expect { 64 | tm.project.create :name => 'Foo Bar' 65 | }.to raise_error(exception, create_error) 66 | end 67 | end 68 | 69 | describe "#save" do 70 | let(:project) { TaskMapper::Provider::Tester::Project.new } 71 | 72 | it "has a custom exception message" do 73 | expect{project.save}.to raise_error(exception, save_error) 74 | end 75 | end 76 | 77 | describe "#destroy" do 78 | let(:project) { TaskMapper::Provider::Tester::Project.new } 79 | 80 | it "has a custom exception message" do 81 | expect{project.destroy}.to raise_error(exception, destroy_error) 82 | end 83 | end 84 | end 85 | 86 | describe TaskMapper::Provider::Tester::Ticket do 87 | let(:ticket) { TaskMapper::Provider::Tester::Ticket.new(1) } 88 | let(:find_by_id_error) { "TaskMapper::Provider::Tester::Ticket::find_by_id method must be implemented by the provider" } 89 | let(:find_by_attributes_error) { "TaskMapper::Provider::Tester::Ticket::find_by_attributes method must be implemented by the provider" } 90 | let(:search_error) { "TaskMapper::Provider::Tester::Ticket::search method must be implemented by the provider" } 91 | let(:create_error) { "TaskMapper::Provider::Tester::Ticket::create method must be implemented by the provider" } 92 | let(:save_error) { "TaskMapper::Provider::Tester::Ticket::save method must be implemented by the provider" } 93 | let(:destroy_error) { "TaskMapper::Provider::Tester::Ticket::destroy method must be implemented by the provider" } 94 | let(:close_error) { "TaskMapper::Provider::Tester::Ticket::close method must be implemented by the provider" } 95 | let(:reload_error) { "TaskMapper::Provider::Tester::Ticket::reload! method must be implemented by the provider" } 96 | 97 | describe "#find_by_id" do 98 | it "has a custom exception message" do 99 | expect{tm.tickets(:id => 22)}.to raise_error(exception, find_by_id_error) 100 | end 101 | end 102 | 103 | describe "#find_by_attributes" do 104 | it "has a custom exception message" do 105 | expect { 106 | tm.ticket.find(1, :all, :title => 'Test ticket') 107 | }.to raise_error(exception, find_by_attributes_error) 108 | end 109 | end 110 | 111 | describe "#search" do 112 | it "has a custom exception message" do 113 | expect { 114 | tm.ticket.search :tag => 'testing' 115 | }.to raise_error(exception, search_error) 116 | end 117 | end 118 | 119 | describe "#create" do 120 | it "has a custom exception message" do 121 | expect { 122 | tm.ticket.create :name => 'Foo Bar' 123 | }.to raise_error(exception, create_error) 124 | end 125 | end 126 | 127 | describe "#save" do 128 | it "has a custom exception message" do 129 | expect{ticket.save}.to raise_error(exception, save_error) 130 | end 131 | end 132 | 133 | describe "#destroy" do 134 | it "has a custom exception message" do 135 | expect{ticket.destroy}.to raise_error(exception, destroy_error) 136 | end 137 | end 138 | 139 | describe "#close" do 140 | it "has a custom exception message" do 141 | expect{ticket.close}.to raise_error(exception, close_error) 142 | end 143 | end 144 | 145 | describe "#reload!" do 146 | it "has a custom exception message" do 147 | expect{ticket.reload!}.to raise_error(exception, reload_error) 148 | end 149 | end 150 | end 151 | 152 | describe TaskMapper::Provider::Tester::Comment do 153 | let(:ticket_with_comments) { TaskMapper::Provider::Tester::Ticket.new(1) } 154 | let(:comment) { TaskMapper::Provider::Tester::Comment.new(1, 1) } 155 | let(:find_by_id_error) { "TaskMapper::Provider::Tester::Comment::find_by_id method must be implemented by the provider" } 156 | let(:find_by_attributes_error) { "TaskMapper::Provider::Tester::Comment::find_by_attributes method must be implemented by the provider" } 157 | let(:search_error) { "TaskMapper::Provider::Tester::Comment::search method must be implemented by the provider" } 158 | let(:create_error) { "TaskMapper::Provider::Tester::Comment::create method must be implemented by the provider" } 159 | let(:save_error) { "TaskMapper::Provider::Tester::Comment::save method must be implemented by the provider" } 160 | let(:destroy_error) { "TaskMapper::Provider::Tester::Comment::destroy method must be implemented by the provider" } 161 | 162 | describe "#find_by_id" do 163 | it "has a custom exception message" do 164 | expect { 165 | ticket_with_comments.comment.find(1,1,[1,2]) 166 | }.to raise_error(exception, find_by_id_error) 167 | end 168 | end 169 | 170 | describe "#find_by_attributes" do 171 | it "has a custom exception message" do 172 | expect { 173 | ticket_with_comments.comment.find(1, 1, :all, :tag => 'tag') 174 | }.to raise_error(exception, find_by_attributes_error) 175 | end 176 | end 177 | 178 | describe "#search" do 179 | it "has a custom exception message" do 180 | expect { 181 | ticket_with_comments.comment.search(1, 1, :tag => 'testing') 182 | }.to raise_error(exception, search_error) 183 | end 184 | end 185 | 186 | describe "#create" do 187 | it "has a custom exception message" do 188 | expect{ 189 | ticket_with_comments.comment.create :name => 'Foo Bar' 190 | }.to raise_error(exception, create_error) 191 | end 192 | end 193 | 194 | describe "#save" do 195 | it "has a custom exception message" do 196 | expect{comment.save}.to raise_error(exception, save_error) 197 | end 198 | end 199 | 200 | describe "#destroy" do 201 | it "has a custom exception message" do 202 | expect{comment.destroy}.to raise_error(exception, destroy_error) 203 | end 204 | end 205 | end 206 | end 207 | 208 | -------------------------------------------------------------------------------- /spec/taskmapper_spec.rb: -------------------------------------------------------------------------------- 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 TaskMapper do 7 | describe "#new" do 8 | it "returns a TaskMapper instance" do 9 | instance = TaskMapper.new :dummy, {} 10 | expect(instance).to be_a TaskMapper 11 | expect(instance).to be_a_kind_of TaskMapper::Provider::Dummy 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/ticket_spec.rb: -------------------------------------------------------------------------------- 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 "Tickets" do 7 | let(:tm) { TaskMapper.new(:dummy, {}) } 8 | let(:project_class) { TaskMapper::Provider::Dummy::Project } 9 | let(:ticket_class) { TaskMapper::Provider::Dummy::Ticket } 10 | let(:project) { tm.projects.first } 11 | 12 | describe "#tickets" do 13 | context "without arguments" do 14 | let(:tickets) { project.tickets } 15 | 16 | it "returns an array of tickets" do 17 | expect(tickets).to be_an Array 18 | expect(tickets.first).to be_a ticket_class 19 | end 20 | end 21 | 22 | context "with an array of IDs" do 23 | let(:tickets) { project.tickets([999]) } 24 | let(:ticket) { tickets.first } 25 | 26 | it "returns an array of all matching tickets" do 27 | expect(tickets).to be_a Array 28 | expect(ticket).to be_a ticket_class 29 | expect(ticket.id).to eq 999 30 | end 31 | end 32 | 33 | context "with a hash containing in ID" do 34 | let(:tickets) { project.tickets(:id => 999) } 35 | let(:ticket) { tickets.first } 36 | 37 | it "returns an array of all matching tickets" do 38 | expect(tickets).to be_a Array 39 | expect(ticket).to be_a ticket_class 40 | expect(ticket.id).to eq 999 41 | end 42 | end 43 | end 44 | 45 | describe "#ticket" do 46 | context "without arguments" do 47 | it "returns the ticket class" do 48 | expect(project.ticket).to eq ticket_class 49 | end 50 | end 51 | 52 | context "with an ID" do 53 | let(:ticket) { project.ticket(888) } 54 | 55 | it "returns the requested ticket" do 56 | expect(ticket).to be_a ticket_class 57 | expect(ticket.id).to eq 888 58 | end 59 | end 60 | 61 | context "with an hash containing an ID" do 62 | let(:ticket) { project.ticket(:id => 888) } 63 | 64 | it "returns the requested ticket" do 65 | expect(ticket).to be_a ticket_class 66 | expect(ticket.id).to eq 888 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /taskmapper.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path '../lib/taskmapper/version', __FILE__ 3 | 4 | Gem::Specification.new do |spec| 5 | spec.name = "taskmapper" 6 | spec.version = TaskMapper::VERSION 7 | spec.authors = ["www.hybridgroup.com"] 8 | spec.email = ["info@hybridgroup.com"] 9 | spec.description = %q{TaskMapper provides a universal API to ticket tracking 10 | and project management systems.} 11 | spec.summary = %q{TaskMapper provides a universal API to ticket tracking 12 | and project management systems.} 13 | spec.homepage = "http://ticketrb.com" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "activeresource", "~> 3.2" 22 | spec.add_dependency "activesupport", "~> 3.2" 23 | spec.add_dependency "hashie", "~> 2.0" 24 | spec.add_development_dependency "rake" 25 | spec.add_development_dependency "rdoc", "~> 4.0" 26 | spec.add_development_dependency "rspec", "~> 2.14.1" 27 | end 28 | --------------------------------------------------------------------------------