diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1f24ca5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +**/.* +**/node_modules +**/dist +**/assets +**/build +**/test diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..22132c8 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,163 @@ +{ + "env": { + "browser": true, + "node": true, + "es6": true, + "mocha": true + }, + "plugins": [ + "mocha" + ], + // https://github.com/feross/eslint-config-standard + "rules": { + "accessor-pairs": 2, + "block-scoped-var": 0, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "camelcase": 0, + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "complexity": 0, + "consistent-return": 0, + "consistent-this": 0, + "curly": [2, "multi-line"], + "default-case": 0, + "dot-location": [2, "property"], + "dot-notation": 0, + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "func-names": 0, + "func-style": 0, + "generator-star-spacing": [2, "both"], + "guard-for-in": 0, + "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], + "indent": [2, 2], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "linebreak-style": 0, + "max-depth": 0, + "max-len": 0, + "max-nested-callbacks": 0, + "max-params": 0, + "max-statements": 0, + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "no-alert": 0, + "no-array-constructor": 2, + "no-bitwise": 0, + "no-caller": 2, + "no-catch-shadow": 0, + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 0, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 0, + "no-empty": 0, + "no-empty-character-class": 2, + "no-eq-null": 0, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-semi": 0, + "no-extra-strict": 0, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inline-comments": 0, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 0, + "no-loop-func": 0, + "no-mixed-requires": 0, + "no-mixed-spaces-and-tabs": [2, false], + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 0, + "no-new": 0, + "no-new-func": 2, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-path-concat": 0, + "no-plusplus": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 2, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-reserved-keys": 0, + "no-restricted-modules": 0, + "no-return-assign": 2, + "no-script-url": 0, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 0, + "no-underscore-dangle": 0, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": 0, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-use-before-define": 0, + "no-var": 0, + "no-void": 0, + "no-warning-comments": 0, + "no-with": 2, + "no-extra-parens": 0, + "object-curly-spacing": 0, + "one-var": [2, { "initialized": "never" }], + "operator-assignment": 0, + "operator-linebreak": [2, "after"], + "padded-blocks": 0, + "quote-props": 0, + "quotes": [1, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "always"], + "semi-spacing": 0, + "sort-vars": 0, + "keyword-spacing": [2], + "space-before-blocks": [2, "always"], + "space-before-function-paren": 0, + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always"], + "strict": 0, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "any"], + "wrap-regex": 0, + "yoda": [2, "never"] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20137e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +npm-debug.log +.idea/* +Thumbs.db +coverage/ +build/ +*.gz +*.sw* +*.un~ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5e5be8f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +sudo: false +node_js: + - 8 +script: + - npm i + - npm run ci +after_script: + - npm install coveralls@2 && cat ./coverage/lcov.info | coveralls diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9eb43c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 xdf + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..224cead --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# git-contributor + +[![NPM version][npm-image]][npm-url] +[![build status][travis-image]][travis-url] +[![Test coverage][coveralls-image]][coveralls-url] +[![node version][node-image]][node-url] +[![npm download][download-image]][download-url] + +[npm-image]: https://img.shields.io/npm/v/git-contributor.svg?style=flat-square +[npm-url]: https://npmjs.org/package/git-contributor +[travis-image]: https://img.shields.io/travis/xudafeng/git-contributor.svg?style=flat-square +[travis-url]: https://travis-ci.org/xudafeng/git-contributor +[coveralls-image]: https://img.shields.io/coveralls/xudafeng/git-contributor.svg?style=flat-square +[coveralls-url]: https://coveralls.io/r/xudafeng/git-contributor?branch=master +[node-image]: https://img.shields.io/badge/node.js-%3E=_8-green.svg?style=flat-square +[node-url]: http://nodejs.org/download/ +[download-image]: https://img.shields.io/npm/dm/git-contributor.svg?style=flat-square +[download-url]: https://npmjs.org/package/git-contributor + +> git-contributor + +## Spec + +- The listings show recently contributors. +- More on the way. + +## Installment + +```bash +$ npm i git-contributor --save-dev +``` + +## License + +The MIT License (MIT) diff --git a/bin/git-contributor b/bin/git-contributor new file mode 100755 index 0000000..48ac294 --- /dev/null +++ b/bin/git-contributor @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +'use strict'; + +const fs = require('fs'); +const _ = require('xutil'); +const path = require('path'); +const program = require('commander'); + +const gen = require('../lib/git-contributor'); + +const pkg = require('../package.json'); + +program + .option('-m, --markdown', 'auto parse and update README.md') + .option('-p, --print', 'render markdown file with pdf style') + .option('-v, --versions', 'output version infomation'); + +const options = Object.assign({ + markdown: true, + print: true +}, program); + +gen.getAuthor() + .then(list => { + const res = gen.genMarkDown(list); + if (options.markdown) { + const readmeFile = path.join(process.cwd(), 'README.md'); + if (_.isExistedFile(readmeFile)) { + let readmeContent = fs.readFileSync(readmeFile, 'utf8'); + const reg = new RegExp(`${res.startToken}[^]*${res.endToken}`); + if (reg.test(readmeContent)) { + readmeContent = readmeContent.replace(reg, res.content); + } else { + readmeContent += res.content; + } + fs.writeFileSync(readmeFile, readmeContent); + } + } + if (options.print) { + console.log(res.content); + } + }) + .catch(e => { + console.log(e); + }); diff --git a/index.js b/index.js new file mode 100644 index 0000000..e254aac --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/git-contributor'); diff --git a/lib/git-contributor.js b/lib/git-contributor.js new file mode 100644 index 0000000..627beaf --- /dev/null +++ b/lib/git-contributor.js @@ -0,0 +1,108 @@ +'use strict'; + +const _ = require('xutil'); +const { + execSync +} = require('child_process'); +const path = require('path'); +const Promise = require('bluebird'); +const request = require('request-promise'); + +const pkg = require('../package'); + +const gitLog2MailList = () => { + const logs = execSync('git log --pretty="%an <%ae>"').toString(); + const list = logs.split(/\n/g).reverse(); + return _.uniq(list) + .filter(item => item.length) + .map(item => { + return item.split(/\s+/g)[1]; + }); +}; + +const getInfoFromGithub = maillist => { + const api = 'https://api.github.com/search/users?q='; + const tasks = maillist.map(email => { + const options = { + uri: `${api}${encodeURIComponent(email + ' in:email type:user')}`, + json: true, + headers: { + 'user-agent': Date.now() + } + }; + if (process.env.OAUTH_TOKEN) { + options.headers['Authorization'] = `token ${process.env.OAUTH_TOKEN}`; + } + return request(options); + }); + + return Promise.all(tasks).then(list => { + return list.map(item => { + if (item && item.total_count === 1) { + return item.items[0]; + } + }); + }); +}; + +const format = list => { + return list.filter(item => item).map(item => { + return { + login: item.login, + avatar_url: item.avatar_url, + html_url: item.html_url + }; + }); +}; + +exports.getAuthor = async (options = {}) => { + const cwd = path.resolve(options.cwd || process.cwd()); + const dotGitDir = path.join(cwd, '.git'); + + if (!_.isExistedDir(dotGitDir)) { + return []; + } + const mailList = gitLog2MailList(); + const infoList = await getInfoFromGithub(_.uniq(mailList)); + return format(infoList); +}; + +exports.genMarkDown = list => { + const startToken = ''; + const endToken = ''; + const content1 = ['']; + const lineMax = 6; + const content1Data = list.splice(0, lineMax); + content1Data.map((item, key) => { + content1.push(`[
${item.login}](${item.html_url})
`); + }); + const content2 = ['']; + list.map((item, key) => { + content2.push(`[
${item.login}](${item.html_url})
`); + }); + const genLine = (number) => { + const r = []; + while(number--) { + r.push(' :---: '); + } + return r; + }; + const res = [ + startToken, + '', + '## Contributors', + '', + content1.join('|'), + `|${genLine(content1Data.length).join('|')}|`, + content2.join('|'), + '', + `This project follows the git-contributor [spec](${pkg.repository.url}), welcome to join in and feel free to contribute.`, + endToken + ]; + + return { + content: res.join('\n'), + startToken, + endToken, + }; +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..bbd1b59 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "git-contributor", + "version": "1.0.0", + "description": "git-contributor", + "keywords": [ + "contributor" + ], + "bin": { + "git-contributor": "./bin/git-contributor" + }, + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/xudafeng/git-contributor.git" + }, + "dependencies": { + "bluebird": "^3.5.1", + "commander": "^2.15.1", + "request": "^2.85.0", + "request-promise": "^4.2.2", + "xutil": "^1.0.11" + }, + "devDependencies": { + "co-mocha": "*", + "eslint": "*", + "eslint-plugin-mocha": "^4.11.0", + "mocha": "*", + "istanbul": "*", + "pre-commit": "*" + }, + "scripts": { + "ci": "npm run lint && npm run test", + "test": "istanbul cover `npm bin`/_mocha", + "lint": "eslint . --fix" + }, + "pre-commit": [ + "lint" + ], + "license": "MIT" +} diff --git a/test/git-contributor.test.js b/test/git-contributor.test.js new file mode 100644 index 0000000..62cf6b5 --- /dev/null +++ b/test/git-contributor.test.js @@ -0,0 +1,10 @@ +'use strict'; + +const assert = require('assert'); + +const gitcontributor = require('..'); + +describe('test', () => { + it('should be ok', () => { + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..5ada47b --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +--reporter spec