Skip to content

Commit

Permalink
Added list-files API
Browse files Browse the repository at this point in the history
  • Loading branch information
martinheidegger committed Mar 17, 2016
1 parent b9e9585 commit e9f418a
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 92 deletions.
94 changes: 62 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
var hq = require('hyperquest')
var toArray = require('stream-to-array')
var cheerio = require('cheerio')
function ls (slug, branch, callback) {
setImmediate(function () {
callback(null, [])
})
var GITHUB = 'https://github.com'
function ls (path, callback) {
var parts = /((https\:\/\/)?(github.com\/))?([^\/]+\/[^\/]+)(\/tree\/([^\/]+)\/?(.*))?\/?$/.exec(path)
if (!parts) {
throw new Error('The provided path "' + path + '" is of the wrong format. It needs to look like:\n' +
'((http://)github.com/){organization|user}/{repo}((/tree/{branch})/{folder}/?)\n' +
'\n' +
'Valid examples:\n' +
'http://github.com/martinheidegger/github-ls/tree/test/\n' +
'nodeschool/admin/tree/master' +
'nodeschool/admin // will assume that you want tree/master/!' +
'github.com/martinheidegger/github-ls/tree/master/test')
}
var slug = parts[4]
var branch = parts[6] || 'master'
var folder = parts[7]
var prefix
if (folder) {
folder += '/'
} else {
folder = ''
}
if (branch === 'master') {
branch = ''
prefix = 'trunk/'
} else {
branch = branch + '/'
prefix = 'branches/' + branch
}
ls.paths(GITHUB, slug, '', ls.errorCatch(callback, function (list) {
var rev
if (branch === '') {
rev = list[0].rev
} else {
rev = list[1].rev
}
ls.paths(GITHUB, slug, rev + branch + folder, ls.errorCatch(callback, function (list) {
callback(null, list.map(function (entry) {
return entry.path.substr(prefix.length)
}))
}))
}))
}
ls.revision = function (base, slug, callback) {
ls.request([base, slug, '!svn/vcc/default'].join('/'), {
Expand All @@ -13,13 +51,10 @@ ls.revision = function (base, slug, callback) {
Depth: '0'
},
body: '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><checked-in xmlns="DAV:"/></prop></propfind>'
}, function (err, res, $, body) {
if (err) {
return callback(err, res)
}
}, ls.errorCatch(callback, function (res, $, body) {
var nextHref = $.xml($('lp1\\:checked-in D\\:href')[0].children)
callback(null, nextHref.substr(slug.length + 2))
})
}))
}
ls.paths = function (base, slug, rev, callback) {
ls.request([base, slug, rev].join('/'), {
Expand All @@ -28,10 +63,7 @@ ls.paths = function (base, slug, rev, callback) {
Depth: '1'
},
body: '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><creator-displayname xmlns="DAV:"/><creationdate xmlns="DAV:"/><version-name xmlns="DAV:"/><deadprop-count xmlns="http://subversion.tigris.org/xmlns/dav/"/><getcontentlength xmlns="DAV:"/><resourcetype xmlns="DAV:"/></prop></propfind>'
}, function (err, res, $, body) {
if (err) {
return callback(err, res)
}
}, ls.errorCatch(callback, function (res, $, body) {
if (res.statusCode !== 207) {
return callback(new Error('bad-http-status'), res)
}
Expand All @@ -46,12 +78,19 @@ ls.paths = function (base, slug, rev, callback) {
})
links.shift()
callback(null, links)
})
}))
}
ls.request = function (url, options, callback) {
if (!options.headers) {
options.headers = {}
ls.errorCatch = function (callback, handler) {
return function (err) {
if (err) {
return callback.apply(null, arguments)
}
var args = Array.prototype.slice.call(arguments)
args.shift()
handler.apply(null, args)
}
}
ls.request = function (url, options, callback) {
options.headers['User-Agent'] = 'SVN/1.7.20 neon/0.29.6'
options.headers['Accept-Encoding'] = 'gzip'
options.headers['Content-Type'] = 'text/xml; charset=utf-8'
Expand All @@ -70,25 +109,16 @@ ls.request = function (url, options, callback) {
delete options.body
}
var info
var stream = hq(url, options, function (err, _info) {
var stream = hq(url, options, ls.errorCatch(callback, function (_info) {
info = _info
if (err) {
return callback(err, info)
}
})
}))
if (body) {
stream.write(body)
stream.end()
}
toArray(stream, function (err, parts) {
if (err) {
return callback(err, info)
}
var buffer = Buffer.concat(parts.map(function (part) {
if (part instanceof Buffer) return part
return new Buffer(part)
}))
callback(err, info, cheerio.load(buffer, {xmlMode: true}), buffer)
})
toArray(stream, ls.errorCatch(callback, function (parts) {
var buffer = Buffer.concat(parts)
callback(null, info, cheerio.load(buffer, {xmlMode: true}), buffer)
}))
}
module.exports = ls
139 changes: 79 additions & 60 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,83 @@ var ls = require('..')
var REPO = 'martinheidegger/github-ls'
var GITHUB = 'https://github.com'
var GITHUB_REPO = GITHUB + '/' + REPO

var testBranchContent = [{
rev: '!svn/bc/6/branches/test/.gitignore',
path: 'branches/test/.gitignore'
}, {
rev: '!svn/bc/6/branches/test/.travis.yml',
path: 'branches/test/.travis.yml'
}, {
rev: '!svn/bc/6/branches/test/index.js',
path: 'branches/test/index.js'
}, {
rev: '!svn/bc/6/branches/test/npm-debug.log',
path: 'branches/test/npm-debug.log'
}, {
rev: '!svn/bc/6/branches/test/package.json',
path: 'branches/test/package.json'
}, {
rev: '!svn/bc/6/branches/test/test/',
path: 'branches/test/test/'
}]
test('invalid path', function (t) {
try {
ls('http://engadget.com')
} catch (e) {
t.notEqual(e, null)
t.end()
return
}
t.fail('no error thrown with wrong path')
t.end()
})
test('list files of master branch', function (t) {
ls(REPO, 'master', function (err, list) {
ls(REPO + '/tree/test/', function (err, list) {
t.equal(err, null)
t.deepEqual(list, testBranchContent.map(function (file) {
return file.path.substr('branches/test/'.length)
}))
t.end()
})
})
test('list files of master branch with github path prefix', function (t) {
ls(GITHUB_REPO + '/tree/test/', function (err, list) {
t.equal(err, null)
t.deepEqual(list, testBranchContent.map(function (file) {
return file.path.substr('branches/test/'.length)
}))
t.end()
})
})
test('list files for a subfolder for a particular branch', function (t) {
ls(REPO + '/tree/test/test', function (err, list) {
t.equal(err, null)
t.deepEqual(list, [ 'test/index.js' ])
t.end()
})
})
test('list files of master by default', function (t) {
ls(REPO, function (err, list) {
t.equal(err, null)
if (!Array.isArray(list)) {
t.fail('List should be an array')
t.fail('List of master is not an array')
}
t.end()
if (list.length === 0) {
t.fail('We expected at least one entry in the list')
}
ls(REPO + '/tree/master', function (err, masterList) {
t.equal(err, null)
t.deepEqual(list, masterList)
t.end()
})
})
})
test('error catching', function (t) {
ls.errorCatch(function (err) {
t.equal(err.message, 'test')
t.end()
})(new Error('test'))
})
test('repo revision', function (t) {
ls.revision(GITHUB, REPO, function (err, rev) {
t.equal(err, null)
Expand Down Expand Up @@ -43,68 +110,20 @@ test('fetching root', function (t) {
t.end()
})
})
test('fetching path for a particular branch', function (t) {
test('fetching path for a particular branch revision', function (t) {
ls.paths(GITHUB, REPO, '!svn/bc/6/branches/', function (err, list) {
t.equal(err, null)
t.deepEqual(list, [{
rev: '!svn/bc/6/branches/test/',
path: 'branches/test/'
}])
rev: '!svn/bc/6/branches/test/',
path: 'branches/test/'
}])
t.end()
})
})

/*
test('handshake request', function (t) {
ls.request(GITHUB_REPO, {
method: 'OPTIONS',
body: '<?xml version="1.0" encoding="utf-8"?><D:options xmlns:D="DAV:"><D:activity-collection-set/></D:options>'
}, function (err, res, $, body) {
test('fetching subfolder for a particular branch', function (t) {
ls.paths(GITHUB, REPO, '!svn/bc/6/branches/test/', function (err, list) {
t.equal(err, null)
t.equal(res.statusCode, 200)
var nextHref = $.xml($('D\\:href')[0].children)
t.equal(nextHref, '/' + REPO + '/!svn/act/')
ls.request(GITHUB_REPO, {
method: 'PROPFIND',
headers: {
Depth: '0'
},
body: '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><version-controlled-configuration xmlns="DAV:"/><resourcetype xmlns="DAV:"/><baseline-relative-path xmlns="http://subversion.tigris.org/xmlns/dav/"/><repository-uuid xmlns="http://subversion.tigris.org/xmlns/dav/"/></prop></propfind>'
}, function (err, res, $, body) {
t.equal(err, null)
t.equal(res.statusCode, 207)
var nextHref = $.xml($('lp1\\:version-controlled-configuration D\\:href')[0].children)
t.equal(nextHref, '/' + REPO + '/!svn/vcc/default')
ls.request(GITHUB_REPO + '/!svn/vcc/default', {
method: 'PROPFIND',
headers: {
Depth: '0'
},
body: '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><checked-in xmlns="DAV:"/></prop></propfind>'
}, function (err, res, $, body) {
t.equal(err, null)
t.equal(res.statusCode, 207)
var nextHref = $.xml($('lp1\\:checked-in D\\:href')[0].children)
ls.request(GITHUB + nextHref, {
method: 'PROPFIND',
headers: {
Depth: '1'
},
body: '<?xml version="1.0" encoding="utf-8"?><propfind xmlns="DAV:"><prop><creator-displayname xmlns="DAV:"/><creationdate xmlns="DAV:"/><version-name xmlns="DAV:"/><deadprop-count xmlns="http://subversion.tigris.org/xmlns/dav/"/><getcontentlength xmlns="DAV:"/><resourcetype xmlns="DAV:"/></prop></propfind>'
}, function (err, res, $, body) {
t.equal(err, null)
t.equal(res.statusCode, 207)
var links = $('D\\:href').toArray().map(function (node) {
return $.xml(node.children)
})
links = links.map(function (link) {
return link.substr(links[0].length)
})
t.deepEqual(links, ['', 'trunk/', 'branches/'])
t.end()
})
})
})
t.deepEqual(list, testBranchContent)
t.end()
})
})
*/

0 comments on commit e9f418a

Please sign in to comment.