Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary project support #12

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,27 @@ $ toggl --help
--set-background - set color theme. Choose more readible: dark or light

Commands:
c current - see details of currently running time entry (if any).
l list [amount|when] - list last <amount> of time entries (default: 8) or <when> (see below)
s smart [name|number] - start or stop the entry, whatever makes more sense.
start [name|number] - start new time entry with the given name, or resume if number is given.
stop - stop running entry.
r rename <new-name> - rename currently running entry to <new-name>.
b browser - open Toggl timer in default browser.


Note:
c current - see details of currently running time entry (if any).
l list [amount|when] - list last <amount> of time entries (default: 8) or <when> (see Notes)
s smart [properties] [name|number] - start or stop the entry, whatever makes more sense.
start [properties] [name|number] - start new time entry with the given name, or resume if number is given.
stop - stop running entry.
r rename <new-name> - rename currently running entry to <new-name>.
pr projects - list existing projects.
cl clients - list existing clients.
b browser - open Toggl timer in default browser.

Properties:
New tasks can have properties defined as the first parameters after "start"
→ project:, proj: - set the project for this task, can be either a number, or partial name of the project
→ billable:, bill: - if passed "yes", will mark the task as billable
→ tag: - can add tags to the task, can be called multiple times

Notes:
→ Values in [square brackets] are optional.
→ <when> is one of:
today, yesterday, last Monday, last tue, etc…


$ toggl --examples

Set default token for all future launches:
Expand Down
30 changes: 22 additions & 8 deletions src/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,21 @@ me.getLong = function ({pkg, pad, chalk: {white, black}}) {
` --set-background - set color theme. Choose more readible: ${white('dark')} or ${black('light')}`,
'',
'Commands:',
' c current - see details of currently running time entry (if any).',
' l list [amount|when] - list last <amount> of time entries (default: 8) or <when> (see Notes)',
` s smart [name|number] - start or stop the entry, whatever makes more sense.`,
' start [name|number] - start new time entry with the given name, or resume if number is given.',
' stop - stop running entry.',
' r rename <new-name> - rename currently running entry to <new-name>.',
' b browser - open Toggl timer in default browser.',
' c current - see details of currently running time entry (if any).',
' l list [amount|when] - list last <amount> of time entries (default: 8) or <when> (see Notes)',
` s smart [properties] [name|number] - start or stop the entry, whatever makes more sense.`,
' start [properties] [name|number] - start new time entry with the given name, or resume if number is given.',
' stop - stop running entry.',
' r rename <new-name> - rename currently running entry to <new-name>.',
' pr projects - list existing projects.',
' cl clients - list existing clients.',
' b browser - open Toggl timer in default browser.',
'',
'Properties:',
' New tasks can have properties defined as the first parameters after "start"',
' → project:, proj: - set the project for this task, can be either a number, or partial name of the project',
' → billable:, bill: - if passed "yes", will mark the task as billable',
' → tag: - can add tags to the task, can be called multiple times',
'',
'Notes:',
' → Values in [square brackets] are optional.',
Expand Down Expand Up @@ -106,13 +114,19 @@ me.getExamples = function ({pad, chalk: {bold}}) {
bold('Start a new task named "Writing toggl-cli docs":'),
' $ toggl start Writing toggl-cli docs',
'',
bold('Start a new task named "Writing toggl-cli docs" in the "toggl" project, with the "test" tag:'),
' $ toggl start tag:test proj:toggl Writing toggl-cli docs',
'',
bold('Resume last running time entry:'),
' $ toggl start 1',
'',
bold('List entries from the last Friday:'),
' $ toggl list last friday',
'',
bold('Alias toggl for work:'),
bold('List all the projects in the workspace:'),
' $ toggl pr',
'',
bold('Alias toggl for work:'),
` $ echo "toggl2='toggl --token <work-token>'" >> ~/.bashrc`,
' $ toggl list yesterday # yesterday entries from your private account',
' $ toggl2 list # last 8 entries from your work account'
Expand Down
107 changes: 106 additions & 1 deletion src/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@ me.list = function ({views, toggl}, token, params) {
});
};

me.projects = function ({toggl, views}, token) {
Promise.resolve()
.then(() => {
toggl.getProjects(token, {})
.then(views.projects)
.then(views.pad)
.then(views.log)
.catch(views.err);
});
};

me.clients = function ({toggl, views}, token) {
Promise.resolve()
.then(() => {
toggl.fetchClientList(token)
.then(views.clients)
.then(views.pad)
.then(views.log)
.catch(views.err);
});
};

