tasks_cli 0.1.2 → 0.1.3
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/LICENSE +21 -0
- data/example.png +0 -0
- data/lib/tasks_cli/cli.rb +16 -5
- data/lib/tasks_cli/task_manager.rb +3 -1
- data/lib/tasks_cli/version.rb +1 -1
- data/readme.md +106 -0
- metadata +5 -3
- data/tasks +0 -259
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23f5574360b1def72fa621127d325779afb0ae22b260755686ff3dd87cf8bef0
|
4
|
+
data.tar.gz: dd4090622a552cde4d2bf01b774f1e2ae53980fae66b767491f7dcc36d9643c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dcf2eef4863b35bb212cec9dbf4e090670d3c84ff734fb8e231eaed9151fd1780dc4ef7a95d640d8f7eeb377779f7fe89d3ea64a3cfadb47f69dbf154d5860c
|
7
|
+
data.tar.gz: f0d031c7ede706093e0ef8f5980ac7c64973a1b230e16a2e7557540267b1a1a3a38773ef1f9464d94f1e817076dcb934561bd1de1b150de6b9f496778728c70a
|
data/Gemfile.lock
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 adilw3nomad
|
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.
|
data/example.png
ADDED
Binary file
|
data/lib/tasks_cli/cli.rb
CHANGED
@@ -12,12 +12,23 @@ module TasksCLI
|
|
12
12
|
print_tasks(manager.all_tasks)
|
13
13
|
end
|
14
14
|
|
15
|
-
desc 'filter FIELD:VALUE
|
16
|
-
|
15
|
+
desc 'filter FIELD:VALUE [FIELD:VALUE ...]',
|
16
|
+
'Filter tasks by one or more field:value pairs. Example: filter status:in_progress priority:high'
|
17
|
+
def filter(*args)
|
18
|
+
criteria = {}
|
19
|
+
args.flatten.each do |arg|
|
20
|
+
field, value = arg.split(':', 2)
|
21
|
+
criteria[field.to_sym] ||= []
|
22
|
+
criteria[field.to_sym] << value
|
23
|
+
end
|
17
24
|
manager = TaskManager.new
|
18
|
-
|
19
|
-
filtered_tasks
|
20
|
-
|
25
|
+
filtered_tasks = manager.filter_tasks(criteria)
|
26
|
+
if filtered_tasks.empty?
|
27
|
+
puts Rainbow('No tasks found matching the given criteria.').yellow
|
28
|
+
else
|
29
|
+
print_tasks(filtered_tasks)
|
30
|
+
puts "\nFiltered tasks: #{Rainbow(filtered_tasks.count).green}"
|
31
|
+
end
|
21
32
|
end
|
22
33
|
|
23
34
|
desc 'view NUMBER', 'View details of a specific task'
|
data/lib/tasks_cli/version.rb
CHANGED
data/readme.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# TasksCLI
|
2
|
+
|
3
|
+
TasksCLI is a command-line interface tool for managing tasks stored in a CSV file. It allows you to list, filter, view, and update tasks efficiently from your terminal.
|
4
|
+
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
- List all tasks
|
9
|
+
- Filter tasks by field and value
|
10
|
+
- View detailed information for a specific task
|
11
|
+
- Update task status
|
12
|
+
- Colorful output for better readability
|
13
|
+
|
14
|
+

