confluence4r 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 41dcb8b373b04bf307d72faacb8344bd9a8beea4
4
+ data.tar.gz: 723450b5298bd65a9190f390baa09fd4801576ca
5
+ SHA512:
6
+ metadata.gz: 5fd675efc44a93f1f50a33d297c560cc9b71421d5dd35f024626b30d59ad4d1a6699b475e069853e0c6a25477ee3a9faed289e0eb41247e15072bf0b9e6bf7c6
7
+ data.tar.gz: 403f58e7d0e9ec5eb4c4120325429088cd1aa3559859380b5b9e9250c19a2ec2d7826c6ac147831e3725342625fd2bd62504faa73784017376940868e560a448
@@ -0,0 +1,64 @@
1
+ require 'yaml'
2
+
3
+ require 'confluence/confluence_rpc'
4
+
5
+ class Confluence::Connector
6
+
7
+ attr_accessor :username, :password, :default_service, :url,
8
+ :admin_proxy_username, :admin_proxy_password
9
+
10
+ def initialize(options = {})
11
+ load_confluence_config
12
+ @url ||= options[:url]
13
+ @username = options[:username] || @username
14
+ @password = options[:password] || @password
15
+ @default_service = options[:service] || 'confluence1'
16
+ end
17
+
18
+ def connect(service = nil)
19
+ unless url and ((admin_proxy_username and admin_proxy_password) or (username and password)) and service || default_service
20
+ raise "Cannot get Confluence::RPC instance because the confluence url, username, password, or service have not been set"
21
+ end
22
+
23
+ rpc = Confluence::RPC.new(url, service || default_service)
24
+ rpc.login(username, password)
25
+
26
+ return rpc
27
+ end
28
+
29
+ def load_confluence_config
30
+ conf = YAML.load_file("#{RAILS_ROOT}/config/confluence.yml")[RAILS_ENV]
31
+ @url = conf['url'] || conf[:url]
32
+ @default_service = conf['service'] || conf[:service]
33
+ @admin_proxy_username = conf['admin_proxy_username'] || conf[:admin_proxy_username]
34
+ @admin_proxy_password = conf['admin_proxy_password'] || conf[:admin_proxy_password]
35
+ # take these params manually if that's what is sent
36
+ @username = conf['username'] || conf[:username] || @admin_proxy_username
37
+ @password = conf['password'] || conf[:password] || @admin_proxy_password
38
+ end
39
+
40
+ def self.default_confluence_url
41
+ conf = YAML.load_file("#{RAILS_ROOT}/config/confluence.yml")[RAILS_ENV]
42
+ conf['url'] || conf[:url]
43
+ end
44
+
45
+ # The given block will be executed using another account, as set in the confluence.yml file under admin_proxy_username
46
+ # and admin_proxy_password. This is useful when you want to execute some function that requires admin privileges. You
47
+ # will of course have to set up a corresponding account on your Confluence server with administrative rights.
48
+ def self.through_admin_proxy
49
+ super_connector = Confluence::Connector.new
50
+
51
+ raise "Cannot execute through_admin_proxy because the admin_proxy_username has not been set." unless super_connector.admin_proxy_username
52
+ raise "Cannot execute through_admin_proxy because the admin_proxy_password has not been set." unless super_connector.admin_proxy_password
53
+
54
+ super_connector.username = super_connector.admin_proxy_username
55
+ super_connector.password = super_connector.admin_proxy_password
56
+
57
+ normal_connector = Confluence::RemoteDataObject.connector
58
+ Confluence::RemoteDataObject.connector = super_connector
59
+
60
+ yield
61
+
62
+ Confluence::RemoteDataObject.connector = normal_connector
63
+ end
64
+ end
@@ -0,0 +1,187 @@
1
+ require 'active_support'
2
+ require 'confluence/confluence_connector'
3
+
4
+ # Abstract object representing some piece of data in Confluence.
5
+ # This must be overridden by a child class that defines values
6
+ # for the class attributes save_method and get_method and/or
7
+ # implements its own get and save methods.
8
+ class Confluence::RemoteDataObject
9
+ # include Reloadable
10
+
11
+ class_inheritable_accessor :attr_conversions, :readonly_attrs
12
+ class_inheritable_accessor :save_method, :get_method, :destroy_method
13
+
14
+ attr_accessor :attributes
15
+
16
+ attr_accessor :confluence, :encore
17
+
18
+ @@connector = nil
19
+
20
+ def self.connector=(connector)
21
+ @@connector = connector
22
+ end
23
+
24
+ def self.connector
25
+ @@connector
26
+ end
27
+
28
+ def self.confluence
29
+ raise "Cannot establish confluence connection because the connector has not been set." unless @@connector
30
+ @@connector.connect
31
+ end
32
+
33
+ def confluence
34
+ raise "Cannot establish confluence connection because the connector has not been set." unless @@connector
35
+ @@connector.connect
36
+ end
37
+
38
+ # TODO: encore-specific code like this probably shouldn't be here...
39
+ def self.encore
40
+ raise "Cannot establish confluence connection because the connector has not been set." unless @@connector
41
+ @@connector.connect("encore")
42
+ end
43
+
44
+ def encore
45
+ raise "Cannot establish confluence connection because the connector has not been set." unless @@connector
46
+ @@connector.connect("encore")
47
+ end
48
+
49
+ def initialize(data_object = nil)
50
+ self.attributes = {}
51
+ load_from_object(data_object) unless data_object.nil?
52
+ end
53
+
54
+ def load_from_object(data_object)
55
+ data_object.each do |attr, value|
56
+ if self.class.attr_conversions.has_key? attr.to_sym
57
+ value = self.send("as_#{attr_conversions[attr.to_sym]}", value)
58
+ end
59
+ self.send("#{attr}=", value)
60
+ end
61
+ end
62
+
63
+ def save
64
+ before_save if respond_to? :before_save
65
+
66
+ data = {} unless data
67
+
68
+ self.attributes.each do |attr,value|
69
+ data[attr.to_s] = value.to_s unless self.readonly_attrs.include? attr
70
+ end
71
+
72
+ raise NotImplementedError.new("Can't call #{self}.save because no @@save_method is defined for this class") unless self.save_method
73
+
74
+ self.confluence.send(self.class.send(:save_method), data)
75
+
76
+ # we need to reload because the version number has probably changed, we want the new ID, etc.
77
+ reload
78
+
79
+ after_save if respond_to? :after_save
80
+ end
81
+
82
+ def reload
83
+ before_reload if respond_to? :before_reload
84
+
85
+ if self.id
86
+ self.load_from_object(self.class.send(:get, self.id))
87
+ else
88
+ # We don't have an ID, so try to use alternate method for reloading. (This is for newly created records that may not yet have an id assigned)
89
+ raise NotImplementedError, "Can't reload this #{self.class} because it does not have an id and does not implement the reload_newly_created! method." unless self.respond_to? :reload_newly_created!
90
+ self.reload_newly_created!
91
+ end
92
+
93
+ after_reload if respond_to? :after_reload
94
+ end
95
+
96
+ def destroy
97
+ before_destroy if respond_to? :before_destroy
98
+
99
+ raise NotImplementedError.new("Can't call #{self}.destroy because no @@destroy_method is defined for this class") unless self.destroy_method
100
+ eval "confluence.#{self.destroy_method}(self.id.to_s)"
101
+
102
+ after_destroy if respond_to? :after_destroy
103
+ end
104
+
105
+ def [](attr)
106
+ self.attributes[attr]
107
+ end
108
+
109
+ def []=(attr, value)
110
+ self.attributes[attr] = value
111
+ end
112
+
113
+ def id
114
+ self[:id]
115
+ end
116
+
117
+ def method_missing(name, *args)
118
+ if name.to_s =~ /^(.*?)=$/
119
+ self[$1.intern] = args[0]
120
+ elsif name.to_s =~ /^[\w_]+$/
121
+ self[name]
122
+ else
123
+ raise NoMethodError, name.to_s
124
+ end
125
+ end
126
+
127
+ def ==(obj)
128
+ if obj.kind_of? self.class
129
+ self.attributes == obj.attributes
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ ### class methods #########################################################
136
+
137
+ def self.find(id)
138
+ r = get(id)
139
+ self.new(r)
140
+ end
141
+
142
+ ### type conversions ######################################################
143
+
144
+ # TODO: put these in a module?
145
+ def as_int(val)
146
+ val.to_i
147
+ end
148
+
149
+ def as_string(val)
150
+ val.to_s
151
+ end
152
+
153
+ def as_boolean(val)
154
+ val == "true"
155
+ end
156
+
157
+ def as_datetime(val)
158
+ if val.is_a?(String)
159
+ # for older versions of Confluence (pre 2.7?)
160
+ val =~ /\w{3} (\w{3}) (\d{2}) (\d{2}):(\d{2}):(\d{2}) (\w{3}) (\d{4})/
161
+ month = $1
162
+ day = $2
163
+ hour = $3
164
+ minute = $4
165
+ second = $5
166
+ tz = $6
167
+ year = $7
168
+ Time.local(year, month, day, hour, minute, second)
169
+ else
170
+ Time.local(val.year, val.month, val.day, val.hour, val.min, val.sec)
171
+ end
172
+ end
173
+
174
+ ###########################################################################
175
+
176
+ protected
177
+ # Returns the raw XML-RPC anonymous object with the data corresponding to
178
+ # the given id. This depends on the get_method class attribute, which must
179
+ # be defined for this method to work.
180
+ def self.get(id)
181
+ raise NotImplementedError.new("Can't call #{self}.get(#{id}) because no get_method is defined for this class") unless self.get_method
182
+ raise ArgumentError.new("You must specify a #{self} id!") unless id
183
+ confluence.log.debug("get_method for #{self} is #{self.get_method}")
184
+ obj = confluence.send(self.send(:get_method), id.to_s)
185
+ return obj
186
+ end
187
+ end
@@ -0,0 +1,83 @@
1
+ require 'xmlrpc/client'
2
+ require 'logger'
3
+
4
+ # A useful helper for running Confluence XML-RPC from Ruby. Takes care of
5
+ # adding the token to each method call (so you can call server.getSpaces()
6
+ # instead of server.getSpaces(token)). Also takes care of re-logging in
7
+ # if your login times out.
8
+ #
9
+ # Usage:
10
+ #
11
+ # server = Confluence::RPC.new("http://confluence.atlassian.com")
12
+ # server.login("user", "password")
13
+ # puts server.getSpaces()
14
+ #
15
+ module Confluence
16
+
17
+ class RPC
18
+ attr_reader :log
19
+
20
+ def initialize(server_url, proxy = "confluence1")
21
+ server_url += "/rpc/xmlrpc" unless server_url[-11..-1] == "/rpc/xmlrpc"
22
+ @server_url = server_url
23
+ server = XMLRPC::Client.new2(server_url)
24
+ @conf = server.proxy(proxy)
25
+ @token = "12345"
26
+
27
+ @log = Logger.new "#{RAILS_ROOT}/log/confluence4r.log"
28
+ end
29
+
30
+ def login(username, password)
31
+ log.info "Logging in as '#{username}'."
32
+ @user = username
33
+ @pass = password
34
+ do_login()
35
+ end
36
+
37
+ def method_missing(method_name, *args)
38
+ log.info "Calling #{method_name}(#{args.inspect})."
39
+ begin
40
+ @conf.send(method_name, *([@token] + args))
41
+ rescue XMLRPC::FaultException => e
42
+ log.error "#{e}: #{e.message}"
43
+ if (e.faultString.include?("InvalidSessionException"))
44
+ do_login
45
+ retry
46
+ else
47
+ raise RemoteException, e.respond_to?(:message) ? e.message : e
48
+ end
49
+ rescue
50
+ log.error "#{$!}"
51
+ raise $!
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def do_login()
58
+ begin
59
+ @token = @conf.login(@user, @pass)
60
+ rescue XMLRPC::FaultException => e
61
+ log.error "#{e}: #{e.faultString}"
62
+ raise RemoteAuthenticationException, e
63
+ end
64
+ end
65
+ end
66
+
67
+
68
+ class RemoteException < Exception
69
+ def initialize(msg = nil, type = nil)
70
+ if msg.kind_of? XMLRPC::FaultException
71
+ msg.faultString =~ /^.*?:\s(.*?):\s(.*)/
72
+ msg = $2
73
+ type = $1
74
+ end
75
+
76
+ super(msg)
77
+ @type = type
78
+ end
79
+ end
80
+
81
+ class RemoteAuthenticationException < RemoteException
82
+ end
83
+ end
@@ -0,0 +1,117 @@
1
+ # Allows for reading and writing a {metadata-list} macro in a page's content.
2
+ #
3
+ # For example, if your wiki content is:
4
+ #
5
+ # Hello, World!
6
+ # {metadata-list}
7
+ # || Foo | Bar |
8
+ # || Fruit | Orange |
9
+ # {metadata-list}
10
+ #
11
+ # You may do this (assuming that you have the above text stored in a variable
12
+ # called 'wiki_content'):
13
+ #
14
+ # m = Confluence::Metadata.new(wiki_content)
15
+ # puts m['Foo'] # outputs "Bar"
16
+ # puts m['Fruit'] # outputs "Orange"
17
+ #
18
+ # m['Fruit'] = "Banana"
19
+ # puts m['Fruit'] # outputs "Banana"
20
+ # m['Hello'] = "Goodbye"
21
+ #
22
+ # ... and your wiki_content now holds:
23
+ #
24
+ # Hello, World!
25
+ # {metadata-list}
26
+ # || Foo | Bar |
27
+ # || Fruit | Banana |
28
+ # || Hello | Goodbye |
29
+ # {metadata-list}
30
+ #
31
+ class Confluence::Metadata
32
+ include Enumerable
33
+
34
+ def initialize(page)
35
+ raise ArgumentError.new("Argument passed to Confluence::Metadata must be a Confluence::Page") unless page.kind_of? Confluence::Page
36
+ @page = page
37
+ end
38
+
39
+ def [](metadata_key)
40
+ extract_metadata_from_content[metadata_key]
41
+ end
42
+
43
+ def []=(metadata_key, value)
44
+ metadata = extract_metadata_from_content
45
+ metadata[metadata_key] = value
46
+ replace_metadata_in_content(metadata)
47
+ value
48
+ end
49
+
50
+ def each
51
+ metadata = extract_metadata_from_content
52
+ metadata.each{|k,v| yield k,v}
53
+ end
54
+
55
+ def include?(key)
56
+ not self.find{|k,v| k}.nil?
57
+ end
58
+ alias_method :has_key?, :include?
59
+
60
+ def empty?
61
+ extract_metadata_from_content.empty?
62
+ end
63
+
64
+ # Merges a hash (or some other Hash-like, Enumerable object) into
65
+ # this metadata. That is, key-value pairs from the given object will be added
66
+ # as metadata to this Confluence::Metadata's page, overriding any existing duplicate keys.
67
+ def merge!(data)
68
+ data.each do |k,v|
69
+ self[k] = v
70
+ end
71
+ end
72
+
73
+ private
74
+ def extract_metadata_from_content
75
+ return {} if @page.content.nil?
76
+
77
+ in_body = false
78
+ metadata = {}
79
+ @page.content.each do |line|
80
+ #TODO: handle other kinds of metadata macros
81
+ in_body = !in_body if line =~ /\{metadata-list\}/
82
+
83
+ if in_body
84
+ metadata[$1] = $2 if line =~ /\|\|\s*(.*?)\s*\|\s*(.*?)\s*\|/
85
+ end
86
+ end
87
+
88
+ return metadata
89
+ end
90
+
91
+ def replace_metadata_in_content(metadata)
92
+ #TODO: handle cases where there are multiple {metadata-list} macros in a page
93
+ #TODO: handle other kinds of metadata macros
94
+
95
+ macro_name = "{metadata-list}"
96
+
97
+ @page.content = "" if @page.content.nil?
98
+
99
+ @page.content += "\n#{macro_name}\n\n#{macro_name}" unless @page.content.include? macro_name
100
+
101
+ metadata_start = @page.content.index(macro_name) + macro_name.size
102
+ metadata_end = @page.content.index(macro_name, metadata_start) - 1
103
+
104
+ longest_key = 0
105
+ metadata.each do |key, val|
106
+ longest_key = key.size if key.size > longest_key
107
+ end
108
+
109
+ metadata_body = ""
110
+ metadata.each do |key, val|
111
+ padding = " " * (longest_key - key.size)
112
+ metadata_body += "|| #{key}#{padding} | #{val} |\n"
113
+ end
114
+
115
+ @page.content[metadata_start..metadata_end] = "\n"+metadata_body
116
+ end
117
+ end
@@ -0,0 +1,139 @@
1
+ require 'confluence/confluence_remote_data_object'
2
+
3
+ # Exposes a vaguely ActiveRecord-like interface for dealing with Confluence::Pages
4
+ # in Confluence.
5
+ class Confluence::Page < Confluence::RemoteDataObject
6
+
7
+ self.save_method = :storePage
8
+ self.get_method = :getPage
9
+ self.destroy_method = :removePage
10
+
11
+ self.attr_conversions = {
12
+ :id => :int,
13
+ :version => :int,
14
+ :parentId => :int,
15
+ :current => :boolean,
16
+ :homePage => :boolean,
17
+ :created => :datetime,
18
+ :modified => :datetime
19
+ }
20
+
21
+ self.readonly_attrs = [:current, :created, :modified, :contentStatus]
22
+
23
+ DEFAULT_SPACE = "encore"
24
+
25
+ def initialize(data_object = nil)
26
+ super
27
+ #self.creator = self.confluence_user unless self.include? :creator
28
+ end
29
+
30
+ def name=(new_name)
31
+ self.title = new_name
32
+ end
33
+
34
+ def name
35
+ self.title
36
+ end
37
+
38
+ def content=(new_content)
39
+ # make sure metadata doesn't get overwritten
40
+ old_metadata = self.metadata.entries if self.content
41
+ super
42
+ self.metadata.merge! old_metadata if old_metadata
43
+ end
44
+
45
+ def load_from_object(data_object)
46
+ super
47
+ self.modifier = self.confluence_username unless self.attributes.include? :modifier
48
+ end
49
+
50
+ def metadata
51
+ Confluence::Metadata.new(self)
52
+ end
53
+
54
+ def parent
55
+ self.class.find(self.parentId)
56
+ end
57
+
58
+ def to_s
59
+ self.title
60
+ end
61
+
62
+ def edit_group=(group)
63
+ encore.setPageEditGroup(self.title, group)
64
+ end
65
+
66
+ def edit_group
67
+ perm = get_permissions
68
+ return nil if perm.nil? or perm.empty?
69
+ perm.each do |p|
70
+ return p['lockedBy'] if p['lockType'] == 'Edit'
71
+ end
72
+ return nil
73
+ end
74
+
75
+ def view_group=(group)
76
+ encore.setPageViewGroup(self.title, group)
77
+ end
78
+
79
+ def view_group
80
+ perm = get_permissions
81
+ return nil if perm.nil? or perm.empty?
82
+ perm.each do |p|
83
+ return p['lockedBy'] if p['lockType'] == 'View'
84
+ end
85
+ return nil
86
+ end
87
+
88
+ def get_permissions
89
+ confluence.getPagePermissions(self.id.to_s)
90
+ end
91
+
92
+ ### callbacks ###############################################################
93
+
94
+ def before_save
95
+ raise "Cannot save this page because it has no title and/or space." unless self.title and self.space
96
+ end
97
+
98
+ #############################################################################
99
+
100
+ ### class methods ###########################################################
101
+
102
+ def self.find_by_name(name, space = DEFAULT_SPACE)
103
+ find_by_title(name, space)
104
+ end
105
+
106
+ def self.find_by_title(title, space = DEFAULT_SPACE)
107
+ begin
108
+ r = confluence.getPage(space, title)
109
+ rescue Confluence::RemoteException => e
110
+ # FIXME: dangerous to be checking based on error string like this...
111
+ if e.message =~ /does not exist/
112
+ return nil
113
+ else
114
+ raise e
115
+ end
116
+ end
117
+ self.new(r)
118
+ end
119
+
120
+
121
+ #############################################################################
122
+
123
+ protected
124
+ def self.metadata_accessor(accessor, metadata_key)
125
+ f = <<-END
126
+ def #{accessor.to_s}
127
+ self.metadata['#{metadata_key}']
128
+ end
129
+ def #{accessor.to_s}=(val)
130
+ self.metadata['#{metadata_key}'] = val.gsub("\n", " ")
131
+ end
132
+ END
133
+ module_eval f
134
+ end
135
+
136
+ def reload_newly_created!
137
+ self.load_from_object(confluence.getPage(self.space, self.title))
138
+ end
139
+ end
@@ -0,0 +1,112 @@
1
+ require 'confluence/confluence_remote_data_object'
2
+
3
+ class Confluence::User < Confluence::RemoteDataObject
4
+
5
+ self.save_method = :editUser
6
+ # TODO: implement :create_method ('addUser')
7
+ self.get_method = :getUser
8
+ self.destroy_method = :removeUser
9
+
10
+ self.readonly_attrs = ['username', 'name']
11
+ self.attr_conversions = {}
12
+
13
+ @groups
14
+
15
+ def id
16
+ self.username
17
+ end
18
+
19
+ def id=(new_id)
20
+ self.username = new_id
21
+ end
22
+
23
+ def username
24
+ self.name
25
+ end
26
+
27
+ def username=(new_username)
28
+ self.username=(new_username)
29
+ end
30
+
31
+ def groups
32
+ # groups are cached for the lifetime of the user object or until a group-modifying method is called
33
+ # FIXME: This is probably a bad idea, since the user's groups may be changed outside of the user object
34
+ # ... currently it's not a serious problem, since this is unlikely to happen within the object's
35
+ # short lifetime, but it may be problematic if start storing the user object in a cache or in the
36
+ # session.
37
+ @groups ||= confluence.getUserGroups(username)
38
+ end
39
+
40
+ def in_group?(group)
41
+ groups.include? group
42
+ end
43
+
44
+ def add_to_group(group)
45
+ @groups = nil # reset cached group list
46
+ confluence.addUserToGroup(username, group)
47
+ end
48
+
49
+ def remove_from_group(group)
50
+ @groups = nil # reset cached group list
51
+ confluence.removeUserFromGroup(username, group)
52
+ end
53
+
54
+ def has_permission?(permtype, page)
55
+ if permtype == :edit
56
+ group_or_name = page.edit_group
57
+ else
58
+ group_or_name = page.view_group
59
+ end
60
+
61
+ return true if group_or_name.nil?
62
+ return true if group_or_name == username
63
+ return in_group?(group_or_name)
64
+ end
65
+
66
+ def add_profile_picture(filename, mime_type, picture_data)
67
+ confluence.addProfilePicture(self.username, filename, mime_type, picture_data)
68
+ end
69
+
70
+ def to_s
71
+ self.username
72
+ end
73
+
74
+ def to_wiki
75
+ "[~#{self.username}]"
76
+ end
77
+
78
+ ### class methods #########################################################
79
+
80
+ def self.find_by_username(username)
81
+ find(username)
82
+ end
83
+
84
+ # DEPRECATED: this method is confusing since it could be taken as meaning "find by first/last name"
85
+ def self.find_by_name(username)
86
+ find_by_username(username)
87
+ end
88
+
89
+ def self.find_by_email(email)
90
+ usernames = confluence.getActiveUsers(true)
91
+ usernames.each do |username|
92
+ user = find_by_username(username)
93
+ return user if user.email == email
94
+ end
95
+
96
+ return nil
97
+ end
98
+
99
+ def self.find_all
100
+ # FIXME: this is really slow... we should probably just look in the confluence database instead
101
+ usernames = find_all_usernames
102
+ usernames.collect{|u| find_by_username(u)}
103
+ end
104
+
105
+ def self.find_all_usernames
106
+ confluence.getActiveUsers(true)
107
+ end
108
+
109
+ class NoSuchUser < Exception
110
+ end
111
+
112
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: confluence4r
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Atlassian and Alagu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Atlassian Confluence XML::RPC client
28
+ email: alagu@goyaka.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/confluence/confluence_connector.rb
34
+ - lib/confluence/confluence_remote_data_object.rb
35
+ - lib/confluence/confluence_rpc.rb
36
+ - lib/confluence/metadata.rb
37
+ - lib/confluence/page.rb
38
+ - lib/confluence/user.rb
39
+ homepage: http://confluence.atlassian.com/display/DISC/Confluence4r
40
+ licenses:
41
+ - unknown
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements:
58
+ - none
59
+ rubyforge_project:
60
+ rubygems_version: 2.2.2
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Atlassian Confluence XML::RPC client
64
+ test_files: []