diff --git a/.gitignore b/.gitignore index b512c09..93e7857 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +gitter.tmproj diff --git a/Makefile b/Makefile index b6cf86d..ff5efe9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VOWS=vows/{blob,branch,commit,raw,repo,tree,user}.js +VOWS=vows/{blob,branch,commit,repo,tree,user}.js spec: vows --spec $(VOWS) diff --git a/Readme.md b/Readme.md index d204584..e436946 100644 --- a/Readme.md +++ b/Readme.md @@ -1,9 +1,11 @@ gitter ====== -A GitHub client inspired by [pengwynn/octopussy](/pengwynn/octopussy). +A GitHub client inspired by [pengwynn/octokit](https://github.com/pengwynn/octokit). -v2 API +v3 API + +Works in Node.js and most web browsers. Installation @@ -22,31 +24,31 @@ Usage console.dir(user) }) - gh.repo('samsonjs/gitter', function(err, repo) { + gh.repo('samsonjs', 'gitter', function(err, repo) { if (err) throw err console.log('---- repo: ' + repo.owner + '/' + repo.name + ' ----') console.dir(repo) - }).getWatchers(function(err, repos) { + }).fetchWatchers(function(err, watchers) { if (err) throw err console.log('---- watchers ----') - console.dir(repos) - }).getBranches(function(err, branches) { + console.dir(watchers) + }).fetchBranches(function(err, branches) { if (err) throw err console.log('---- branches: samsonjs/gitter ----') console.dir(branches) - gh.commit(this.repo, branches['master'], function(err, commit) { + gh.commit(this.user, this.repo, branches['master'], function(err, commit) { if (err) throw err console.log('---- samsonjs/gitter/master commit: ' + commit.id + ' ----') console.dir(commit.data()) }) }) -For the full API have a look at the top of [lib/index.js](/samsonjs/gitter/blob/master/lib/index.js). +For the full API have a look at the top of [lib/index.js](https://github.com/samsonjs/gitter/blob/master/lib/index.js). License ======= -Copyright 2010 Sami Samhuri sami.samhuri@gmail.com +Copyright 2010 - 2012 Sami Samhuri sami@samhuri.net -MIT (see included [LICENSE](/samsonjs/gitter/blob/master/LICENSE)) +[MIT License](http://sjs.mit-license.org) diff --git a/lib/index.js b/lib/index.js index 6c6cb9b..5bd2e95 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,645 +2,765 @@ /// http://github.com/samsonjs/gitter /// @_sjs /// -/// Copyright 2010 Sami Samhuri +/// Copyright 2010 - 2012 Sami Samhuri /// MIT License -// TODO: -// - authentication and write APIs - (function() { - var global = this - , isBrowser = 'document' in global - , ie + "use strict" - if (isBrowser) { - ie = (function() { - var undef - , v = 3 - , div = document.createElement('div') - , all = div.getElementsByTagName('i') + var global = (function() { return this || (1, eval)('this') }()) + , isBrowser = 'document' in global + , ie - while ( - div.innerHTML = '', - all[0] - ); + if (isBrowser) { + ie = (function() { + var undef + , v = 3 + , div = document.createElement('div') + , all = div.getElementsByTagName('i') - return v > 4 ? v : undef - }()) - } + while ( + div.innerHTML = '', + all[0] + ); - var inherits - if ('create' in Object) { - // util.inherits from node - inherits = function(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false - } - }) + return v > 4 ? v : undef + }()) + } + + var inherits + if ('create' in Object) { + // util.inherits from node + inherits = function(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false } + }) } - else if ([].__proto__) { - inherits = function(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype.__proto__ = superCtor.prototype - ctor.prototype.constructor = ctor - } + } + else if ([].__proto__) { + inherits = function(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype.__proto__ = superCtor.prototype + ctor.prototype.constructor = ctor } - else { // ie8 - var __hasProp = Object.prototype.hasOwnProperty - inherits = function(child, parent) { - for (var key in parent) { - if (__hasProp.call(parent, key)) child[key] = parent[key] - } - function ctor() { this.constructor = child } - ctor.prototype = parent.prototype - child.prototype = new ctor - child.__super__ = parent.prototype - return child - } + } + else { // ie8 + var __hasProp = Object.prototype.hasOwnProperty + inherits = function(child, parent) { + for (var key in parent) { + if (__hasProp.call(parent, key)) child[key] = parent[key] + } + function ctor() { this.constructor = child } + ctor.prototype = parent.prototype + child.prototype = new ctor + child.__super__ = parent.prototype + return child + } + } + + var api = { + // Blob + blob: function(user, repo, sha, cb) { + return new Blob(user, repo, sha, cb) } - // when running in the browser request is set later - var request = isBrowser ? null : require('request') - , Blob, Branch, Commit, Raw, Repo, Tree, User - , api - - api = { - blob: function(repo, sha, path, cb) { - return new Blob(repo, sha, path, cb) - }, - branch: function(repo, branch, cb) { - return new Branch(repo, branch, cb) - }, - commits: function(repo, branch, cb) { - return new Branch(repo, branch).getCommits(cb) - }, - commit: function(repo, sha, cb) { - return new Commit(repo, sha, cb) - }, - // raw doesn't work with jsonp ... cors? - raw: isBrowser ? undefined : function(repo, sha, cb) { - return new Raw(repo, sha, cb) - }, - repo: function(repo, cb) { - return new Repo(repo, cb) - }, - branches: function(repo, cb) { - return new Repo(repo).getBranches(cb) - }, - collaborators: function(repo, cb) { - return new Repo(repo).getCollaborators(cb) - }, - contributors: function(repo, cb) { - return new Repo(repo).getContributors(cb) - }, - languages: function(repo, cb) { - return new Repo(repo).getLanguages(cb) - }, - network: function(repo, cb) { - return new Repo(repo).getNetwork(cb) - }, - tags: function(repo, cb) { - return new Repo(repo).getTags(cb) - }, - watchers: function(repo, cb) { - return new Repo(repo).getWatchers(cb) - }, - tree: function(repo, sha, cb) { - return new Tree(repo, sha, cb) - }, - blobs: function(repo, sha, cb) { - return new Tree(repo, sha).getBlobs(cb) - }, - user: function(user, cb) { - return new User(user, cb) - }, - followers: function(user, cb) { - return new User(user).getFollowers(cb) - }, - following: function(user, cb) { - return new User(user).getFollowing(cb) - }, - repos: function(user, cb) { - return new User(user).getRepos(cb) - }, - watched: function(user, cb) { - return new User(user).getWatched(cb) - } - } - if (isBrowser) global.GITR = api - else module.exports = api - - if (isBrowser) shim() - - // Define resources // - - Blob = createResource('blob/show/:repo/:tree/:path', { - has: [ ['commits', 'commits/list/:repo/:tree/:path'] ] - }) - Branch = createResource('commits/show/:repo/:branch', { - has: [ ['commits', 'commits/list/:repo/:branch'] ] - }) - Commit = createResource('commits/show/:repo/:sha') - // raw doesn't work with jsonp ... cors? - if (!isBrowser) Raw = createResource('blob/show/:repo/:sha') - Repo = createResource('repos/show/:repo', { - has: [ 'branches' - , 'collaborators' - , 'contributors' - , 'languages' - , 'network' - , 'tags' - , 'watchers' - ] - }) - Tree = createResource('tree/show/:repo/:sha', { - has: [ ['blobs', 'blob/all/:repo/:sha'] - , ['fullBlobs', 'blob/full/:repo/:sha'] - , ['fullTree', 'tree/full/:repo/:sha'] - ] - }) - Tree.prototype._processData = function(data) { - var result = Resource.prototype._processData.call(this, data) - this.blobs = this.data() - return result + // Branch + , branch: function(user, repo, name, cb) { + return new Branch(user, repo, name, cb) } - User = createResource('user/show/:user', { - has: [ 'followers' - , 'following' - , ['repos', 'repos/show/:user'] - , ['watched', 'repos/watched/:user'] - ] - }) + // Commit + , commit: function(user, repo, sha, cb) { + return new Commit(user, repo, sha, cb) + } - // Construct a new github resource. - // - // options: - // - params: params for constructor (optional, inferred from route if missing) - // - has: list of related resources, accessors are created for each item - // - // The members of the `has` list are arrays of the form [name, route, unpack]. - // The first member, name, is used to create an accessor (e.g. getName), and - // is required. - // - // Route and unpack are optional. Route specifies the endpoint for this - // resource and defaults to the name appended to the main resource's endpoint. - // - // Unpack is a function that extracts the desired value from the object fetched - // for this resource. It defaults to a function that picks out the only property - // from an object, or returns the entire walue if not an object or it contains - // more than one property. - // - // When passing only the name you may pass it directly without wrapping it in - // an array. - function createResource(route, options) { - if (!route) throw new Error('route is required') - options = options || {} + // Download + , download: function(user, repo, id, cb) { + return new Download(user, repo, id, cb) + } - var resource = function() { Resource.apply(this, [].slice.call(arguments)) } - inherits(resource, Resource) + // Issue + , issue: function(user, repo, id, cb) { + return new Issue(user, repo, id, cb) + } - resource.prototype._route = route - resource.prototype._params = options.params || paramsFromRoute(route) + // Organization + , org: function(name, cb) { + return new Org(name, cb) + } + , members: function(name, cb) { + return new Org(name).fetchMembers(cb) + } - resource.has = function(prop, route, unpack) { - unpack = unpack || onlyProp - var dataProp = '_' + prop - , fn = 'get' + titleCaseFirst(prop) - , processData = function(d) { - if (ie < 9) { - this[dataProp] = camelize(unpack(d)) - } else { - getter(this, dataProp, function() { return camelize(unpack(d))}) - } - } - , result = function(resource) { return resource[dataProp] } - resource.prototype[fn] = function(cb, force) { - return this._fetch({ prop: dataProp - , route: route || this._route + '/' + prop - , processData: processData.bind(this) - , result: result - }, cb.bind(this), force) - } - return resource - } - if (options.has) options.has.forEach(function(args) { - resource.has.apply(resource, Array.isArray(args) ? args : [args]) + // Repo + , repo: function(user, repo, cb) { + return new Repo(user, repo, cb) + } + , branches: function(user, repo, cb) { + return new Repo(user, repo).fetchBranches(cb) + } + , collaborators: function(user, repo, cb) { + return new Repo(user, repo).fetchCollaborators(cb) + } + , contributors: function(user, repo, cb) { + return new Repo(user, repo).fetchContributors(cb) + } + , downloads: function(user, repo, cb) { + return new Repo(user, repo).fetchDownloads(cb) + } + , forks: function(user, repo, cb) { + return new Repo(user, repo).fetchForks(cb) + } + , issues: function(user, repo, cb) { + return new Repo(user, repo).fetchIssues(cb) + } + , languages: function(user, repo, cb) { + return new Repo(user, repo).fetchLanguages(cb) + } + , tags: function(user, repo, cb) { + return new Repo(user, repo).fetchTags(cb) + } + , watchers: function(user, repo, cb) { + return new Repo(user, repo).fetchWatchers(cb) + } + + , ref: function(user, repo, name, cb) { + return new Ref(user, repo, name, cb) + } + + // Tag + , tag: function(user, repo, name, cb) { + return new Tag(user, repo, name, cb) + } + + // Tree + , tree: function(user, repo, sha, cb) { + return new Tree(user, repo, sha, cb) + } + + // User + , user: function(login, cb) { + return new User(login, cb) + } + , followers: function(login, cb) { + return new User(login).fetchFollowers(cb) + } + , following: function(login, cb) { + return new User(login).fetchFollowing(cb) + } + , repos: function(login, cb) { + return new User(login).fetchRepos(cb) + } + , watched: function(login, cb) { + return new User(login).fetchWatched(cb) + } + + // Why not, expose the resources directly as well. + , Blob: Blob + , Branch: Branch + , Commit: Commit + , Download: Download + , Issue: Issue + , Org: Org + , Ref: Ref + , Repo: Repo + , Tree: Tree + , User: User + } + + // when running in the browser request is set later, in shim() + var request + + if (isBrowser) { + shim() + global.GITR = api + } + else { + var https = require('https') + request = function(options, cb) { + var req = https.request(options, function(response) { + var bodyParts = [] + response.on('data', function(b) { bodyParts.push(b) }) + response.on('end', function() { + var body = bodyParts.join('') + if (response.statusCode === 200) { + cb(null, body, response) + } + else { + console.dir(options, response, body) + var err = new Error('http error') + err.statusCode = response.statusCode + err.body = body + cb(err) + } }) - - return resource + }) + req.end() + req.on('error', function(err) { cb(err) }) } + module.exports = api + } - // Assigns the given resource args to the new instance. Sets the path to the - // endpoint for main resource data. - // - // If the optional last arg is a function main data is fetched immediately, - // and that function is used as the callback. - // - // If the optional last arg is an object then it is set as the main resource - // data. - function Resource(/* ...args, opt: data or callback */) { - var args = [].slice.call(arguments) - , last = args[args.length - 1] - // assign params from args - this._params.forEach(function(param, i) { - this[param] = args[i] - }.bind(this)) + // Generic Resource // + // + // Used as the prototype by createResource. Provides + // methods for fetching the resource and related + // sub-resources. + function Resource() {} - // set the resource path - this.urlPath = this.resolve(this._route) - - if (typeof last === 'function') this.fetch(last) - else if (typeof last === 'object') this.data(last) + // Fetch data for this resource and pass it to the + // callback after mixing the data into the object. + // Data is also available via the `data` property. + Resource.prototype.fetch = function(cb) { + if (this.data) { + cb(null, this.data) } - - // Set or get main data for this resource, or fetch - // a specific property from the data. - // - // When the data param is empty cached data is returned. - // - // When the data param is a string the property by that name - // is looked up in the cached data. - // - // Otherwise cached data is set to the data param. - Resource.prototype.data = function(data) { - if (!data) return this._data - if (typeof data === 'string' && typeof this._data === 'object') return this._data[data] - - getter(this, '_data', function() { return data }, {configurable: true}) - return this + else { + var self = this + fetch(this.path, function(err, data) { + // console.log('FETCH', self.path, err, data) + if (err) { + // console.log(err) + } + else { + self.data = data + mixin(self, data) + } + if (typeof cb === 'function') { + cb.call(self, err, data) + } + }) } + return this + } - // Fetch the main data for this resource. - // - // cb: callback(err, data) - // force: if true load data from github, bypassing the local cache - Resource.prototype.fetch = function(cb, force) { - return this._fetch({ prop: '_data' - , route: this.urlPath - , processData: this._processData.bind(this) - , result: function(resource) { return resource } - }, cb.bind(this), force) + Resource.prototype.fetchSubResource = function(thing, cb) { + if (this['_' + thing]) { + cb(null, this['_' + thing]) } - - // 'repos/show/:user/:repo/branches' -> 'repos/show/samsonjs/gitter - Resource.prototype.resolve = function(route) { // based on crock's supplant - if (route.indexOf(':') < 0) return route - return route.replace(/:(\w+)\b/g, function (s, prop) { - var val = this[prop] - if (typeof val !== 'string' && typeof val !== 'number') - throw new Error('no suitable property named "' + prop + '" (found ' + val + ')') - return val - }.bind(this)) + else { + var self = this + fetch(this.path + '/' + thing, function(err, data) { + // console.log('FETCH SUBRESOURCE', self.path, thing, err, data) + if (err) { + // console.log(self.path, err) + } + else { + self['_' + thing] = data + } + if (typeof cb === 'function') { + cb.call(self, err, data) + } + }) } + return this + } - // Fetch arbitrary data from github. - // - // options: - // - prop: name of data cache property - // - route: route to github endpoint (can contain resource params) - // - processData: function that processes fetched data - // - result: function to obtain the result passed to the callback - // cb: callback(err, data) - // force: if true load data from github, bypassing the local cache - Resource.prototype._fetch = function(options, cb, force) { - if (!force && this[options.prop]) { - cb(null, options.result(this)) - return this + var __slice = Array.prototype.slice + + // Create a resource w/ Resource as the prototype. + // + // spec: an object with the following properties: + // + // - constructor: a constructor function + // - has: a list of resources belonging to this resource + // + // Typically the constructor accepts one or more arguments specifying + // the name or pieces of info identifying the specific resource and + // used to build the URL to fetch it. It also accepts an optional + // callback as the last parameter. + // + // The constructor must set the `path` property which is used to + // fetch the resource. + // + // If a callback is provided then the resource is immediately + // fetched and the callback is threaded through to the `fetch` + // method. The callback function has the signature + // `function(err, data)`. + // + // The `has` list specifies sub-resources, e.g. a user has repos, + // followers, etc. An organization has members. + // + // Each related sub-resource gets a method named appropriately, + // e.g. the User resource has followers so User objects have a + // `fetchFollowers` method. + function createResource(spec) { + var subResources = spec.has ? __slice.call(spec.has) : null + , resource = function(/* ..., cb */) { + var args = __slice.call(arguments) + , lastArgIsCallback = typeof args[args.length - 1] === 'function' + , cb = lastArgIsCallback ? args.pop() : null + , result = spec.constructor.apply(this, args) + + if (typeof cb === 'function') { + this.fetch(cb) + } + + return result } - // Interpolate resource params - var path = this.resolve(options.route) + inherits(resource, Resource) - // Make the request - return this._get(path, function(err, data) { - if (err) { - cb(err) - return + if (subResources) { + subResources.forEach(function(thing) { + var fnName = 'fetch' + toTitleCase(thing) + resource.prototype[fnName] = function(cb) { + return this.fetchSubResource(thing, cb) + } + }) + } + + return resource + } + + + // Define Resources // + + var Blob = createResource({ + constructor: function(user, repo, sha) { + this.user = user + this.repo = repo + this.sha = sha + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/blobs/' + sha + } + }) + + var Branch = createResource({ + constructor: function (user, repo, name) { + this.user = user + this.repo = repo + this.name = name + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/heads/' + name + } + }) + + var Commit = createResource({ + constructor: function Commit(user, repo, sha) { + this.user = user + this.repo = repo + this.sha = sha + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/commits/' + sha + } + }) + + var Download = createResource({ + constructor: function(user, repo, id) { + this.user = user + this.repo = repo + this.id = id + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/downloads/' + id + } + }) + + var Issue = createResource({ + constructor: function(user, repo, id) { + this.user = user + this.repo = repo + this.id = id + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/issues/' + id + } + }) + + var Org = createResource({ + constructor: function(name) { + this.name = name + this.path = '/orgs/' + encodeURIComponent(nam) + } + + , has: 'members repos'.split(' ') + }) + + var Ref = createResource({ + constructor: function (user, repo, name) { + this.user = user + this.repo = repo + this.name = name + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/' + name + } + }) + + var Repo = createResource({ + constructor: function(user, repo) { + this.user = user + this.repo = repo + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + } + + , has: ('branches collaborators contributors downloads' + + ' forks languages tags teams watchers').split(' ') + }) + + var Tag = createResource({ + constructor: function (user, repo, name) { + this.user = user + this.repo = repo + this.name = name + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/refs/tags/' + name + } + }) + + var Tree = createResource({ + constructor: function(user, repo, sha) { + this.user = user + this.repo = repo + this.sha = sha + this.path = '/repos/' + [user, repo].map(encodeURIComponent).join('/') + '/git/trees/' + sha + } + }) + + var User = createResource({ + constructor: function(login) { + // Allow creating a user from an object returned by the API + if (login.login) { + login = login.login + } + this.login = login + this.path = '/users/' + encodeURIComponent(login) + } + + , has: 'followers following repos watched'.split(' ') + }) + + + // Fetch data from github. JSON is parsed and keys are camelized. + // + // path: the path to the resource + // cb: callback(err, data) + function fetch(path, cb) { + request({ host: 'api.github.com', path: path }, function(err, body, response) { + // JSONP requests in the browser return the object directly + var data = body + + // Requests in Node return json text, try to parse it + if (response && /json/i.exec(response.headers['content-type'])) { + try { + data = JSON.parse(body) + } + catch (e) { + err = e + data = null + } + } + + cb(err, camelize(data)) + }) + } + + // created_at => createdAt + function camel(s) { + return s.replace(/_(.)/g, function(_, l) { return l.toUpperCase() }) + } + + // camelize all keys of an object, or all objects in an array + function camelize(obj) { + if (!obj || typeof obj === 'string') return obj + if (Array.isArray(obj)) return obj.map(camelize) + if (typeof obj === 'object') { + return Object.keys(obj).reduce(function(camelizedObj, k) { + camelizedObj[camel(k)] = camelize(obj[k]) + return camelizedObj + }, {}) + } + return obj + } + + function toTitleCase(s) { + return s.charAt(0).toUpperCase() + s.slice(1) + } + + function mixin(a, b) { + for (var k in b) { + if (b.hasOwnProperty(k)) a[k] = b[k] + } + } + + + // Browser Utilities // + + function shim() { + shimBind() + shimES5() + shimRequest() + } + + function shimBind() { + // bind from Prototype + if (!Function.prototype.bind) { + (function(){ + function update(array, args) { + var arrayLength = array.length, length = args.length + while (length--) array[arrayLength + length] = args[length] + return array + } + function merge(array, args) { + array = __slice.call(array, 0) + return update(array, args) + } + Function.prototype.bind = function(context) { + if (arguments.length < 2 && typeof arguments[0] === 'undefined') return this + var __method = this, args = __slice.call(arguments, 1) + return function() { + var a = merge(args, arguments) + return __method.apply(context, a) + } + } + }()) + } + } + + // a few functions from Kris Kowal's es5-shim + // https://github.com/kriskowal/es5-shim + function shimES5() { + var has = Object.prototype.hasOwnProperty + + // ES5 15.2.3.6 + if (!Object.defineProperty || ie === 8) { // ie8 + Object.defineProperty = function(object, property, descriptor) { + if (typeof descriptor == "object" && object.__defineGetter__) { + if (has.call(descriptor, "value")) { + if (!object.__lookupGetter__(property) && !object.__lookupSetter__(property)) { + // data property defined and no pre-existing accessors + object[property] = descriptor.value } - options.processData(data) - cb(null, options.result(this)) - }.bind(this)) + if (has.call(descriptor, "get") || has.call(descriptor, "set")) { + // descriptor has a value property but accessor already exists + throw new TypeError("Object doesn't support this action") + } + } + // fail silently if "writable", "enumerable", or "configurable" + // are requested but not supported + else if (typeof descriptor.get == "function") { + object.__defineGetter__(property, descriptor.get) + } + if (typeof descriptor.set == "function") { + object.__defineSetter__(property, descriptor.set) + } + } + return object + } } - // Fetch data from github. JSON responses are parsed. + // ES5 15.2.3.14 + // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation + if (!Object.keys) { // ie8 + (function() { + var hasDontEnumBug = true, + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length + + for (var key in {"toString": null}) { + hasDontEnumBug = false + } + + Object.keys = function (object) { + + if ( + typeof object !== "object" && typeof object !== "function" + || object === null + ) + throw new TypeError("Object.keys called on a non-object") + + var keys = [] + for (var name in object) { + if (has.call(object, name)) { + keys.push(name) + } + } + + if (hasDontEnumBug) { + for (var i = 0, ii = dontEnumsLength; i < ii; i++) { + var dontEnum = dontEnums[i] + if (has.call(object, dontEnum)) { + keys.push(dontEnum) + } + } + } + + return keys + } + }()) + } // Object.keys + // - // path: github endpoint - // cb: callback(err, data) - Resource.prototype._get = function(path, cb) { - request({uri: 'http://github.com/api/v2/json/' + path}, function(err, response, body) { - if (err) - cb(err) - else if (isBrowser) - cb(null, body) // body is an object - else if (response.statusCode !== 200) - cb(new Error('failed to fetch ' + path + ': ' + response.statusCode)) - else if (response.headers['content-type'].match(/json/)) - cb(null, JSON.parse(body)) - else - cb(null, body) - }) - return this + // Array + // ===== + // + + // ES5 15.4.3.2 + if (!Array.isArray) { + Array.isArray = function(obj) { + return Object.prototype.toString.call(obj) == "[object Array]" + } } - // Descendents of Resource can overwrite _processData and _unpack to process - // the main resource data differently. - - Resource.prototype._processData = function(data) { - return this.data(camelize(this._unpack(data))) - } - Resource.prototype._unpack = onlyProp - - - // Utilities // - - function camel(s) { // created_at => createdAt - return s.replace(/_(.)/g, function(_, l) { return l.toUpperCase() }) - } - function camelize(obj) { // camelize all keys of an object, or all objects in an array - if (!obj || typeof obj === 'string') return obj - if (Array.isArray(obj)) return obj.map(camelize) - return Object.keys(obj).reduce(function(newObj, k) { - newObj[camel(k)] = obj[k] - return newObj - }, {}) + // ES5 15.4.4.18 + if (!Array.prototype.forEach) { // ie8 + Array.prototype.forEach = function(block, thisObject) { + var len = this.length >>> 0 + for (var i = 0; i < len; i++) { + if (i in this) { + block.call(thisObject, this[i], i, this) + } + } + } } - function getter(obj, prop, fn, opts) { // minor convenience - opts = opts || {} - opts.get = fn - Object.defineProperty(obj, prop, opts) + // ES5 15.4.4.19 + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map + if (!Array.prototype.map) { // ie8 + Array.prototype.map = function(fun /*, thisp*/) { + var len = this.length >>> 0 + if (typeof fun != "function") { + throw new TypeError() + } + + var res = new Array(len) + var thisp = arguments[1] + for (var i = 0; i < len; i++) { + if (i in this) { + res[i] = fun.call(thisp, this[i], i, this) + } + } + + return res + } } - // get an only property, if any - function onlyProp(obj) { - if (obj && typeof obj === 'object') { - var keys = Object.keys(obj) - if (keys.length === 1) return obj[keys[0]] + // ES5 15.4.4.20 + if (!Array.prototype.filter) { // ie8 + Array.prototype.filter = function (block /*, thisp */) { + var values = [] + , thisp = arguments[1] + for (var i = 0; i < this.length; i++) { + if (block.call(thisp, this[i])) { + values.push(this[i]) + } } - return obj + return values + } } - // 'repos/show/:user/:repo/branches' -> ['user', 'repo'] - function paramsFromRoute(route) { - if (route.indexOf(':') === -1) return [] - return route.split('/') - .filter(function(s) { return s.charAt(0) === ':' }) - .map(function(s) { return s.slice(1) }) - } - - function titleCaseFirst(s) { return s.charAt(0).toUpperCase() + s.slice(1) } - - - // Browser Utilities // - - function shim() { - // bind from Prototype - if (!Function.prototype.bind) { - (function(){ - function update(array, args) { - var arrayLength = array.length, length = args.length - while (length--) array[arrayLength + length] = args[length] - return array - } - function merge(array, args) { - array = [].slice.call(array, 0) - return update(array, args) - } - Function.prototype.bind = function(context) { - if (arguments.length < 2 && typeof arguments[0] === 'undefined') return this - var __method = this, args = [].slice.call(arguments, 1) - return function() { - var a = merge(args, arguments) - return __method.apply(context, a) - } - } - }()) + // ES5 15.4.4.21 + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce + if (!Array.prototype.reduce) { // ie8 + Array.prototype.reduce = function(fun /*, initial*/) { + var len = this.length >>> 0 + if (typeof fun != "function") { + throw new TypeError() } - // a few functions from Kris Kowal's es5-shim - // https://github.com/kriskowal/es5-shim - - var has = Object.prototype.hasOwnProperty; - - // ES5 15.2.3.6 - if (!Object.defineProperty || ie === 8) { // ie8 - Object.defineProperty = function(object, property, descriptor) { - if (typeof descriptor == "object" && object.__defineGetter__) { - if (has.call(descriptor, "value")) { - if (!object.__lookupGetter__(property) && !object.__lookupSetter__(property)) - // data property defined and no pre-existing accessors - object[property] = descriptor.value; - if (has.call(descriptor, "get") || has.call(descriptor, "set")) - // descriptor has a value property but accessor already exists - throw new TypeError("Object doesn't support this action"); - } - // fail silently if "writable", "enumerable", or "configurable" - // are requested but not supported - else if (typeof descriptor.get == "function") - object.__defineGetter__(property, descriptor.get); - if (typeof descriptor.set == "function") - object.__defineSetter__(property, descriptor.set); - } - return object; - }; + // no value to return if no initial value and an empty array + if (len == 0 && arguments.length == 1) { + throw new TypeError() } - // ES5 15.2.3.14 - // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation - if (!Object.keys) { // ie8 - (function() { - var hasDontEnumBug = true, - dontEnums = [ - 'toString', - 'toLocaleString', - 'valueOf', - 'hasOwnProperty', - 'isPrototypeOf', - 'propertyIsEnumerable', - 'constructor' - ], - dontEnumsLength = dontEnums.length; - - for (var key in {"toString": null}) - hasDontEnumBug = false; - - Object.keys = function (object) { - - if ( - typeof object !== "object" && typeof object !== "function" - || object === null - ) - throw new TypeError("Object.keys called on a non-object"); - - var keys = []; - for (var name in object) { - if (has.call(object, name)) { - keys.push(name); - } - } - - if (hasDontEnumBug) { - for (var i = 0, ii = dontEnumsLength; i < ii; i++) { - var dontEnum = dontEnums[i]; - if (has.call(object, dontEnum)) { - keys.push(dontEnum); - } - } - } - - return keys; - }; - }()) - } - - // - // Array - // ===== - // - - // ES5 15.4.3.2 - if (!Array.isArray) { - Array.isArray = function(obj) { - return Object.prototype.toString.call(obj) == "[object Array]"; - }; - } - - // ES5 15.4.4.18 - if (!Array.prototype.forEach) { // ie8 - Array.prototype.forEach = function(block, thisObject) { - var len = this.length >>> 0; - for (var i = 0; i < len; i++) { - if (i in this) { - block.call(thisObject, this[i], i, this); - } - } - }; - } - - // ES5 15.4.4.19 - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map - if (!Array.prototype.map) { // ie8 - Array.prototype.map = function(fun /*, thisp*/) { - var len = this.length >>> 0; - if (typeof fun != "function") - throw new TypeError(); - - var res = new Array(len); - var thisp = arguments[1]; - for (var i = 0; i < len; i++) { - if (i in this) - res[i] = fun.call(thisp, this[i], i, this); - } - - return res; - }; - } - - // ES5 15.4.4.20 - if (!Array.prototype.filter) { // ie8 - Array.prototype.filter = function (block /*, thisp */) { - var values = []; - var thisp = arguments[1]; - for (var i = 0; i < this.length; i++) - if (block.call(thisp, this[i])) - values.push(this[i]); - return values; - }; - } - - // ES5 15.4.4.21 - // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce - if (!Array.prototype.reduce) { // ie8 - Array.prototype.reduce = function(fun /*, initial*/) { - var len = this.length >>> 0; - if (typeof fun != "function") - throw new TypeError(); - - // no value to return if no initial value and an empty array - if (len == 0 && arguments.length == 1) - throw new TypeError(); - - var i = 0; - if (arguments.length >= 2) { - var rv = arguments[1]; - } else { - do { - if (i in this) { - rv = this[i++]; - break; - } - - // if array contains no values, no initial value to return - if (++i >= len) - throw new TypeError(); - } while (true); - } - - for (; i < len; i++) { - if (i in this) - rv = fun.call(null, rv, this[i], i, this); - } - - return rv; - }; - } - - var load, _jsonpCounter = 1 - request = function(options, cb) { // jsonp request, quacks like mikeal's request module - var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++ - , url = options.uri + '?callback=GITR.' + jsonpCallbackName - GITR[jsonpCallbackName] = function(obj) { - cb(null, null, obj) - setTimeout(function() { delete GITR[jsonpCallbackName] }, 0) + var i = 0 + if (arguments.length >= 2) { + var rv = arguments[1] + } else { + do { + if (i in this) { + rv = this[i++] + break } - load(url) - } - // bootstrap loader from LABjs - load = function(url) { - var oDOC = document - , handler - , head = oDOC.head || oDOC.getElementsByTagName("head") - - // loading code borrowed directly from LABjs itself - // (now removes script elem when done and nullifies its reference --sjs) - setTimeout(function () { - if ("item" in head) { // check if ref is still a live node list - if (!head[0]) { // append_to node not yet ready - setTimeout(arguments.callee, 25) - return - } - head = head[0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists - } - var scriptElem = oDOC.createElement("script"), - scriptdone = false - scriptElem.onload = scriptElem.onreadystatechange = function () { - if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) { - return false - } - scriptElem.onload = scriptElem.onreadystatechange = null - scriptElem.parentNode.removeChild(scriptElem) - scriptElem = null - scriptdone = true - }; - scriptElem.src = url - head.insertBefore(scriptElem, head.firstChild) - }, 0) - - // required: shim for FF <= 3.5 not having document.readyState - if (oDOC.readyState == null && oDOC.addEventListener) { - oDOC.readyState = "loading" - oDOC.addEventListener("DOMContentLoaded", function handler() { - oDOC.removeEventListener("DOMContentLoaded", handler, false) - oDOC.readyState = "complete" - }, false) + // if array contains no values, no initial value to return + if (++i >= len) { + throw new TypeError() } + } while (true) } + + for (; i < len; i++) { + if (i in this) { + rv = fun.call(null, rv, this[i], i, this) + } + } + + return rv + } + } // Array.prototype.reduce + } // function shimES5() + + // jsonp request, quacks like node's http.request + function shimRequest() { + var load, _jsonpCounter = 1 + + // request is declared earlier + request = function(options, cb) { + var jsonpCallbackName = '_jsonpCallback' + _jsonpCounter++ + , url = 'https://' + options.host + options.path + '?callback=GITR.' + jsonpCallbackName + GITR[jsonpCallbackName] = function(response) { + if (response.meta.status >= 200 && response.meta.status < 300) { + cb(null, response.data) + } + else { + var err = new Error('http error') + err.statusCode = response.meta.status + err.response = response + cb(err) + } + setTimeout(function() { delete GITR[jsonpCallbackName] }, 0) + } + load(url) } + + // bootstrap loader from LABjs (load is declared earlier) + load = function(url) { + var oDOC = document + , handler + , head = oDOC.head || oDOC.getElementsByTagName("head") + + // loading code borrowed directly from LABjs itself + // (now removes script elem when done and nullifies its reference --sjs) + setTimeout(function () { + if ("item" in head) { // check if ref is still a live node list + if (!head[0]) { // append_to node not yet ready + setTimeout(arguments.callee, 25) + return + } + head = head[0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists + } + var scriptElem = oDOC.createElement("script") + , scriptdone = false + + scriptElem.onload = scriptElem.onreadystatechange = function () { + if ((scriptElem.readyState && scriptElem.readyState !== "complete" && scriptElem.readyState !== "loaded") || scriptdone) { + return false + } + scriptElem.onload = scriptElem.onreadystatechange = null + scriptElem.parentNode.removeChild(scriptElem) + scriptElem = null + scriptdone = true + } + scriptElem.src = url + head.insertBefore(scriptElem, head.firstChild) + }, 0) // setTimeout + + // required: shim for FF <= 3.5 not having document.readyState + if (oDOC.readyState == null && oDOC.addEventListener) { + oDOC.readyState = "loading" + oDOC.addEventListener("DOMContentLoaded", function handler() { + oDOC.removeEventListener("DOMContentLoaded", handler, false) + oDOC.readyState = "complete" + }, false) + } + + } // function load(url) + + } // function shimRequest() + }()) diff --git a/package.json b/package.json index 2d00f05..dc0a2c4 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,23 @@ { "name" : "gitter" -, "description" : "GitHub client (API v2)" -, "version" : "0.1.2" +, "description" : "GitHub client (API v3)" +, "version" : "0.2.0" , "homepage" : "http://samhuri.net/proj/gitter" -, "author" : "Sami Samhuri " +, "author" : "Sami Samhuri " , "repository" : { "type" : "git" , "url" : "https://github.com/samsonjs/gitter" } , "bugs" : - { "mail" : "sami.samhuri+gitter@gmail.com" - , "web" : "http://github.com/samsonjs/gitter/issues" + { "mail" : "sami@samhuri.net" + , "url" : "http://github.com/samsonjs/gitter/issues" } , "directories" : { "lib" : "./lib" } , "main" : "./lib/index" -, "engines" : { "node" : ">=0.3.0" } +, "engines" : { "node" : ">=0.6.0" } , "licenses" : [ { "type" : "MIT" - , "url" : "http://github.com/samsonjs/gitter/raw/master/LICENSE" + , "url" : "http://sjs.mit-license.org" } ] -, "dependencies" : { "request" : "0.10.0 - 0.10.999" } -, "devDependencies" : { "vows" : "0.5.0 - 0.5.999" } +, "devDependencies" : { "vows" : "0.6.2" } } diff --git a/vows/blob.js b/vows/blob.js index bc56ac8..571b4de 100644 --- a/vows/blob.js +++ b/vows/blob.js @@ -1,35 +1,19 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') , User = 'samsonjs' , Repo = 'gitter' - , Proj = User + '/' + Repo - , TreeSha = '3363be22e88e50d6dd15f9a4b904bfe41cdd22bc' - , Path = 'lib/index.js' + , Sha = '6c6cb9b3449c17e3ae4eee9061b4081ff33c8c64' vows.describe('Blob').addBatch({ - 'after fetching a blob': { - topic: function() { gh.blob(Proj, TreeSha, Path, this.callback) }, - 'the data object can be accessed with the data() method': function(err, blob) { - assert.ifError(err) - assert.ok(blob) - assert.instanceOf(blob.data(), Object) - }, - 'data is a blob': function(err, blob) { - assert.ifError(err) - assert.ok(h.looksLikeABlob(blob.data())) - }, - }, - 'after fetching commits for a blob': { - topic: function() { gh.blob(Proj, TreeSha, Path).getCommits(this.callback) }, - 'list of commits is available': function(err, commits) { - assert.ifError(err) - assert.ok(commits) - assert.instanceOf(commits, Array) - assert.equal(commits.length, 1) - assert.ok(commits.every(function(c) { return h.looksLikeACommit(c) })) - } - }, -}).export(module) \ No newline at end of file + 'after fetching a blob': { + topic: function() { gh.blob(User, Repo, Sha, this.callback) }, + 'the data can be accessed via the content attribute': function(err, blob) { + assert.ifError(err) + assert.ok(blob) + assert.ok(blob.content) + } + } +}).export(module) diff --git a/vows/branch.js b/vows/branch.js index 4146390..4662ca7 100644 --- a/vows/branch.js +++ b/vows/branch.js @@ -1,37 +1,23 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') , User = 'samsonjs' , Repo = 'gitter' - , Proj = User + '/' + Repo , Branch = 'master' vows.describe('Branch').addBatch({ - 'after fetching a branch': { - topic: function() { gh.branch(Proj, Branch, this.callback) }, - 'the data object can be accessed with the data() method': function(err, branch) { - assert.ifError(err) - assert.ok(branch) - assert.instanceOf(branch.data(), Object) - }, - 'attributes can be accessed with the data() method': function(err, branch) { - assert.ifError(err) - assert.instanceOf(branch.data('author'), Object) - }, - 'expected fields are present': function(err, branch) { - assert.ifError(err) - assert.ok(h.looksLikeABranch(branch.data())) - }, + 'after fetching a branch': { + topic: function() { gh.branch(User, Repo, Branch, this.callback) }, + 'attributes can be accessed': function(err, branch) { + assert.ifError(err) + assert.typeOf(branch.ref, 'string') + assert.instanceOf(branch.object, Object) }, - 'after fetching commits': { - topic: function() { gh.commits(Proj, Branch, this.callback) }, - 'list of commits is available': function(err, commits) { - assert.ifError(err) - assert.ok(commits) - assert.instanceOf(commits, Array) - assert.ok(commits.every(function(c) { return h.looksLikeACommit(c) })) - } - } + 'expected fields are present': function(err, branch) { + assert.ifError(err) + assert.ok(h.looksLikeABranch(branch)) + }, + } }).export(module) diff --git a/vows/commit.js b/vows/commit.js index 480ec17..0593402 100644 --- a/vows/commit.js +++ b/vows/commit.js @@ -1,28 +1,22 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') , User = 'samsonjs' , Repo = 'gitter' - , Proj = User + '/' + Repo , Sha = '3363be22e88e50d6dd15f9a4b904bfe41cdd22bc' vows.describe('Commit').addBatch({ - 'after fetching a commit': { - topic: function() { gh.commit(Proj, Sha, this.callback) }, - 'the data object can be accessed with the data() method': function(err, commit) { - assert.ifError(err) - assert.ok(commit) - assert.instanceOf(commit.data(), Object) - }, - 'attributes can be accessed with the data() method': function(err, commit) { - assert.ifError(err) - assert.instanceOf(commit.data('author'), Object) - }, - 'expected fields are present': function(err, commit) { - assert.ifError(err) - assert.ok(h.looksLikeACommit(commit.data())) - }, - } + 'after fetching a commit': { + topic: function() { gh.commit(User, Repo, Sha, this.callback) }, + 'attributes can be accessed': function(err, commit) { + assert.ifError(err) + assert.instanceOf(commit.author, Object) + }, + 'expected fields are present': function(err, commit) { + assert.ifError(err) + assert.ok(h.looksLikeACommit(commit)) + }, + } }).export(module) diff --git a/vows/helper.js b/vows/helper.js index 3fb7b6f..c35cc31 100644 --- a/vows/helper.js +++ b/vows/helper.js @@ -1,33 +1,56 @@ module.exports = { looksLikeABlob: looksLikeABlob - , looksLikeABranch: looksLikeACommit + , looksLikeAFullBlob: looksLikeAFullBlob + , looksLikeABranch: looksLikeARef + , looksLikeABranchList: looksLikeABranchList , looksLikeACommit: looksLikeACommit + , looksLikeACollaborator: looksLikeAShortUser , looksLikeAContributor: looksLikeAContributor + , looksLikeAFollower: looksLikeAShortUser , looksLikeARepo: looksLikeARepo , looksLikeASha: looksLikeASha , looksLikeATree: looksLikeATree , looksLikeAUser: looksLikeAUser + , looksLikeAWatcher: looksLikeAShortUser } -var BlobKeys = ('mimeType mode name sha size').split(' ') +var BlobKeys = 'mode path sha type url'.split(' ') +var FullBlobKeys = 'content encoding sha size url'.split(' ') -var CommitKeys = ('author authoredDate committedDate committer id ' + - 'message parents tree url').split(' ') +var CommitKeys = 'author committer message parents sha tree url'.split(' ') -var ContributorKeys = ('blog contributions email location login name type').split(' ') +var ContributorKeys = 'avatarUrl contributions gravatarId id login url'.split(' ') + +var RefKeys = 'object ref url'.split(' ') +var BranchListKeys = 'commit name'.split(' ') +var RefObjectKeys = 'sha type url'.split(' ') var RepoKeys = ('createdAt fork forks hasDownloads hasIssues hasWiki ' + 'name openIssues owner private pushedAt url watchers').split(' ') -var UserKeys = ('blog company createdAt email followersCount ' + - 'followingCount id location login name ' + - 'publicRepoCount publicGistCount type').split(' ') +var ShortUserKeys = 'avatarUrl gravatarId id login url'.split(' ') + +var TreeKeys = 'tree sha url'.split(' ') + +var UserKeys = ('blog company createdAt email followers ' + + 'following id location login name ' + + 'publicRepos publicGists type').split(' ') function looksLikeABlob(obj) { return hasKeys(obj, BlobKeys) } +function looksLikeAFullBlob(obj) { return hasKeys(obj, FullBlobKeys) } function looksLikeACommit(obj) { return hasKeys(obj, CommitKeys) } function looksLikeAContributor(obj) { return hasKeys(obj, ContributorKeys) } +function looksLikeARef(obj) { + return hasKeys(obj, RefKeys) && hasKeys(obj.object, RefObjectKeys) +} +function looksLikeABranchList(obj) { + return obj.every(function(branch) { return hasKeys(branch, BranchListKeys) }) +} function looksLikeARepo(obj) { return hasKeys(obj, RepoKeys) } function looksLikeASha(s) { return s && s.length === 40 } -function looksLikeATree(obj) { return obj && obj.every(function(b) { return looksLikeABlob(b) }) } +function looksLikeAShortUser(obj) { return hasKeys(obj, ShortUserKeys) } +function looksLikeATree(obj) { + return hasKeys(obj, TreeKeys) && obj.tree.every(looksLikeABlob) +} function looksLikeAUser(obj) { return hasKeys(obj, UserKeys) } function hasKeys(obj, keys) { diff --git a/vows/raw.js b/vows/raw.js deleted file mode 100644 index c7d5ddf..0000000 --- a/vows/raw.js +++ /dev/null @@ -1,20 +0,0 @@ -var gh = require('../lib') - , vows = require('vows') - , assert = require('assert') - , h = require('./helper') - - , User = 'samsonjs' - , Repo = 'gitter' - , Proj = User + '/' + Repo - , Sha = 'a0a2d307cfe7810ccae0aec2ec6854d079de6511' - -vows.describe('Raw').addBatch({ - 'after fetching a raw blob': { - topic: function() { gh.raw(Proj, Sha, this.callback) }, - 'the data object can be accessed with the data() method': function(err, raw) { - assert.ifError(err) - assert.ok(raw) - assert.equal(typeof raw.data(), 'string') - } - } -}).export(module) diff --git a/vows/repo.js b/vows/repo.js index c586b78..519370a 100644 --- a/vows/repo.js +++ b/vows/repo.js @@ -1,100 +1,96 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') , User = 'samsonjs' , Repo = 'gitter' - , Proj = User + '/' + Repo + , ForkedRepo = 'strftime' + , CollaboratorsRepo = 'mojo.el' vows.describe('Repo').addBatch({ - 'after fetching a repo': { - topic: function() { gh.repo(Proj, this.callback) }, - 'the data object can be accessed with the data() method': function(err, repo) { - assert.ifError(err) - assert.ok(repo) - assert.instanceOf(repo.data(), Object) - }, - 'attributes can be accessed with the data() method': function(err, repo) { - assert.ifError(err) - assert.equal(repo.data('owner'), User) - }, - 'expected fields are present': function(err, repo) { - assert.ifError(err) - assert.ok(h.looksLikeARepo(repo.data())) - }, + 'after fetching a repo': { + topic: function() { gh.repo(User, Repo, this.callback) }, + 'attributes can be accessed': function(err, repo) { + assert.ifError(err) + assert.equal(repo.owner.login, User) }, - 'after fetching branches': { - topic: function() { gh.branches(Proj, this.callback) }, - 'map of branches is available': function(err, branches) { - assert.ifError(err) - assert.ok(branches) - assert.instanceOf(branches, Object) - assert.ok('master' in branches) - }, - 'names and commit ids of branches are available': function(err, branches) { - assert.ifError(err) - assert.ok(Object.keys(branches).every(function(b) { return h.looksLikeASha(branches[b]) })) - } + 'expected fields are present': function(err, repo) { + assert.ifError(err) + assert.ok(h.looksLikeARepo(repo)) }, - 'after fetching collaborators': { - topic: function() { gh.collaborators(Proj, this.callback) }, - 'list of collaborators is available': function(err, collaborators) { - assert.ifError(err) - assert.ok(collaborators && collaborators.length >= 1) - assert.ok(collaborators.indexOf(User) !== -1) - }, - 'names of collaborators are available': function(err, collaborators) { - assert.ifError(err) - assert.ok(collaborators.every(function(c) { return c && c.length >= 1 })) - } + }, + 'after fetching branches': { + topic: function() { gh.branches(User, Repo, this.callback) }, + 'list of branches is available': function(err, branches) { + assert.ifError(err) + assert.ok(branches) + assert.instanceOf(branches, Array) + assert.ok(branches.some(function(branch) { return branch.name === 'master' })) }, - 'after fetching contributors': { - topic: function() { gh.contributors(Proj, this.callback) }, - 'list of contributors is available': function(err, contributors) { - assert.ifError(err) - assert.ok(contributors && contributors.length >= 1) - }, - 'names of contributors are available': function(err, contributors) { - assert.ifError(err) - assert.ok(contributors.every(function(c) { return h.looksLikeAContributor(c) })) - } - }, - 'after fetching languages': { - topic: function() { gh.languages(Proj, this.callback) }, - 'map of languages is available': function(err, languages) { - assert.ifError(err) - assert.ok(languages) - assert.instanceOf(languages, Object) - assert.ok('JavaScript' in languages) - } - }, - 'after fetching network': { - topic: function() { gh.network(Proj, this.callback) }, - 'map of network is available': function(err, network) { - assert.ifError(err) - assert.ok(network && network.length >= 1) - assert.ok(network.every(function(r) { return h.looksLikeARepo(r) })) - } - }, - 'after fetching tags': { - topic: function() { gh.tags(Proj, this.callback) }, - 'map of tags is available': function(err, tags) { - assert.ifError(err) - assert.ok(tags) - assert.instanceOf(tags, Object) - } - }, - 'after fetching watchers': { - topic: function() { gh.watchers(Proj, this.callback) }, - 'list of watchers is available': function(err, watchers) { - assert.ifError(err) - assert.ok(watchers && watchers.length >= 1) - assert.ok(watchers.indexOf(User) !== -1) - }, - 'names of watchers are available': function(err, watchers) { - assert.ifError(err) - assert.ok(watchers.every(function(w) { return w && w.length >= 1 })) - } + 'names and commit ids of branches are available': function(err, branches) { + assert.ifError(err) + assert.ok(h.looksLikeABranchList(branches)) } + }, + 'after fetching collaborators': { + topic: function() { gh.collaborators(User, CollaboratorsRepo, this.callback) }, + 'list of collaborators is available': function(err, collaborators) { + assert.ifError(err) + assert.ok(collaborators && collaborators.length >= 1) + assert.ok(collaborators.some(function(c) { return c.login === User })) + }, + 'names of collaborators are available': function(err, collaborators) { + assert.ifError(err) + assert.ok(collaborators.every(function(c) { return h.looksLikeACollaborator(c) })) + } + }, + 'after fetching contributors': { + topic: function() { gh.contributors(User, Repo, this.callback) }, + 'list of contributors is available': function(err, contributors) { + assert.ifError(err) + assert.ok(contributors && contributors.length >= 1) + }, + 'names of contributors are available': function(err, contributors) { + assert.ifError(err) + assert.ok(contributors.every(function(c) { return h.looksLikeAContributor(c) })) + } + }, + 'after fetching languages': { + topic: function() { gh.languages(User, Repo, this.callback) }, + 'map of languages is available': function(err, languages) { + assert.ifError(err) + assert.ok(languages) + assert.instanceOf(languages, Object) + assert.ok('JavaScript' in languages) + } + }, + 'after fetching fork': { + topic: function() { gh.forks(User, ForkedRepo, this.callback) }, + 'list of forks is available': function(err, forks) { + assert.ifError(err) + assert.ok(forks && forks.length >= 1) + assert.ok(forks.every(function(r) { return h.looksLikeARepo(r) })) + } + }, + 'after fetching tags': { + topic: function() { gh.tags(User, Repo, this.callback) }, + 'map of tags is available': function(err, tags) { + assert.ifError(err) + assert.ok(tags) + assert.instanceOf(tags, Object) + } + }, + 'after fetching watchers': { + topic: function() { gh.watchers(User, Repo, this.callback) }, + 'list of watchers is available': function(err, watchers) { + assert.ifError(err) + assert.ok(watchers && watchers.length >= 1) + assert.ok(watchers.some(function(w) { return w.login === User })) + }, + 'names of watchers are available': function(err, watchers) { + assert.ifError(err) + assert.ok(watchers.every(h.looksLikeAWatcher)) + } + } }).export(module) diff --git a/vows/tree.js b/vows/tree.js index a8de6ff..6fb5acb 100644 --- a/vows/tree.js +++ b/vows/tree.js @@ -1,53 +1,18 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') , User = 'samsonjs' , Repo = 'gitter' - , Proj = User + '/' + Repo , TreeSha = '3363be22e88e50d6dd15f9a4b904bfe41cdd22bc' vows.describe('Tree').addBatch({ - 'after fetching a tree': { - topic: function() { gh.tree(Proj, TreeSha, this.callback) }, - 'the data object can be accessed with the data() method': function(err, tree) { - assert.ifError(err) - assert.ok(tree) - assert.instanceOf(tree.data(), Array) - }, - 'data is a git tree': function(err, tree) { - assert.ifError(err) - assert.ok(h.looksLikeATree(tree.blobs)) - }, - }, - 'after fetching blobs': { - topic: function() { gh.blobs(Proj, TreeSha, this.callback) }, - 'list of blobs is available': function(err, blobs) { - assert.ifError(err) - assert.ok(blobs) - assert.instanceOf(blobs, Object) - assert.ok(Object.keys(blobs).length > 1) - assert.ok('package.json' in blobs) - assert.ok(Object.keys(blobs).every(function(k) { return h.looksLikeASha(blobs[k]) })) - } - }, - 'after fetching full blobs': { - topic: function() { gh.tree(Proj, TreeSha).getFullBlobs(this.callback) }, - 'full blobs are available': function(err, blobs) { - assert.ifError(err) - assert.ok(blobs) - assert.instanceOf(blobs, Array) - assert.ok(blobs.every(function(b) { return h.looksLikeABlob(b) })) - } - }, - 'after fetching the full tree': { - topic: function() { gh.tree(Proj, TreeSha).getFullTree(this.callback) }, - 'full contents of tree are available': function(err, tree) { - assert.ifError(err) - assert.ok(tree) - assert.instanceOf(tree, Array) - assert.ok(tree.every(function(b) { return h.looksLikeABlob(b) })) - } - } + 'after fetching a tree': { + topic: function() { gh.tree(User, Repo, TreeSha, this.callback) }, + 'data is a git tree': function(err, tree) { + assert.ifError(err) + assert.ok(h.looksLikeATree(tree)) + } + } }).export(module) diff --git a/vows/user.js b/vows/user.js index f94db7e..da44773 100644 --- a/vows/user.js +++ b/vows/user.js @@ -1,4 +1,4 @@ -var gh = require('../lib') +var gh = require('../') , vows = require('vows') , assert = require('assert') , h = require('./helper') @@ -8,18 +8,9 @@ var gh = require('../lib') vows.describe('User').addBatch({ 'after fetching a user': { topic: function() { gh.user(User, this.callback) }, - 'the data object can be accessed with the data() method': function(err, user) { - assert.ifError(err) - assert.ok(user) - assert.instanceOf(user.data(), Object) - }, - 'attributes can be accessed with the data() method': function(err, user) { - assert.ifError(err) - assert.equal(user.data('login'), User) - }, 'expected fields are present': function(err, user) { assert.ifError(err) - assert.ok(h.looksLikeAUser(user.data())) + assert.ok(h.looksLikeAUser(user)) }, }, 'after fetching their followers': { @@ -30,7 +21,7 @@ vows.describe('User').addBatch({ }, 'usernames of followers are available': function(err, followers) { assert.ifError(err) - assert.ok(followers.every(function(f) { return f && f.length > 1 })) + assert.ok(followers.every(h.looksLikeAFollower)) } }, 'after fetching users they follow': { @@ -41,7 +32,7 @@ vows.describe('User').addBatch({ }, 'names of following users are available': function(err, following) { assert.ifError(err) - assert.ok(following.every(function(f) { return f && f.length > 1 })) + assert.ok(following.every(h.looksLikeAFollower)) } }, 'after fetching their public repos': {