@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
package/src/cli.js ADDED
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const os = require('os');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const readline = require('readline');
8
+ const program = require('commander');
9
+ const info = require('../package.json');
10
+ const log = require('./utils/log.js');
11
+ const config = require('./utils/config.js');
12
+ const finish = require('./utils/finish.js');
13
+ const interactive = require('./interactive.js');
14
+
15
+ /**
16
+ * Directory containing command files
17
+ * @private
18
+ */
19
+ const CMD_DIR = path.normalize(__dirname + '/cmd/');
20
+
21
+
22
+
23
+
24
+ // Setup the Program options and commands
25
+ setup();
26
+
27
+ // Parse the CLI arguments and start the program
28
+ start();
29
+
30
+
31
+
32
+
33
+
34
+ /**
35
+ * Set up the program arguments, commands and options
36
+ * @private
37
+ */
38
+ function setup() {
39
+
40
+ // Set Process Name
41
+ process.title = 'rtm';
42
+
43
+ // Add Program Information
44
+ program
45
+ .version(info.version)
46
+ .usage("[options] <command> [command arguments]");
47
+
48
+ // Add Options
49
+ program
50
+ .option('-p, --plain', 'do not use styled/colored text (overrides --color)')
51
+ .option('-c, --color', 'force the use of styled/colored text')
52
+ .option('-s, --status', 'toggle the display of the status spinner')
53
+ .option('-x, --completed [value]', 'set display of completed tasks (true/false/number of days)')
54
+ .option('-d, --hideDue [value]', 'hide tasks due more than n days from today (false/number of days)')
55
+ .option('-f, --config [file]', 'specify configuration file', function(file) {
56
+ config.reset(file);
57
+ parsePlugins();
58
+ parseAliases();
59
+ })
60
+ .option('-v, --verbose', 'print stack traces on errors');
61
+
62
+ // Add additional Help information
63
+ program.on('--help', function() {
64
+ console.log('');
65
+ console.log('');
66
+ console.log(' Documentation:');
67
+ console.log('');
68
+ console.log(' Additional docs can be found in the GitHub repo:');
69
+ console.log(' https://github.com/dwaring87/rtm-cli');
70
+ console.log(' This project uses the RTM API Wrapper for NodeJS:');
71
+ console.log(' https://github.com/dwaring87/rtm-api');
72
+ if ( config.get()._user ) {
73
+ console.log('');
74
+ console.log('');
75
+ console.log(' Logged In As: ' + config.get()._user.username);
76
+ }
77
+ });
78
+
79
+ // Error on Unknown Command
80
+ program.on('command:*', function () {
81
+ console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
82
+ process.exit(1);
83
+ });
84
+
85
+ // Add Program Commands
86
+ parseCommands();
87
+
88
+ // Add Any Plugins
89
+ parsePlugins();
90
+
91
+ // Parse Aliases
92
+ parseAliases();
93
+
94
+ // Set program to global namespace
95
+ global._program = program;
96
+
97
+ // Set readline interface
98
+ global._rl = readline.createInterface({
99
+ input: process.stdin,
100
+ output: process.stdout,
101
+ completer: function completer(line) {
102
+ let completions = [];
103
+ if ( global._mainPrompt ) {
104
+ completions.push('help');
105
+ for ( let i = 0; i < program.commands.length; i++ ) {
106
+ completions.push(program.commands[i].name());
107
+ }
108
+ completions.push('quit');
109
+ }
110
+ let hits = completions.filter(function(c) { return c.indexOf(line) === 0 });
111
+ return [hits.length ? hits : completions, line]
112
+ }
113
+ }).on('SIGINT', function() {
114
+ process.exit(0);
115
+ });
116
+
117
+ // Catch Thrown Errors
118
+ process.on('uncaughtException', function(err) {
119
+ log.error("ERROR: " + err);
120
+ if ( program.verbose ) {
121
+ console.log(err);
122
+ }
123
+ finish();
124
+ });
125
+
126
+ }
127
+
128
+
129
+
130
+ // ===== COMMAND PARSING ===== //
131
+
132
+
133
+ /**
134
+ * Parse the commands in the command directory
135
+ * @private
136
+ */
137
+ function parseCommands() {
138
+ let files = fs.readdirSync(CMD_DIR);
139
+ for ( let i = 0; i < files.length; i++ ) {
140
+ let file = path.normalize(CMD_DIR + '/' + files[i]);
141
+ let command = require(file);
142
+ parseCmd(command);
143
+ }
144
+ }
145
+
146
+
147
+ /**
148
+ * Parse the command file
149
+ * @param {object} command The command object to parse
150
+ * @private
151
+ */
152
+ function parseCmd(command) {
153
+
154
+ // Check for existing command name
155
+ let existing = program.commands;
156
+ for ( let i = 0; i < existing.length; i++ ) {
157
+ if ( existing[i].name() === command.command.split(' ')[0] ) {
158
+ log.error("ERROR: Command name (" + command.command.split(' ')[0] + ") already exists");
159
+ process.exit(1);
160
+ }
161
+ }
162
+
163
+ // Build Command
164
+ let cmd = program.command(command.command);
165
+ cmd.description(command.description);
166
+ cmd.alias(command.alias);
167
+
168
+ // Add Command Options
169
+ if ( command.options ) {
170
+ for ( let i = 0; i < command.options.length; i++ ) {
171
+ let option = command.options[i];
172
+ cmd.option(option.option, option.description);
173
+ }
174
+ }
175
+
176
+ // Add Command Action
177
+ cmd.action(function() {
178
+
179
+ // Process command arguments
180
+ let args = [];
181
+ let env = undefined;
182
+ for ( let i = 0; i < arguments.length; i++ ) {
183
+ let arg = arguments[i];
184
+ if ( typeof arg === 'object' && !Array.isArray(arg) ) {
185
+ env = arg;
186
+ }
187
+ else if ( Array.isArray(arg) && arg.length > 0 ) {
188
+ args.push(arg);
189
+ }
190
+ else if ( !Array.isArray(arg) && arg !== undefined ) {
191
+ args.push(arg);
192
+ }
193
+ }
194
+
195
+ // Skip Login Check
196
+ if ( command.disableLogin ) {
197
+ command.action(args, env);
198
+ }
199
+
200
+ // Check Login Before Running Command
201
+ else {
202
+ config.user(function() {
203
+ command.action(args, env);
204
+ });
205
+ }
206
+
207
+ });
208
+ }
209
+
210
+
211
+
212
+ // ===== PLUGIN PARSING ===== //
213
+
214
+
215
+ /**
216
+ * Parse the plugins listed in the configuration
217
+ */
218
+ function parsePlugins() {
219
+
220
+ // Get plugin locations from the configuration
221
+ let locations = config.get().plugins;
222
+
223
+ // Parse each location
224
+ for ( let i = 0; i < locations.length; i++ ) {
225
+ parsePluginLocation(locations[i]);
226
+ }
227
+
228
+ }
229
+
230
+
231
+ /**
232
+ * Parse the specified plugin location
233
+ * @param {string} location Plugin location (file, directory or module name)
234
+ * @private
235
+ */
236
+ function parsePluginLocation(location) {
237
+
238
+ // Replace {{HOME}} with home directory location
239
+ location = location.replace("{{HOME}}", os.homedir());
240
+
241
+ // Try to load as a module (either directory or file)
242
+ try {
243
+ require.resolve(location);
244
+ parsePluginModule(location);
245
+ }
246
+
247
+ // Not a module...
248
+ catch (err) {
249
+
250
+ // Check to make sure file/directory exists
251
+ if ( fs.existsSync(location) ) {
252
+
253
+ // Check if a directory
254
+ if ( fs.lstatSync(location).isDirectory() ) {
255
+ parsePluginDirectory(location);
256
+ }
257
+
258
+ }
259
+
260
+ }
261
+
262
+ }
263
+
264
+ /**
265
+ * Parse the Plugin as a Module
266
+ * @param {string} location Module location (for `require`)
267
+ * @private
268
+ */
269
+ function parsePluginModule(location) {
270
+
271
+ // Load the module
272
+ let module = require(location);
273
+
274
+ // Check the root module for a command
275
+ _check(module);
276
+
277
+ // Check children objects for a command
278
+ for ( let key in module ) {
279
+ if ( module.hasOwnProperty(key) ) {
280
+ _check(module[key]);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Check the object for the required command properties and parse the
286
+ * object as a command if they are all found
287
+ * @param obj
288
+ * @private
289
+ */
290
+ function _check(obj) {
291
+ if ( obj.hasOwnProperty('command') && obj.hasOwnProperty('description') && obj.hasOwnProperty('action') ) {
292
+ parseCmd(obj);
293
+ }
294
+ }
295
+
296
+ }
297
+
298
+ /**
299
+ * Process a plugin directory
300
+ * @param location
301
+ */
302
+ function parsePluginDirectory(location) {
303
+
304
+ // Check if location exists
305
+ if ( fs.existsSync(location) ) {
306
+
307
+ // Get all files in directory
308
+ let files = fs.readdirSync(location);
309
+
310
+ // Parse each location
311
+ for ( let i = 0; i < files.length; i++ ) {
312
+ parsePluginLocation(path.normalize(location + '/' + files[i]));
313
+ }
314
+
315
+ }
316
+
317
+ }
318
+
319
+
320
+
321
+ // ===== ALIAS PARSING ===== //
322
+
323
+
324
+ /**
325
+ * Parse the Alias Commands from the Config Files
326
+ */
327
+ function parseAliases() {
328
+
329
+ // Remove existing RTM Aliases
330
+ let keep = [];
331
+ let current = program.commands;
332
+ for ( let i = 0; i < current.length; i++ ) {
333
+ let cmd = current[i];
334
+ if ( !cmd._isRTMAlias ) {
335
+ keep.push(cmd);
336
+ }
337
+ }
338
+ program.commands = keep;
339
+
340
+ // Parse aliases in configuration
341
+ if ( config.get().aliases ) {
342
+ for ( let i = 0; i < config.get().aliases.length; i++ ) {
343
+ let alias = config.get().aliases[i];
344
+
345
+ // Check for existing command name
346
+ let found = false;
347
+ let existing = program.commands;
348
+ for ( let j = 0; j < existing.length; j++ ) {
349
+ if ( existing[j].name() === alias.name ) {
350
+ found = true;
351
+ }
352
+ }
353
+
354
+ // Add command to program, if not already added
355
+ if ( !found ) {
356
+
357
+ // Create Command with description
358
+ let cmd = program.command(alias.name);
359
+ cmd.description(alias.description);
360
+
361
+ // Set Command Action
362
+ cmd.action(function () {
363
+
364
+ // Set Initial Args
365
+ let args = [
366
+ process.argv[0],
367
+ process.argv[1],
368
+ alias.command
369
+ ];
370
+
371
+ // Add Arguments
372
+ if ( alias.args ) {
373
+ let aa = alias.args.split(' ');
374
+ for ( let j = 0; j < aa.length; j++ ) {
375
+ args.push(aa[j]);
376
+ }
377
+ }
378
+
379
+ // Add CLI arguments
380
+ if ( process.argv.length > 3 ) {
381
+ for ( let j = 3; j < process.argv.length; j++ ) {
382
+ args.push(process.argv[j]);
383
+ }
384
+ }
385
+
386
+ // Parse the command
387
+ global._program.parse(args);
388
+
389
+ });
390
+
391
+ // Flag command as RTM Alias
392
+ cmd._isRTMAlias = true;
393
+
394
+ }
395
+ }
396
+ }
397
+
398
+ }
399
+
400
+
401
+
402
+
403
+ // ===== START FUNCTIONS ===== //
404
+
405
+
406
+ /**
407
+ * Parse the CLI arguments and run the specified command
408
+ * or start the interactive mode
409
+ */
410
+ function start() {
411
+
412
+ // Start interactive mode
413
+ if ( process.argv.length <= 2 ) {
414
+ startInteractive();
415
+ }
416
+
417
+ // Parse the process arguments
418
+ else {
419
+ program.parse(process.argv);
420
+ }
421
+
422
+
423
+ }
424
+
425
+
426
+ /**
427
+ * Start the interactive mode
428
+ */
429
+ function startInteractive() {
430
+ if ( config.get()._user ) {
431
+ log.style("Logged In As: " + config.get()._user.username, "dim", true);
432
+ }
433
+ interactive();
434
+ }
package/src/cmd/add.js ADDED
@@ -0,0 +1,100 @@
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 adds 1 or more tasks to the User's account
12
+ * @param args task
13
+ * @param env
14
+ */
15
+ function action(args, env) {
16
+
17
+ // Prompt for new tasks
18
+ if ( args.length === 0 ) {
19
+ prompt('New Task:', _promptFinished);
20
+ }
21
+
22
+ // Add provided task
23
+ else {
24
+ _process(args[0].join(' '));
25
+ }
26
+
27
+ }
28
+
29
+
30
+ /**
31
+ * Process the returned answers
32
+ * @private
33
+ */
34
+ function _promptFinished(answers) {
35
+ for ( let i = 0; i < answers.length; i++ ) {
36
+ let answer = answers[i];
37
+ _process(answer[0], i+1, answers.length);
38
+ }
39
+ }
40
+
41
+
42
+ /**
43
+ * Process the request
44
+ * @private
45
+ */
46
+ function _process(task, count=1, max=1) {
47
+ log.spinner.start("Adding New Task(s)...");
48
+ config.user(function(user) {
49
+ task = _parseTask(task);
50
+
51
+ // Add Task
52
+ user.tasks.add(task, function(err) {
53
+ if ( err ) {
54
+ log.spinner.error("Could not add new Task #" + count + " (" + err.msg + ")");
55
+ }
56
+ _processFinished(count, max);
57
+ });
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Request Callback
63
+ * @private
64
+ */
65
+ function _processFinished(count, max) {
66
+ log.spinner.start("Adding Task [" + count + "/" + max + "]...");
67
+ if ( count === max ) {
68
+ log.spinner.success("Task(s) Added");
69
+ return finish();
70
+ }
71
+ }
72
+
73
+
74
+ /**
75
+ * Parse the Task to Add
76
+ *
77
+ * Replace p: with !
78
+ * Replace l: with #
79
+ * Replace t: with #
80
+ * Replace due: with ^
81
+ * @param task
82
+ * @returns {string}
83
+ * @private
84
+ */
85
+ function _parseTask(task) {
86
+ task = task.replace(/(\s+)p:([0-4]{1})($|[\s\r\n]+)/, " !$2$3").trim();
87
+ task = task.replace(/(\s+)l:([\w\d]+)/, " #$2").trim();
88
+ task = task.replace(/(\s+)t:([\w\d]+)/g, " #$2").trim();
89
+ task = task.replace(/(\s+)due:([\w\d]+)/, " ^$2").trim();
90
+ return task;
91
+ }
92
+
93
+
94
+
95
+ module.exports = {
96
+ command: 'add [task...]',
97
+ alias: 'a',
98
+ description: 'Add a new Task',
99
+ action: action
100
+ };
@@ -0,0 +1,92 @@
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 adds 1 or more Lists to the User's account
12
+ * @param args name
13
+ * @param env
14
+ */
15
+ function action(args, env) {
16
+ let _args = [];
17
+ for ( let i = 0; i < args.length; i++ ) {
18
+ _args = _args.concat(args[i]);
19
+ }
20
+
21
+ // Prompt for new lists
22
+ if ( args.length === 0 ) {
23
+ prompt('New List:', _promptFinished);
24
+ }
25
+
26
+ // Add provided task
27
+ else {
28
+ _process(_args.join(' '));
29
+ }
30
+
31
+ }
32
+
33
+
34
+ /**
35
+ * Process the returned answers
36
+ * @private
37
+ */
38
+ function _promptFinished(answers) {
39
+ for ( let i = 0; i < answers.length; i++ ) {
40
+ let answer = answers[i];
41
+ _process(answer[0], i+1, answers.length);
42
+ }
43
+ }
44
+
45
+
46
+ /**
47
+ * Process the request
48
+ * @private
49
+ */
50
+ function _process(list, count=1, max=1) {
51
+ log.spinner.start("Adding New List(s)...");
52
+ config.user(function(user) {
53
+ list = list.trim();
54
+ let filter = undefined;
55
+
56
+ // Parse as Smart List
57
+ if ( list.indexOf(':') > -1 ) {
58
+ let colon = list.indexOf(':');
59
+ let divider = list.substring(0, colon).lastIndexOf(' ');
60
+ filter = list.substring(divider+1);
61
+ list = list.substring(0, divider);
62
+ }
63
+
64
+ // Add List
65
+ user.lists.add(list, filter, function(err) {
66
+ if ( err ) {
67
+ log.spinner.error("Could not add new List " + list + " (" + err.msg + ")");
68
+ }
69
+ _processFinished(count, max);
70
+ });
71
+ });
72
+ }
73
+
74
+ /**
75
+ * Request Callback
76
+ * @private
77
+ */
78
+ function _processFinished(count, max) {
79
+ log.spinner.start("Adding List [" + count + "/" + max + "]...");
80
+ if ( count === max ) {
81
+ log.spinner.success("List(s) Added");
82
+ return finish();
83
+ }
84
+ }
85
+
86
+
87
+ module.exports = {
88
+ command: 'addList [name] [filter...]',
89
+ alias: 'al',
90
+ description: 'Add a new List or Smart List',
91
+ action: action
92
+ };
@@ -0,0 +1,85 @@
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 adds 1 or more tags to a Task
12
+ * @param args index tags...
13
+ * @param env
14
+ */
15
+ function action(args, env) {
16
+
17
+ // Prompt for new tags
18
+ if ( args.length < 2 ) {
19
+ prompt('Task:', 'Tags:', _promptFinished);
20
+ }
21
+
22
+ // Add provided tags
23
+ else {
24
+ _process(args[0], args[1]);
25
+ }
26
+
27
+ }
28
+
29
+
30
+ /**
31
+ * Process the returned answers
32
+ * @private
33
+ */
34
+ function _promptFinished(answers) {
35
+ for ( let i = 0; i < answers.length; i++ ) {
36
+ let answer = answers[i];
37
+ _process(answer[0], answer[1], i+1, answers.length);
38
+ }
39
+ }
40
+
41
+
42
+ /**
43
+ * Process the request
44
+ * @private
45
+ */
46
+ function _process(task, tags, count=1, max=1) {
47
+ log.spinner.start("Adding Tags(s)...");
48
+ config.user(function(user) {
49
+
50
+ // Parse arguments
51
+ task = parseInt(task.trim());
52
+ if ( !Array.isArray(tags) ) {
53
+ tags = tags.trim().split(' ');
54
+ }
55
+
56
+ // Add Tags
57
+ user.tasks.addTags(task, tags, function(err) {
58
+ if ( err ) {
59
+ log.spinner.error("Could not add tags to Task #" + task + " (" + err.msg + ")");
60
+ }
61
+ _processFinished(count, max);
62
+ });
63
+
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Request Callback
69
+ * @private
70
+ */
71
+ function _processFinished(count, max) {
72
+ log.spinner.start("Adding Tags [" + count + "/" + max + "]...");
73
+ if ( count === max ) {
74
+ log.spinner.success("Tag(s) Added");
75
+ return finish();
76
+ }
77
+ }
78
+
79
+
80
+ module.exports = {
81
+ command: 'addTags [index] [tags...]',
82
+ alias: 'at',
83
+ description: 'Add one or more tags to a Task',
84
+ action: action
85
+ };