Adding contributors to config file

This commit is contained in:
Jeroen Engels 2016-03-02 23:45:23 +01:00
parent f2c8733e03
commit ff7457c825
7 changed files with 281 additions and 15 deletions

28
cli.js
View file

@ -7,20 +7,23 @@ var assign = require('lodash.assign');
var generate = require('./lib/generate'); var generate = require('./lib/generate');
var markdown = require('./lib/markdown'); var markdown = require('./lib/markdown');
var getUserInfo = require('./lib/github'); var updateContributors = require('./lib/contributors');
var cwd = process.cwd(); var cwd = process.cwd();
var defaultRCFile = path.join(cwd, '.all-contributorsrc'); var defaultRCFile = path.join(cwd, '.all-contributorsrc');
var argv = require('yargs') var argv = require('yargs')
.help('help')
.alias('h', 'help')
.command('generate', 'Generate the list of contributors') .command('generate', 'Generate the list of contributors')
.usage('Usage: $0 generate') .usage('Usage: $0 generate')
.command('add', 'add a new contributor') .command('add', 'add a new contributor')
.usage('Usage: $0 add <username> <contribution>') .usage('Usage: $0 add <username> <contribution>')
.demand(2) .demand(2)
.default('config', defaultRCFile)
.default('file', 'README.md') .default('file', 'README.md')
.default('contributorsPerLine', 7) .default('contributorsPerLine', 7)
.default('contributors', [])
.default('config', defaultRCFile)
.config('config', function(configPath) { .config('config', function(configPath) {
try { try {
return JSON.parse(fs.readFileSync(configPath, 'utf-8')); return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
@ -31,7 +34,6 @@ var argv = require('yargs')
} }
} }
}) })
.help('help')
.argv; .argv;
argv.file = path.join(cwd, argv.file); argv.file = path.join(cwd, argv.file);
@ -52,19 +54,19 @@ function onError(error) {
} }
} }
if (argv[0] === 'generate') { var command = argv._[0];
if (command === 'generate') {
startGeneration(argv, onError); startGeneration(argv, onError);
} else if (argv[0] === 'add') { } else if (command === 'add') {
// Fetch user var username = argv._[1];
argv.username = argv._[1]; var contributions = argv._[2];
argv.contributions = argv._[2].split(','); // Add/update contributor and save him to the config file
getUserInfo(argv.username, function(error, user) { updateContributors(argv, username, contributions, function(error, contributors) {
if (error) { if (error) {
return console.error(error); return onError(error);
} }
// TODO argv.contributors = contributors;
// Add him to the contributors
// Save rc file with updated contributors key
startGeneration(argv, onError); startGeneration(argv, onError);
}); });
} }

28
lib/configFile.js Normal file
View file

