From 7a3600bf9a5487cb26e3eb4b65ac774a68b6b91e Mon Sep 17 00:00:00 2001 From: Johnny Estilles Date: Tue, 12 May 2015 20:44:38 +0800 Subject: [PATCH] refactor(lib): simplify methods and refactor into separate files --- tasks/git_changelog_generate.js | 464 ++----------------------------- tasks/lib/check-path.js | 39 +++ tasks/lib/current-date.js | 16 ++ tasks/lib/generate.js | 64 +++++ tasks/lib/get-gitlog-commands.js | 11 + tasks/lib/get-previous-tag.js | 33 +++ tasks/lib/get-provider-links.js | 24 ++ tasks/lib/get-repo-url.js | 32 +++ tasks/lib/get-stream.js | 26 ++ tasks/lib/init-options.js | 22 ++ tasks/lib/init.js | 37 +++ tasks/lib/link-to-commit.js | 11 + tasks/lib/link-to-issue.js | 11 + tasks/lib/log.js | 11 + tasks/lib/message.js | 14 + tasks/lib/organize-commits.js | 31 +++ tasks/lib/parse-raw-commit.js | 56 ++++ tasks/lib/print-salute.js | 11 + tasks/lib/print-section.js | 51 ++++ tasks/lib/read-gitlog.js | 46 +++ tasks/lib/set-defaults.js | 20 ++ tasks/lib/warn.js | 10 + tasks/lib/write-change-log.js | 47 ++++ 23 files changed, 646 insertions(+), 441 deletions(-) create mode 100644 tasks/lib/check-path.js create mode 100644 tasks/lib/current-date.js create mode 100644 tasks/lib/generate.js create mode 100644 tasks/lib/get-gitlog-commands.js create mode 100644 tasks/lib/get-previous-tag.js create mode 100644 tasks/lib/get-provider-links.js create mode 100644 tasks/lib/get-repo-url.js create mode 100644 tasks/lib/get-stream.js create mode 100644 tasks/lib/init-options.js create mode 100644 tasks/lib/init.js create mode 100644 tasks/lib/link-to-commit.js create mode 100644 tasks/lib/link-to-issue.js create mode 100644 tasks/lib/log.js create mode 100644 tasks/lib/message.js create mode 100644 tasks/lib/organize-commits.js create mode 100644 tasks/lib/parse-raw-commit.js create mode 100644 tasks/lib/print-salute.js create mode 100644 tasks/lib/print-section.js create mode 100644 tasks/lib/read-gitlog.js create mode 100644 tasks/lib/set-defaults.js create mode 100644 tasks/lib/warn.js create mode 100644 tasks/lib/write-change-log.js diff --git a/tasks/git_changelog_generate.js b/tasks/git_changelog_generate.js index 84cc13e..6dab922 100644 --- a/tasks/git_changelog_generate.js +++ b/tasks/git_changelog_generate.js @@ -12,8 +12,6 @@ var debug = require('debug')('changelog'); var _ = require('lodash'); var q = require('q'); -var defaults = require('./defaults'); - //ALLOWED_COMMITS = '^fix|^feat|^docs|BREAKING', //git-describe - Show the most recent tag that is reachable from a commit @@ -22,445 +20,29 @@ var Changelog = function Changelog() { this.setDefaults(); }; -Changelog.prototype.setDefaults = function setDefaults() { - debug('setting defaults'); - this.options = {}; - this.cmd = { - gitTag: 'git describe --tags --abbrev=0', - gitRepoUrl: 'git config --get remote.origin.url', - gitLog: null, - gitLogNoTag: null - }; - this.header = '%s\n# %s (%s)\n\n'; - this.emptyComponent = '$$'; - this.links = null; - this.provider = null; -}; - -Changelog.prototype.message = function message() { - debug('adding message'); - Array.prototype.slice.call(arguments).forEach(function(value, index) { - this.options.msg += (index ? ': ' : '') + value; - }, this); - - this.options.msg += ';'; -}; - -Changelog.prototype.initOptions = function initOptions(params) { - debug('initializing options'); - this.setDefaults(); - - this.options = _.defaults(params, defaults); - this.options.msg = ''; - - this.message('name', this.options.app_name); - this.message('file', this.options.file); - this.message('grep_commits', this.options.grep_commits); - this.message('debug', this.options.debug); - this.message('version', this.options.version); - -}; - -Changelog.prototype.getProviderLinks = function getProviderLinks() { - debug('getting provider links'); - // This is just in case they differ their urls at some point in the future. - // Also brings the posibility of adding more providers - var providerLinks = { - github: { - issue: '[#%s](' + this.options.repo_url + '/issues/%s)', - commit: '[%s](' + this.options.repo_url + '/commit/%s)' - }, - bitbucket: { - issue: '[#%s](' + this.options.repo_url + '/issues/%s)', - commit: '[%s](' + this.options.repo_url + '/commits/%s)' - } - }; - - this.provider = this.options.repo_url.indexOf('github.com') !== -1 ? 'github' :'bitbucket'; - this.links = providerLinks[this.provider]; -}; - -Changelog.prototype.getGitLogCommands = function getGitLogCommands() { - debug('getting log commands'); - this.cmd.gitLog = 'git log ' + this.options.branch_name + ' --grep="%s" -E --format=%s %s..HEAD'; - this.cmd.gitLogNoTag = 'git log ' + this.options.branch_name + ' --grep="%s" -E --format=%s'; -}; - -Changelog.prototype.init = function init(params) { - debug('initializing ...'); - var self = this; - var deferred = q.defer(); - - this.initOptions(params); - - this.getRepoUrl().then(function(url) { - var provider; - - self.options.repo_url = url; - self.message('remote', self.options.repo_url); - - self.getProviderLinks(); - self.getGitLogCommands(); - - deferred.resolve(self.options); - }) - .catch(function(err) { - self.message('not remote'); - deferred.reject("Sorry, you doesn't have configured any origin remote or passed a `repo_url` config value"); - }); - - return deferred.promise; -}; - -Changelog.prototype.parseRawCommit = function parseRawCommit(raw) { - debug('parsing raw commit'); - if (!raw) { - return null; - } - - var lines = raw.split('\n'); - var msg = {}, match; - - msg.closes = []; - msg.breaks = []; - - lines.forEach(function(line) { - match = line.match(/(?:Closes|Fixes)\s#(\d+)/); - if (match) { - msg.closes.push(parseInt(match[1], 10)); - } - }); - - msg.hash = lines.shift(); - msg.subject = lines.shift(); - - match = raw.match(/BREAKING CHANGE:([\s\S]*)/); - if (match) { - msg.breaking = match[1]; - } - - msg.body = lines.join('\n'); - match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/); - - if (!match) { - match = msg.subject.match(/^(.*)\:\s(.*)$/); - if (!match) { - this.warn('Incorrect message: %s %s', msg.hash, msg.subject); - return null; - } - msg.type = match[1]; - msg.subject = match[2]; - - return msg; - } - - msg.type = match[1]; - msg.component = match[2]; - msg.subject = match[3]; - - return msg; -}; - -Changelog.prototype.linkToIssue = function linkToIssue(issue) { - debug('generating link to issue'); - return format(this.links.issue, issue, issue); -}; - -Changelog.prototype.linkToCommit = function linkToCommit(hash) { - debug('generating link to commit'); - return format(this.links.commit, hash.substr(0, 8), hash); -}; - -Changelog.prototype.currentDate = function currentDate() { - debug('getting current date'); - var now = new Date(); - var pad = function(i) { - return ('0' + i).substr(-2); - }; - - return format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate())); -}; - -Changelog.prototype.printSection = function printSection(stream, title, section, printCommitLinks) { - debug('printing section ...'); - printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks; - var components = Object.keys(section).sort(); - - if (!components.length) { - return; - } - - stream.write(format('\n## %s\n\n', title)); - - components.forEach(function(name) { - var prefix = '-'; - var nested = section[name].length > 1; - - if (name !== this.emptyComponent) { - if (nested) { - stream.write(format('- **%s:**\n', name)); - prefix = ' -'; - } else { - prefix = format('- **%s:**', name); - } - } - - section[name].forEach(function(commit) { - if (printCommitLinks) { - stream.write(format('%s %s\n (%s', prefix, commit.subject, this.linkToCommit(commit.hash))); - - if (commit.closes.length) { - stream.write(',\n ' + commit.closes.map(this.linkToIssue, this).join(', ')); - } - stream.write(')\n'); - } else { - stream.write(format('%s %s\n', prefix, commit.subject)); - } - }, this); - }, this); - - stream.write('\n'); -}; - -Changelog.prototype.printSalute = function printSalute(stream) { - debug('printing salute'); - stream.write('\n\n---\n'); - stream.write('*Generated with [git-changelog](https://github.com/rafinskipg/git-changelog). If you have any problem or suggestion, create an issue.* :) **Thanks** '); -}; - -Changelog.prototype.readGitLog = function readGitLog(git_log_command, from) { - debug('reading git log ...'); - var self = this; - var deferred = q.defer(); - - git_log_command = git_log_command === this.cmd.gitLog ? format(git_log_command, this.options.grep_commits, '%H%n%s%n%b%n==END==', from) : format(git_log_command, this.options.grep_commits, '%H%n%s%n%b%n==END=='); - - this.log('Executing : ', git_log_command); - - debug('executing git log command'); - child.exec(git_log_command , {timeout: 1000}, function(code, stdout, stderr) { - debug('returning from git log command'); - var commits = []; - stdout.split('\n==END==\n').forEach(function(rawCommit) { - var commit = self.parseRawCommit(rawCommit); - if (commit) { - commits.push(commit); - } - }); - - deferred.resolve(commits); - }); - - return deferred.promise; -}; - -Changelog.prototype.writeChangelog = function writeChangelog(stream, commits) { - debug('writing change log'); - var deferred = q.defer(); - var sections = { - fix: {}, - feat: {}, - breaks: {}, - style: {}, - refactor: {}, - test: {}, - chore: {}, - docs: {} - }; - - sections.breaks[this.emptyComponent] = []; - - this.organizeCommits(commits, sections); - - stream.on('open', function() { - stream.write(format(this.header, this.options.version, this.options.app_name, this.options.version, this.currentDate())); - - this.printSection(stream, 'Bug Fixes', sections.fix); - this.printSection(stream, 'Features', sections.feat); - this.printSection(stream, 'Refactor', sections.refactor, false); - this.printSection(stream, 'Style', sections.style, false); - this.printSection(stream, 'Test', sections.test, false); - this.printSection(stream, 'Chore', sections.chore, false); - this.printSection(stream, 'Documentation', sections.docs, false); - if (sections.breaks[this.emptyComponent].length > 0 ) { - this.printSection(stream, 'Breaking Changes', sections.breaks, false); - } - - this.printSalute(stream); - stream.end(); - stream.on('finish', function() { - deferred.resolve(); - }); - - }.bind(this)); - - return deferred.promise; -}; - -Changelog.prototype.organizeCommits = function organizeCommits(commits, sections) { - debug('organizaing commits'); - commits.forEach(function(commit) { - var section = sections[commit.type]; - var component = commit.component || this.emptyComponent; - - if (section) { - section[component] = section[component] || []; - section[component].push(commit); - } - - if (commit.breaking) { - sections.breaks[component] = sections.breaks[component] || []; - sections.breaks[component].push({ - subject: format("due to %s,\n %s", this.linkToCommit(commit.hash), commit.breaking), - hash: commit.hash, - closes: [] - }); - } - }, this); - - return sections; -}; - -Changelog.prototype.getPreviousTag = function getPreviousTag() { - debug('getting previous tag'); - var deferred = q.defer(); - - if (this.options.tag) { - deferred.resolve(this.options.tag); - } else if (this.options.tag === false) { - deferred.resolve(false); - } else { - //IF we dont find a previous tag, we get all the commits from the beggining - The bigbang of the code - debug('calling git tag command'); - child.exec(this.cmd.gitTag, function(code, stdout, stderr) { - debug('returning from git tag'); - if (code) { - deferred.reject(); - } else { - deferred.resolve(stdout.replace('\n', '')); - } - }); - } - - return deferred.promise; -}; - -Changelog.prototype.getRepoUrl = function getRepoUrl() { - debug('getting repo url'); - var deferred = q.defer(); - - if (this.options.repo_url) { - deferred.resolve(this.options.repo_url); - } else { - //IF we dont find a previous tag, we get all the commits from the beggining - The bigbang of the code - debug('calling git repo url command'); - child.exec(this.cmd.gitRepoUrl, function(code, stdout, stderr) { - debug('returning git repo url command'); - if (code) { - deferred.reject(); - } else { - stdout = stdout.replace('\n', '').replace('.git', ''); - deferred.resolve(stdout); - } - }); - } - - return deferred.promise; -}; - -Changelog.prototype.checkPath = function chekPath(dirname, done) { - fs.stat(dirname, function (err, stats) { - if (err) { - if (err.code === 'ENOENT') { - this.checkPath(path.dirname(dirname), function (err) { - if (err) { - throw err; - } else { - fs.mkdir(dirname, function (err) { - if (err) { - throw err; - } - done(); - }); - } - }); - } else { - throw err; - } - } else if (stats.isDirectory()) { - done(); - } else { - throw new Error(dirname + ' exists and is not a directory'); - } - }.bind(this)); -}; - -Changelog.prototype.getStream = function getStream(filename) { - debug('getting stream ...'); - var deferred = q.defer(); - var stream; - - if (filename) { - this.checkPath(path.dirname(filename), function() { - deferred.resolve(fs.createWriteStream(filename)); - }); - } else { - deferred.resolve(process.stdout); - } - - return deferred.promise; -}; - -Changelog.prototype.generate = function generate(params) { - debug('generating ...'); - var self = this; - var deferred = q.defer(); - - this.init(params).then(function() { - return self.getPreviousTag(); - }).then(function(tag) { - var readGitLog; - - if (typeof(tag) !== 'undefined' && tag !== false) { - self.log('Reading git log since', tag); - self.message('since tag', tag); - readGitLog = self.readGitLog.bind(self, self.cmd.gitLog, tag); - } else { - self.log('Reading git log since the beggining'); - self.message('since beggining'); - readGitLog = self.readGitLog.bind(self, self.cmd.gitLogNoTag); - } - - readGitLog().then(function(commits) { - self.message('parsed commits', commits.length); - self.log('Parsed', commits.length, 'commits'); - self.log('Generating changelog to', self.options.file || 'stdout', '(', self.options.version, ')'); - - self.getStream(self.options.file).then(function(stream) { - self.writeChangelog(stream, commits).then(function() { - deferred.resolve(self.options); - }); - }); - }).catch(function(err) { - console.log('error', err); - }); - }).catch(function(err) { - console.log('Error generating changelog ', err); - deferred.reject(err); - }); - - return deferred.promise; -}; - -Changelog.prototype.log = function log() { - if (this.options.debug) { - console.log.apply(null, arguments); - } -}; - -Changelog.prototype.warn = function warn() { - this.log('WARNING:', format.apply(null, arguments)); -}; +Changelog.prototype.init = require('./lib/init'); +Changelog.prototype.initOptions = require('./lib/init-options'); +Changelog.prototype.setDefaults = require('./lib/set-defaults'); +Changelog.prototype.message = require('./lib/message'); +Changelog.prototype.getProviderLinks = require('./lib/get-provider-links'); +Changelog.prototype.getGitLogCommands = require('./lib/get-gitlog-commands'); +Changelog.prototype.parseRawCommit = require('./lib/parse-raw-commit'); +Changelog.prototype.linkToIssue = require('./lib/link-to-issue'); +Changelog.prototype.linkToCommit = require('./lib/link-to-commit'); +Changelog.prototype.currentDate = require('./lib/current-date'); +Changelog.prototype.printSection = require('./lib/print-section'); +Changelog.prototype.printSalute = require('./lib/print-salute'); +Changelog.prototype.readGitLog = require('./lib/read-gitlog'); +Changelog.prototype.writeChangelog = require('./lib/write-change-log'); +Changelog.prototype.organizeCommits = require('./lib/organize-commits'); +Changelog.prototype.getPreviousTag = require('./lib/get-previous-tag'); +Changelog.prototype.getRepoUrl = require('./lib/get-repo-url'); +Changelog.prototype.checkPath = require('./lib/check-path'); +Changelog.prototype.getStream = require('./lib/get-stream'); +Changelog.prototype.generate = require('./lib/generate'); + +Changelog.prototype.log = require('./lib/log'); +Changelog.prototype.warn = require('./lib/warn'); var changelog = new Changelog(); diff --git a/tasks/lib/check-path.js b/tasks/lib/check-path.js new file mode 100644 index 0000000..7e055a9 --- /dev/null +++ b/tasks/lib/check-path.js @@ -0,0 +1,39 @@ +'use strict'; + +var debug = require('debug')('changelog:checkPath'); +var fs = require('fs'); + +function makePathDone(done, err) { + if (err) { + throw err; + } + done(); +} + +function makePath(dirname, done, err) { + if (err) { + throw err; + } else { + fs.mkdir(dirname, makePathDone.bind(null, done)); + } +} + +function processPath(dirname, done, err, stats) { + if (err) { + if (err.code === 'ENOENT') { + this.checkPath(path.dirname(dirname), makePath.bind(null, dirname, done)); + } else { + throw err; + } + } else if (stats.isDirectory()) { + done(); + } else { + throw new Error(dirname + ' exists and is not a directory'); + } +} + +function checkPath(dirname, done) { + fs.stat(dirname, processPath.bind(this, dirname, done)); +} + +module.exports = checkPath; diff --git a/tasks/lib/current-date.js b/tasks/lib/current-date.js new file mode 100644 index 0000000..2e98c0f --- /dev/null +++ b/tasks/lib/current-date.js @@ -0,0 +1,16 @@ +'use strict'; + +var debug = require('debug')('changelog:currentDate'); +var format = require('util').format; + +function pad(i) { + return ('0' + i).substr(-2); +} + +function currentDate() { + debug('getting current date'); + var now = new Date(); + return format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate())); +} + +module.exports = currentDate; diff --git a/tasks/lib/generate.js b/tasks/lib/generate.js new file mode 100644 index 0000000..dccd7cc --- /dev/null +++ b/tasks/lib/generate.js @@ -0,0 +1,64 @@ +'use strict'; + +var debug = require('debug')('changelog:generate'); +var q = require('q'); + +function writeChangelogDone(deferred) { + deferred.resolve(this.options); +} + +function writeCommitsToStream(deferred, commits, stream) { + this.writeChangelog(stream, commits) + .then(writeChangelogDone.bind(this, deferred)); +} + +function generateFromCommits(deferred, commits) { + this.message('parsed commits', commits.length); + this.log('Parsed', commits.length, 'commits'); + this.log('Generating changelog to', this.options.file || 'stdout', '(', this.options.version, ')'); + + this.getStream(this.options.file) + .then(writeCommitsToStream.bind(this, deferred, commits)); +} + +function handleReadGitLogError(err) { + console.log('error', err); +} + +function generateFromTag(deferred, tag) { + var readGitLog; + + if (typeof(tag) !== 'undefined' && tag !== false) { + this.log('Reading git log since', tag); + this.message('since tag', tag); + readGitLog = this.readGitLog.bind(this, this.cmd.gitLog, tag); + } else { + this.log('Reading git log since the beggining'); + this.message('since beggining'); + readGitLog = this.readGitLog.bind(this, this.cmd.gitLogNoTag); + } + + readGitLog() + .then(generateFromCommits.bind(this, deferred)) + .catch(handleReadGitLogError); +} + +function handleGenerateError(deferred, err) { + console.log('Error generating changelog ', err); + deferred.reject(err); +} + +function generate(params) { + debug('generating ...'); + var self = this; + var deferred = q.defer(); + + this.init(params) + .then(this.getPreviousTag.bind(this)) + .then(generateFromTag.bind(this, deferred)) + .catch(handleGenerateError.bind(null, deferred)); + + return deferred.promise; +} + +module.exports = generate; diff --git a/tasks/lib/get-gitlog-commands.js b/tasks/lib/get-gitlog-commands.js new file mode 100644 index 0000000..7743f03 --- /dev/null +++ b/tasks/lib/get-gitlog-commands.js @@ -0,0 +1,11 @@ +'use strict'; + +var debug = require('debug')('changelog:getGitLogCommands'); + +function getGitLogCommands() { + debug('getting log commands'); + this.cmd.gitLog = 'git log ' + this.options.branch_name + ' --grep="%s" -E --format=%s %s..HEAD'; + this.cmd.gitLogNoTag = 'git log ' + this.options.branch_name + ' --grep="%s" -E --format=%s'; +} + +module.exports = getGitLogCommands; diff --git a/tasks/lib/get-previous-tag.js b/tasks/lib/get-previous-tag.js new file mode 100644 index 0000000..71210c5 --- /dev/null +++ b/tasks/lib/get-previous-tag.js @@ -0,0 +1,33 @@ +'use strict'; + +var debug = require('debug')('changelog:getPreviousTag'); +var child = require('child_process'); +var q = require('q'); + +function cmdDone(deferred, code, stdout, stderr) { + debug('returning from git tag'); + if (code) { + deferred.reject(); + } else { + deferred.resolve(stdout.replace('\n', '')); + } +} + +function getPreviousTag() { + debug('getting previous tag'); + var deferred = q.defer(); + + if (this.options.tag) { + deferred.resolve(this.options.tag); + } else if (this.options.tag === false) { + deferred.resolve(false); + } else { + //IF we dont find a previous tag, we get all the commits from the beggining - The bigbang of the code + debug('calling git tag command'); + child.exec(this.cmd.gitTag, cmdDone.bind(null, deferred)); + } + + return deferred.promise; +} + +module.exports = getPreviousTag; diff --git a/tasks/lib/get-provider-links.js b/tasks/lib/get-provider-links.js new file mode 100644 index 0000000..8df88f9 --- /dev/null +++ b/tasks/lib/get-provider-links.js @@ -0,0 +1,24 @@ +'use strict'; + +var debug = require('debug')('changelog:getProviderLinks'); + +function getProviderLinks() { + debug('getting provider links'); + // This is just in case they differ their urls at some point in the future. + // Also brings the posibility of adding more providers + var providerLinks = { + github: { + issue: '[#%s](' + this.options.repo_url + '/issues/%s)', + commit: '[%s](' + this.options.repo_url + '/commit/%s)' + }, + bitbucket: { + issue: '[#%s](' + this.options.repo_url + '/issues/%s)', + commit: '[%s](' + this.options.repo_url + '/commits/%s)' + } + }; + + this.provider = this.options.repo_url.indexOf('github.com') !== -1 ? 'github' :'bitbucket'; + this.links = providerLinks[this.provider]; +} + +module.exports = getProviderLinks; diff --git a/tasks/lib/get-repo-url.js b/tasks/lib/get-repo-url.js new file mode 100644 index 0000000..e11e822 --- /dev/null +++ b/tasks/lib/get-repo-url.js @@ -0,0 +1,32 @@ +'use strict'; + +var debug = require('debug')('changelog:getRepoUrl'); +var child = require('child_process'); +var q = require('q'); + +function cmdDone(deferred, code, stdout, stderr) { + debug('returning git repo url command'); + if (code) { + deferred.reject(); + } else { + stdout = stdout.replace('\n', '').replace('.git', ''); + deferred.resolve(stdout); + } +} + +function getRepoUrl() { + debug('getting repo url'); + var deferred = q.defer(); + + if (this.options.repo_url) { + deferred.resolve(this.options.repo_url); + } else { + //IF we dont find a previous tag, we get all the commits from the beggining - The bigbang of the code + debug('calling git repo url command'); + child.exec(this.cmd.gitRepoUrl, cmdDone.bind(null, deferred)); + } + + return deferred.promise; +} + +module.exports = getRepoUrl; diff --git a/tasks/lib/get-stream.js b/tasks/lib/get-stream.js new file mode 100644 index 0000000..283a805 --- /dev/null +++ b/tasks/lib/get-stream.js @@ -0,0 +1,26 @@ +'use strict'; + +var debug = require('debug')('changelog:getStream'); +var fs = require('fs'); +var path = require('path'); +var q = require('q'); + +function checkPathDone(deferred, filename) { + deferred.resolve(fs.createWriteStream(filename)); +} + +function getStream(filename) { + debug('getting stream ...'); + var deferred = q.defer(); + var stream; + + if (filename) { + this.checkPath(path.dirname(filename), checkPathDone.bind(null, deferred, filename)); + } else { + deferred.resolve(process.stdout); + } + + return deferred.promise; +} + +module.exports = getStream; diff --git a/tasks/lib/init-options.js b/tasks/lib/init-options.js new file mode 100644 index 0000000..430a830 --- /dev/null +++ b/tasks/lib/init-options.js @@ -0,0 +1,22 @@ +'use strict'; + +var debug = require('debug')('changelog:initOptions'); +var _ = require('lodash'); + +var defaults = require('../defaults'); + +function initOptions(params) { + debug('initializing options'); + this.setDefaults(); + + this.options = _.defaults(params, defaults); + this.options.msg = ''; + this.message('name', this.options.app_name); + this.message('file', this.options.file); + this.message('grep_commits', this.options.grep_commits); + this.message('debug', this.options.debug); + this.message('version', this.options.version); + +} + +module.exports = initOptions; diff --git a/tasks/lib/init.js b/tasks/lib/init.js new file mode 100644 index 0000000..e441208 --- /dev/null +++ b/tasks/lib/init.js @@ -0,0 +1,37 @@ +'use strict'; + +var debug = require('debug')('changelog:init'); +var q = require('q'); + +function getRepoSuccess(deferred, url) { + var provider; + + this.options.repo_url = url; + this.message('remote', this.options.repo_url); + + this.getProviderLinks(); + this.getGitLogCommands(); + + deferred.resolve(this.options); +} + +function getRepoFailure(deferred, err) { + this.message('not remote'); + deferred.reject("Sorry, you doesn't have configured any origin remote or passed a `repo_url` config value"); +} + +function init(params) { + debug('initializing ...'); + var self = this; + var deferred = q.defer(); + + this.initOptions(params); + + this.getRepoUrl() + .then(getRepoSuccess.bind(this, deferred)) + .catch(getRepoFailure.bind(this, deferred)); + + return deferred.promise; +} + +module.exports = init; diff --git a/tasks/lib/link-to-commit.js b/tasks/lib/link-to-commit.js new file mode 100644 index 0000000..b98244d --- /dev/null +++ b/tasks/lib/link-to-commit.js @@ -0,0 +1,11 @@ +'use strict'; + +var debug = require('debug')('changelog:linkToCommit'); +var format = require('util').format; + +function linkToCommit(hash) { + debug('generating link to commit'); + return format(this.links.commit, hash.substr(0, 8), hash); +} + +module.exports = linkToCommit; diff --git a/tasks/lib/link-to-issue.js b/tasks/lib/link-to-issue.js new file mode 100644 index 0000000..6da7e8b --- /dev/null +++ b/tasks/lib/link-to-issue.js @@ -0,0 +1,11 @@ +'use strict'; + +var debug = require('debug')('changelog:linkToIssue'); +var format = require('util').format; + +function linkToIssue(issue) { + debug('generating link to issue'); + return format(this.links.issue, issue, issue); +} + +module.exports = linkToIssue; diff --git a/tasks/lib/log.js b/tasks/lib/log.js new file mode 100644 index 0000000..6c67fc8 --- /dev/null +++ b/tasks/lib/log.js @@ -0,0 +1,11 @@ +'use strict'; + +var debug = require('debug')('changelog:log'); + +function log() { + if (this.options.debug) { + console.log.apply(null, arguments); + } +} + +module.exports = log; diff --git a/tasks/lib/message.js b/tasks/lib/message.js new file mode 100644 index 0000000..1268b4b --- /dev/null +++ b/tasks/lib/message.js @@ -0,0 +1,14 @@ +'use strict'; + +var debug = require('debug')('changelog:message'); + +function message() { + debug('adding message'); + Array.prototype.slice.call(arguments).forEach(function(value, index) { + this.options.msg += (index ? ': ' : '') + value; + }, this); + + this.options.msg += ';'; +} + +module.exports = message; diff --git a/tasks/lib/organize-commits.js b/tasks/lib/organize-commits.js new file mode 100644 index 0000000..8e1efa8 --- /dev/null +++ b/tasks/lib/organize-commits.js @@ -0,0 +1,31 @@ +'use strict'; + +var debug = require('debug')('changelog:organizeCommits'); +var format = require('util').format; + +function organizeCommit(sections, commit) { + var section = sections[commit.type]; + var component = commit.component || this.emptyComponent; + + if (section) { + section[component] = section[component] || []; + section[component].push(commit); + } + + if (commit.breaking) { + sections.breaks[component] = sections.breaks[component] || []; + sections.breaks[component].push({ + subject: format("due to %s,\n %s", this.linkToCommit(commit.hash), commit.breaking), + hash: commit.hash, + closes: [] + }); + } +} + +function organizeCommits(commits, sections) { + debug('organizaing commits'); + commits.forEach(organizeCommit.bind(this, sections), this); + return sections; +} + +module.exports = organizeCommits; diff --git a/tasks/lib/parse-raw-commit.js b/tasks/lib/parse-raw-commit.js new file mode 100644 index 0000000..f979633 --- /dev/null +++ b/tasks/lib/parse-raw-commit.js @@ -0,0 +1,56 @@ +'use strict'; + +var debug = require('debug')('changelog:parseRawCommit'); + +function parseLine(msg, line) { + var match = line.match(/(?:Closes|Fixes)\s#(\d+)/); + if (match) { + msg.closes.push(parseInt(match[1], 10)); + } +} + +function parseRawCommit(raw) { + debug('parsing raw commit'); + if (!raw) { + return null; + } + + var lines = raw.split('\n'); + var msg = {}, match; + + msg.closes = []; + msg.breaks = []; + + lines.forEach(parseLine.bind(null, msg)); + + msg.hash = lines.shift(); + msg.subject = lines.shift(); + + match = raw.match(/BREAKING CHANGE:([\s\S]*)/); + if (match) { + msg.breaking = match[1]; + } + + msg.body = lines.join('\n'); + match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/); + + if (!match) { + match = msg.subject.match(/^(.*)\:\s(.*)$/); + if (!match) { + this.warn('Incorrect message: %s %s', msg.hash, msg.subject); + return null; + } + msg.type = match[1]; + msg.subject = match[2]; + + return msg; + } + + msg.type = match[1]; + msg.component = match[2]; + msg.subject = match[3]; + + return msg; +} + +module.exports = parseRawCommit; diff --git a/tasks/lib/print-salute.js b/tasks/lib/print-salute.js new file mode 100644 index 0000000..fc75f9f --- /dev/null +++ b/tasks/lib/print-salute.js @@ -0,0 +1,11 @@ +'use strict'; + +var debug = require('debug')('changelog:printSalute'); + +function printSalute(stream) { + debug('printing salute'); + stream.write('\n\n---\n'); + stream.write('*Generated with [git-changelog](https://github.com/rafinskipg/git-changelog). If you have any problem or suggestion, create an issue.* :) **Thanks** '); +} + +module.exports = printSalute; diff --git a/tasks/lib/print-section.js b/tasks/lib/print-section.js new file mode 100644 index 0000000..bf64820 --- /dev/null +++ b/tasks/lib/print-section.js @@ -0,0 +1,51 @@ +'use strict'; + +var debug = require('debug')('changelog:printSection'); +var format = require('util').format; + +function printCommit(stream, printCommitLinks, prefix, commit) { + if (printCommitLinks) { + stream.write(format('%s %s\n (%s', prefix, commit.subject, this.linkToCommit(commit.hash))); + + if (commit.closes.length) { + stream.write(',\n ' + commit.closes.map(this.linkToIssue, this).join(', ')); + } + stream.write(')\n'); + } else { + stream.write(format('%s %s\n', prefix, commit.subject)); + } +} + +function printComponent(stream, section, printCommitLinks, name) { + var prefix = '-'; + var nested = section[name].length > 1; + + if (name !== this.emptyComponent) { + if (nested) { + stream.write(format('- **%s:**\n', name)); + prefix = ' -'; + } else { + prefix = format('- **%s:**', name); + } + } + + section[name].forEach(printCommit.bind(this, stream, printCommitLinks, prefix), this); +} + +function printSection(stream, title, section, printCommitLinks) { + debug('printing section ...'); + printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks; + var components = Object.keys(section).sort(); + + if (!components.length) { + return; + } + + stream.write(format('\n## %s\n\n', title)); + + components.forEach(printComponent.bind(this, stream, section, printCommitLinks), this); + + stream.write('\n'); +} + +module.exports = printSection; diff --git a/tasks/lib/read-gitlog.js b/tasks/lib/read-gitlog.js new file mode 100644 index 0000000..92869d2 --- /dev/null +++ b/tasks/lib/read-gitlog.js @@ -0,0 +1,46 @@ +'use strict'; + +var debug = require('debug')('changelog:readGitLog'); +var format = require('util').format; +var child = require('child_process'); +var q = require('q'); + +function processRawCommit(commits, rawCommit) { + var commit = this.parseRawCommit(rawCommit); + if (commit) { + commits.push(commit); + } +} + +function cmdDone(deferred, code, stdout, stderr) { + debug('returning from git log command'); + var commits = []; + + stdout + .split('\n==END==\n') + .forEach(processRawCommit.bind(this, commits), this); + + deferred.resolve(commits); +} + +function gitLogCommand(git_log_command, from) { + if (git_log_command === this.cmd.gitLog) { + return format(git_log_command, this.options.grep_commits, '%H%n%s%n%b%n==END==', from) + } else { + return format(git_log_command, this.options.grep_commits, '%H%n%s%n%b%n==END=='); + } +} + +function readGitLog(git_log_command, from) { + debug('reading git log ...'); + var deferred = q.defer(); + + git_log_command = gitLogCommand.call(this, git_log_command, from); + this.log('Executing : ', git_log_command); + debug('executing git log command'); + child.exec(git_log_command , {timeout: 1000}, cmdDone.bind(this, deferred)); + + return deferred.promise; +} + +module.exports = readGitLog; diff --git a/tasks/lib/set-defaults.js b/tasks/lib/set-defaults.js new file mode 100644 index 0000000..7bbdf3b --- /dev/null +++ b/tasks/lib/set-defaults.js @@ -0,0 +1,20 @@ +'use strict'; + +var debug = require('debug')('changelog:setDefaults'); + +function setDefaults() { + debug('setting defaults'); + this.options = {}; + this.cmd = { + gitTag: 'git describe --tags --abbrev=0', + gitRepoUrl: 'git config --get remote.origin.url', + gitLog: null, + gitLogNoTag: null + }; + this.header = '%s\n# %s (%s)\n\n'; + this.emptyComponent = '$$'; + this.links = null; + this.provider = null; +} + +module.exports = setDefaults; diff --git a/tasks/lib/warn.js b/tasks/lib/warn.js new file mode 100644 index 0000000..7fdc5d4 --- /dev/null +++ b/tasks/lib/warn.js @@ -0,0 +1,10 @@ +'use strict'; + +var debug = require('debug')('changelog:warn'); +var format = require('util').format; + +function warn() { + this.log('WARNING:', format.apply(null, arguments)); +} + +module.exports = warn; diff --git a/tasks/lib/write-change-log.js b/tasks/lib/write-change-log.js new file mode 100644 index 0000000..3021e0c --- /dev/null +++ b/tasks/lib/write-change-log.js @@ -0,0 +1,47 @@ +'use strict'; + +var debug = require('debug')('changelog:writeChangelog'); +var format = require('util').format; +var q = require('q'); + +function sendToStream(stream, sections, deferred) { + stream.write(format(this.header, this.options.version, this.options.app_name, this.options.version, this.currentDate())); + + this.printSection(stream, 'Bug Fixes', sections.fix); + this.printSection(stream, 'Features', sections.feat); + this.printSection(stream, 'Refactor', sections.refactor, false); + this.printSection(stream, 'Style', sections.style, false); + this.printSection(stream, 'Test', sections.test, false); + this.printSection(stream, 'Chore', sections.chore, false); + this.printSection(stream, 'Documentation', sections.docs, false); + if (sections.breaks[this.emptyComponent].length > 0 ) { + this.printSection(stream, 'Breaking Changes', sections.breaks, false); + } + + this.printSalute(stream); + stream.end(); + stream.on('finish', deferred.resolve); +} + +function writeChangelog(stream, commits) { + debug('writing change log'); + var deferred = q.defer(); + var sections = { + fix: {}, + feat: {}, + breaks: {}, + style: {}, + refactor: {}, + test: {}, + chore: {}, + docs: {} + }; + + sections.breaks[this.emptyComponent] = []; + this.organizeCommits(commits, sections); + stream.on('open', sendToStream.bind(this, stream, sections, deferred)); + + return deferred.promise; +} + +module.exports = writeChangelog;