|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Install the gem by running:
|
21
|
+
|
22
|
+
```
|
23
|
+
gem install tasks_cli
|
24
|
+
```
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
TasksCLI expects a CSV file located at `~/tasks.csv`. Ensure this file exists and has the correct format before using the tool.
|
29
|
+
|
30
|
+
### CSV Format
|
31
|
+
|
32
|
+
The CSV file should have the following columns:
|
33
|
+
|
34
|
+
1. Epic Name
|
35
|
+
2. Ticket Number
|
36
|
+
3. Ticket Name
|
37
|
+
4. Ticket Description
|
38
|
+
5. Priority
|
39
|
+
6. Status
|
40
|
+
7. Relates To [Array]
|
41
|
+
8. Blocked By [Array]
|
42
|
+
9. Updated At (optional)
|
43
|
+
|
44
|
+
```
|
45
|
+
Epic Name,Ticket Number,Ticket Name,Ticket Description,Priority,Status,Relates To,Blocked By,Updated At
|
46
|
+
Epic 1,1,Task 1,Description 1,High,To Do,,,
|
47
|
+
Epic 1,2,Task 2,Description 2,Medium,In Progress,,,
|
48
|
+
Epic 1,3,Task 3,Description 3,Low,Done,,,
|
49
|
+
```
|
50
|
+
|
51
|
+
What I find best is to use the following process;
|
52
|
+
1. Loosely define the project with an LLM of your choice.
|
53
|
+
2. Ask LLM to generate a list of tasks for the project.
|
54
|
+
3. Iterate on the LLM generated tasks until you are satisfied.
|
55
|
+
4. Ask LLM to generate the CSV of the tasks using the above format.
|
56
|
+
|
57
|
+
## Usage
|
58
|
+
|
59
|
+
### Listing Tasks
|
60
|
+
|
61
|
+
To list all tasks, run:
|
62
|
+
|
63
|
+
```
|
64
|
+
tasks list
|
65
|
+
```
|
66
|
+
|
67
|
+
### Filtering Tasks
|
68
|
+
|
69
|
+
To filter tasks by a specific field and value, run:
|
70
|
+
|
71
|
+
```
|
72
|
+
tasks filter FIELD:VALUE
|
73
|
+
```
|
74
|
+
|
75
|
+
Replace `FIELD` with the column name and `VALUE` with the value you want to filter by.
|
76
|
+
|
77
|
+
For example, to filter tasks by Epic 1:
|
78
|
+
|
79
|
+
```
|
80
|
+
tasks filter "Epic Name":"Epic 1"
|
81
|
+
```
|
82
|
+
|
83
|
+
### Viewing Task Details
|
84
|
+
|
85
|
+
To view details of a specific task, run:
|
86
|
+
|
87
|
+
```
|
88
|
+
tasks view NUMBER
|
89
|
+
```
|
90
|
+
|
91
|
+
### Updating Task Status
|
92
|
+
|
93
|
+
To update the status of a task, run:
|
94
|
+
|
95
|
+
```
|
96
|
+
tasks update NUMBER STATUS
|
97
|
+
```
|
98
|
+
|
99
|
+
Replace `NUMBER` with the ticket number and `STATUS` with the new status you want to set.
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
This project is licensed under the MIT License. See the `LICENSE` file for more details.
|
104
|
+
|
105
|
+
|
106
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tasks_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- adilw3nomad
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: csv
|
@@ -104,13 +104,15 @@ extra_rdoc_files: []
|
|
104
104
|
files:
|
105
105
|
- Gemfile
|
106
106
|
- Gemfile.lock
|
107
|
+
- LICENSE
|
107
108
|
- bin/tasks
|
109
|
+
- example.png
|
108
110
|
- lib/tasks_cli.rb
|
109
111
|
- lib/tasks_cli/cli.rb
|
110
112
|
- lib/tasks_cli/task.rb
|
111
113
|
- lib/tasks_cli/task_manager.rb
|
112
114
|
- lib/tasks_cli/version.rb
|
113
|
-
-
|
115
|
+
- readme.md
|
114
116
|
- tasks_cli.gemspec
|
115
117
|
homepage: https://github.com/adilw3nomad/tasks_cli
|
116
118
|
licenses:
|
data/tasks
DELETED
@@ -1,259 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'csv'
|
4
|
-
require 'optparse'
|
5
|
-
require 'rainbow'
|
6
|
-
require 'time'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
Rainbow.enabled = true
|
10
|
-
|
11
|
-
class Task
|
12
|
-
attr_reader :epic, :number, :name, :description, :priority, :status, :relates_to, :blocked_by, :updated_at
|
13
|
-
|
14
|
-
def initialize(row)
|
15
|
-
@epic = extract_value(row[0])
|
16
|
-
@number = extract_value(row[1])
|
17
|
-
@name = extract_value(row[2])
|
18
|
-
@description = extract_value(row[3])
|
19
|
-
@priority = extract_value(row[4])
|
20
|
-
@status = extract_value(row[5])
|
21
|
-
@relates_to = extract_value(row[6])
|
22
|
-
@blocked_by = extract_value(row[7])
|
23
|
-
@updated_at = extract_value(row[8]) || Time.now.iso8601
|
24
|
-
end
|
25
|
-
|
26
|
-
def status_color
|
27
|
-
case @status.downcase
|
28
|
-
when 'to do' then :red
|
29
|
-
when 'in progress' then :yellow
|
30
|
-
when 'done' then :green
|
31
|
-
else :white
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def extract_value(field)
|
38
|
-
return field if field.is_a?(String)
|
39
|
-
field.is_a?(Array) ? field[1].to_s.strip : field.to_s.strip
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_s
|
43
|
-
"#{Rainbow(@number).cyan}: #{Rainbow(@name).bright} (#{status_color}) - Priority: #{priority_colored} - Updated: #{@updated_at}"
|
44
|
-
end
|
45
|
-
|
46
|
-
def detailed_view
|
47
|
-
<<~TASK
|
48
|
-
#{Rainbow("Epic:").bright} #{@epic}
|
49
|
-
#{Rainbow("Number:").bright} #{Rainbow(@number).cyan}
|
50
|
-
#{Rainbow("Name:").bright} #{Rainbow(@name).bright}
|
51
|
-
#{Rainbow("Description:").bright} #{@description}
|
52
|
-
#{Rainbow("Priority:").bright} #{priority_colored}
|
53
|
-
#{Rainbow("Status:").bright} #{status_color}
|
54
|
-
#{Rainbow("Relates To:").bright} #{@relates_to}
|
55
|
-
#{Rainbow("Blocked By:").bright} #{@blocked_by}
|
56
|
-
#{Rainbow("Last Updated:").bright} #{@updated_at}
|
57
|
-
TASK
|
58
|
-
end
|
59
|
-
|
60
|
-
def to_csv
|
61
|
-
[@epic, @number, @name, @description, @priority, @status, @relates_to, @blocked_by, @updated_at]
|
62
|
-
end
|
63
|
-
|
64
|
-
def matches?(field, value)
|
65
|
-
send(field.to_sym).to_s.downcase.include?(value.to_s.downcase)
|
66
|
-
rescue NoMethodError
|
67
|
-
false
|
68
|
-
end
|
69
|
-
|
70
|
-
def update_timestamp
|
71
|
-
@updated_at = Time.now.iso8601
|
72
|
-
end
|
73
|
-
|
74
|
-
def status_color
|
75
|
-
case @status.downcase
|
76
|
-
when 'to do' then Rainbow(@status).red
|
77
|
-
when 'in progress' then Rainbow(@status).yellow
|
78
|
-
when 'done' then Rainbow(@status).green
|
79
|
-
else Rainbow(@status).white
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def priority_colored
|
84
|
-
case @priority.downcase
|
85
|
-
when 'high' then Rainbow(@priority).red
|
86
|
-
when 'medium' then Rainbow(@priority).yellow
|
87
|
-
when 'low' then Rainbow(@priority).green
|
88
|
-
else Rainbow(@priority).white
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
class TaskManager
|
94
|
-
def initialize(file_path)
|
95
|
-
@file_path = file_path
|
96
|
-
load_tasks
|
97
|
-
end
|
98
|
-
|
99
|
-
def load_tasks
|
100
|
-
@tasks = []
|
101
|
-
CSV.foreach(@file_path, headers: false) do |row|
|
102
|
-
next if row == ["Epic Name", "Ticket Number", "Ticket Name", "Ticket Description", "Priority", "Status", "Relates To", "Blocked By", "Updated At"]
|
103
|
-
begin
|
104
|
-
parsed_row = row.map do |field|
|
105
|
-
parse_field(field)
|
106
|
-
end
|
107
|
-
@tasks << Task.new(parsed_row)
|
108
|
-
rescue => e
|
109
|
-
puts "Error processing row: #{row.inspect}"
|
110
|
-
puts "Error message: #{e.message}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
rescue CSV::MalformedCSVError => e
|
114
|
-
puts Rainbow("Error reading CSV: #{e.message}. Trying to parse with alternative method...").red
|
115
|
-
parse_csv_manually
|
116
|
-
rescue => e
|
117
|
-
puts Rainbow("Unexpected error while loading tasks: #{e.message}").red
|
118
|
-
puts e.backtrace
|
119
|
-
end
|
120
|
-
|
121
|
-
def parse_field(field)
|
122
|
-
return field if field.nil? || !(field.start_with?('[') && field.end_with?(']'))
|
123
|
-
parsed = JSON.parse(field.gsub('=>', ':'))
|
124
|
-
[parsed[0], parsed[1]]
|
125
|
-
rescue JSON::ParserError
|
126
|
-
[nil, field]
|
127
|
-
end
|
128
|
-
|
129
|
-
def parse_csv_manually
|
130
|
-
@tasks = []
|
131
|
-
lines = File.readlines(@file_path)
|
132
|
-
lines[1..-1].each do |line|
|
133
|
-
fields = line.strip.split(',')
|
134
|
-
parsed_fields = fields.map { |field| parse_field(field) }
|
135
|
-
@tasks << Task.new(parsed_fields)
|
136
|
-
rescue => e
|
137
|
-
puts "Error processing line: #{line}"
|
138
|
-
puts "Error message: #{e.message}"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def save_tasks(task_number)
|
143
|
-
lines = File.readlines(@file_path)
|
144
|
-
File.open(@file_path, 'w') do |file|
|
145
|
-
file.puts lines[0].chomp # Write header as is
|
146
|
-
lines[1..-1].each do |line|
|
147
|
-
fields = line.strip.split(',').map { |f| parse_field(f) }
|
148
|
-
if fields[1][1] == task_number
|
149
|
-
task = find_task(task_number)
|
150
|
-
updated_line = task.to_csv.map { |value| [fields[_1][0], value].to_s }.join(',')
|
151
|
-
file.puts updated_line
|
152
|
-
else
|
153
|
-
file.puts line.chomp # Write unchanged lines as is
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def all_tasks
|
160
|
-
@tasks
|
161
|
-
end
|
162
|
-
|
163
|
-
def filter_tasks(criteria)
|
164
|
-
@tasks.select do |task|
|
165
|
-
criteria.all? { |field, value| task.matches?(field, value) }
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
def find_task(number)
|
170
|
-
@tasks.find { |task| task.number == number }
|
171
|
-
end
|
172
|
-
|
173
|
-
def update_status(number, new_status)
|
174
|
-
task = find_task(number)
|
175
|
-
if task
|
176
|
-
old_status = task.status
|
177
|
-
task.instance_variable_set(:@status, new_status)
|
178
|
-
task.update_timestamp
|
179
|
-
save_tasks(number)
|
180
|
-
puts Rainbow("Updated status of task #{number} from #{old_status} to #{new_status}").green
|
181
|
-
puts Rainbow("Last updated: #{task.updated_at}").cyan
|
182
|
-
else
|
183
|
-
puts Rainbow("Task not found").red
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def print_tasks(tasks)
|
189
|
-
if tasks.empty?
|
190
|
-
puts Rainbow("No tasks found.").yellow
|
191
|
-
return
|
192
|
-
end
|
193
|
-
|
194
|
-
puts Rainbow("\n%-10s %-30s %-15s %-10s %-25s" % ["Number", "Name", "Status", "Priority", "Last Updated"]).underline
|
195
|
-
tasks.each do |task|
|
196
|
-
puts "%-10s %-30s %-15s %-10s %-25s" % [
|
197
|
-
Rainbow(task.number).cyan,
|
198
|
-
Rainbow(task.name.to_s.truncate(30)).magenta,
|
199
|
-
task.status_color,
|
200
|
-
task.priority_colored,
|
201
|
-
Rainbow(task.updated_at).blue
|
202
|
-
]
|
203
|
-
end
|
204
|
-
puts "\nTotal tasks: #{Rainbow(tasks.count).green}"
|
205
|
-
end
|
206
|
-
|
207
|
-
# Main program
|
208
|
-
if __FILE__ == $0
|
209
|
-
begin
|
210
|
-
file_path = File.expand_path('~/tasks.csv') # Store the CSV in the user's home directory
|
211
|
-
manager = TaskManager.new(file_path)
|
212
|
-
|
213
|
-
options = {}
|
214
|
-
OptionParser.new do |opts|
|
215
|
-
opts.banner = "Usage: tasks [options]"
|
216
|
-
|
217
|
-
opts.on("-a", "--all", "View all tasks") do
|
218
|
-
options[:all] = true
|
219
|
-
end
|
220
|
-
|
221
|
-
opts.on("-f FIELD:VALUE", "--filter FIELD:VALUE", "Filter tasks by field and value (can be used multiple times)") do |f|
|
222
|
-
options[:filter] ||= {}
|
223
|
-
key, value = f.split(':')
|
224
|
-
options[:filter][key] = value
|
225
|
-
end
|
226
|
-
|
227
|
-
opts.on("-v NUMBER", "--view NUMBER", "View a single task") do |v|
|
228
|
-
options[:view] = v
|
229
|
-
end
|
230
|
-
|
231
|
-
opts.on("-u NUMBER,STATUS", "--update NUMBER,STATUS", Array, "Update a task's status") do |u|
|
232
|
-
options[:update] = u
|
233
|
-
end
|
234
|
-
end.parse!
|
235
|
-
|
236
|
-
if options[:all]
|
237
|
-
print_tasks(manager.all_tasks)
|
238
|
-
elsif options[:filter]
|
239
|
-
print_tasks(manager.filter_tasks(options[:filter]))
|
240
|
-
elsif options[:view]
|
241
|
-
task = manager.find_task(options[:view])
|
242
|
-
if task
|
243
|
-
puts task.detailed_view
|
244
|
-
else
|
245
|
-
puts Rainbow("Task not found").red
|
246
|
-
end
|
247
|
-
elsif options[:update]
|
248
|
-
number, status = options[:update]
|
249
|
-
manager.update_status(number, status)
|
250
|
-
else
|
251
|
-
puts Rainbow("No valid option provided. Use -h for help.").yellow
|
252
|
-
end
|
253
|
-
rescue Errno::ENOENT
|
254
|
-
puts Rainbow("Error: CSV file '#{file_path}' not found. Please ensure the file exists.").red
|
255
|
-
rescue => e
|
256
|
-
puts Rainbow("An unexpected error occurred: #{e.message}").red
|
257
|
-
puts e.backtrace
|
258
|
-
end
|
259
|
-
end
|