kenai_tools 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/kenai_tools.iml +40 -0
- data/.idea/misc.xml +8 -0
- data/.idea/modules.xml +9 -0
- data/.idea/vcs.xml +7 -0
- data/Gemfile +4 -0
- data/README.md +28 -0
- data/Rakefile +2 -0
- data/bin/dlutil +139 -0
- data/kenai_tools.gemspec +43 -0
- data/lib/kenai_tools/downloads_client.rb +241 -0
- data/lib/kenai_tools/kenai_client.rb +168 -0
- data/lib/kenai_tools/version.rb +3 -0
- data/lib/kenai_tools.rb +7 -0
- data/spec/downloads_client_spec.rb +302 -0
- data/spec/fixtures/data/irs_docs/irs-form-1040.pdf +0 -0
- data/spec/fixtures/data/irs_docs/irs-p555.pdf +0 -0
- data/spec/fixtures/data/sax.tgz +0 -0
- data/spec/fixtures/data/sax2/.cvsignore +9 -0
- data/spec/fixtures/data/sax2/CHANGES +245 -0
- data/spec/fixtures/data/sax2/COPYING +12 -0
- data/spec/fixtures/data/sax2/ChangeLog +666 -0
- data/spec/fixtures/data/sax2/Makefile +77 -0
- data/spec/fixtures/data/sax2/README +62 -0
- data/spec/fixtures/data/sax2/build.xml +68 -0
- data/spec/fixtures/data/sax2/src/SAXDump.java +238 -0
- data/spec/fixtures/data/sax2/src/SAXTest.java +351 -0
- data/spec/fixtures/data/sax2/src/org/xml/sax/Attributes.java +257 -0
- data/spec/fixtures/data/sax2/src/org/xml/sax/package.html +297 -0
- data/spec/fixtures/data/sax2r2.jar +0 -0
- data/spec/fixtures/data/text1.txt +4 -0
- data/spec/spec_helper.rb +9 -0
- metadata +222 -0
@@ -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
|
data/lib/kenai_tools.rb
ADDED
@@ -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
|
Binary file
|
Binary file
|