From e9f418a5d9fa43bac3f7350399f78008a09a1062 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Thu, 17 Mar 2016 13:39:18 +0900 Subject: [PATCH] Added list-files API --- index.js | 94 ++++++++++++++++++++++------------ test/index.js | 139 ++++++++++++++++++++++++++++---------------------- 2 files changed, 141 insertions(+), 92 deletions(-) diff --git a/index.js b/index.js index 993bbbb..f63229e 100644 --- a/index.js +++ b/index.js @@ -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('/'), { @@ -13,13 +51,10 @@ ls.revision = function (base, slug, callback) { Depth: '0' }, body: '' - }, 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('/'), { @@ -28,10 +63,7 @@ ls.paths = function (base, slug, rev, callback) { Depth: '1' }, body: '' - }, 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) } @@ -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' @@ -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 diff --git a/test/index.js b/test/index.js index 83fe66d..2aa552f 100644 --- a/test/index.js +++ b/test/index.js @@ -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) @@ -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: '' - }, 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: '' - }, 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: '' - }, 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: '' - }, 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() }) }) -*/ \ No newline at end of file