brightspark3-rira 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +9 -0
- data/VERSION.yml +4 -0
- data/lib/rira/base.rb +85 -0
- data/lib/rira/model.rb +95 -0
- data/lib/rira.rb +31 -0
- data/test/rira_test.rb +27 -0
- data/test/test_helper.rb +10 -0
- metadata +61 -0
data/README.rdoc
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Rira
|
2
|
+
====
|
3
|
+
|
4
|
+
Rira is a ruby gem for interfacing Jira. Check out the Jira documentation for the {origin of the name}[http://confluence.atlassian.com/pages/viewpage.action?pageId=192544].
|
5
|
+
|
6
|
+
COPYRIGHT
|
7
|
+
=========
|
8
|
+
|
9
|
+
Copyright (c) 2008 László Bácsi. See LICENSE for details.
|
data/VERSION.yml
ADDED
data/lib/rira/base.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module Rira
|
2
|
+
class Base
|
3
|
+
def initialize(url, username, password, options = {})
|
4
|
+
@url, @username, @password = url, username, password
|
5
|
+
@token = xmlrpc_client.call("jira1.login", @username, @password)
|
6
|
+
rescue XMLRPC::FaultException => e
|
7
|
+
raise RPCError.new(e)
|
8
|
+
end
|
9
|
+
|
10
|
+
def xmlrpc_client
|
11
|
+
XMLRPC::Client.new2(@url)
|
12
|
+
end
|
13
|
+
|
14
|
+
MAPPINGS = {
|
15
|
+
:projects => ['project', "getProjectsNoSchemes", "getProjects"],
|
16
|
+
:projects_no_schemes => ['project', "getProjectsNoSchemes", "getProjects"],
|
17
|
+
:components => ['component', "getComponents"],
|
18
|
+
:versions => ['version', "getVersions"],
|
19
|
+
:issue_types => ['issue_type', "getIssueTypes"],
|
20
|
+
:issue_types_for_project => ['issue_type', "getIssueTypesForProject"],
|
21
|
+
:sub_task_issue_types => ['issue_type', "getSubTaskIssueTypes"],
|
22
|
+
:sub_task_issue_types_for_project => ['issue_type', "getSubTaskIssueTypesForProject"],
|
23
|
+
:create_issue => ['issue', "createIssue"],
|
24
|
+
:update_issue => ['issue', "updateIssue"],
|
25
|
+
:issue => ['issue', "getIssue"],
|
26
|
+
:issues_from_filter => ['issue', "getIssuesFromFilter"],
|
27
|
+
:search => ['issue', "getIssuesFromTextSearch"],
|
28
|
+
:search_with_project => ['issue', "getIssuesFromTextSearchWithProject"],
|
29
|
+
:add_comment => [nil, "addComment"],
|
30
|
+
:comments => ['comment', "getComments"],
|
31
|
+
:priorities => ['priority', "getPriorities"],
|
32
|
+
:resolutions => ['resolution', "getResolutions"],
|
33
|
+
:statuses => ['status', "getStatuses"],
|
34
|
+
:favourite_filters => ['filter', "getFavouriteFilters"],
|
35
|
+
:saved_filters => ['filter', "getSavedFilters"],
|
36
|
+
:server_info => ['server_info', "getServerInfo"],
|
37
|
+
:user => ['user', "getUser"],
|
38
|
+
:logout => [nil, "logout"],
|
39
|
+
}
|
40
|
+
|
41
|
+
def method_missing(method, *args)
|
42
|
+
model, *methods = MAPPINGS[method]
|
43
|
+
|
44
|
+
super(method, *args) if methods.empty?
|
45
|
+
|
46
|
+
retried = false
|
47
|
+
begin
|
48
|
+
case result = xmlrpc_client.call("jira1.#{methods.first}", @token, *args)
|
49
|
+
when Hash
|
50
|
+
model_or_struct(model, result)
|
51
|
+
when Array
|
52
|
+
result.map do |item|
|
53
|
+
if item.is_a?(Hash)
|
54
|
+
model_or_struct(model, item)
|
55
|
+
else
|
56
|
+
item
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
result
|
61
|
+
end
|
62
|
+
rescue XMLRPC::FaultException => e
|
63
|
+
if e.faultString =~ /session timed out/ and not retried
|
64
|
+
@token = xmlrpc_client.call("jira1.login", @username, @password)
|
65
|
+
retried = true
|
66
|
+
retry
|
67
|
+
else
|
68
|
+
methods.shift
|
69
|
+
retry unless methods.empty?
|
70
|
+
raise RPCError.new(e)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def model_or_struct(model, result)
|
78
|
+
if model
|
79
|
+
Model.new(self, model, result)
|
80
|
+
else
|
81
|
+
OpenStruct.new(result)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/rira/model.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Rira
|
4
|
+
class Model
|
5
|
+
ASSOCIATIONS = {
|
6
|
+
"add_comment" => ["issue", "key"],
|
7
|
+
"comments" => ["issue", "key"],
|
8
|
+
"components" => ["project", "key"],
|
9
|
+
"issues" => ["filter", "id",
|
10
|
+
"issues_from_filter"],
|
11
|
+
"search" => ["project", "keys",
|
12
|
+
"search_with_project"],
|
13
|
+
"issue_types" => ["project", "id", "issue_types_for_project"],
|
14
|
+
"sub_task_issue_types" => ["project", "id",
|
15
|
+
"sub_task_issue_types_for_project"],
|
16
|
+
"versions" => ["project", "key"],
|
17
|
+
"update" => ["issue", "key",
|
18
|
+
"update_issue"],
|
19
|
+
}
|
20
|
+
|
21
|
+
# FIXME: DRY this up a little bit
|
22
|
+
CONVERTERS = {
|
23
|
+
'resolution' => lambda {|s,x| s.base.resolutions.detect {|r| r.id == x}},
|
24
|
+
'priority' => lambda {|s,x| s.base.priorities.detect {|p| p.id == x}},
|
25
|
+
'status' => lambda {|s,x| s.base.statuses.detect {|p| p.id == x}},
|
26
|
+
'project' => lambda {|s,x| s.base.projects.detect {|p| p.key == x}},
|
27
|
+
'type' => lambda { |s,x|
|
28
|
+
p = s.project
|
29
|
+
(p.issue_types + p.sub_task_issue_types).detect {|it| it.id == x}
|
30
|
+
},
|
31
|
+
'lead' => lambda {|s,x| s.base.user(x)},
|
32
|
+
'author' => lambda {|s,x| s.base.user(x)},
|
33
|
+
'reporter' => lambda {|s,x| s.base.user(x)},
|
34
|
+
'assignee' => lambda {|s,x| s.base.user(x)},
|
35
|
+
'components' => lambda {|s,x| x.map {|c| Model.new(s.base, 'component', c)}},
|
36
|
+
'affects_versions' => lambda {|s,x| x.map {|v| Model.new(s.base, 'version', v)}},
|
37
|
+
'fix_versions' => lambda {|s,x| x.map {|v| Model.new(s.base, 'version', v)}},
|
38
|
+
'created' => lambda {|s,x| Time.parse(x)},
|
39
|
+
'updated' => lambda {|s,x| Time.parse(x)},
|
40
|
+
'release_date' => lambda {|s,x| Date.parse(x)},
|
41
|
+
'build_date' => lambda {|s,x| Date.parse(x)},
|
42
|
+
'archived' => lambda {|s,x| x == 'true'},
|
43
|
+
'released' => lambda {|s,x| x == 'true'},
|
44
|
+
'sub_task' => lambda {|s,x| x == 'true'},
|
45
|
+
}
|
46
|
+
|
47
|
+
BOOLEANS = %w{archived released sub_task}
|
48
|
+
|
49
|
+
attr_reader :base, :model
|
50
|
+
|
51
|
+
def initialize(base, model, hash)
|
52
|
+
@base, @model, @attributes = base, model, hash
|
53
|
+
@attributes.keys.each do |key|
|
54
|
+
new_key = key.gsub(/[A-Z]/) {"_#{$&.downcase}"}
|
55
|
+
if key != new_key
|
56
|
+
@attributes[new_key] = @attributes.delete(key)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def id
|
62
|
+
@attributes['id']
|
63
|
+
end
|
64
|
+
|
65
|
+
def type
|
66
|
+
CONVERTERS['type'].call(self, @attributes['type'])
|
67
|
+
end
|
68
|
+
|
69
|
+
def respond_to?(method)
|
70
|
+
@attributes.include? method.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(method, *args)
|
74
|
+
method = method.to_s
|
75
|
+
if method[-1] == ?? and BOOLEANS.include?(method[0..-2])
|
76
|
+
method = method[0..-2]
|
77
|
+
end
|
78
|
+
if args.empty? and @attributes.has_key?(method)
|
79
|
+
att = @attributes[method]
|
80
|
+
if converter = CONVERTERS[method]
|
81
|
+
converter.call(self, att)
|
82
|
+
else
|
83
|
+
att
|
84
|
+
end
|
85
|
+
elsif map = ASSOCIATIONS[method] and map.first == @model
|
86
|
+
_, att, rp = map
|
87
|
+
scope = att[-1] == ?s ? Array(@attributes[att[0..-2]]) : @attributes[att]
|
88
|
+
args.unshift(scope)
|
89
|
+
@base.send(rp || method, *args)
|
90
|
+
else
|
91
|
+
super(method.to_sym, *args)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/rira.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'xmlrpc/client'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__), 'rira', 'model')
|
5
|
+
require File.join(File.dirname(__FILE__), 'rira', 'base')
|
6
|
+
|
7
|
+
def Rira(url)
|
8
|
+
unless url =~ %r{/rpc/xmlrpc$}
|
9
|
+
url += '/' unless url[-1] == ?/
|
10
|
+
url += 'rpc/xmlrpc'
|
11
|
+
end
|
12
|
+
Rira::Client.new(url)
|
13
|
+
end
|
14
|
+
|
15
|
+
module Rira
|
16
|
+
class RPCError < StandardError
|
17
|
+
def initialize(fault_exception)
|
18
|
+
super("#{fault_exception.faultCode}: #{fault_exception.faultString}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Client
|
23
|
+
def initialize(url)
|
24
|
+
@url = url
|
25
|
+
end
|
26
|
+
|
27
|
+
def login(username, password)
|
28
|
+
Rira::Base.new(@url, username, password)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/test/rira_test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class RiraTest < Test::Unit::TestCase
|
4
|
+
JIRA_URL = "http://jira.lackac.hu"
|
5
|
+
JIRA_USER = "rubi"
|
6
|
+
JIRA_PASS = "j1r4b0t"
|
7
|
+
|
8
|
+
context "A Rira Client" do
|
9
|
+
should "be able to login with correct password" do
|
10
|
+
rira = Rira(JIRA_URL + "/rpc/xmlrpc")
|
11
|
+
assert_nothing_raised do
|
12
|
+
rira = rira.login(JIRA_USER, JIRA_PASS)
|
13
|
+
end
|
14
|
+
assert rira.instance_variable_get('@token').length > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
should "be created with the short URL (without /rpc/xmlrpc)" do
|
18
|
+
rira = Rira(JIRA_URL)
|
19
|
+
assert_nothing_raised { rira.login(JIRA_USER, JIRA_PASS) }
|
20
|
+
end
|
21
|
+
|
22
|
+
should "not get token with wrong password" do
|
23
|
+
rira = Rira(JIRA_URL)
|
24
|
+
assert_raise(Rira::RPCError) { rira.login("wrong", "authentication") }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brightspark3-rira
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "L\xC3\xA1szl\xC3\xB3 B\xC3\xA1csi"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-04 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Nice interface for Jira, the Ruby way.
|
17
|
+
email: lackac@lackac.hu
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.rdoc
|
26
|
+
- VERSION.yml
|
27
|
+
- lib/rira
|
28
|
+
- lib/rira/base.rb
|
29
|
+
- lib/rira/model.rb
|
30
|
+
- lib/rira.rb
|
31
|
+
- test/rira_test.rb
|
32
|
+
- test/test_helper.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/lackac/rira
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --inline-source
|
38
|
+
- --charset=UTF-8
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: Nice interface for Jira, the Ruby way.
|
60
|
+
test_files: []
|
61
|
+
|