├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── bin
└── timetracking
├── lib
└── README.md
├── package-lock.json
├── package.json
├── src
├── core
│ ├── task-status.ts
│ ├── task.ts
│ └── timetracking.ts
└── main.ts
├── test
└── test.js
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### GIS ###
3 | *.gpx
4 | *.kml
5 |
6 | ### Node ###
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (http://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Typescript v1 declaration files
46 | typings/
47 |
48 | # Optional npm cache directory
49 | .npm
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Optional REPL history
55 | .node_repl_history
56 |
57 | # Output of 'npm pack'
58 | *.tgz
59 |
60 | # Yarn Integrity file
61 | .yarn-integrity
62 |
63 | # dotenv environment variables file
64 | .env
65 |
66 |
67 | ### VisualStudioCode ###
68 | .vscode/*
69 | !.vscode/settings.json
70 | !.vscode/tasks.json
71 | !.vscode/launch.json
72 | !.vscode/extensions.json
73 | .history
74 |
75 | ### Windows ###
76 | # Windows thumbnail cache files
77 | Thumbs.db
78 | ehthumbs.db
79 | ehthumbs_vista.db
80 |
81 | # Folder config file
82 | Desktop.ini
83 |
84 | # Recycle Bin used on file shares
85 | $RECYCLE.BIN/
86 |
87 | # Windows Installer files
88 | *.cab
89 | *.msi
90 | *.msm
91 | *.msp
92 |
93 | # Windows shortcuts
94 | *.lnk
95 |
96 |
97 | ### Others ###
98 | lib/**/*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 |
2 | ### GIS ###
3 | *.gpx
4 | *.kml
5 |
6 | ### Node ###
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (http://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Typescript v1 declaration files
46 | typings/
47 |
48 | # Optional npm cache directory
49 | .npm
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Optional REPL history
55 | .node_repl_history
56 |
57 | # Output of 'npm pack'
58 | *.tgz
59 |
60 | # Yarn Integrity file
61 | .yarn-integrity
62 |
63 | # dotenv environment variables file
64 | .env
65 |
66 |
67 | ### VisualStudioCode ###
68 | .vscode/*
69 | !.vscode/settings.json
70 | !.vscode/tasks.json
71 | !.vscode/launch.json
72 | !.vscode/extensions.json
73 | .history
74 |
75 | ### Windows ###
76 | # Windows thumbnail cache files
77 | Thumbs.db
78 | ehthumbs.db
79 | ehthumbs_vista.db
80 |
81 | # Folder config file
82 | Desktop.ini
83 |
84 | # Recycle Bin used on file shares
85 | $RECYCLE.BIN/
86 |
87 | # Windows Installer files
88 | *.cab
89 | *.msi
90 | *.msm
91 | *.msp
92 |
93 | # Windows shortcuts
94 | *.lnk
95 |
96 |
97 | ### NPM ###
98 | tsconfig.json
99 | tslint.json
100 | /src/**/*
101 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [v1.3.0](https://github.com/mvmjacobs/timetracking/releases/tag/v1.3.0) | 2019-02-03
3 |
4 | ### Improvements
5 | * **test**: Add initial unit test coverage. ([#22](https://github.com/mvmjacobs/timetracking/issues/22)) ([58255fb1](https://github.com/mvmjacobs/timetracking/commit/58255fb1))
6 |
7 |
8 | ## [v1.2.2](https://github.com/mvmjacobs/timetracking/releases/tag/v1.2.2) | 2017-12-11
9 |
10 | ### Bug Fixes
11 | * **pause**: Fixed the command that was finishing the task instead of pausing. ([#23](https://github.com/mvmjacobs/timetracking/issues/23)) ([074242e](https://github.com/mvmjacobs/timetracking/commit/074242e))
12 |
13 |
14 | ## [v1.2.1](https://github.com/mvmjacobs/timetracking/releases/tag/v1.2.1) | 2017-10-24
15 |
16 | ### Bug Fixes
17 | * **finish|pause**: Fixed commands when the `-t` option is not enter. ([#21](https://github.com/mvmjacobs/timetracking/issues/21)) ([beefa0d](https://github.com/mvmjacobs/timetracking/commit/beefa0d))
18 |
19 |
20 | ## [v1.2.0](https://github.com/mvmjacobs/timetracking/releases/tag/v1.2.0) | 2017-10-23
21 |
22 | ### Improvements
23 | * **config**: Default date format in config file was changed to `MM/dd/yyyy`. ([#19](https://github.com/mvmjacobs/timetracking/issues/19)) ([96d9474](https://github.com/mvmjacobs/timetracking/commit/96d9474))
24 | * **start**: Now the task description is informed from an option. [See here](https://github.com/mvmjacobs/timetracking#start) to know how to use it. ([#16](https://github.com/mvmjacobs/timetracking/issues/16)) ([f7cbc67](https://github.com/mvmjacobs/timetracking/commit/f7cbc67))
25 | * **finish|pause**: Now you can stop a task with date and time. [See here](https://github.com/mvmjacobs/timetracking#pause) to know how. ([#18](https://github.com/mvmjacobs/timetracking/issues/18)) ([81f4c69](https://github.com/mvmjacobs/timetracking/commit/81f4c69), [40ced82](https://github.com/mvmjacobs/timetracking/commit/40ced82))
26 |
27 | ### Bug Fixes
28 | * **finish|pause**: Fix console message when the task has already been stopped. ([#20](https://github.com/mvmjacobs/timetracking/issues/20)) ([d2fb57c](https://github.com/mvmjacobs/timetracking/commit/d2fb57c))
29 |
30 |
31 | ## [v1.1.1](https://github.com/mvmjacobs/timetracking/releases/tag/v1.1.1) | 2017-09-04
32 |
33 | ### Improvements
34 | * **list**: Now displays the task status. ([#14](https://github.com/mvmjacobs/timetracking/issues/14)) ([61d25ce](https://github.com/mvmjacobs/timetracking/commit/61d25ce))
35 |
36 | ### Bug Fixes
37 | * **add**: Fix expression to validate `[time_spent]` parameter. ([#13](https://github.com/mvmjacobs/timetracking/issues/13)) ([9a495f1](https://github.com/mvmjacobs/timetracking/commit/9a495f1))
38 |
39 |
40 | ## [v1.1.0](https://github.com/mvmjacobs/timetracking/releases/tag/v1.1.0) | 2017-09-04
41 |
42 | ### Features
43 | * **add**: New command! Now you can add tasks running the command `add`. [Here](https://github.com/mvmjacobs/timetracking/tree/master#add) you can see how to use. ([#8](https://github.com/mvmjacobs/timetracking/issues/8)) ([a1f5a6](https://github.com/mvmjacobs/timetracking/commit/a1f5a6))
44 | * **pause**: Now `task` parameter is optional, you can pause all tasks in progress. ([#4](https://github.com/mvmjacobs/timetracking/issues/4)) ([4ab557f](https://github.com/mvmjacobs/timetracking/commit/4ab557f))
45 | * **finish**: Now `task` parameter is optional, you can complete all tasks in progress. ([#4](https://github.com/mvmjacobs/timetracking/issues/4)) ([4ab557f](https://github.com/mvmjacobs/timetracking/commit/4ab557f))
46 |
47 |
48 | ## [v1.0.1](https://github.com/mvmjacobs/timetracking/releases/tag/v1.0.1) | 2017-09-02
49 |
50 | ### Bug Fixes
51 | * Now when you type `tm` command will show help section.
52 | * Tasks list now displays the hours in `HH:mm` format instead `H:mm`.
53 | * Date format used is the same as the one saved in the settings file.
54 |
55 |
56 | # [v1.0.0](https://github.com/mvmjacobs/timetracking/releases/tag/v1.0.0) | 2017-08-27
57 |
58 | * Initial release.
59 | * Basic options: start, pause, finish, list.
60 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Want to contribute?
2 |
3 | Found a bug or have an idea to improvement? First [open an issue](https://github.com/mvmjacobs/timetracking/issues) and we will discuss about it.
4 |
5 | But if you want to resolve an issue, first make sure the issue is not tagged with `committed` neither `in progress`. After that, follow the instructions below to make a pull request.
6 |
7 | ### How to make a clean pull request
8 |
9 | - Create a personal fork of the project on Github.
10 | - Clone the fork on your local machine. Your remote repo on Github is called `origin`.
11 | - Add the original repository as a remote called `upstream`.
12 | ```
13 | $ git remote add upstream https://github.com/mvmjacobs/timetracking.git
14 | ```
15 | - If you created your fork a while ago be sure to pull upstream changes into your local repository.
16 | - Create a new branch to work on! Branch from `dev-z.x.y` (current version number) if it exists, else from `master`.
17 | - Implement/fix your feature, comment your code.
18 | - Add or change the documentation as needed.
19 | - Use [keywords](https://help.github.com/articles/closing-issues-using-keywords/) in your commit message. To bug fixes use `(fixes #n)`, to improvements and new features use `(resolves #n)`. (When `n` is the issue number).
20 | - Squash your commits into a single commit with git's [interactive rebase](https://help.github.com/articles/interactive-rebase). Create a new branch if necessary.
21 | - Push your branch to your fork on Github, the remote `origin`.
22 | - From your fork open a pull request in the correct branch. Target the project's `dev-z.x.y` (current version number) branch if there is one, else go for `master`
23 | - Once the pull request is approved and merged you can pull the changes from `upstream` to your local repo and delete
24 | your extra branch(es).
25 |
26 | And last but not least: Always write your commit messages in the present tense. Your commit message should describe what the commit, when applied, does to the code – not what you did to the code.
27 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Marcos Vinicius Jacobs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Timetracking :hourglass:
2 | [](https://www.npmjs.com/package/timetracking)
3 | [](https://github.com/mvmjacobs/timetracking/blob/master/LICENSE.md)
4 | [](https://www.npmjs.com/package/timetracking)
5 |
6 | **This project is not maintained anymore :(**
7 |
8 | A simple command line app to track your time. It was inspired by [Timet](https://github.com/fabiorogeriosj/timet) and [Time tracker](https://github.com/danibram/time-tracker-cli).
9 |
10 |
11 | ## What's new?
12 | [**Here**](https://github.com/mvmjacobs/timetracking/blob/master/CHANGELOG.md) you can find a complete list of improvements and fixes made in the current version.
13 |
14 |
15 | ## Installation
16 | ```
17 | $ npm install timetracking -g
18 | ```
19 | Now you can start to call `timetracking` or `tm` command.
20 |
21 | The data and the default config are stored inside `~/.config/configstore/timetracking.json`.
22 |
23 |
24 | ## How to use
25 | [Get started in 5 minutes](https://github.com/mvmjacobs/timetracking/wiki).
26 |
27 |
28 | ## How to contribute
29 | Found a bug? Wrote a cool code? Do you want to help with the documentation? Great! [Learn how to contribute](https://github.com/mvmjacobs/timetracking/blob/master/CONTRIBUTING.md) or [open an issue](https://github.com/mvmjacobs/timetracking/issues) and help make the app even better.
30 |
31 |
32 | ## License
33 | This repository is licensed under the [MIT License](https://github.com/mvmjacobs/timetracking/blob/master/LICENSE.md).
34 |
--------------------------------------------------------------------------------
/bin/timetracking:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 |
4 | require('../lib/main.js')
5 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 | ## Read This!
2 |
3 | **These files are not meant to be edited by hand.**
4 |
5 | Files in this folder are generated automatically when generating the build.
6 |
7 | If you need to make modifications, the respective files should be changed within the repository's top-level `src` directory.
8 |
9 | Running `tsc` will then appropriately update the files in this directory.
10 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "timetracking",
3 | "version": "1.3.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/lodash": {
8 | "version": "4.14.88",
9 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.88.tgz",
10 | "integrity": "sha512-S+7dONqCP8kXqOPCPxra7Vw8FndWS8pNfVkClMSzRCPMjXkA3BaI3ybMBCiSd4y+r1vC49UEjNthnm+fO8J/dw==",
11 | "dev": true
12 | },
13 | "@types/node": {
14 | "version": "8.0.58",
15 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.58.tgz",
16 | "integrity": "sha512-V746iUU7eHNdzQipoACuguDlVhC7IHK8CES1jSkuFt352wwA84BCWPXaGekBd7R5XdNK5ReHONDVKxlL9IreAw=="
17 | },
18 | "colors": {
19 | "version": "1.1.2",
20 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
21 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
22 | },
23 | "commander": {
24 | "version": "2.12.2",
25 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
26 | "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA=="
27 | },
28 | "configstore": {
29 | "version": "3.1.1",
30 | "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz",
31 | "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==",
32 | "requires": {
33 | "dot-prop": "4.2.0",
34 | "graceful-fs": "4.1.11",
35 | "make-dir": "1.1.0",
36 | "unique-string": "1.0.0",
37 | "write-file-atomic": "2.3.0",
38 | "xdg-basedir": "3.0.0"
39 | }
40 | },
41 | "crypto-random-string": {
42 | "version": "1.0.0",
43 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
44 | "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
45 | },
46 | "dot-prop": {
47 | "version": "4.2.0",
48 | "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
49 | "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
50 | "requires": {
51 | "is-obj": "1.0.1"
52 | }
53 | },
54 | "graceful-fs": {
55 | "version": "4.1.11",
56 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
57 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
58 | },
59 | "imurmurhash": {
60 | "version": "0.1.4",
61 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
62 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
63 | },
64 | "is-obj": {
65 | "version": "1.0.1",
66 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
67 | "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
68 | },
69 | "lodash": {
70 | "version": "4.17.4",
71 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
72 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
73 | },
74 | "make-dir": {
75 | "version": "1.1.0",
76 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz",
77 | "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==",
78 | "requires": {
79 | "pify": "3.0.0"
80 | }
81 | },
82 | "moment": {
83 | "version": "2.19.4",
84 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.4.tgz",
85 | "integrity": "sha512-1xFTAknSLfc47DIxHDUbnJWC+UwgWxATmymaxIPQpmMh7LBm7ZbwVEsuushqwL2GYZU0jie4xO+TK44hJPjNSQ=="
86 | },
87 | "pify": {
88 | "version": "3.0.0",
89 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
90 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
91 | },
92 | "signal-exit": {
93 | "version": "3.0.2",
94 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
95 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
96 | },
97 | "typescript": {
98 | "version": "2.6.2",
99 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz",
100 | "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=",
101 | "dev": true
102 | },
103 | "unique-string": {
104 | "version": "1.0.0",
105 | "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
106 | "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
107 | "requires": {
108 | "crypto-random-string": "1.0.0"
109 | }
110 | },
111 | "write-file-atomic": {
112 | "version": "2.3.0",
113 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
114 | "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
115 | "requires": {
116 | "graceful-fs": "4.1.11",
117 | "imurmurhash": "0.1.4",
118 | "signal-exit": "3.0.2"
119 | }
120 | },
121 | "xdg-basedir": {
122 | "version": "3.0.0",
123 | "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
124 | "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "timetracking",
3 | "version": "1.3.0",
4 | "description": "A simple command line app to track your time.",
5 | "keywords": [
6 | "timetracking",
7 | "time",
8 | "tracker"
9 | ],
10 | "author": "@mvmjacobs",
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/mvmjacobs/timetracking"
15 | },
16 | "bugs": {
17 | "url": "https://github.com/mvmjacobs/timetracking/issues"
18 | },
19 | "main": "lib/main.js",
20 | "types": "lib/main",
21 | "bin": {
22 | "tm": "./bin/timetracking",
23 | "timetracking": "./bin/timetracking"
24 | },
25 | "scripts": {
26 | "build": "tsc",
27 | "test": "tsc && mocha",
28 | "pack-build": "tsc && npm pack && move ./*.tgz ./build/",
29 | "install-build-dev": "npm run pack-build && npm install ./build/timetracking-1.3.0.tgz -g"
30 | },
31 | "devDependencies": {
32 | "@types/lodash": "^4.14.88",
33 | "mocha": "^5.2.0",
34 | "typescript": "^2.6.2"
35 | },
36 | "dependencies": {
37 | "@types/node": "^8.0.58",
38 | "colors": "^1.1.2",
39 | "commander": "^2.12.2",
40 | "configstore": "^3.1.1",
41 | "lodash": "^4.17.4",
42 | "moment": "^2.19.4"
43 | }
44 | }
--------------------------------------------------------------------------------
/src/core/task-status.ts:
--------------------------------------------------------------------------------
1 | export enum TaskStatus {
2 | IN_PROGRESS,
3 | PAUSED,
4 | FINISHED
5 | }
6 |
--------------------------------------------------------------------------------
/src/core/task.ts:
--------------------------------------------------------------------------------
1 | // import 3th party packages
2 | import * as _ from 'lodash/array';
3 | import * as moment from 'moment';
4 |
5 | // import models
6 | import { TaskStatus } from './task-status';
7 |
8 | export class Task {
9 | public name: string;
10 | public description: string;
11 | public status: TaskStatus;
12 | public log: any[];
13 |
14 | constructor(name: string, values: any) {
15 | this.name = name;
16 | this.description = values ? values.description : '';
17 | this.status = values ? values.status : TaskStatus.IN_PROGRESS;
18 | this.log = values ? values.log : [];
19 | }
20 |
21 | public setDescription(description: string): void {
22 | this.description = description ? description : this.description ? this.description : '';
23 | }
24 |
25 | public setStatus(status: TaskStatus): void {
26 | this.status = status;
27 | }
28 |
29 | public start(description: string): boolean {
30 | let lastTime = _.last(this.log);
31 | if (lastTime && lastTime.start && !lastTime.stop) {
32 | console.log('This task already has been started.');
33 | return false;
34 | }
35 | this.log.push({
36 | start: moment().format(),
37 | });
38 | this.setDescription(description);
39 | this.setStatus(TaskStatus.IN_PROGRESS);
40 | return true;
41 | }
42 |
43 | public stop(status: TaskStatus, timestamp?: moment.Moment): boolean {
44 | let lastTime = _.last(this.log);
45 | if (this.status === status) {
46 | let msg = status === TaskStatus.FINISHED ? 'completed' : 'paused';
47 | console.log('This task already has been %s.', msg);
48 | return false;
49 | }
50 | if (lastTime && !lastTime.stop) {
51 | this.log[this.log.length - 1].stop = (timestamp || moment()).format();
52 | }
53 | this.setStatus(status);
54 | return true;
55 | }
56 |
57 | public add(date: string, dateFormat: string, hour: number, min: number): boolean {
58 | let stop = moment(date, dateFormat);
59 | stop.add(hour, 'h');
60 | stop.add(min, 'm');
61 | this.log.unshift({
62 | start: moment(date, dateFormat).format(),
63 | stop: stop.format()
64 | });
65 | return true;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/core/timetracking.ts:
--------------------------------------------------------------------------------
1 | // import 3th party packages
2 | import * as colors from 'colors';
3 | import * as Configstore from 'configstore';
4 | import * as _ from 'lodash';
5 | import * as moment from 'moment';
6 |
7 | // import models
8 | import { Task } from './task';
9 | import { TaskStatus } from './task-status';
10 |
11 | export class Timetracking {
12 | public config: any;
13 | private configStore: Configstore;
14 | private tasks: Task[];
15 |
16 | constructor(configStore) {
17 | this.configStore = configStore;
18 | this.tasks = configStore.all.tasks ? configStore.all.tasks : [];
19 | this.config = configStore.all.config ? configStore.all.config : {};
20 | }
21 |
22 | public start(taskName: string, description: string, pauseOthers: boolean): void {
23 | if (pauseOthers) {
24 | if (this.tasks && this.tasks.length > 0) {
25 | this.tasks.forEach((t, i = 0) => {
26 | if (t.name !== taskName && t.status === TaskStatus.IN_PROGRESS) {
27 | let tsk = this.getTask(t.name);
28 | if (tsk.stop(TaskStatus.PAUSED)) {
29 | this.tasks[i] = tsk;
30 | }
31 | }
32 | i++;
33 | });
34 | }
35 | }
36 | let task = this.getTask(taskName);
37 | if (task.start(description)) {
38 | let idx = _.findIndex(this.tasks, ['name', task.name]);
39 | if (idx === -1) {
40 | this.tasks.push(task);
41 | } else {
42 | this.tasks[idx] = task;
43 | }
44 | if (this.updateTasks()) {
45 | console.log('Task %s started.', task.name);
46 | }
47 | }
48 | }
49 |
50 | public stop(taskName: string, status: TaskStatus, timestamp?: string): void {
51 | if (!this.tasks || this.tasks.length === 0) {
52 | console.log('There are no tasks added yet.');
53 | return;
54 | }
55 | if (!taskName) {
56 | this.tasks.forEach((t, idx = 0) => {
57 | if (t.status === TaskStatus.IN_PROGRESS) {
58 | let task = this.getTask(t.name);
59 | if (task.stop(status)) {
60 | this.tasks[idx] = task;
61 | }
62 | }
63 | idx++;
64 | });
65 | } else {
66 | let idx = _.findIndex(this.tasks, ['name', taskName]);
67 | if (idx === -1) {
68 | console.log('Task %s not found.', taskName);
69 | return;
70 | }
71 | let task = this.getTask(taskName);
72 | let dateFormat = this.getConfigDateFormat();
73 | let fullDate = moment();
74 | timestamp = timestamp || fullDate.format(dateFormat);
75 | let isFullDate = timestamp.includes('/');
76 | if (isFullDate) {
77 | if (!moment(timestamp, dateFormat).isValid()) {
78 | console.log('Time it is not in a valid format.');
79 | return;
80 | }
81 | } else {
82 | if (!this.timeIsValid(timestamp, ['([0-9]{1,3})\:+([0-5]{1}[0-9]{1})'])) {
83 | console.log('Time it is not in a valid format.');
84 | return;
85 | }
86 | fullDate.hour(+timestamp.split(':')[0]);
87 | fullDate.minute(+timestamp.split(':')[1]);
88 | }
89 | let lastTime = _.last(task.log);
90 | if (moment(lastTime.start) > fullDate) {
91 | console.log('The time entered must be greater than the start time of the task.');
92 | return;
93 | }
94 | if (task.stop(status, fullDate)) {
95 | this.tasks[idx] = task;
96 | } else {
97 | // If the task could not be stopped, quit the command
98 | return;
99 | }
100 | }
101 | if (this.updateTasks()) {
102 | let msg = status === TaskStatus.FINISHED ? 'completed' : 'paused';
103 | if (taskName) {
104 | console.log('Task %s has been %s.', taskName, msg);
105 | } else {
106 | console.log('All tasks in progress have been %s.', msg);
107 | }
108 | }
109 | }
110 |
111 | public list(date: string): void {
112 | if (!this.tasks || this.tasks.length === 0) {
113 | console.log('There are no tasks added yet.');
114 | return;
115 | }
116 | let dateFormat = this.config && this.config.date_format ? this.config.date_format.toUpperCase() : 'MM/DD/YYYY';
117 | if (date === undefined) {
118 | date = moment().format(dateFormat);
119 | } else {
120 | if (!moment(date, dateFormat).isValid()) {
121 | console.log('Date it is not in a valid format.');
122 | return;
123 | }
124 | }
125 | let timings: any[] = [];
126 | let beginTotal = moment();
127 | let diffTotal = moment();
128 | let hours;
129 | let min;
130 | this.tasks.forEach((t) => {
131 | let times = _.filter(t.log, (l) => {
132 | return moment(l.start).format(dateFormat) === moment(date, dateFormat).format(dateFormat);
133 | });
134 | if (times && times.length > 0) {
135 | let beginTask = moment();
136 | let diffTask = moment();
137 | times.forEach((time) => {
138 | diffTask.add(moment(time.stop).diff(moment(time.start), 'ms'));
139 | diffTotal.add(moment(time.stop).diff(moment(time.start), 'ms'));
140 | });
141 | hours = diffTask.diff(beginTask, 'hours');
142 | min = moment.utc(moment(diffTask, 'HH:mm:ss').diff(moment(beginTask, 'HH:mm:ss'))).format('mm');
143 | timings.push({ name: t.name, time: ((hours < 10 ? '0' : '') + hours) + ':' + min, status: this.formatStatus(t.status) });
144 | }
145 | });
146 | hours = diffTotal.diff(beginTotal, 'hours');
147 | min = moment.utc(moment(diffTotal, 'HH:mm:ss').diff(moment(beginTotal, 'HH:mm:ss'))).format('mm');
148 | console.log('');
149 | console.log(' %s %s ', colors.bgGreen(' ' + ((hours < 10 ? '0' : '') + hours) + ':' + min + ' '), colors.inverse(' DATE: ' + date + ' '));
150 | console.log(colors.white(' %s | %s | %s'), 'TIME', 'STATUS', 'TASK');
151 | if (!timings || timings.length === 0) {
152 | console.log(colors.grey(' --- | --- | ---'));
153 | } else {
154 | timings.forEach((time) => {
155 | console.log(colors.grey(' %s | %s | %s'), time.time, time.status + (' '.repeat(11 - time.status.length)), time.name);
156 | });
157 | }
158 | }
159 |
160 | public add(taskName: string, timeSpent: string, date: string) {
161 | let dateFormat = this.config && this.config.date_format ? this.config.date_format.toUpperCase() + ' h:mm' : 'MM/DD/YYYY h:mm';
162 | if (date === undefined) {
163 | date = moment().format(dateFormat);
164 | } else {
165 | if (!moment(date, dateFormat).isValid()) {
166 | console.log('Date it is not in a valid format.');
167 | return;
168 | }
169 | }
170 | if (!this.timeIsValid(timeSpent)) {
171 | console.log('Time spent it is not in a valid format.');
172 | return;
173 | }
174 | let isFullHour = timeSpent.indexOf(':') > 0;
175 | let hour: number;
176 | let min: number;
177 | if (isFullHour) {
178 | let split = timeSpent.split(':');
179 | hour = +split[0];
180 | min = +split[1];
181 | } else {
182 | let valueOfSubstr: number = +timeSpent.substring(0, timeSpent.length - 1);
183 | hour = timeSpent.indexOf('h') > -1 ? valueOfSubstr : 0;
184 | min = timeSpent.indexOf('m') > -1 ? valueOfSubstr : 0;
185 | }
186 | let idx = _.findIndex(this.tasks, ['name', taskName]);
187 | let task = this.getTaskByIndex(taskName, idx);
188 | if (!task.add(date, dateFormat, hour, min)) {
189 | return;
190 | }
191 | if (idx === -1) {
192 | task.setStatus(TaskStatus.FINISHED);
193 | this.tasks.push(task);
194 | } else {
195 | this.tasks[idx] = task;
196 | }
197 | if (this.updateTasks()) {
198 | if (idx === -1) {
199 | console.log('Task %s added.', task.name);
200 | } else {
201 | console.log('The entered time was added in the task %s.', task.name);
202 | }
203 | }
204 | }
205 |
206 | private getTask(key: string): Task {
207 | return new Task(key, _.find(this.tasks, ['name', key]));
208 | }
209 |
210 | private getTaskByIndex(key: string, idx: number) {
211 | if (idx === -1) {
212 | return new Task(key, undefined);
213 | }
214 | return new Task(key, this.tasks[idx]);
215 | }
216 |
217 | private updateTasks(): boolean {
218 | try {
219 | this.configStore.set('tasks', this.tasks);
220 | return true;
221 | } catch (error) {
222 | console.log('An error occurred.');
223 | return false;
224 | }
225 | }
226 |
227 | private timeIsValid(time: string, regList?: string[]): boolean {
228 | if (!time || time.length > 5) {
229 | return false;
230 | }
231 | regList = regList || ['([0-9]{1,3})\:+([0-5]{1}[0-9]{1})', '([0-9]{1,3}\m)', '([0-9]{1,3}\h)'];
232 | let i = 0;
233 | let value: any;
234 | for (i; i < regList.length; i++) {
235 | if (new RegExp(regList[i]).test(time)) {
236 | value = new RegExp(regList[i]).exec(time);
237 | break;
238 | }
239 | }
240 | if (i >= regList.length || (value == null || value[0] !== time)) {
241 | return false;
242 | }
243 | return true;
244 | }
245 |
246 | private formatStatus(status: TaskStatus): string {
247 | switch (status) {
248 | case TaskStatus.IN_PROGRESS:
249 | return 'In Progress';
250 | case TaskStatus.PAUSED:
251 | return 'Paused';
252 | case TaskStatus.FINISHED:
253 | return 'Finished';
254 | default:
255 | return '';
256 | }
257 | }
258 |
259 | private getConfigDateFormat(): string {
260 | return this.config && this.config.date_format ? this.config.date_format.toUpperCase() + ' h:mm' : 'MM/DD/YYYY h:mm';
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | // import 3th party packages
2 | import * as program from 'commander';
3 | const Configstore = require('configstore');
4 | const pkg = require('../package.json');
5 |
6 | // import models
7 | import { TaskStatus } from './core/task-status';
8 | import { Timetracking } from './core/timetracking';
9 |
10 | const config = new Configstore(pkg.name, {
11 | config: {
12 | date_format: 'MM/dd/yyyy',
13 | pause_others_on_start: true
14 | },
15 | tasks: []
16 | });
17 | const timetracking = new Timetracking(config);
18 |
19 | program
20 | .command('start ')
21 | .description('Start a task.')
22 | .alias('s')
23 | .option('-d, --description ', 'Set task description.')
24 | .option('-y', 'Pause others taks in progress.')
25 | .option('-n', 'Do not pause others taks in progress.')
26 | .action((task, options) => {
27 | let description = '';
28 | if (options.description && typeof options.description !== 'function') {
29 | description = options.description;
30 | }
31 | timetracking.start(task, description, !options.N && (options.Y || timetracking.config.pause_others_on_start));
32 | });
33 |
34 | program
35 | .command('finish [task]')
36 | .description('Finish a task. If you do not inform the [task] all tasks in progress will be completed.')
37 | .alias('f')
38 | .option('-t ', 'Set date|hour of finish.')
39 | .action((task, options) => {
40 | let date = '';
41 | if (options.T && typeof options.T !== 'function') {
42 | if (!task) {
43 | console.log('To use this option you must enter a task.');
44 | return;
45 | }
46 | date = options.T;
47 | }
48 | timetracking.stop(task, TaskStatus.FINISHED, date);
49 | });
50 |
51 | program
52 | .command('pause [task]')
53 | .description('Pause a task. If you do not inform the [task] all tasks in progress will be paused.')
54 | .alias('p')
55 | .option('-t ', 'Set date|hour of pause.')
56 | .action((task, options) => {
57 | let date = '';
58 | if (options.T && typeof options.T !== 'function') {
59 | if (!task) {
60 | console.log('To use this option you must enter a task.');
61 | return;
62 | }
63 | date = options.T;
64 | }
65 | timetracking.stop(task, TaskStatus.PAUSED, date);
66 | });
67 |
68 | program
69 | .command('list [date]')
70 | .description('Resume time of the taks. You can pass the date on format configured (' + (config && config.all.config ? config.all.config.date_format : 'MM/dd/yyyy') + ').')
71 | .alias('l')
72 | .action((date) => {
73 | timetracking.list(date);
74 | });
75 |
76 | program
77 | .command('add [date]')
78 | .description('Add a task with a specific time spent and on a specific date. You can pass the date on format configured (' + (config && config.all.config ? config.all.config.date_format : 'MM/dd/yyyy') + ').')
79 | .alias('a')
80 | .action((task, timeSpent, date) => {
81 | timetracking.add(task, timeSpent, date);
82 | });
83 |
84 | program
85 | .version(pkg.version)
86 | .parse(process.argv);
87 |
88 | if (!program.args.length) {
89 | program.help();
90 | }
91 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const time = require('../lib/core/timetracking');
3 | const taskStatus = require('../lib/core/task-status');
4 |
5 | const Configstore = require('configstore');
6 | const pkg = require('../package.json');
7 |
8 | const TEST_NAMESPACE = `${pkg.name}_test`;
9 |
10 | const config = new Configstore(TEST_NAMESPACE, {
11 | config: {
12 | date_format: 'MM/dd/yyyy',
13 | pause_others_on_start: true
14 | },
15 | tasks: []
16 | });
17 |
18 | const timetracking = new time.Timetracking(config);
19 |
20 | describe(`${pkg.name} - ${pkg.description}`, () => {
21 |
22 | before(() => {
23 | config.set('tasks', []);
24 | });
25 |
26 | it(':: start a new task', () => {
27 | timetracking.start('new task', 'this is a new task', true);
28 | let tasks = config.get('tasks');
29 | assert.equal(tasks.length, 1);
30 | assert.equal(tasks[0].status, 0)
31 | });
32 |
33 | it(':: list all tasks by creation date', () => {
34 | timetracking.list(new Date());
35 | });
36 |
37 | it(':: pause task', () => {
38 | let date = new Date();
39 | let formattedDate = `${date.getHours()}:${date.getMinutes().toString().length == 1 ? '0' + date.getMinutes(): date.getMinutes()}`;
40 | timetracking.stop('new task', taskStatus.TaskStatus.PAUSED, formattedDate);
41 | let tasks = config.get('tasks');
42 | assert.equal(tasks.length, 1);
43 | assert.equal(tasks[0].status, 1)
44 | });
45 |
46 | it(':: stop task', () => {
47 | let date = new Date();
48 | let formattedDate = `${date.getHours()}:${date.getMinutes().toString().length == 1 ? '0' + date.getMinutes(): date.getMinutes()}`;
49 | timetracking.stop('new task', taskStatus.TaskStatus.FINISHED, formattedDate);
50 | let tasks = config.get('tasks');
51 | assert.equal(tasks.length, 1);
52 | assert.equal(tasks[0].status, 2)
53 | });
54 |
55 | after(() => {
56 | config.set('tasks', []);
57 | });
58 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": [
5 | "es6"
6 | ],
7 | "module": "commonjs",
8 | "noImplicitAny": false,
9 | "removeComments": true,
10 | "declaration": true,
11 | "outDir": "./lib",
12 | "strict": true,
13 | "types": [
14 | "node"
15 | ]
16 | },
17 | "include": [
18 | "src/**/*"
19 | ],
20 | "exclude": [
21 | "node_modules",
22 | "lib",
23 | "**/*.spec.ts"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "ordered-imports": [
9 | true
10 | ],
11 | "indent": [
12 | true,
13 | "tabs",
14 | 2
15 | ],
16 | "member-access": [
17 | true,
18 | "check-accessor"
19 | ],
20 | "curly": true,
21 | "no-console": [
22 | false
23 | ],
24 | "quotemark": [
25 | true,
26 | "single"
27 | ],
28 | "trailing-comma": [
29 | false
30 | ],
31 | "no-consecutive-blank-lines": true,
32 | "eofline": true,
33 | "prefer-const": false,
34 | "no-var-keyword": true,
35 | "ban-types": false,
36 | "no-var-requires": false,
37 | "max-line-length": [
38 | false
39 | ]
40 | },
41 | "rulesDirectory": []
42 | }
--------------------------------------------------------------------------------