me.start = function ({toggl, views}, token, description) {
Promise.resolve(description)
.then(which => {
Expand All @@ -76,13 +98,88 @@ me.start = function ({toggl, views}, token, description) {
.then(entries => entries[limit - 1])
.then(({description, pid, billable, tags}) => ({description, pid, billable, tags}));
})
.then(me.recurseDescriptionTags)
.then(entryData => me.parseProjectToken(token, entryData))
.then(entryData => toggl.startTimeEntry(token, entryData))
.then(views.started)
.then(views.pad)
.then(views.log)
.catch(views.err);
};

me.recurseDescriptionTags = function (_, time_entry) {
const {description} = time_entry;

const words = description.split(' ');
const firstWord = words.shift();

if (firstWord.indexOf(':') !== -1) {
const [key, value] = firstWord.split(':');

switch(key) {
case 'project': case 'proj':
time_entry.project_token = value;
time_entry.description = words.join(' ');
return me.recurseDescriptionTags(time_entry);
break;

case 'billable': case 'bill':
time_entry.billable = (value === 'yes' || value === '1');
time_entry.description = words.join(' ');
return me.recurseDescriptionTags(time_entry);

case 'tag':
if (!Array.isArray(time_entry.tags)) time_entry.tags = [];
time_entry.tags.push(value);
time_entry.description = words.join(' ');
return me.recurseDescriptionTags(time_entry);
}
} else {
return time_entry;
}
};

me.parseProjectToken = function({toggl}, token, time_entry) {
const projectToken = time_entry.project_token;

// Time Entry has no project defined
if (projectToken === undefined) {
return time_entry;
}

const projectTokenInt = parseInt(projectToken);
// The project defined is probably Toggl project ID
if (!isNaN(projectTokenInt) && projectTokenInt > 16) {
time_entry.pid = projectToken;
return time_entry;
}

// The project defined is either an internal number, or a partial string
return Promise.resolve()
.then(() => toggl.fetchProjectsList(token))
.then((projects) => {
if (!isNaN(projectTokenInt)) {
const projectIndex = projectTokenInt - 1;
time_entry.pid = projects[projectIndex].id;
delete time_entry.project_token;
} else {
const foundProject = projects.find((item) => {
const itemName = item.name.toLowerCase().replace('_', ' ');
const tokenName = projectToken.toLowerCase().replace('_', ' ');

return (itemName.indexOf(tokenName) !== -1)
});

if (foundProject !== undefined) {
time_entry.pid = foundProject.id;
delete time_entry.project_token;
}
}

return time_entry;
});
};

me.rename = function ({toggl, views}, token, newName) {
me.getCurrent(token)
.then(entry => {
Expand Down Expand Up @@ -172,7 +269,15 @@ me.execute = function ({open, help, views, toggl}, {cmd, token}) {
open(toggl.TIMER_URL);
break;

default:
case 'projects': case 'pr':
me.projects(token);
break;

case 'clients': case 'cl':
me.clients(token);
break;

default:
views.log(help.getHint());
}
};
Expand Down
13 changes: 11 additions & 2 deletions src/toggl.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ const DEFS = {
details: {
endpoint: ['clients', ':id'],
method: 'GET'
}
},
},
list: {
endpoint: ['me', 'clients'],
method: 'GET',
version: 'v9'
}
},
project: {
details: {
endpoint: ['projects', ':id'],
Expand Down Expand Up @@ -164,6 +169,10 @@ me.fetchClients = function (_, token, ids) {
return me.fetchMany(me.fetchClient, token, ids);
};

me.fetchClientList = function (_, token) {
return me.request(token, DEFS.client.list);
};

me.fetchTimeEntries = function (_, token, {days = 90}) {
const params = {
start_date: new Date(new Date().setDate(new Date().getDate() - days)).toISOString()
Expand Down
32 changes: 32 additions & 0 deletions src/views.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ me.list = function (_, entries) {
return entries.map(me.listLine);
};

me.projects = function (_, entries) {
return entries.map(me.projectLine);
};

me.clients = function (_, entries) {
return entries.map(me.clientLine);
};

me.started = function ({chalk}, {id, description, start}) {
return [
chalk.green('Started'),
Expand Down Expand Up @@ -157,6 +165,30 @@ me.metaLine = function (_, {id, project}) {
return line.join(' ');
};

me.projectLine = function({chalk}, {_id, name, client}, idx) {
idx = _id || idx;

const response = [
chalk.blue(`${(++idx < 10 ? ' ' : '') + idx})`),
name
];

if (client) {
response.push(chalk.red(`[${client.name}]`));
}

return response.join(' ');
};

me.clientLine = function({chalk}, {_id, name}, idx) {
idx = _id || idx;

return [
chalk.blue(`${(++idx < 10 ? ' ' : '') + idx})`),
name
].join(' ');
};

// elements
me.getId = function (_, {id}, label = 'id') {
return me.dim(`[${label}:#${id}]`);
Expand Down