diff --git a/Dockerfile b/Dockerfile index 6ff332f..cf64544 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:8.2.1-alpine -ENV GTT_VERSION 1.6.10 +ENV GTT_VERSION 1.7.2 RUN yarn global add --prefix /usr/local "gitlab-time-tracker@$GTT_VERSION" diff --git a/package.json b/package.json index 23f8741..ec92e80 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlab-time-tracker", - "version": "1.6.10", + "version": "1.7.2", "description": "A command line interface for GitLabs time tracking feature.", "bugs": { "url": "https://github.com/kriskbx/gitlab-time-tracker/issues" diff --git a/readme.md b/readme.md index c01d2d4..be553d1 100755 --- a/readme.md +++ b/readme.md @@ -55,6 +55,7 @@ npm install -g gitlab-time-tracker ``` Run the config command to create a config file and open it in your default editor. +In linux terminal, you must set your preferred editor in the environment. For example, use `export EDITOR=vim` to edit the files with vim (put this in `.bashrc` or similar to have it always configured). If nothing happens, open the file manually: `~/.gtt/config.yml` - on Windows: `C:\Users\YourUserName\.gtt\config.yml` ```shell @@ -484,6 +485,10 @@ token: abcdefghijklmnopqrst # defaults to false proxy: http://localhost:8080 +# Don't check SSL certificate +# defaults to false +insecure: true + # Project # defaults to false project: namespace/projectname @@ -543,7 +548,7 @@ hoursPerDay: 8 issueColumns: - iid - title -- estimation +- total_estimate # Include the given columns in the merge request table # See --merge_request_columns option for more information @@ -551,7 +556,7 @@ issueColumns: mergeRequestColumns: - iid - title -- estimation +- total_estimate # Include the given columns in the time record table # See --record_columns option for more information @@ -576,6 +581,16 @@ dateFormat: DD.MM.YYYY HH:mm:ss # defaults to "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" timeFormat: "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" +# Time format for different parts of the report +# Instead of specifying one global time format you can specify one for every +# part of the report and the log command +timeFormat: + log: "[%sign][%hours_overall]" + stats: "[%sign][%days_overall]" + issues: "[%sign][%hours_overall]" + merge_requests: "[%sign][%hours_overall]" + records: "[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s]" + # Output type # Available: csv, table, markdown, pdf # defaults to table @@ -593,6 +608,12 @@ includeLabels: - pending - approved +# Only works if using a local configuration file! +# Extend the global configuration if set to true, pass a string to extend +# the configuration file stored at the given path +# defaults to true +extend: true + # Change number of concurrent connections/http queries # Note: Handle with care, we don't want to spam GitLabs API too much # defaults to 10 @@ -602,11 +623,14 @@ _parallel: 20 # defaults to 100 _perPage: 100 -# Only works if using a local configuration file! -# Extend the global configuration if set to true, pass a string to extend -# the configuration file stored at the given path -# defaults to true -extend: true +# Verbose output +_verbose: false + +# Check access token validity up front +_checkToken: false + +# Skip parsing the issue/merge_request description for time records +_skipDescriptionParsing: false ``` ### Time format @@ -698,6 +722,23 @@ timeFormat: "[%sign][%minutes_overall]" 1095 ``` +##### `[%hours_overall:2]`, `[%days_overall:3]` + +You can ceil any float value by adding the number of decimals to keep separated with a `:`. + +**Example config:** + +```yaml +timeFormat: "[%sign][%hours_overall:2]" +``` + +**Example outputs:** + +```shell +0,51 +18,25 +``` + ## how to use gtt as a library Add as a dependency using yarn: @@ -763,12 +804,6 @@ report.mergeRequests.forEach(mergeRequest => { It can include times outside the queried time frame. `spent` on the other hand is the total amount of time spent in the given time frame. -#### Why 'total spent' and 'spent' are showing different amounts. - -gtt can only track time records from notes/comments. If you start your -issue or merge request with `/spend [time]` in its description, gtt won't -take it into consideration (for now). - ## contributing I would love to integrate unit testing in this project, but unfortunately my knowledge of @@ -789,4 +824,4 @@ or already have done so. 💜 ## license -GPL v2 \ No newline at end of file +GPL v2 diff --git a/spec/include/config.spec.js b/spec/include/config.spec.js index 6e909d8..41cf003 100755 --- a/spec/include/config.spec.js +++ b/spec/include/config.spec.js @@ -42,8 +42,33 @@ describe('The config class', () => { }); }); + it('returns values from objects and falls back to the default', () => { + let Config = new config(), + objectsWithDefaults = ['timeFormat'], + defaults = Object.assign({}, Config.data), + stringData = 'booze', + objectData = { + foo: 'bar', + baz: 'bar' + }; + + objectsWithDefaults.forEach(key => { + Config.set(key, objectData); + + expect(Config.get(key, 'foo')).to.equal('bar'); + expect(Config.get(key, 'baz')).to.equal('bar'); + expect(Config.get(key, 'not_a_real_key')).to.equal(defaults[key]); + + Config.set(key, stringData); + + expect(Config.get(key, 'foo')).to.equal('booze'); + expect(Config.get(key, 'baz')).to.equal('booze'); + expect(Config.get(key, 'not_a_real_key')).to.equal('booze'); + }); + }); + it('makes durations human readable', () => { - let Config = new config, + let Config = new config(), humanReadable = "1d 4h 30m 10s", seconds = 45010; diff --git a/src/gtt-log.js b/src/gtt-log.js index f060529..16c6b41 100755 --- a/src/gtt-log.js +++ b/src/gtt-log.js @@ -11,16 +11,17 @@ const Tasks = require('./include/tasks'); program .option('--verbose', 'show verbose output') .option('--hours_per_day ', 'hours per day for human readable time formats') + .option('--time_format ', 'time format') .parse(process.argv); Cli.verbose = program.verbose; -let config = new Config(__dirname) - .set('hoursPerDay', program.hours_per_day), - tasks = new Tasks(config); +let config = new Config(__dirname).set('hoursPerDay', program.hours_per_day), + tasks = new Tasks(config), + timeFormat = config.set('timeFormat', program.time_format).get('timeFormat', 'log'); function toHumanReadable(input) { - return Time.toHumanReadable(Math.ceil(input), config.get('hoursPerDay'), config.get('timeFormat')); + return Time.toHumanReadable(Math.ceil(input), config.get('hoursPerDay'), timeFormat); } tasks.log() diff --git a/src/gtt-resume.js b/src/gtt-resume.js new file mode 100755 index 0000000..baf3261 --- /dev/null +++ b/src/gtt-resume.js @@ -0,0 +1,24 @@ +const program = require('commander'); +const colors = require('colors'); +const moment = require('moment'); + +const Config = require('./include/file-config'); +const Cli = require('./include/cli'); +const Tasks = require('./include/tasks'); + +program + .arguments('[project]') + .option('--verbose', 'show verbose output') + .parse(process.argv); + +Cli.verbose = program.verbose; + +let config = new Config(process.cwd()).set('project', program.args[0]), + tasks = new Tasks(config); + +if (!config.get('project')) + Cli.error('No project set'); + +tasks.resume() + .then(frame => console.log(`Starting project ${config.get('project').magenta} ${frame.resource.type.blue} ${('#' + frame.resource.id).blue} at ${moment().format('HH:mm').green}`)) + .catch(error => Cli.error(error)); diff --git a/src/gtt-start.js b/src/gtt-start.js index 91a226c..cdc53e5 100755 --- a/src/gtt-start.js +++ b/src/gtt-start.js @@ -9,6 +9,8 @@ const Tasks = require('./include/tasks'); program .arguments('[project] [id]') .option('-t, --type ', 'specify resource type: issue, merge_request') + .option('-m', 'shorthand for --type=merge_request') + .option('-i', 'shorthand for --type=issue') .option('--verbose', 'show verbose output') .parse(process.argv); @@ -20,6 +22,12 @@ let config = new Config(process.cwd()), id = program.args.length === 1 ? parseInt(program.args[0]) : parseInt(program.args[1]), project = program.args.length === 2 ? program.args[0] : null; +if (program.I) { + type = 'issue'; +} else if (program.M) { + type = 'merge_request'; +} + if (program.args.length < 2 && !config.get('project')) Cli.error('No project set'); diff --git a/src/gtt.js b/src/gtt.js index a5b41ad..52f7289 100755 --- a/src/gtt.js +++ b/src/gtt.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -const version = '1.6.10'; +const version = '1.7.2'; const program = require('commander'); program @@ -9,6 +9,7 @@ program .command('create [project] [title]', 'start monitoring time for the given project and create a new issue or merge request with the given title') .command('status', 'shows if time monitoring is running') .command('stop', 'stop monitoring time') + .command('resume [project]', 'resume monitoring time for last stopped record') .command('cancel', 'cancel and discard active monitoring time') .command('log', 'log recorded time records') .command('sync', 'sync local time records to GitLab') diff --git a/src/include/cli.js b/src/include/cli.js index 49552de..697dbcb 100755 --- a/src/include/cli.js +++ b/src/include/cli.js @@ -241,7 +241,7 @@ class cli { if (this.data.project) return this.data.project; - let projects = _.uniq(_.filter(this.args, arg => !parseInt(arg))); + let projects = _.uniq(_.filter(this.args, arg => !Number.isNaN(new Number(arg)))); this.args = _.difference(this.args, projects); return this.data.project = projects; diff --git a/src/include/config.js b/src/include/config.js index e536e31..d83b82b 100755 --- a/src/include/config.js +++ b/src/include/config.js @@ -1,14 +1,17 @@ const moment = require('moment'); +const _ = require('underscore'); const Time = require('./../models/time'); const dates = ['from', 'to']; +const objectsWithDefaults = ['timeFormat', 'columns']; const defaults = { type: 'project', subgroups: false, url: 'https://gitlab.com/api/v4', token: false, proxy: false, + insecure: false, project: false, from: "1970-01-01", to: moment().format(), @@ -36,7 +39,8 @@ const defaults = { _perPage: 100, _parallel: 10, _verbose: false, - _checkToken: false + _checkToken: false, + _skipDescriptionParsing: false }; /** @@ -47,7 +51,7 @@ class config { * construct */ constructor() { - this.data = defaults; + this.data = _.extend({}, defaults); } /** @@ -70,21 +74,27 @@ class config { /** * get a value by the given key * @param key + * @param subKey * @returns {*} */ - get(key) { - if (!dates.includes(key)) return this.data[key]; + get(key, subKey = false) { + if (dates.includes(key)) + return moment(this.data[key]); - return moment(this.data[key]); + if (objectsWithDefaults.includes(key) && _.isObject(this.data[key])) + return subKey && this.data[key][subKey] ? this.data[key][subKey] : defaults[key]; + + return this.data[key]; } /** * get a human readable version of the given time * @param input + * @param timeFormat * @returns {string} */ - toHumanReadable(input) { - return Time.toHumanReadable(input, this.get('hoursPerDay'), this.get('timeFormat')); + toHumanReadable(input, timeFormat = false) { + return Time.toHumanReadable(input, this.get('hoursPerDay'), this.get('timeFormat', timeFormat)); } } diff --git a/src/include/file-config.js b/src/include/file-config.js index 0f5e8af..a7a646c 100755 --- a/src/include/file-config.js +++ b/src/include/file-config.js @@ -82,9 +82,9 @@ class fileConfig extends config { } assertGlobalConfig() { - if (!fs.existsSync(this.globalDir)) fs.mkdirSync(this.globalDir, '0644', true); - if (!fs.existsSync(this.frameDir)) fs.mkdirSync(this.frameDir, '0744', true); - if (!fs.existsSync(this.cacheDir)) fs.mkdirSync(this.cacheDir, '0744', true); + if (!fs.existsSync(this.globalDir)) fs.mkdirSync(this.globalDir, '0750', true); + if (!fs.existsSync(this.frameDir)) fs.mkdirSync(this.frameDir, '0750', true); + if (!fs.existsSync(this.cacheDir)) fs.mkdirSync(this.cacheDir, '0750', true); if (!fs.existsSync(this.global)) fs.appendFileSync(this.global, ''); } @@ -133,4 +133,4 @@ class fileConfig extends config { } } -module.exports = fileConfig; \ No newline at end of file +module.exports = fileConfig; diff --git a/src/include/filesystem.js b/src/include/filesystem.js index 747bc7b..7de072f 100755 --- a/src/include/filesystem.js +++ b/src/include/filesystem.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const open = require('open'); const find = require('find-in-files'); +const child_process = require('child_process'); class filesystem { static find(pattern, dir) { @@ -22,7 +23,15 @@ class filesystem { } static open(file) { - return open(file); + let editor = process.env.VISUAL; + + if (editor || (editor = process.env.EDITOR)) { + return child_process.spawn(editor, [file], { + stdio: 'inherit' + }); + } else { + return open(file); + } } static join(...args) { @@ -38,4 +47,4 @@ class filesystem { } } -module.exports = filesystem; \ No newline at end of file +module.exports = filesystem; diff --git a/src/include/tasks.js b/src/include/tasks.js index 6a05a16..79a3b16 100755 --- a/src/include/tasks.js +++ b/src/include/tasks.js @@ -115,7 +115,7 @@ class tasks { resource.createTime(Math.ceil(time)) .then(() => resource.getNotes()) .then(() => { - if(frame.resource.new) { + if (frame.resource.new) { delete frame.resource.new; frame.resource.id = resource.data.iid; } @@ -174,6 +174,28 @@ class tasks { }); } + /** + * + * @returns {Promise} + */ + resume() { + return new Promise((resolve, reject) => { + let project = this.config.get('project'), + frames = new FrameCollection(this.config); + + if (!project) return reject("No project set."); + + frames + .filter(frame => frame.project === project) + .sort((a, b) => moment(a.stop).isBefore(moment(b.stop)) ? 1 : -1); + + let last = frames.frames[0]; + this.start(last.project, last.resource.type, last.resource.id) + .then(frame => resolve(frame)) + .catch(error => reject(error)); + }); + } + /** * * @param project diff --git a/src/models/base.js b/src/models/base.js index dac2c00..3b0fcc0 100755 --- a/src/models/base.js +++ b/src/models/base.js @@ -19,6 +19,7 @@ class base { this._perPage = this.config ? this.config.get('_perPage') : 100; this._parallel = this.config ? this.config.get('_parallel') : 4; this._proxy = this.config && this.config.get('proxy') ? this.config.get('proxy') : undefined; + this._insecure = this.config && this.config.get('unsecure') ? this.config.get('unsecure') : false; } /** @@ -33,6 +34,7 @@ class base { return request.post(`${this.url}${path}`, { json: true, body: data, + insecure: this._insecure, proxy: this._proxy, resolveWithFullResponse: true, headers: { @@ -54,6 +56,7 @@ class base { return request(`${this.url}${path}`, { json: true, + insecure: this._insecure, proxy: this._proxy, resolveWithFullResponse: true, headers: { diff --git a/src/models/frameCollection.js b/src/models/frameCollection.js index 8ebd491..96f19f2 100755 --- a/src/models/frameCollection.js +++ b/src/models/frameCollection.js @@ -18,6 +18,12 @@ class frameCollection extends Base { .filter(frame => frame); } + sort(func) { + this.frames.sort(func); + + return this; + } + filter(func) { let arr = []; @@ -29,9 +35,11 @@ class frameCollection extends Base { if (func(frame)) { arr.push(frame); } - - this.frames = arr; }); + + this.frames = arr; + + return this; } forEach(iterator) { diff --git a/src/models/hasTimes.js b/src/models/hasTimes.js index f4c3c9d..400cae3 100755 --- a/src/models/hasTimes.js +++ b/src/models/hasTimes.js @@ -55,9 +55,11 @@ class hasTimes extends Base { * @returns {Promise} */ getTimes() { - let times = []; - let timeSpent = 0; - let timeUsers = {}; + let times = [], + timeSpent = 0, + totalTimeSpent = 0, + timeUsers = {}, + timeFormat = this.config.get('timeFormat', this._type); // sort by created at this.notes.sort((a, b) => { @@ -66,24 +68,33 @@ class hasTimes extends Base { }); let promise = this.parallel(this.notes, (note, done) => { - let created = moment(note.created_at), match, subMatch, removeMatch; + let created = moment(note.created_at), match, subMatch; - if ( + + if ( // // filter out user notes !note.system || + // filter out notes that are no time things + !(match = regex.exec(note.body)) && !(subMatch = subRegex.exec(note.body)) && !removeRegex.exec(note.body) + ) return done(); + + // create a time string and a time object + let timeString = match ? match[1] : (subMatch ? `-${subMatch[1]}` : `-${Time.toHumanReadable(timeSpent, this.config.get('hoursPerDay'))}`); + let time = new Time(timeString, note, this, this.config); + + // add to total time spent + totalTimeSpent += time.seconds; + + if ( // // only include times by the configured user (this.config.get('user') && this.config.get('user') !== note.author.username) || // filter out times that are not in the given time frame - !(created.isSameOrAfter(moment(this.config.get('from'))) && created.isSameOrBefore(moment(this.config.get('to')))) || - // filter out notes that are no time things - !(match = regex.exec(note.body)) && !(subMatch = subRegex.exec(note.body)) && !(removeMatch = removeRegex.exec(note.body)) + !(created.isSameOrAfter(moment(this.config.get('from'))) && created.isSameOrBefore(moment(this.config.get('to')))) ) return done(); if (!timeUsers[note.author.username]) timeUsers[note.author.username] = 0; - let timeString = match ? match[1] : (subMatch ? `-${subMatch[1]}` : `-${Time.toHumanReadable(timeSpent, this.config.get('hoursPerDay'))}`); - let time = new Time(timeString, note, this, this.config); - + // add to time spent & add to user specific time spent timeSpent += time.seconds; timeUsers[note.author.username] += time.seconds; @@ -93,8 +104,33 @@ class hasTimes extends Base { done(); }); + promise = promise.then(() => new Promise(resolve => { + let created = moment(this.data.created_at); + + if ( // + // skip if description parsing is disabled + this.config.get('_skipDescriptionParsing') || + // or time stats are not available + !this.data.time_stats || !this.data.time_stats.total_time_spent || + // or the total time matches + !this.data.time_stats || + totalTimeSpent === this.data.time_stats.total_time_spent || + // or the user is filtered out + (this.config.get('user') && this.config.get('user') !== this.data.author.username) || + // or the issue is not within the given time frame + !(created.isSameOrAfter(moment(this.config.get('from'))) && created.isSameOrBefore(moment(this.config.get('to')))) + ) return resolve(); + + let difference = this.data.time_stats.total_time_spent - totalTimeSpent, + note = Object.assign({noteable_type: this._typeSingular}, this.data); + + times.unshift(new Time(Time.toHumanReadable(difference, this.config.get('hoursPerDay')), note, this, this.config)); + + resolve(); + })); + promise.then(() => { - _.each(timeUsers, (time, name) => this[`time_${name}`] = this.config.toHumanReadable(time)); + _.each(timeUsers, (time, name) => this[`time_${name}`] = Time.toHumanReadable(time, this.config.get('hoursPerDay'), timeFormat)); this.timeSpent = timeSpent; this.times = times }); diff --git a/src/models/issue.js b/src/models/issue.js index 9171778..ddf7c5c 100755 --- a/src/models/issue.js +++ b/src/models/issue.js @@ -87,20 +87,24 @@ class issue extends hasTimes { } get spent() { - return this.config.toHumanReadable(this.timeSpent); + return this.config.toHumanReadable(this.timeSpent, this._type); } get total_spent() { - return this.stats ? this.config.toHumanReadable(this.stats.total_time_spent) : null; + return this.stats ? this.config.toHumanReadable(this.stats.total_time_spent, this._type) : null; } get total_estimate() { - return this.stats ? this.config.toHumanReadable(this.stats.time_estimate) : null; + return this.stats ? this.config.toHumanReadable(this.stats.time_estimate, this._type) : null; } get _type() { return 'issues'; } + + get _typeSingular() { + return 'Issue'; + } } module.exports = issue; \ No newline at end of file diff --git a/src/models/mergeRequest.js b/src/models/mergeRequest.js index 8a354c1..5ea2f95 100755 --- a/src/models/mergeRequest.js +++ b/src/models/mergeRequest.js @@ -80,20 +80,24 @@ class mergeRequest extends hasTimes { } get spent() { - return this.config.toHumanReadable(this.timeSpent); + return this.config.toHumanReadable(this.timeSpent, this._type); } get total_spent() { - return this.stats ? this.config.toHumanReadable(this.stats.total_time_spent) : null; + return this.stats ? this.config.toHumanReadable(this.stats.total_time_spent, this._type) : null; } get total_estimate() { - return this.stats ? this.config.toHumanReadable(this.stats.time_estimate) : null; + return this.stats ? this.config.toHumanReadable(this.stats.time_estimate, this._type) : null; } get _type() { return 'merge_requests'; } + + get _typeSingular() { + return 'Merge Request'; + } } module.exports = mergeRequest; \ No newline at end of file diff --git a/src/models/owner.js b/src/models/owner.js index 3706ada..55bdd68 100755 --- a/src/models/owner.js +++ b/src/models/owner.js @@ -36,11 +36,13 @@ class owner extends Base { */ getGroup() { return new Promise((resolve, reject) => { - this.get(`groups/?search=${encodeURIComponent(this.config.get('project'))}`) - .then(group => { - if (group.body.length === 0) return reject('Group not found'); - let filtered = group.body.filter(u => u.path === this.config.get('project')); - if (filtered.length === 0) return reject(); + this.get(`groups`) + .then(groups => { + if (groups.body.length === 0) return reject('Group not found'); + groups = groups.body; + + let filtered = groups.filter(group => group.full_path === this.config.get('project')); + if (filtered.length === 0) return reject('Group not found'); this.groups = this.groups.concat(filtered); resolve(); }) @@ -57,8 +59,10 @@ class owner extends Base { this.get(`groups`) .then(groups => { if (groups.body.length === 0) return resolve(); - let filtered = groups.body.filter(u => this.groups.map(g => g.id).indexOf(u.parent_id) !== -1); + + let filtered = this._filterGroupsByParents(groups.body, this.groups.map(g => g.id)); if (filtered.length === 0) return resolve(); + this.groups = this.groups.concat(filtered); resolve(); }) @@ -66,6 +70,18 @@ class owner extends Base { }); } + _filterGroupsByParents(groups, parents) { + let filtered = groups.filter(group => { + return parents.indexOf(group.parent_id) !== -1; + }); + + if (filtered.length !== 0) { + filtered = filtered.concat(this._filterGroupsByParents(groups, filtered.map(g => g.id))); + } + + return filtered; + } + // /** // * query and set the user // * @returns {Promise} diff --git a/src/models/time.js b/src/models/time.js index 29a3eb9..7bd267e 100755 --- a/src/models/time.js +++ b/src/models/time.js @@ -5,6 +5,8 @@ const defaultTimeFormat = '[%sign][%days>d ][%hours>h ][%minutes>m ][%seconds>s] const mappings = ['complete', 'sign', 'weeks', 'days', 'hours', 'minutes', 'seconds']; const regex = /^(?:([-])\s*)?(?:(\d+)w\s*)?(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s\s*)?$/; const conditionalRegex = /(\[\%([^\>\]]*)\>([^\]]*)\])/ig; +const roundedRegex = /(\[\%([^\>\]]*)\:([^\]]*)\])/ig; +const conditionalSimpleRegex = /([0-9]*)\>(.*)/ig; const defaultRegex = /(\[\%([^\]]*)\])/ig; Number.prototype.padLeft = function (n, str) { @@ -61,7 +63,7 @@ class time { } get _timeFormat() { - return this.config && this.config.get('timeFormat') ? this.config.get('timeFormat') : ''; + return this.config && this.config.get('timeFormat', 'records') ? this.config.get('timeFormat', 'records') : ''; } get _hoursPerDay() { @@ -121,6 +123,20 @@ class time { inserts.seconds = ((input % secondsInADay) % secondsInAnHour) % secondsInAMinute; inserts.Seconds = inserts.seconds.padLeft(2, 0); + // rounded + while ((match = roundedRegex.exec(format)) !== null) { + if (match.index === roundedRegex.lastIndex) roundedRegex.lastIndex++; + let time, conditionalMatch, decimals = match[3]; + + if ((conditionalMatch = conditionalSimpleRegex.exec(decimals)) !== null) { + decimals = conditionalMatch[1] + } + + decimals = parseInt(decimals); + time = Math.ceil(inserts[match[2]] * Math.pow(10, decimals)) / Math.pow(10, decimals); + output = output.replace(match[0], time !== 0 && conditionalMatch ? time + conditionalMatch[2] : time); + } + // conditionals while ((match = conditionalRegex.exec(format)) !== null) { if (match.index === conditionalRegex.lastIndex) conditionalRegex.lastIndex++; diff --git a/src/output/base.js b/src/output/base.js index ff05b94..f2fcb73 100755 --- a/src/output/base.js +++ b/src/output/base.js @@ -90,7 +90,7 @@ class base { */ toFile(file, resolve) { fs.writeFileSync(file, this.out); - if(resolve) resolve(); + if (resolve) resolve(); } /** @@ -123,12 +123,12 @@ class base { }); this.times = times; - this.users = _.mapObject(users, user => this.config.toHumanReadable(user)); - this.projects = _.mapObject(projects, project => this.config.toHumanReadable(project)); + this.users = _.mapObject(users, user => this.config.toHumanReadable(user, 'stats')); + this.projects = _.mapObject(projects, project => this.config.toHumanReadable(project, 'stats')); this.stats = { - 'total estimate': this.config.toHumanReadable(totalEstimate), - 'total spent': this.config.toHumanReadable(totalSpent), - 'spent': this.config.toHumanReadable(spent) + 'total estimate': this.config.toHumanReadable(totalEstimate, 'stats'), + 'total spent': this.config.toHumanReadable(totalSpent, 'stats'), + 'spent': this.config.toHumanReadable(spent, 'stats') }; }