kenai_tools 0.0.7

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.
@@ -0,0 +1,168 @@
1
+ require 'rubygems'
2
+ require "bundler/setup"
3
+
4
+ require 'rest_client'
5
+ require 'json'
6
+
7
+ module KenaiTools
8
+ class KenaiClient
9
+ DEFAULT_HOST = 'https://kenai.com/'
10
+
11
+ RestClient.proxy = ENV['http_proxy'] if ENV['http_proxy']
12
+
13
+ attr_reader :host, :user, :password
14
+
15
+ def initialize(host = nil, opts = {})
16
+ @host = host || DEFAULT_HOST
17
+ @opts = opts
18
+ end
19
+
20
+ # check credentials using the login/authenticate method; if successful,
21
+ # cache the credentials for future calls
22
+ def authenticate(user, password)
23
+ @user = user
24
+ @password = password
25
+ begin
26
+ client = self['login/authenticate']
27
+ client["?username=#{@user}&password=#{@password}"].get
28
+ @auth = true
29
+ rescue RestClient::Unauthorized, RestClient::RequestFailed
30
+ @auth = false
31
+ @user = @password = nil
32
+ end
33
+
34
+ return @auth
35
+ end
36
+
37
+ def authenticated?
38
+ @auth
39
+ end
40
+
41
+ def project(proj_name)
42
+ begin
43
+ JSON.parse(self["projects/#{proj_name}"].get)
44
+ rescue RestClient::ResourceNotFound
45
+ nil
46
+ end
47
+ end
48
+
49
+ # collect all project hashes (scope may be :all, or all projects, or
50
+ # :mine, for projects in which the current user has some role)
51
+ def projects(scope=:all)
52
+ fetch_all('projects', 'projects')
53
+ end
54
+
55
+ def my_projects
56
+ fetch_all('projects/mine', 'projects')
57
+ end
58
+
59
+ # get wiki images for a project
60
+ def wiki_images(project, on_page = nil)
61
+ fetch_all("projects/#{project}/features/wiki/images", 'images', on_page)
62
+ end
63
+
64
+ # get the wiki raw image data for an image
65
+ def wiki_image_data(image)
66
+ RestClient.get(image['image_url'], :accept => image['image_content_type'])
67
+ end
68
+
69
+ # hash has the following keys
70
+ # +:uploaded_data+ = raw image data, required only if creating a new image
71
+ # +:comments+ = optional comments for the image
72
+ # throws IOError unless create or update was successful
73
+ def create_or_update_wiki_image(proj_name, image_filename, hash)
74
+ req_params = {}
75
+ if data = hash[:uploaded_data]
76
+ upload_io = UploadIO.new(StringIO.new(data), "image/png", image_filename)
77
+ req_params["image[uploaded_data]"] = upload_io
78
+ end
79
+ if comments = hash[:comments]
80
+ req_params["image[comments]"] = comments
81
+ end
82
+ return false if req_params.empty?
83
+ end
84
+
85
+ # get wiki pages for a project
86
+ def wiki_pages(project, on_page = nil)
87
+ fetch_all("projects/#{project}/features/wiki/pages", 'pages', on_page)
88
+ end
89
+
90
+ def wiki_page(proj_name, page_name)
91
+ page = wiki_page_client(proj_name, page_name)
92
+ JSON.parse(page.get)
93
+ end
94
+
95
+ # edit a single wiki page -- yields the current page contents, and
96
+ # saves them back if the result of the block is different
97
+ def edit_wiki_page(proj_name, page_name)
98
+ # fetch current page contents
99
+ page = wiki_page_client(proj_name, page_name)
100
+ begin
101
+ page_data = JSON.parse(page.get)
102
+ current_src = page_data['text']
103
+ rescue RestClient::ResourceNotFound
104
+ page_data = {}
105
+ current_src = ''
106
+ end
107
+
108
+ new_src = yield(current_src)
109
+
110
+ changed = !(new_src.nil? || new_src == current_src)
111
+
112
+ if changed
113
+ new_data = {
114
+ 'page' => {
115
+ 'text' => new_src,
116
+ 'description' => 'edited with kenai-client',
117
+ 'number' => page_data['number']
118
+ }
119
+ }
120
+ page.put(JSON.dump(new_data), :content_type => 'application/json')
121
+ end
122
+
123
+ return changed
124
+ end
125
+
126
+ def api_client(fragment='')
127
+ params = {:headers => {:accept => 'application/json'}}
128
+ if @auth
129
+ params[:user] = @user
130
+ params[:password] = @password
131
+ end
132
+ params.merge!(@opts)
133
+
134
+ if fragment =~ %r{^https://}
135
+ RestClient::Resource.new(fragment, params)
136
+ else
137
+ RestClient::Resource.new(@host, params)['api'][fragment]
138
+ end
139
+ end
140
+
141
+ alias :[] :api_client
142
+
143
+ private
144
+
145
+ # +on_page+ means only on that particular page or all pages if nil
146
+ def fetch_all(initial_url, item_key, on_page = nil)
147
+ unless on_page
148
+ next_page = initial_url
149
+ results = []
150
+
151
+ begin
152
+ curr_page = JSON.parse(self[next_page].get)
153
+ results += curr_page[item_key]
154
+ next_page = curr_page['next']
155
+ end until next_page.nil?
156
+
157
+ results
158
+ else
159
+ url = on_page ? initial_url + "?page=#{on_page}" : initial_url
160
+ JSON.parse(self[url].get)[item_key]
161
+ end
162
+ end
163
+
164
+ def wiki_page_client(project, page)
165
+ self["projects/#{project}/features/wiki/pages/#{page}"]
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,3 @@
1
+ module KenaiTools
2
+ VERSION = "0.0.7"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'kenai_tools/version'
2
+ require 'kenai_tools/kenai_client'
3
+ require 'kenai_tools/downloads_client'
4
+
5
+ module KenaiTools
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,302 @@
1
+ require 'spec_helper'
2
+ require "open-uri"
3
+
4
+ # This rspec test assumes that a development kenai/junction2 server is running
5
+ SITE = "http://localhost:3000"
6
+ begin
7
+ RestClient::Resource.new(SITE).get
8
+ rescue
9
+ fail("Check that a Rails kenai/junction2 development mode server is running at #{SITE}")
10
+ end
11
+
12
+ describe KenaiTools::DownloadsClient do
13
+ before :all do
14
+ # Init downloads feature for test project oasis
15
+ dlclient = KenaiTools::DownloadsClient.new(SITE, "oasis", :downloads_name => "downloads")
16
+ dlclient.authenticate("mehdi", "mehdi") unless dlclient.authenticated?
17
+ dlclient.delete_feature('yes') if dlclient.ping
18
+ dlclient.get_or_create
19
+ end
20
+
21
+ # Larger timeout used here to debug server-side code or handling a large amount of data
22
+ let(:dlclient) { KenaiTools::DownloadsClient.new(SITE, "oasis", :timeout => 36000) }
23
+ let(:data) { Pathname.new(File.dirname(__FILE__) + '/fixtures/data') }
24
+ let(:file1) { data + "text1.txt" }
25
+
26
+ def ensure_write_permission
27
+ dlclient.authenticate("mehdi", "mehdi") unless dlclient.authenticated?
28
+ end
29
+
30
+ def ensure_remote_dir(remote_dir)
31
+ unless dlclient.entry_type(remote_dir) == 'directory'
32
+ ensure_write_permission
33
+ dlclient.rm_r(remote_dir) if dlclient.exist?(remote_dir)
34
+ dlclient.mkdir(remote_dir)
35
+ end
36
+ end
37
+
38
+ def ensure_remote_sample_data(rel_path)
39
+ ensure_write_permission
40
+ pn = data + rel_path
41
+ dlclient.push(pn) unless dlclient.exist?(rel_path)
42
+ end
43
+
44
+ describe "authentication" do
45
+ it "should authenticate with valid credentials" do
46
+ dlclient.authenticate("mehdi", "mehdi").should be_true
47
+ dlclient.authenticated?.should be_true
48
+ end
49
+
50
+ it "should fail to authenticate with invalid credentials" do
51
+ dlclient.authenticate("mehdi", "xmehdi").should be_false
52
+ dlclient.authenticated?.should be_false
53
+ end
54
+ end
55
+
56
+ describe "bootstrap" do
57
+ context "basic" do
58
+ # Note: this test depend upon sample downloads data in the development DB
59
+ it "should detect existence of a file" do
60
+ dlclient = KenaiTools::DownloadsClient.new(SITE, "glassfish")
61
+ dlclient.exist?("glassfishv4solaris.zip").should be_true
62
+ end
63
+
64
+ it "should detect non-existence of a file" do
65
+ dlclient = KenaiTools::DownloadsClient.new(SITE, "glassfish")
66
+ dlclient.exist?("non-existent-download.zip").should be_false
67
+ end
68
+
69
+ it "should return the entry_type of an entry" do
70
+ dlclient = KenaiTools::DownloadsClient.new(SITE, "glassfish")
71
+ dlclient.entry_type("glassfishv4solaris.zip").should == 'file'
72
+ end
73
+ end
74
+
75
+ context "authenticated" do
76
+ before :each do
77
+ dlclient.authenticate("mehdi", "mehdi")
78
+ end
79
+
80
+ it "should upload a single file to the top level" do
81
+ dlclient.rm_r(file1.basename) if dlclient.exist?(file1.basename)
82
+
83
+ dlclient.push(file1)
84
+ dlclient.exist?(file1.basename).should be_true
85
+ end
86
+
87
+ it "should destroy a single file at the top level" do
88
+ ensure_remote_sample_data(file1.basename)
89
+
90
+ dlclient.rm(file1.basename).should be_true
91
+ end
92
+
93
+ it "should make a directory" do
94
+ dirname = "x11r5"
95
+ dlclient.rm_r(dirname) if dlclient.exist?(dirname)
96
+
97
+ dlclient.mkdir(dirname)
98
+ dlclient.entry_type(dirname).should == 'directory'
99
+ end
100
+
101
+ it "should delete a directory" do
102
+ dirname = "x11r5"
103
+ dlclient.rm_r(dirname) if dlclient.exist?(dirname)
104
+ dlclient.mkdir(dirname)
105
+
106
+ dlclient.exist?(dirname).should be_true
107
+ dlclient.rm_r(dirname)
108
+ dlclient.exist?(dirname).should be_false
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "listing" do
114
+ before :each do
115
+ dlclient.authenticate("mehdi", "mehdi")
116
+ @dir19 = 'version-1.9'
117
+ ensure_remote_dir(@dir19)
118
+ ensure_remote_sample_data(file1.basename)
119
+ dlclient.authenticate("mehdi", "wrong-password").should be_false
120
+ end
121
+
122
+ it "should list the top level downloads of a project as a directory named '/'" do
123
+ dlclient.ls.keys.should =~ ['href', 'display_name', 'entry_type', 'description', 'tags', 'children',
124
+ 'created_at', 'updated_at', 'content_type']
125
+ dlclient.ls['entry_type'].should == 'directory'
126
+ dlclient.ls['display_name'].should == '/'
127
+ dlclient.ls['children'].map { |ch| ch['display_name'] }.should include(file1.basename.to_s, @dir19)
128
+ end
129
+
130
+ it "should list a file" do
131
+ entry = dlclient.ls(file1.basename)
132
+ entry['entry_type'].should == 'file'
133
+ entry['entry_content_type'].should == 'text/plain'
134
+ open(entry['content_url']).read == file1.open.read
135
+ entry.keys.should =~ ['href', 'display_name', 'entry_type', 'description', 'tags', 'size',
136
+ 'created_at', 'updated_at', 'content_url', 'entry_content_type', 'content_type']
137
+ end
138
+
139
+ it "should list a subdirectory" do
140
+ entry = dlclient.ls(@dir19)
141
+ entry['entry_type'].should == 'directory'
142
+ entry.keys.should =~ ['href', 'display_name', 'entry_type', 'description', 'tags', 'children',
143
+ 'created_at', 'updated_at', 'content_type']
144
+ end
145
+ end
146
+
147
+ describe "push" do
148
+ before :each do
149
+ dlclient.authenticate("mehdi", "mehdi")
150
+ end
151
+
152
+ it "should upload a single file to a remote directory specified with a relative path" do
153
+ target_dir = "version-1.9"
154
+ ensure_remote_dir(target_dir)
155
+
156
+ dlclient.push(file1, target_dir)
157
+ target_file = File.join(target_dir, file1.basename)
158
+ dlclient.exist?(target_file).should be_true
159
+ end
160
+
161
+ it "should upload a single file to a remote directory specified with an absolute path" do
162
+ target_dir = "/version-1.9"
163
+ ensure_remote_dir(target_dir)
164
+
165
+ dlclient.push(file1, target_dir)
166
+ target_file = File.join(target_dir, file1.basename)
167
+ dlclient.exist?(target_file).should be_true
168
+ end
169
+
170
+ it "should recursively upload a directory into a new target directory" do
171
+ target_dir = "tax_year_2010"
172
+ ensure_remote_dir(target_dir)
173
+ src_dir = data + "irs_docs"
174
+
175
+ dlclient.push(src_dir, target_dir)
176
+ target_subdir = File.join(target_dir, src_dir.basename)
177
+ dlclient.entry_type(target_subdir).should == 'directory'
178
+ expected_names = src_dir.children.map { |ch| ch.basename.to_s }
179
+ actual_names = dlclient.ls(target_subdir)['children'].map { |ch| ch['display_name'] }
180
+ actual_names.should =~ expected_names
181
+ end
182
+
183
+ it "should recursively upload source directory contents if the source argument ends with a '/'" do
184
+ target_dir = "sax2r2"
185
+ ensure_remote_dir(target_dir)
186
+ src_dir = data + "sax2/"
187
+
188
+ dlclient.push(src_dir, target_dir)
189
+ expected_names = src_dir.children.map { |ch| ch.basename.to_s }
190
+ actual_names = dlclient.ls(target_dir)['children'].map { |ch| ch['display_name'] }
191
+ actual_names.should =~ expected_names
192
+ end
193
+ end
194
+
195
+ describe "remove files" do
196
+ before :each do
197
+ dlclient.authenticate("mehdi", "mehdi")
198
+ end
199
+
200
+ it "should remove a directory and its contents" do
201
+ dir = "irs_docs"
202
+ ensure_remote_sample_data(dir)
203
+
204
+ dlclient.rm_r(dir)
205
+ dlclient.exist?(dir).should be_false
206
+ end
207
+
208
+ it "should not remove a directory and its contents for rmdir" do
209
+ dir = "irs_docs"
210
+ ensure_remote_sample_data(dir)
211
+
212
+ lambda { dlclient.rmdir(dir) }.should raise_error(/not empty/)
213
+ dlclient.exist?(dir).should be_true
214
+ end
215
+ end
216
+
217
+ describe "miscellaneous" do
218
+ it "should ping a working service" do
219
+ dlclient.ping.should be_true
220
+ end
221
+
222
+ it "should fail to ping a non-working service" do
223
+ down_dlclient = KenaiTools::DownloadsClient.new(SITE, "bad-project")
224
+ down_dlclient.ping.should be_false
225
+ end
226
+
227
+ it "should discover the name of a downloads feature if a project only has one" do
228
+ dlclient2 = KenaiTools::DownloadsClient.new(SITE, "oasis")
229
+ dlclient2.downloads_name.should == 'downloads'
230
+ end
231
+
232
+ it "should discover the downloads feature if a project only has one" do
233
+ dlclient2 = KenaiTools::DownloadsClient.new(SITE, "oasis")
234
+ dlclient2.downloads_feature['type'].should == 'downloads'
235
+ end
236
+
237
+ it "should delete a downloads feature" do
238
+ dlclient2 = KenaiTools::DownloadsClient.new(SITE, "openjdk")
239
+ dlclient2.authenticate("craigmcc", "craigmcc") unless dlclient2.authenticated?
240
+ dlclient2.get_or_create
241
+
242
+ dlclient2.ping.should be_true
243
+ lambda { dlclient2.delete_feature }.should raise_error(/[Cc]onfirm/)
244
+ dlclient2.delete_feature('yes').should be_true
245
+ dlclient2.ping.should be_false
246
+ end
247
+
248
+ it "should create a downloads feature" do
249
+ dlclient2 = KenaiTools::DownloadsClient.new(SITE, "openjdk")
250
+ dlclient2.authenticate("craigmcc", "craigmcc") unless dlclient2.authenticated?
251
+ dlclient2.delete_feature('yes') if dlclient2.ping
252
+
253
+ dlclient2.ping.should be_false
254
+ dlclient2.get_or_create.should == 'downloads'
255
+ dlclient2.ping.should be_true
256
+ end
257
+
258
+ it "should make a directory with a name that needs to be encoded" do
259
+ dirname = "Web 2.0"
260
+ ensure_write_permission
261
+ dlclient.rm_r(dirname) if dlclient.exist?(dirname)
262
+
263
+ dlclient.mkdir(dirname)
264
+ dlclient.entry_type(dirname).should == 'directory'
265
+
266
+ dlclient.push(file1, dirname).should be_true
267
+ end
268
+ end
269
+
270
+ describe "pull" do
271
+ it "should download a single file to a local directory" do
272
+ ensure_remote_sample_data(file1.basename)
273
+
274
+ Dir.mktmpdir do |dir|
275
+ dlclient.pull(file1.basename, dir)
276
+ (Pathname(dir) + file1.basename).read.should == file1.read
277
+ end
278
+ end
279
+
280
+ it "should recursively download a remote subdirectory to a local directory" do
281
+ sample_dir = "irs_docs"
282
+ ensure_remote_sample_data(sample_dir)
283
+
284
+ Dir.mktmpdir do |dir|
285
+ dlclient.pull(sample_dir, dir)
286
+ dest_dir = Pathname(dir) + sample_dir
287
+ system("diff -r #{data + sample_dir} #{dest_dir}").should be_true
288
+ end
289
+ end
290
+
291
+ it "should recursively download all entries to a local directory" do
292
+ sample_dir = "irs_docs"
293
+ ensure_remote_sample_data(sample_dir)
294
+
295
+ Dir.mktmpdir do |dir|
296
+ dlclient.pull('/', dir)
297
+ dest_dir = Pathname(dir) + sample_dir
298
+ dest_dir.should be_exist
299
+ end
300
+ end
301
+ end
302
+ end
Binary file
@@ -0,0 +1,9 @@
1
+ .cdtproject
2
+ .classpath
3
+ .project
4
+ Log
5
+ apidoc
6
+ classes
7
+ docs
8
+ sax.jar
9
+ sax2dist.jar