taskninja 1.0.0

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 (2) hide show
  1. package/app.js +273 -0
  2. package/package.json +21 -0
package/app.js ADDED
@@ -0,0 +1,273 @@
1
+ #! /usr/bin/env node
2
+
3
+ /**
4
+ * Task Manager CLI Application
5
+ * This application allows users to manage their tasks via command line interface.
6
+ * It supports adding, listing, and removing tasks.
7
+ * Dependencies:
8
+ * - commander: For command line argument parsing
9
+ * - inquirer: For interactive prompts
10
+ * - fs: For file system operations
11
+ * Author: Mohamed Bakr
12
+ * Date: June 2024
13
+ * Version: 1.0.0
14
+ */
15
+
16
+ // for using commands in terminal
17
+ import { Command } from "commander";
18
+ // for interactive command line prompts
19
+ import inquirer from "inquirer";
20
+ // for file system operations
21
+ import fs from "fs/promises";
22
+
23
+ // assigning Commander to a variable
24
+ const program = new Command();
25
+
26
+ // file to store tasks
27
+ const TASKS_FILE = "./todos.json";
28
+ // allowed task statuses
29
+ const ALLOWED_STATUSES = ["todo", "in-progress", "done"];
30
+ // allowed task priorities
31
+ const ALLOWED_PRIORITIES = ["low", "medium", "high"];
32
+
33
+
34
+ // function to load tasks from file
35
+ const loadTasks = async () => {
36
+ try{
37
+ const data = await fs.readFile(TASKS_FILE, 'utf8');
38
+ return JSON.parse(data);
39
+ } catch (error) {
40
+ if (error.code === 'ENOENT') return [];
41
+ throw error;
42
+ }
43
+ };
44
+ // function to save tasks to file
45
+ const saveTasks = async (tasks) => {
46
+ await fs.writeFile(TASKS_FILE, JSON.stringify(tasks, null, 2));
47
+ };
48
+ // get next task ID
49
+ const getNextId = (tasks) => {
50
+ return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 : 1;
51
+ };
52
+ // validate task status
53
+ const validateStatus = (status) => {
54
+ if (!ALLOWED_STATUSES.includes(status)) {
55
+ throw new Error(`Invalid status. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`);
56
+ }
57
+ };
58
+ // validate task priority
59
+ const validatePriority = (priority) => {
60
+ if (!ALLOWED_PRIORITIES.includes(priority)) {
61
+ throw new Error(`Invalid priority. Allowed priorities are: ${ALLOWED_PRIORITIES.join(", ")}`);
62
+ }
63
+ };
64
+ // verify due date format
65
+ const validateDueDate = (dueDate) => {
66
+ if (isNaN(Date.parse(dueDate))) {
67
+ throw new Error("Invalid due date. Please use a valid date format (YYYY-MM-DD).");
68
+ }
69
+ };
70
+
71
+
72
+ // setting up
73
+ program
74
+ .name("todo-cli")
75
+ .description("A simple CLI application to manage your tasks")
76
+ .version("1.0.0");
77
+
78
+ // use command 'add' with title + status + priority + dueDate + description and action
79
+ program
80
+ .command('add')
81
+ .alias('a')
82
+ .description('Add a new task')
83
+ .action(async (title, status, priority, dueDate, description = "") => {
84
+ const answers = await inquirer.prompt([
85
+ {
86
+ type: 'input',
87
+ name: 'title',
88
+ message : 'Task Title:',
89
+ validate : input => input ? true : 'Title cannot be empty!'
90
+ },
91
+ {
92
+ type: 'list',
93
+ name: 'status',
94
+ message : 'Task Status:',
95
+ choices : ALLOWED_STATUSES,
96
+ default : 'todo'
97
+ },
98
+ {
99
+ type: 'list',
100
+ name: 'priority',
101
+ message : 'Task Priority:',
102
+ choices : ALLOWED_PRIORITIES,
103
+ default : 'medium'
104
+ },
105
+ {
106
+ type: 'input',
107
+ name: 'dueDate',
108
+ message : 'Due Date (YYYY-MM-DD):',
109
+ validate : input => {
110
+ try {
111
+ validateDueDate(input);
112
+ return true;
113
+ } catch (error) {
114
+ return error.message;
115
+ }
116
+ }
117
+ },
118
+ {
119
+ type: 'input',
120
+ name: 'description',
121
+ message : 'Task Description (optional):'
122
+ }
123
+ ]);
124
+ // load existing tasks
125
+ const tasks = await loadTasks();
126
+
127
+ // create new task object
128
+ const newTask = {
129
+ id: getNextId(tasks),
130
+ title: answers.title,
131
+ status: answers.status,
132
+ priority: answers.priority,
133
+ dueDate: answers.dueDate,
134
+ description: answers.description || ''
135
+ };
136
+ // add new task to tasks array
137
+ tasks.push(newTask);
138
+ // save updated tasks to file
139
+ await saveTasks(tasks);
140
+ console.log("Task added successfully!");
141
+ });
142
+
143
+
144
+ // use command 'list' with optional status filter and action
145
+ program
146
+ .command('list')
147
+ .alias('ls')
148
+ .description('List all tasks')
149
+ .option('-s, --status <status>', 'Filter tasks by status')
150
+ .action(async (options) => {
151
+ let tasks = await loadTasks();
152
+
153
+ if (options.status) {
154
+ if (!ALLOWED_STATUSES.includes(options.status)){
155
+ console.error(`Invalid status filter. Allowed statuses are: ${ALLOWED_STATUSES.join(", ")}`);
156
+ process.exit(1);
157
+ }
158
+ tasks = tasks.filter(task => task.status === options.status);
159
+ }
160
+ if (tasks.length === 0) {
161
+ console.log('No tasks found.');
162
+ return;
163
+ }
164
+ // display tasks in a table format
165
+ console.table([{
166
+ ID: task.id,
167
+ Title: task.title,
168
+ Status: task.status,
169
+ Priority: task.priority,
170
+ DueDate: task.dueDate,
171
+ Description: task.description || ''
172
+ }]);
173
+ });
174
+
175
+ // use command 'update' with task ID and action
176
+ program
177
+ .command('update')
178
+ .alias('up')
179
+ .description('Update a task by ID')
180
+ .action(async () =>{
181
+ const tasks = await loadTasks();
182
+ if (tasks.length === 0) {
183
+ console.log('No tasks found to update.');
184
+ return;
185
+ }
186
+
187
+ const { id } = await inquirer.prompt([
188
+ {
189
+ type: 'list',
190
+ name: 'id',
191
+ message: 'Select the task to update:',
192
+ choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
193
+ }
194
+ ]);
195
+ const { status } = await inquirer.prompt([
196
+ {
197
+ type: 'list',
198
+ name: 'status',
199
+ message: 'Select the new status:',
200
+ choices: ALLOWED_STATUSES
201
+ }
202
+ ]);
203
+
204
+ const task = tasks.find(t => t.id === Number(id));
205
+ if (!task) {
206
+ console.error('Task not found!');
207
+ return;
208
+ }
209
+ task.status = status;
210
+
211
+ await saveTasks(tasks);
212
+ console.log('Task updated successfully!');
213
+
214
+ console.table([{
215
+ ID: task.id,
216
+ Title: task.title,
217
+ Status: task.status,
218
+ Priority: task.priority,
219
+ DueDate: task.dueDate,
220
+ Description: task.description || ''
221
+ }]);
222
+ });
223
+
224
+ // use command 'delete' with task ID and action
225
+ program
226
+ .command('delete')
227
+ .alias('del')
228
+ .description('delete a task by ID')
229
+ .action(async () => {
230
+ const tasks = await loadTasks();
231
+ if (tasks.length === 0) {
232
+ console.log('No tasks found to delete.');
233
+ return;
234
+ }
235
+
236
+ const { id } = await inquirer.prompt([
237
+ {
238
+ type: 'list',
239
+ name: 'id',
240
+ message: 'Select the task to delete:',
241
+ choices: tasks.map(task => ({ name: `${task.id}: ${task.title}`, value: task.id }))
242
+ }
243
+ ]);
244
+
245
+ const { confirm } = await inquirer.prompt([
246
+ {
247
+ type: 'confirm',
248
+ name: 'confirm',
249
+ message: 'Are you sure you want to delete this task?',
250
+ default: false
251
+ }
252
+ ]);
253
+ if (!confirm) {
254
+ console.log('Task deletion cancelled.');
255
+ return;
256
+ }
257
+
258
+ const newTasks = tasks.filter(t => t.id !== id);
259
+ await saveTasks(newTasks);
260
+ console.log('Task deleted successfully!');
261
+
262
+ console.table([{
263
+ ID: task.id,
264
+ Title: task.title,
265
+ Status: task.status,
266
+ Priority: task.priority,
267
+ DueDate: task.dueDate,
268
+ Description: task.description || ''
269
+ }]);
270
+ });
271
+
272
+ // parse command line arguments
273
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "taskninja",
3
+ "version": "1.0.0",
4
+ "description": "a simple CLI application with JS as a (To-Do Application)",
5
+ "main": "app.js",
6
+ "type" : "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1",
9
+ "start": "node app.js",
10
+ "dev": "node app.js"
11
+ },
12
+ "bin" : {
13
+ "dotask": "./app.js"
14
+ },
15
+ "author": "Moahmed Bakr",
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "commander": "^14.0.2",
19
+ "inquirer": "^13.2.1"
20
+ }
21
+ }