kenai_tools 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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