@ -0,0 +1,28 @@
'use strict';
var fs = require('fs');
var _ = require('lodash/fp');
function formatCommaFirst(o) {
return JSON.stringify(o, null, 2)
.split(/(,\n\s+)/)
.map(function (e, i) {
return i%2 ? '\n'+e.substring(4)+', ' : e
})
.join('');
}
function readConfig(configPath) {
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
function writeContributors(configPath, contributors, cb) {
var config = readConfig(configPath);
var content = _.assign(config, { contributors: contributors });
return fs.writeFile(configPath, formatCommaFirst(content), cb);
}
module.exports = {
readConfig: readConfig,
writeContributors: writeContributors
}

57
lib/contributors/add.js Normal file
View file

@ -0,0 +1,57 @@
'use strict';
var _ = require('lodash/fp');
function matchContribution(type) {
return function(existing) {
return type === existing || type === existing.type;
};
}
function uniqueTypes(contribution) {
return contribution.type || contribution;
}
function formatContributions(options, existing, newTypes) {
var types = newTypes.split(',');
if (options.url) {
return (existing || []).concat(types.map(function(type) {
return { type: type, url: options.url };
}));
}
return _.uniqBy(uniqueTypes, (existing || []).concat(types));
}
function updateContributor(options, contributor, contributions) {
return _.assign(contributor, {
contributions: formatContributions(options, contributor.contributions, contributions)
});
}
function updateExistingContributor(options, username, contributions) {
return options.contributors.map(function(contributor, index) {
if (username !== contributor.login) {
return contributor;
}
return updateContributor(options, contributor, contributions);
});
}
function addNewContributor(options, username, contributions, infoFetcher, cb) {
infoFetcher(username, function(error, userData) {
if (error) {
return cb(error);
}
var contributor = _.assign(userData, {
contributions: formatContributions(options, [], contributions)
});
return cb(null, options.contributors.concat(contributor));
});
}
module.exports = function addContributor(options, username, contributions, infoFetcher, cb) {
if (_.find({login: username}, options.contributors)) {
return cb(null, updateExistingContributor(options, username, contributions));
}
return addNewContributor(options, username, contributions, infoFetcher, cb);
}

View file

@ -0,0 +1,161 @@
import test from 'ava';
import addContributor from './add';
function mockInfoFetcher(username, cb) {
return cb(null, {
login: username,
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url'
});
}
function fixtures() {
const options = {
contributors: [{
login: 'login1',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
'code'
]
}, {
login: 'login2',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
{ type: 'blog', url: 'www.blog.url/path' },
'code'
]
}]
};
return {options};
}
test.cb('should callback with error if infoFetcher fails', t => {
t.plan(1);
const {options} = fixtures();
const username = 'login3';
const contributions = ['doc'];
function infoFetcher(username, cb) {
return cb(new Error('infoFetcher error'));
}
return addContributor(options, username, contributions, infoFetcher, function(error) {
t.is(error.message, 'infoFetcher error');
t.end();
});
});
test.cb('should add new contributor at the end of the list of contributors', t => {
t.plan(3);
const {options} = fixtures();
const username = 'login3';
const contributions = 'doc';
return addContributor(options, username, contributions, mockInfoFetcher, function(error, contributors) {
t.notOk(error);
t.is(contributors.length, 3);
t.same(contributors[2], {
login: 'login3',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
'doc'
]
});
t.end();
});
});
test.cb('should add new contributor at the end of the list of contributors with a url link', t => {
t.plan(3);
const {options} = fixtures();
const username = 'login3';
const contributions = 'doc';
options.url = 'www.foo.bar';
return addContributor(options, username, contributions, mockInfoFetcher, function(error, contributors) {
t.notOk(error);
t.is(contributors.length, 3);
t.same(contributors[2], {
login: 'login3',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
{ type: 'doc', url: 'www.foo.bar' }
]
});
t.end();
});
});
test.cb(`should not update an existing contributor's contributions where nothing has changed`, t => {
t.plan(2);
const {options} = fixtures();
const username = 'login2';
const contributions = 'blog,code';
return addContributor(options, username, contributions, mockInfoFetcher, function(error, contributors) {
t.notOk(error);
t.same(contributors, options.contributors);
t.end();
});
});
test.cb(`should update an existing contributor's contributions if a new type is added`, t => {
t.plan(3);
const {options} = fixtures();
const username = 'login1';
const contributions = 'bug';
return addContributor(options, username, contributions, mockInfoFetcher, function(error, contributors) {
t.notOk(error);
t.is(contributors.length, 2);
t.same(contributors[0], {
login: 'login1',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
'code',
'bug'
]
});
t.end();
});
});
test.cb(`should update an existing contributor's contributions if a new type is added with a link`, t => {
t.plan(3);
const {options} = fixtures();
const username = 'login1';
const contributions = 'bug';
options.url = 'www.foo.bar';
return addContributor(options, username, contributions, mockInfoFetcher, function(error, contributors) {
t.notOk(error);
t.is(contributors.length, 2);
t.same(contributors[0], {
login: 'login1',
name: 'Some name',
avatar_url: 'www.avatar.url',
html_url: 'www.html.url',
contributions: [
'code',
{ type: 'bug', url: 'www.foo.bar' },
]
});
t.end();
});
});

View file

@ -1,5 +1,6 @@
'use strict'; 'use strict';
var _ = require('lodash/fp');
var request = require('request'); var request = require('request');
module.exports = function getUserInfo(username, cb) { module.exports = function getUserInfo(username, cb) {
@ -12,6 +13,7 @@ module.exports = function getUserInfo(username, cb) {
if (error) { if (error) {
return cb(error); return cb(error);
} }
return cb(null, JSON.parse(res.body)); var user = JSON.parse(res.body);
return cb(null, _.pick(['login', 'name', 'avatar_url', 'html_url'], user));
}); });
} }

16
lib/contributors/index.js Normal file
View file

@ -0,0 +1,16 @@
'use strict';
var add = require('./add');
var github = require('./github');
var configFile = require('../configFile');
module.exports = function addContributor(options, username, contributions, cb) {
add(options, username, contributions, github, function(error, contributors) {
if (error) {
return cb(error);
}
configFile.writeContributors(options.config, contributors, function(error) {
return cb(error, contributors);
});
});
};

View file

@ -24,7 +24,7 @@
}, },
"homepage": "https://github.com/jfmengels/all-contributors-cli#readme", "homepage": "https://github.com/jfmengels/all-contributors-cli#readme",
"dependencies": { "dependencies": {
"lodash": "^4.5.1", "lodash": "^4.6.1",
"lodash.assign": "^4.0.4", "lodash.assign": "^4.0.4",
"lodash.findindex": "^4.2.0", "lodash.findindex": "^4.2.0",
"lodash.template": "^4.2.1", "lodash.template": "^4.2.1",