@beauraines/rtm-cli 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.github/FUNDING.yml +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +143 -0
  4. package/build.js +86 -0
  5. package/config.json +40 -0
  6. package/package.json +58 -0
  7. package/screens/ls.png +0 -0
  8. package/screens/lsd.png +0 -0
  9. package/screens/lsp.png +0 -0
  10. package/screens/planner.png +0 -0
  11. package/src/cli.js +434 -0
  12. package/src/cmd/add.js +100 -0
  13. package/src/cmd/addList.js +92 -0
  14. package/src/cmd/addTags.js +85 -0
  15. package/src/cmd/archiveList.js +78 -0
  16. package/src/cmd/comp.js +82 -0
  17. package/src/cmd/decPri.js +82 -0
  18. package/src/cmd/due.js +82 -0
  19. package/src/cmd/edit.js +82 -0
  20. package/src/cmd/incPri.js +82 -0
  21. package/src/cmd/lists.js +60 -0
  22. package/src/cmd/login.js +27 -0
  23. package/src/cmd/logout.js +31 -0
  24. package/src/cmd/ls.js +181 -0
  25. package/src/cmd/lsd.js +193 -0
  26. package/src/cmd/lsp.js +171 -0
  27. package/src/cmd/move.js +83 -0
  28. package/src/cmd/notes.js +150 -0
  29. package/src/cmd/planner.js +510 -0
  30. package/src/cmd/postpone.js +82 -0
  31. package/src/cmd/pri.js +82 -0
  32. package/src/cmd/remove.js +82 -0
  33. package/src/cmd/removeList.js +79 -0
  34. package/src/cmd/removeTags.js +85 -0
  35. package/src/cmd/renameList.js +80 -0
  36. package/src/cmd/reset.js +35 -0
  37. package/src/cmd/setUrl.js +99 -0
  38. package/src/cmd/tags.js +101 -0
  39. package/src/cmd/uncomp.js +82 -0
  40. package/src/cmd/url.js +92 -0
  41. package/src/cmd/whoami.js +36 -0
  42. package/src/interactive.js +65 -0
  43. package/src/utils/config.js +236 -0
  44. package/src/utils/filter.js +43 -0
  45. package/src/utils/finish.js +21 -0
  46. package/src/utils/index.js +10 -0
  47. package/src/utils/log.js +163 -0
  48. package/src/utils/login.js +69 -0
  49. package/src/utils/printIndicator.js +43 -0
  50. package/src/utils/prompt.js +126 -0
  51. package/src/utils/sort.js +283 -0
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/log.js');
4
+ const config = require('../utils/config.js');
5
+ const filter = require('../utils/filter')
6
+ const finish = require('../utils/finish.js');
7
+ const styles = config.get().styles;
8
+
9
+ /**
10
+ * This command lists all of the User's current RTM Task Tags
11
+ */
12
+ function action(args, env) {
13
+ config.user(function(user) {
14
+ log.spinner.start("Getting Tags...");
15
+
16
+ // Get Tasks
17
+ const filterString = filter('isTagged:true');
18
+ // TODO use a new fetch function that doesn't create RTM Tasks for everything returned
19
+ // TODO this should call the rtm.tags.getList, but then it wouldn't be able to show the task counts
20
+ user.tasks.get(filterString, function(err, tasks) {
21
+ if ( err ) {
22
+ log.spinner.error("Could not get tags (" + err.msg + ")");
23
+ return finish();
24
+ }
25
+ log.spinner.stop();
26
+
27
+ // List of Tags
28
+ let tags = [];
29
+
30
+ // Get Tags from Tasks
31
+ for ( let i = 0; i < tasks.length; i++ ) {
32
+ let task = tasks[i];
33
+ let _tags = task.tags;
34
+
35
+ // Parse Each Task's Tag
36
+ for ( let j = 0; j < _tags.length; j++ ) {
37
+ let _tag = _tags[j];
38
+
39
+ // Check for matching Tag
40
+ let found = false;
41
+ for ( let k = 0; k < tags.length; k++ ) {
42
+ if ( tags[k].name === _tag ) {
43
+ if ( task.isCompleted ) {
44
+ tags[k].completed = tags[k].completed + 1;
45
+ }
46
+ else {
47
+ tags[k].incomplete = tags[k].incomplete + 1;
48
+ }
49
+ tags[k].count = tags[k].count + 1;
50
+ found = true;
51
+ }
52
+ }
53
+
54
+ // Add new Tag
55
+ if ( !found ) {
56
+ tags.push({
57
+ name: _tag,
58
+ completed: task.isCompleted ? 1 : 0,
59
+ incomplete: task.isCompleted ? 0 : 1,
60
+ count: 1
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ // Get max tag length
67
+ let max = 0;
68
+ for ( let i = 0; i < tags.length; i++ ) {
69
+ let l = tags[i].name.length;
70
+ max = l > max ? l : max;
71
+ }
72
+
73
+ // Print Tags
74
+ for ( let i = 0; i < tags.length; i++ ) {
75
+ let tag = tags[i];
76
+ log.style(tag.name, styles.tags);
77
+
78
+ let delta = max - tag.name.length;
79
+ for ( let i = 0; i < delta + 1; i++ ) {
80
+ log.style(' ');
81
+ }
82
+
83
+ log.style(tag.incomplete);
84
+ log.style('/');
85
+ log.style(tag.completed, 'dim', true);
86
+ }
87
+
88
+ // Return
89
+ return finish();
90
+
91
+ });
92
+ });
93
+ }
94
+
95
+
96
+ module.exports = {
97
+ command: 'tags',
98
+ alias: 't',
99
+ description: 'Display all tags',
100
+ action: action
101
+ };
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/log.js');
4
+ const config = require('../utils/config.js');
5
+ const { prompt } = require('../utils/prompt.js');
6
+
7
+ const finish = require('../utils/finish.js');
8
+
9
+
10
+ /**
11
+ * This command marks 1 or more Tasks as complete
12
+ * @param args indices...
13
+ * @param env
14
+ */
15
+ function action(args, env) {
16
+
17
+ // Prompt for tasks
18
+ if ( args.length === 0 ) {
19
+ prompt('Task:', _promptFinished);
20
+ }
21
+
22
+ // Process Tasks
23
+ else {
24
+ for ( let i = 0; i < args[0].length; i++ ) {
25
+ _process(args[0][i], i+1, args[0].length);
26
+ }
27
+ }
28
+
29
+ }
30
+
31
+
32
+ /**
33
+ * Process the returned answers
34
+ * @private
35
+ */
36
+ function _promptFinished(answers) {
37
+ for ( let i = 0; i < answers.length; i++ ) {
38
+ let answer = answers[i];
39
+ _process(answer[0], i+1, answers.length);
40
+ }
41
+ }
42
+
43
+
44
+ /**
45
+ * Process the request
46
+ * @private
47
+ */
48
+ function _process(task, count=1, max=1) {
49
+ log.spinner.start("Un-completing Task(s)...");
50
+ config.user(function(user) {
51
+ task = parseInt(task.trim());
52
+
53
+ // Complete Task
54
+ user.tasks.uncomplete(task, function(err) {
55
+ if ( err ) {
56
+ log.spinner.error("Could not un-complete Task #" + task + " (" + err.msg + ")");
57
+ }
58
+ _processFinished(count, max);
59
+ });
60
+
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Request Callback
66
+ * @private
67
+ */
68
+ function _processFinished(count, max) {
69
+ log.spinner.start("Un-completing Task [" + count + "/" + max + "]...");
70
+ if ( count === max ) {
71
+ log.spinner.success("Task(s) Un-Completed");
72
+ return finish();
73
+ }
74
+ }
75
+
76
+
77
+ module.exports = {
78
+ command: 'uncomp [indices...]',
79
+ alias: 'unc',
80
+ description: 'Mark one or more Tasks as not complete',
81
+ action: action
82
+ };
package/src/cmd/url.js ADDED
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ const log = require('../utils/log.js');
4
+ const config = require('../utils/config.js');
5
+ const finish = require('../utils/finish.js');
6
+ const filter = require('../utils/filter');
7
+ const { indexPrompt } = require('../utils/prompt')
8
+ const opn = require('opn');
9
+
10
+
11
+ let URLS = [];
12
+ let OPEN = false;
13
+
14
+
15
+ /**
16
+ * This command displays a task url
17
+ * @param args index
18
+ * @param env
19
+ */
20
+ async function action(args, env) {
21
+
22
+ // Reset URLs
23
+ URLS = [];
24
+
25
+ // Set Open flag
26
+ OPEN = env.open === undefined ? false : env.open;
27
+
28
+ const user = config.user(user => user)
29
+
30
+ let indices = []
31
+
32
+ // Prompt for task
33
+ if ( args.length < 1 ) {
34
+ indices = indexPrompt('Task:')
35
+ args[0] = indices
36
+ }
37
+
38
+
39
+ log.spinner.start("Getting Task(s)");
40
+ // Use provided args
41
+ for (const arg in args[0]) {
42
+ if (Object.hasOwnProperty.call(args[0], arg)) {
43
+ const index = args[0][arg];
44
+ const filterString = filter('hasUrl:true');
45
+ let task = await user.tasks.rtmIndexFetchTask(index,filterString)
46
+ if (task.err == undefined ) {
47
+ task = task.task
48
+ } else {
49
+ log.spinner.warn('Task #' + index + ' does not have a URL or is not found');
50
+ }
51
+
52
+ // Push to URLS
53
+ if ( task && task.url !== undefined ) {
54
+ URLS.push({
55
+ index: index,
56
+ url: task.url,
57
+ name: task.name
58
+ });
59
+ }
60
+ }
61
+ }
62
+ // Print URLs
63
+ log.spinner.stop();
64
+ for ( let i = 0; i < URLS.length; i++ ) {
65
+ log(URLS[i].index + " " + URLS[i].name + " " + URLS[i].url);
66
+ }
67
+
68
+ // Open URL
69
+ if ( OPEN ) {
70
+ for ( let i = 0; i < URLS.length; i++ ) {
71
+ opn(URLS[i].url, {wait: false})//.then(function() {});
72
+ }
73
+ }
74
+
75
+
76
+ finish()
77
+
78
+
79
+ }
80
+
81
+
82
+ module.exports = {
83
+ command: 'url [indices...]',
84
+ options: [
85
+ {
86
+ option: "-o, --open",
87
+ description: "Open the URLs in a browser"
88
+ }
89
+ ],
90
+ description: 'Display the associated URL of a Task',
91
+ action: action
92
+ };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const config = require('../utils/config.js');
4
+ const finish = require('../utils/finish.js');
5
+ const log = require('../utils/log.js');
6
+
7
+
8
+ /**
9
+ * This command displays the user information of the logged in
10
+ * Remember the Milk account
11
+ */
12
+ function action(args, env) {
13
+ let user = config.get()._user;
14
+ if ( user ) {
15
+ log("RTM ID: " + user.id);
16
+ log("Username: " + user.username);
17
+ log("Full Name: " + user.fullname);
18
+ log("RTM Auth Token: " + user.authToken);
19
+ }
20
+ else {
21
+ log.spinner.error("No User Information Found");
22
+ log.style("Use the ");
23
+ log.style("login", "bgYellow.black");
24
+ log.style(" command to login to RTM.");
25
+ log();
26
+ }
27
+ finish();
28
+ }
29
+
30
+
31
+ module.exports = {
32
+ command: 'whoami',
33
+ description: 'Display RTM user information',
34
+ action: action,
35
+ disableLogin: true
36
+ };
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+
4
+ /**
5
+ * Display the Interactive Mode Prompt
6
+ */
7
+ function prompt() {
8
+ global._interactive = true;
9
+
10
+ // Display the Prompt
11
+ global._mainPrompt = true;
12
+ global._rl.question("> ", function(line) {
13
+ global._mainPrompt = false;
14
+
15
+ // Parse the command from the entered line
16
+ let params = line.trim().split(' ');
17
+ let cmd = params[0];
18
+ params.unshift(process.argv[0], process.argv[1]);
19
+
20
+ // Get possible commands
21
+ let commands = [];
22
+ let aliases = [];
23
+ for ( let i = 0; i < global._program.commands.length; i++ ) {
24
+ commands.push(global._program.commands[i].name());
25
+ if ( global._program.commands[i].alias() !== undefined ) {
26
+ aliases.push(global._program.commands[i].alias());
27
+ }
28
+ }
29
+
30
+ // Exit
31
+ if ( cmd === 'quit' || cmd === 'exit' ) {
32
+ global._rl.close();
33
+ process.exit(0);
34
+ }
35
+
36
+ // Help
37
+ else if ( cmd === 'help' ) {
38
+ global._program.outputHelp();
39
+ prompt();
40
+ }
41
+
42
+ // Parse the line command with commander
43
+ else if ( commands.indexOf(cmd) > -1 || aliases.indexOf(cmd) > -1 ) {
44
+ global._program.parse(params);
45
+ }
46
+
47
+ // Nothing entered
48
+ else if ( cmd === '' ) {
49
+ prompt();
50
+ }
51
+
52
+ // Unknown command
53
+ else {
54
+ const log = require('./utils/log.js');
55
+ log.spinner.error("Unknown Command");
56
+ log.style("Commands: help," + commands.join(',') + ",quit", "dim", true);
57
+ prompt();
58
+ }
59
+
60
+ });
61
+
62
+ }
63
+
64
+
65
+ module.exports = prompt;
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const api = require('rtm-api');
7
+ const merge = require('deepmerge');
8
+ const login = require('./login.js');
9
+
10
+
11
+ /**
12
+ * Default Configuration
13
+ */
14
+ const BASE_CONFIG = path.normalize(__dirname + '/../../config.json');
15
+
16
+ /**
17
+ * Default User Configuration
18
+ */
19
+ let DEFAULT_USER_CONFIG = path.normalize(os.homedir() + '/.rtm.json');
20
+ let DEFAULT_EXTRA_CONFIG = path.normalize(os.homedir() + '/.rtm.config.json');
21
+
22
+
23
+ /**
24
+ * RTM CLI Configuration
25
+ */
26
+ class Config {
27
+
28
+
29
+ /**
30
+ * Set up a new Configuration
31
+ */
32
+ constructor() {
33
+ this.reset(DEFAULT_USER_CONFIG);
34
+ this.read(DEFAULT_EXTRA_CONFIG);
35
+ }
36
+
37
+ /**
38
+ * Reset the RTM Configuration with the specified User config file
39
+ * @param {string} file User Config File
40
+ */
41
+ reset(file) {
42
+ this._CONFIG = {};
43
+ this.read(BASE_CONFIG);
44
+ this.read(file);
45
+ this.USER_CONFIG = file;
46
+ }
47
+
48
+
49
+ /**
50
+ * Get the Configuration Object
51
+ * @returns {object}
52
+ */
53
+ get() {
54
+ return this.config;
55
+ }
56
+
57
+ /**
58
+ * Get the Configuration Object
59
+ * @returns {object}
60
+ */
61
+ get config() {
62
+ if ( this._CONFIG === {} ) {
63
+ throw "No Configuration Set";
64
+ }
65
+ this._parseOptions();
66
+ return this._CONFIG;
67
+ }
68
+
69
+ /**
70
+ * Get the RTMClient from the configuration
71
+ * @returns {RTMClient}
72
+ */
73
+ get client() {
74
+ if ( !this._CONFIG._client ) {
75
+ throw "No Client configuration set";
76
+ }
77
+ return this._CONFIG._client;
78
+ }
79
+
80
+ /**
81
+ * Get the RTMUser from the configuration
82
+ *
83
+ * If no RTMUser is saved, start the login process
84
+ * @param {function} callback callback function
85
+ * @param {RTMUser} callback.user The RTM User
86
+ */
87
+ user(callback) {
88
+ if ( !this._CONFIG._user ) {
89
+ return login(callback);
90
+ }
91
+ return callback(this._CONFIG._user);
92
+ }
93
+
94
+
95
+ /**
96
+ * Read a configuration file to merge with the existing configuration
97
+ * @param {string} file Configuration file path
98
+ */
99
+ read(file) {
100
+ if ( file && fs.existsSync(file) ) {
101
+
102
+ // Read the config file
103
+ let config = JSON.parse(fs.readFileSync(file, 'utf-8'));
104
+
105
+ // Merge config into CONFIG
106
+ this._CONFIG = merge(this._CONFIG, config, {
107
+ arrayMerge: function (d, s) {
108
+ return d.concat(s);
109
+ }
110
+ });
111
+
112
+ // Parse the Config Object
113
+ this._parseConfig();
114
+
115
+ }
116
+ }
117
+
118
+
119
+ /**
120
+ * Save the RTM User to the user configuration file
121
+ * @param {RTMUser} user RTM User to save
122
+ */
123
+ saveUser(user) {
124
+ this._CONFIG._user = user;
125
+ let config = {};
126
+ if ( fs.existsSync(this.USER_CONFIG) ) {
127
+ config = JSON.parse(fs.readFileSync(this.USER_CONFIG, 'utf-8'));
128
+ }
129
+ config.user = this.client.user.export(user);
130
+ fs.writeFileSync(this.USER_CONFIG, JSON.stringify(config, null, 2));
131
+ }
132
+
133
+
134
+ /**
135
+ * Remove any saved RTM User information from the User config file
136
+ */
137
+ removeUser() {
138
+ if ( fs.existsSync(this.USER_CONFIG) ) {
139
+ let config = JSON.parse(fs.readFileSync(this.USER_CONFIG, 'utf-8'));
140
+ if ( config.user ) {
141
+ delete config.user;
142
+ fs.writeFileSync(this.USER_CONFIG, JSON.stringify(config, null, 2));
143
+ }
144
+ }
145
+ if ( this._CONFIG.user ) {
146
+ delete this._CONFIG.user;
147
+ }
148
+ if ( this._CONFIG._user ) {
149
+ delete this._CONFIG._user;
150
+ }
151
+ }
152
+
153
+
154
+ /**
155
+ * Parse the command line options (override from config files)
156
+ */
157
+ _parseOptions() {
158
+ if ( global._program ) {
159
+
160
+ // Parse plain & styled flags
161
+ if ( global._program.color ) {
162
+ this._CONFIG.plain = false;
163
+ delete global._program.color;
164
+ }
165
+ if ( global._program.plain ) {
166
+ this._CONFIG.plain = true;
167
+ delete global._program.plain;
168
+ }
169
+
170
+ // Parse completed
171
+ let completed = global._program.completed === undefined ? this._CONFIG.completed : global._program.completed;
172
+ if ( completed.toString().toLowerCase() === 'true' ) {
173
+ completed = true;
174
+ }
175
+ else if ( completed.toString().toLowerCase() === 'false' ) {
176
+ completed = false;
177
+ }
178
+ else {
179
+ completed = parseInt(completed);
180
+ }
181
+ this._CONFIG.completed = completed;
182
+
183
+ // Parse hide due
184
+ let hideDue = global._program.hideDue === undefined ? this._CONFIG.hideDue : global._program.hideDue;
185
+ if ( hideDue.toString().toLowerCase() === 'false' ) {
186
+ hideDue = false;
187
+ }
188
+ else {
189
+ hideDue = parseInt(hideDue);
190
+ }
191
+ this._CONFIG.hideDue = hideDue;
192
+
193
+ // Parse status
194
+ if ( global._program.status ) {
195
+ this._CONFIG.status = !this._CONFIG.status;
196
+ delete global._program.status;
197
+ }
198
+
199
+ }
200
+ }
201
+
202
+
203
+ /**
204
+ * Parse the Configuration properties
205
+ * @private
206
+ */
207
+ _parseConfig() {
208
+
209
+ // Reset CONFIG client and user
210
+ delete this._CONFIG._client;
211
+ delete this._CONFIG._user;
212
+
213
+ // Set the RTM Client
214
+ if ( this._CONFIG.client ) {
215
+ this._CONFIG._client = new api(
216
+ this._CONFIG.client.key,
217
+ this._CONFIG.client.secret,
218
+ this._CONFIG.client.perms
219
+ );
220
+ }
221
+
222
+ // Set the RTM User, if present
223
+ if ( this._CONFIG.user ) {
224
+ try {
225
+ this._CONFIG._user = this._CONFIG._client.user.import(this._CONFIG.user);
226
+ }
227
+ catch(exception) {}
228
+ }
229
+
230
+ }
231
+
232
+
233
+ }
234
+
235
+
236
+ module.exports = new Config();
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const config = require('../utils/config.js');
4
+
5
+
6
+ /**
7
+ * Parse filter text to append preconfigured filter
8
+ * @param filter
9
+ */
10
+ function filter(filter) {
11
+ if ( filter === undefined ) {
12
+ filter = '';
13
+ }
14
+
15
+ // Filters
16
+ let filters = [];
17
+
18
+ // Generate Completed Filter
19
+ let comp = config.get().completed;
20
+ if ( comp === false ) {
21
+ filters.push('(status:incomplete)');
22
+ }
23
+ else if ( comp !== true && comp > 0 ) {
24
+ filters.push('(status:incomplete OR completedWithin:"' + comp + ' days")');
25
+ }
26
+
27
+ // Generate Due Filter
28
+ let due = config.get().hideDue;
29
+ if ( typeof due === 'number' && due > 0 ) {
30
+ filters.push('(NOT dueAfter:"' + due + ' days")');
31
+ }
32
+
33
+ // Add user filter
34
+ if ( filter.trim() !== '' ) {
35
+ filters.push('(' + filter + ')');
36
+ }
37
+
38
+ // Join filters
39
+ return filters.join(' AND ');
40
+
41
+ }
42
+
43
+ module.exports = filter;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const interactive = require('../interactive.js');
4
+
5
+
6
+ /**
7
+ * This function is called when a command is finished. It will
8
+ * return to an interactive prompt, if in interactive mode, or
9
+ * exit the process.
10
+ */
11
+ function finish() {
12
+ if ( global._interactive ) {
13
+ interactive();
14
+ }
15
+ else {
16
+ process.exit(0);
17
+ }
18
+ }
19
+
20
+
21
+ module.exports = finish;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ config: require('./config.js'),
5
+ filter: require('./filter.js'),
6
+ finish: require('./finish.js'),
7
+ log: require('./log.js'),
8
+ login: require('./login.js'),
9
+ prompt: require('./prompt.js')
10
+ };