qarioz-confluencer 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Secret Sauce Partners, Inc.
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.
@@ -0,0 +1,135 @@
1
+ = confluencer
2
+
3
+ Easy to use, ActiveRecord-like classes to find spaces, pages and bookmarks as well as adding new ones and updating existing ones.
4
+ It uses ruby's method missing to translate methods to Confluence XMLRPC API (v2) call.
5
+
6
+ For Confluence XML RPC API documentation, please take a look at these links:
7
+
8
+ {Confluence XML-RPC and SOAP APIs}[https://developer.atlassian.com/display/CONFDEV/Confluence+XML-RPC+and+SOAP+APIs]
9
+
10
+ {Remote Confluence Data Objects}[https://developer.atlassian.com/display/CONFDEV/Remote+Confluence+Data+Objects]
11
+
12
+ {https://developer.atlassian.com/display/CONFDEV/Remote+Confluence+Methods}[https://developer.atlassian.com/display/CONFDEV/Remote+Confluence+Methods]
13
+
14
+ == Installation
15
+ === Install from rubygems
16
+ gem install qarioz-confluencer
17
+ === Build and install from source
18
+ Clone the repository, build the gem, install the gem.
19
+
20
+ git clone git://github.com/qarioz/confluencer.git
21
+ cd confluencer
22
+ gem install jeweler log4r
23
+ gem build confluencer.gemspec
24
+ gem install qarioz-confluencer-0.6.0.gem
25
+ === Include in Gemfile
26
+ Or if you are using Gemfile, add this to your Gemfile
27
+
28
+ gem 'qarioz-confluencer', :git => 'git://github.com/qarioz/confluencer.git'
29
+
30
+ == Usage
31
+
32
+ This is the usage example for confluencer
33
+
34
+ require 'confluencer'
35
+ begin
36
+ #login
37
+ client = Confluence::Client.new(url: "https://mywiki.atlassian.net/wiki/")
38
+ client.login("username", "password")
39
+ # no need to save resulting token; api handles it
40
+
41
+ # get all spaces
42
+ all_spaces = client.get_spaces
43
+ # => [{'url', 'name', 'key', 'type'}]
44
+
45
+ # get specific space
46
+ test_space = client.get_space( "TestSpace")
47
+ # => {'url', 'name', 'key', 'type', 'homepage'}
48
+
49
+ # create a new space
50
+ s = Confluence::Space.new
51
+ # => {}
52
+
53
+ # get pages from a space
54
+ ps = client.get_pages( "TestSpace")
55
+ # => [{ 'url', 'id', 'version', 'title', 'space', 'parentId', 'permissions'}]
56
+ page_id = ps[0]['id'] # => "8345678"
57
+ pidcl = page_id.class # => String
58
+
59
+ # get comments from a page
60
+ comments = client.get_comments( page_id)
61
+ # => [{ 'modifier', 'creator', 'content', 'title',
62
+ # 'parentId', 'id', 'pageId', 'modified', 'url', 'created'}]
63
+
64
+ # create a comment
65
+ new_comment = client.add_comment( {pageId: page_id, title: "ruby sez", content: "blah blah blah"})
66
+
67
+ # create a label for a page
68
+ label = client.add_label_by_name( "new-label", page_id)
69
+ # => true
70
+
71
+ #update a page
72
+ npage = client.store_page(
73
+ space: "TestSpace", title: "yapage29", content: "Here is the content, also from ruby")
74
+ # => {'id', 'current', 'content', 'title', 'version', 'modifier',
75
+ # 'url', 'homePage', 'creator', 'contentStatus', 'modified',
76
+ # 'created', 'space', 'parentId', 'permissions'}
77
+ # if "yapage29" exists, throws {Confluence::Error}Could not save or update record
78
+ rescue Exception => e
79
+ puts "error: " + e.to_s
80
+ end
81
+
82
+ === Troubleshooting
83
+ ==== SSL Problem
84
+
85
+ If you get SSL Problem while using this gem on windows, follow the steps below to fix it.
86
+
87
+ Uncaught exception: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
88
+
89
+ This assumes your have already installed the [Rails Installer](http://railsinstaller.org) for Windows.
90
+
91
+ Download the ruby script to your *Desktop* folder from [https://gist.github.com/raw/867550/win_fetch_cacerts.rb](https://gist.github.com/raw/867550/win_fetch_cacerts.rb). Then in your command prompt, execute the ruby script:
92
+
93
+ ruby "%USERPROFILE%\Desktop\win_fetch_cacerts.rb"
94
+
95
+ Or copy paste the script below:
96
+
97
+ require 'net/http'
98
+
99
+ # create a path to the file "C:\RailsInstaller\cacert.pem"
100
+ cacert_file = File.join(%w{c: RailsInstaller cacert.pem})
101
+
102
+ Net::HTTP.start("curl.haxx.se") do |http|
103
+ resp = http.get("/ca/cacert.pem")
104
+ if resp.code == "200"
105
+ open(cacert_file, "wb") { |file| file.write(resp.body) }
106
+ puts "\n\nA bundle of certificate authorities has been installed to"
107
+ puts "C:\\RailsInstaller\\cacert.pem\n"
108
+ puts "* Please set SSL_CERT_FILE in your current command prompt session with:"
109
+ puts " set SSL_CERT_FILE=C:\\RailsInstaller\\cacert.pem"
110
+ puts "* To make this a permanent setting, add it to Environment Variables"
111
+ puts " under Control Panel -> Advanced -> Environment Variables"
112
+ else
113
+ abort "\n\n>>>> A cacert.pem bundle could not be downloaded."
114
+ end
115
+ end
116
+
117
+ Now make ruby aware of your certificate authority bundle by setting `SSL_CERT_FILE`. To set this in your current command prompt session, type:
118
+
119
+ set SSL_CERT_FILE=C:\RailsInstaller\cacert.pem
120
+
121
+ To make this a permanent setting, add this in your [control panel](http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/environment_variables.mspx?mfr=true).
122
+
123
+ ==== The Manual Way (Boring)
124
+
125
+ Download the `cacert.pem` file from [http://curl.haxx.se/ca/cacert.pem](http://curl.haxx.se/ca/cacert.pem). Save this file to `C:\RailsInstaller\cacert.pem`.
126
+
127
+ Now make ruby aware of your certificate authority bundle by setting `SSL_CERT_FILE`. To set this in your current command prompt session, type:
128
+
129
+ set SSL_CERT_FILE=C:\RailsInstaller\cacert.pem
130
+
131
+ To make this a permanent setting, add this in your [control panel](http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/environment_variables.mspx?mfr=true).
132
+
133
+ == Copyright
134
+
135
+ Copyright (c) 2010 Secret Sauce Partners, Inc. See LICENSE for details.
@@ -0,0 +1,36 @@
1
+ module Confluence
2
+ class Attachment < Record
3
+ extend Findable
4
+
5
+ record_attr_accessor :id => :attachment_id
6
+ record_attr_accessor :pageId => :page_id
7
+ record_attr_accessor :fileName => :filename
8
+ record_attr_accessor :fileSize => :filesize
9
+ record_attr_accessor :contentType => :content_type
10
+ record_attr_accessor :title, :creator, :created, :url, :comment
11
+
12
+ attr_writer :data
13
+
14
+ def data
15
+ @data ||= client.getAttachmentData(page_id, filename, "0")
16
+ end
17
+
18
+ def store
19
+ # reinitialize attachment after storing it
20
+ initialize(client.addAttachment(page_id, self.to_hash, XMLRPC::Base64.new(@data)))
21
+
22
+ # return self
23
+ self
24
+ end
25
+
26
+ def remove
27
+ client.removeAttachment(page_id, filename)
28
+ end
29
+
30
+ def self.find_criteria(args)
31
+ if args.key? :page_id and args.key? :filename
32
+ self.new(client.getAttachment(args[:page_id], args[:filename], args[:version] || "0"))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module Confluence
2
+ class BlogEntry < Record
3
+ extend Findable
4
+
5
+ record_attr_accessor :id => :entry_id
6
+ record_attr_accessor :space
7
+ record_attr_accessor :title, :content
8
+ record_attr_accessor :publishDate
9
+ record_attr_accessor :url
10
+
11
+ def store
12
+ # reinitialize blog entry after storing it
13
+ initialize(client.storeBlogEntry(self.to_hash))
14
+ end
15
+
16
+ def remove
17
+ client.removePage(self.entry_id)
18
+ end
19
+
20
+ private
21
+
22
+ def self.find_criteria(args)
23
+ if args.key? :id
24
+ self.new(client.getBlogEntry(args[:id]))
25
+ elsif args.key? :space
26
+ client.getBlogEntries(args[:space]).collect { |summary| BlogEntry.find(:id => summary["id"]) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,85 @@
1
+ module Confluence
2
+ class Bookmark < Page
3
+ attr_accessor :bookmark_url, :description
4
+
5
+ BOOKMARK_REGEXP = /\{bookmark.*\}[^\{]*\{bookmark\}/
6
+ BOOKMARK_URL_REGEXP = /\{bookmark:url=([^\}]+)\}/
7
+ DESCRIPTION_REGEXP = /\{bookmark.*\}([^\{]*)\{bookmark\}/
8
+
9
+ def initialize(hash)
10
+ # set and delete bookmark_url and description coming from hash
11
+ @bookmark_url = hash.delete :bookmark_url
12
+ @description = hash.delete :description
13
+
14
+ # initialize page
15
+ super(hash)
16
+
17
+ if content
18
+ # if no bookmark_url from hash, initialize from content
19
+ unless @bookmark_url
20
+ @bookmark_url = content[BOOKMARK_URL_REGEXP, 1]
21
+ @description = content[DESCRIPTION_REGEXP, 1]
22
+ end
23
+
24
+ # remove {bookmark} macro from content
25
+ content.gsub!(BOOKMARK_REGEXP, "")
26
+ end
27
+ end
28
+
29
+ def [](attr)
30
+ case attr
31
+ when :bookmark_url
32
+ @bookmark_url
33
+ when :description
34
+ @description
35
+ else
36
+ super(attr)
37
+ end
38
+ end
39
+
40
+ def []=(attr, value)
41
+ case attr
42
+ when :bookmark_url
43
+ @bookmark_url = value
44
+ when :description
45
+ @description = value
46
+ else
47
+ super(attr, value)
48
+ end
49
+ end
50
+
51
+ def store(args = {})
52
+ # always set .bookmarks as the parent page
53
+ self.parent_id = Page.find(:space => space, :title => Space::BOOKMARKS_PAGE_TITLE).page_id
54
+
55
+ # continue with storing the page
56
+ super(args)
57
+ end
58
+
59
+ def to_hash
60
+ page_hash = super
61
+
62
+ page_hash["content"] << "\n" unless page_hash["content"].empty?
63
+ page_hash["content"] << bookmark_content
64
+
65
+ page_hash
66
+ end
67
+
68
+ private
69
+
70
+ def bookmark_content
71
+ "{bookmark:url=#{@bookmark_url}}#{@description}{bookmark}"
72
+ end
73
+
74
+ def self.find_criteria(args)
75
+ result = super(args) || begin
76
+ if args.key? :space
77
+ space = Space.find :key => args[:space]
78
+ space.bookmarks
79
+ end
80
+ end
81
+
82
+ result
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,145 @@
1
+ require 'xmlrpc/client'
2
+
3
+ # Module containing Confluence-related classes.
4
+ module Confluence
5
+ # Originally confluence4r, available at: http://confluence.atlassian.com/display/DISC/Confluence4r
6
+
7
+ # A useful helper for running Confluence XML-RPC from Ruby. Takes care of
8
+ # adding the token to each method call (so you can call server.getSpaces()
9
+ # instead of server.getSpaces(token)).
10
+ #
11
+ # Usage:
12
+ #
13
+ # client = Confluence::Client.new(:url => "http://confluence.atlassian.com")
14
+ # client.login("user", "password")
15
+ # p client.getSpaces
16
+ #
17
+ class Client
18
+ PREFIX = "confluence2"
19
+ XMLRPC_SUFFIX = "/rpc/xmlrpc"
20
+
21
+ attr_reader :url, :username, :token
22
+
23
+ # Initializes a new client with the given arguments.
24
+ #
25
+ # ==== Parameters
26
+ # arguments<Hash>:: Described below.
27
+ #
28
+ # ==== Arguments
29
+ # :url - The url of the Confluence instance. The trailing '/rpc/xmlrpc' path is optional.
30
+ # :token - An existing session token to reuse.
31
+ #
32
+ def initialize(arguments = {})
33
+ @url = arguments[:url]
34
+ @token = arguments[:token]
35
+
36
+ Log4r::MDC.put('token', @token || 'nil')
37
+ log.info "initialized client (:url => #{@url}, :token => #{@token || 'nil'})"
38
+ end
39
+
40
+ # Returns true, if the client has a session token.
41
+ #
42
+ def has_token?
43
+ !@token.nil?
44
+ end
45
+
46
+ # Logs in and returns the newly acquired session token.
47
+ #
48
+ # ==== Parameters
49
+ # username<String>:: The username.
50
+ # password<String>:: The password.
51
+ #
52
+ def login(username, password)
53
+ handle_fault do
54
+ if @token = proxy.login(username, password)
55
+ Log4r::MDC.put('token', @token)
56
+ log.info "logged in as '#{username}' and acquired token."
57
+
58
+ @username = username
59
+ @password = password
60
+ end
61
+ end
62
+
63
+ @token
64
+ end
65
+
66
+ # Logs out and invalidates the session token.
67
+ #
68
+ def logout
69
+ handle_fault do
70
+ @token = nil if @token and result = proxy.logout(@token)
71
+ log.info "logged out"
72
+ Log4r::MDC.put('token', 'nil')
73
+ result
74
+ end
75
+ end
76
+
77
+ # Translates every call into XMLRPC calls.
78
+ #
79
+ def method_missing(method_name, *args)
80
+ handle_fault do
81
+ if args.empty?
82
+ log.debug "#{method_name}"
83
+ else
84
+ log.debug "#{method_name}(#{(args.collect {|a| a.inspect}).join(', ')})"
85
+ end
86
+
87
+ begin
88
+ method_name = camel_case_lower(method_name.to_s).to_sym if !(method_name.match(/[A-Z]/))
89
+ result = proxy.send(method_name, *([@token] + args))
90
+ log.debug(result.inspect)
91
+ result
92
+ rescue EOFError => e
93
+ log.warn "Could not complete XMLRPC call, retrying... Error: #{e.inspect}"
94
+ retry
95
+ end
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # Returns the Confluence::Client logger.
102
+ def log
103
+ Log4r::Logger[Confluence::Client.to_s] || Log4r::Logger.root
104
+ end
105
+
106
+ # Returns the Confluence XMLRPC endpoint url.
107
+ #
108
+ def xmlrpc_url
109
+ unless @url[-11..-1] == XMLRPC_SUFFIX
110
+ @url + XMLRPC_SUFFIX
111
+ else
112
+ @url
113
+ end
114
+ end
115
+
116
+ # Returns the XMLRPC client proxy for the Confluence API v1.
117
+ #
118
+ def proxy
119
+ @proxy ||= XMLRPC::Client.new_from_uri(xmlrpc_url).proxy(PREFIX)
120
+ end
121
+
122
+ # Yields and translates any XMLRPC::FaultExceptions raised by Confluence to Confluence::Errors.
123
+ #
124
+ def handle_fault(&block)
125
+ begin
126
+ block.call
127
+ rescue XMLRPC::FaultException => e
128
+ log.warn message = e.faultString.rpartition(':').last.strip
129
+
130
+ case message
131
+ when /Transaction rolled back/
132
+ raise Confluence::Error, "Could not save or update record."
133
+ else
134
+ raise Confluence::Error, message
135
+ end
136
+ end
137
+ end
138
+
139
+ # Make camel case with lower first letter
140
+ def camel_case_lower(original_string)
141
+ original_string.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,4 @@
1
+ module Confluence
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,23 @@
1
+ module Confluence
2
+ module Findable
3
+ # Finds records by the given criteria.
4
+ #
5
+ # ==== Parameters
6
+ # args<Hash>:: The search arguments.
7
+ def find(args)
8
+ if args.has_key?(:all) && !respond_to?(:find_all)
9
+ raise "Cannot find all #{self.class.name.downcase}s, find by criteria instead."
10
+ end
11
+
12
+ begin
13
+ case args
14
+ when :all
15
+ find_all
16
+ when Hash
17
+ find_criteria(args)
18
+ end
19
+ rescue Confluence::Error
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,160 @@
1
+ module Confluence
2
+ class Page < Record
3
+ INVALID_TITLE_CHARS = ":@/\\|^#;[]{}<>"
4
+
5
+ class Details < Hash
6
+ REGEXP = /\{details:label=([^\}]+)\}([^\{}]*)\{details\}/m
7
+ PAIR_REGEXP = /([^:]+):([^\n]+)/m
8
+
9
+ attr_reader :label
10
+
11
+ def initialize(args)
12
+ @label = args[:label]
13
+
14
+ parse(args[:content])
15
+ end
16
+
17
+ def to_s
18
+ # details macro
19
+ content = "{details:label=#{label}}\n"
20
+
21
+ each_pair do |key, value|
22
+ content << "#{key}:#{value}\n"
23
+ end
24
+
25
+ # end of details macro
26
+ content << "{details}\n"
27
+ end
28
+
29
+ private
30
+
31
+ def parse(content)
32
+ if content && content =~ REGEXP
33
+ # match label and the key/value pairs
34
+ @label, pairs = content.match(REGEXP).captures
35
+
36
+ pairs.strip.lines.each do |line|
37
+ if line =~ PAIR_REGEXP
38
+ self[$1.to_sym] = $2.strip
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class DetailsCollection < Hash
46
+ def initialize(content)
47
+ if content
48
+ content.gsub!(Details::REGEXP) do |content|
49
+ self[$1.to_sym] = Details.new(:content => content)
50
+ ""
51
+ end
52
+ end
53
+ end
54
+
55
+ def [](key)
56
+ super(key) or self[key] = Details.new(:label => key)
57
+ end
58
+
59
+ def to_s
60
+ values.join("\n")
61
+ end
62
+ end
63
+
64
+ extend Findable
65
+
66
+ record_attr_accessor :id => :page_id
67
+ record_attr_accessor :parentId => :parent_id
68
+ record_attr_accessor :space
69
+ record_attr_accessor :title, :creator, :modifier, :content
70
+ record_attr_accessor :created, :modified, :version
71
+ record_attr_accessor :url
72
+
73
+ attr_accessor :details
74
+
75
+ def initialize(hash)
76
+ super(hash)
77
+
78
+ @details = DetailsCollection.new(content)
79
+ end
80
+
81
+ def children(klass = self.class)
82
+ children = client.getChildren(page_id)
83
+ children.collect { |hash| klass.find(:id => hash["id"]) } if children
84
+ end
85
+
86
+ def attachments
87
+ attachments = client.getAttachments(page_id)
88
+ attachments.collect { |hash| Attachment.new(hash) } if attachments
89
+ end
90
+
91
+ def store(args = {})
92
+ unless self.version
93
+ # check for existing page by id or title
94
+ existing_page = if page_id
95
+ Page.find :id => page_id
96
+ else
97
+ Page.find :space => space, :title => title
98
+ end
99
+
100
+ # take page_id and version from existing page if available
101
+ if existing_page
102
+ if args[:recreate_if_exists]
103
+ # remove existing page
104
+ existing_page.remove
105
+ else
106
+ # update page with page_id and version info
107
+ self.page_id = existing_page.page_id
108
+ self.version = existing_page.version
109
+ end
110
+ end
111
+ end
112
+
113
+ # reinitialize page after storing it
114
+ initialize(client.storePage(self.to_hash))
115
+
116
+ # return self
117
+ self
118
+ end
119
+
120
+ def remove
121
+ client.removePage(page_id)
122
+ end
123
+
124
+ def add_attachment(filename, content_type, data = IO.read(filename), comment = "")
125
+ attachment = Attachment.new :pageId => page_id, :fileName => filename, :contentType => content_type, :comment => comment
126
+ attachment.data = data
127
+ attachment.store
128
+ end
129
+
130
+ def to_hash
131
+ # record hash
132
+ record_hash = super
133
+
134
+ # always include content in hash
135
+ record_hash["content"] ||= ""
136
+
137
+ # prepend details sections before content
138
+ record_hash["content"].insert(0, details.to_s)
139
+
140
+ # result
141
+ record_hash
142
+ end
143
+
144
+ private
145
+
146
+ def self.find_criteria(args)
147
+ if args.key? :id
148
+ self.new(client.getPage(args[:id]))
149
+ elsif args.key?(:space) && args.key?(:title)
150
+ self.new(client.getPage(args[:space], args[:title]))
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ class String
157
+ def to_page_title
158
+ self.delete(Confluence::Page::INVALID_TITLE_CHARS).strip
159
+ end
160
+ end
@@ -0,0 +1,107 @@
1
+ module Confluence
2
+ # Base class for working with Confluence records.
3
+ #
4
+ class Record
5
+ class << self
6
+ # The client used for Confluence API calls.
7
+ #
8
+ def client
9
+ raise "Confluence client is unavailable. Did you forget to use Confluence::Session.new?" unless @@client
10
+ @@client
11
+ end
12
+
13
+ # Sets the client.
14
+ #
15
+ def client=(value)
16
+ @@client = value
17
+ end
18
+
19
+ # Defines an attr_accessor for a Record attribute.
20
+ #
21
+ def record_attr_accessor(*args)
22
+ attributes = {}
23
+
24
+ # iterate through each argument
25
+ args.each do |arg|
26
+ attributes = case arg
27
+ when Symbol
28
+ { arg => arg }
29
+ when Hash
30
+ arg
31
+ else
32
+ break
33
+ end
34
+
35
+ attributes.each_pair do |key, name|
36
+ class_eval %Q{
37
+ def #{name}
38
+ self[:#{key}]
39
+ end
40
+
41
+ def #{name}=(value)
42
+ self[:#{key}] = value
43
+ end
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Returns the the Confluence API client.
51
+ #
52
+ def client
53
+ Record.client
54
+ end
55
+
56
+ # Initializes a new record.
57
+ #
58
+ # ==== Parameters
59
+ # hash<Hash>:: A hash containing the attributes and its values. Keys can be Strings or Symbols.
60
+ def initialize(hash = {})
61
+ @attributes = {}
62
+
63
+ # iterate through each key/value pair and set attribute keyed by symbol
64
+ hash.each_pair do |key, value|
65
+ self[key.to_sym] = value
66
+ end
67
+ end
68
+
69
+ def [](attr)
70
+ @attributes[attr]
71
+ end
72
+
73
+ def []=(attr, value)
74
+ @attributes[attr] = value
75
+ end
76
+
77
+ # Returns the id of the record.
78
+ #
79
+ def record_id
80
+ self[:id]
81
+ end
82
+
83
+ # Retrieves the labels of the record.
84
+ #
85
+ def labels
86
+ @labels ||= client.getLabelsById(record_id).collect {|label| label["name"]}
87
+ end
88
+
89
+ # Sets the labels of the record.
90
+ #
91
+ def labels=(value)
92
+ removed_labels = labels - value
93
+ added_labels = value - labels
94
+
95
+ client.removeLabelByName(removed_labels.join(" "), record_id) unless removed_labels.empty?
96
+ client.addLabelByName(added_labels.join(" "), record_id) unless added_labels.empty?
97
+
98
+ @labels = value
99
+ end
100
+
101
+ def to_hash
102
+ hash = Hash.new
103
+ @attributes.each { |key, value| hash[key.to_s] = value }
104
+ hash
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,88 @@
1
+ module Confluence
2
+ # Wraps a Confluence::Client and manages the lifetime of a session.
3
+ #
4
+ class Session
5
+ attr_reader :client
6
+
7
+ # Initializes a new session with the given arguments and sets it for other classes like Confluence::Page.
8
+ #
9
+ # If a block is given to initialize, initialize yields with the Confluence::Client and automatically logs out of the session afterwards.
10
+ # Otherwise Session#destroy should be called after finished.
11
+ #
12
+ # ==== Parameters
13
+ # arguments<Hash>:: Described below.
14
+ #
15
+ # ==== Arguments
16
+ # :url - The url of the Confluence instance.
17
+ # :username - The username.
18
+ # :password - The password.
19
+ #
20
+ def initialize(arguments = {})
21
+ raise ArgumentError, "Required argument 'url' is missing." unless arguments.key? :url
22
+
23
+ @client = Confluence::Client.new(arguments)
24
+
25
+ unless @client.has_token?
26
+ raise ArgumentError, "Required argument 'username' is missing." unless arguments.key? :username
27
+ raise ArgumentError, "Required argument 'password' is missing." unless arguments.key? :password
28
+
29
+ @client.login(arguments[:username], arguments[:password])
30
+ end
31
+
32
+ # set client for records
33
+ Confluence::Record.client = @client
34
+
35
+ # yield if block was given and destroy afterwards
36
+ if block_given?
37
+ yield @client
38
+
39
+ self.destroy
40
+ end
41
+ end
42
+
43
+ # Returns the current session token.
44
+ #
45
+ def token
46
+ client.token if client
47
+ end
48
+
49
+ # Returns the currently logged in username.
50
+ #
51
+ def username
52
+ client.username if client
53
+ end
54
+
55
+ # Logs in and acquire a new token after destroying the previous session.
56
+ #
57
+ # ==== Parameters
58
+ # arguments<Hash>:: Described below.
59
+ #
60
+ # ==== Arguments
61
+ # :username - The username.
62
+ # :password - The password.
63
+ #
64
+ def login(arguments)
65
+ raise ArgumentError, "Required argument 'username' is missing." unless arguments.key? :username
66
+ raise ArgumentError, "Required argument 'password' is missing." unless arguments.key? :password
67
+
68
+ # log out first
69
+ client.logout if client
70
+
71
+ # log in with credentials
72
+ @client.login(arguments[:username], arguments[:password])
73
+ end
74
+
75
+ # Destroys the session by logging out and resets other classes like Confluence::Page.
76
+ #
77
+ def destroy
78
+ # invalidate the token
79
+ client.logout
80
+
81
+ # client is not valid anymore
82
+ @client = nil
83
+
84
+ # reset client for records
85
+ Confluence::Record.client = nil
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,59 @@
1
+ module Confluence
2
+ # A Confluence space.
3
+ class Space < Record
4
+ extend Findable
5
+
6
+ BOOKMARKS_PAGE_TITLE = ".bookmarks"
7
+
8
+ record_attr_accessor :key, :name, :url, :description
9
+ record_attr_accessor :homePage => :homepage
10
+
11
+ def bookmark_page
12
+ @bookmark_page ||= Page.find :space => self.key, :title => BOOKMARKS_PAGE_TITLE
13
+ end
14
+
15
+ def bookmarks
16
+ @bookmarks ||= bookmark_page ? bookmark_page.children(Bookmark) : []
17
+ end
18
+
19
+ def blog_entries
20
+ client.getBlogEntries(self.key).collect {|summary| BlogEntry.new(client.getBlogEntry(summary["id"]))}
21
+ end
22
+
23
+ def find_page(args)
24
+ if args.key? :title
25
+ Page.find :space => self.key, :title => args[:title]
26
+ end
27
+ end
28
+
29
+ def get_page(args)
30
+ args[:parent_title] ||= "Home"
31
+
32
+ # check if page already exists
33
+ find_page(args) or begin
34
+ # page does not exist yet, create it
35
+ page = Confluence::Page.new :space => self.key, :title => args[:title]
36
+
37
+ # look for the parent by title, set parentId if found
38
+ if parent_page = find_page(:title => args[:parent_title])
39
+ page.parent_id = parent_page.page_id
40
+ end
41
+
42
+ # store the page
43
+ page.store
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def self.find_all
50
+ client.getSpaces.collect { |summary| Space.find(:key => summary["key"]) }
51
+ end
52
+
53
+ def self.find_criteria(args)
54
+ if args.key? :key
55
+ Space.new(client.getSpace(args[:key]))
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ module Confluence
2
+ class User < Record
3
+ extend Findable
4
+
5
+ record_attr_accessor :name, :fullname, :email, :url
6
+
7
+ private
8
+
9
+ def self.find_all
10
+ client.getActiveUsers(true).collect { |name| User.find :name => name }
11
+ end
12
+
13
+ def self.find_criteria(args)
14
+ if args.key? :name
15
+ User.new(client.getUser(args[:name]))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # ensure that lib is in the load path
2
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'log4r'
6
+
7
+ require 'confluence/error'
8
+
9
+ require 'confluence/client'
10
+ require 'confluence/session'
11
+
12
+ require 'confluence/findable'
13
+ require 'confluence/record'
14
+ require 'confluence/user'
15
+ require 'confluence/space'
16
+ require 'confluence/page'
17
+ require 'confluence/bookmark'
18
+ require 'confluence/blog_entry'
19
+ require 'confluence/attachment'
20
+
21
+ module Confluencer
22
+ VERSION = "0.6.0"
23
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ libs = " -r irb/completion"
4
+
5
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/confluencer.rb'}"
6
+
7
+ # load console_init
8
+ libs << " -r #{File.dirname(__FILE__) + '/console_init.rb'}"
9
+
10
+ puts "Loading confluencer..."
11
+ exec "irb #{libs} --simple-prompt"
@@ -0,0 +1,5 @@
1
+ begin
2
+ # try to load awesome_print
3
+ require 'ap'
4
+ rescue LoadError
5
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qarioz-confluencer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Secret Sauce Partners, Inc.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2010-06-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: log4r
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.1.7
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.1.7
46
+ description: ActiveRecord-like classes to access Confluence through XMLRPC.
47
+ email: github@secretsaucepartners.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - script/console
53
+ - script/console_init.rb
54
+ - lib/confluencer.rb
55
+ - lib/confluence/client.rb
56
+ - lib/confluence/bookmark.rb
57
+ - lib/confluence/error.rb
58
+ - lib/confluence/page.rb
59
+ - lib/confluence/space.rb
60
+ - lib/confluence/blog_entry.rb
61
+ - lib/confluence/attachment.rb
62
+ - lib/confluence/record.rb
63
+ - lib/confluence/user.rb
64
+ - lib/confluence/findable.rb
65
+ - lib/confluence/session.rb
66
+ - LICENSE
67
+ - README.rdoc
68
+ homepage: http://github.com/sspinc/confluencer
69
+ licenses: []
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --charset=UTF-8
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: 1.3.6
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.25
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Useful classes to manage Confluence.
93
+ test_files: []