tasks_cli 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c56bae0c638f891b0b2d8893cbedefbeb8a3f9a2ca695eb16d9331f2c4f5af
4
- data.tar.gz: d22a346ea8346f234e6849ef23073ad41ab671dc6652fb7b5fbba8c462e0e75e
3
+ metadata.gz: 23f5574360b1def72fa621127d325779afb0ae22b260755686ff3dd87cf8bef0
4
+ data.tar.gz: dd4090622a552cde4d2bf01b774f1e2ae53980fae66b767491f7dcc36d9643c2
5
5
  SHA512:
6
- metadata.gz: ea2f7bb708341e7aa16a16be841036a937a0f3a7b3bc189379b1f3157d7a6c2da921bea6861b79a9063669702b1cd44f5300045cc86ec22bdca2a6baeb6aa478
7
- data.tar.gz: e89f423a18348ecf83cefbd0e46c66eda9c64004b23f198aced45e3e7eb5ccb561c662901641edbb4b61eef66ef65f73c6f6ddeffcf567b2f3a1b75e84e2a772
6
+ metadata.gz: 1dcf2eef4863b35bb212cec9dbf4e090670d3c84ff734fb8e231eaed9151fd1780dc4ef7a95d640d8f7eeb377779f7fe89d3ea64a3cfadb47f69dbf154d5860c
7
+ data.tar.gz: f0d031c7ede706093e0ef8f5980ac7c64973a1b230e16a2e7557540267b1a1a3a38773ef1f9464d94f1e817076dcb934561bd1de1b150de6b9f496778728c70a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tasks_cli (0.1.1)
4
+ tasks_cli (0.1.3)
5
5
  csv (~> 3.0)
6
6
  logger (~> 1.5)
7
7
  ostruct (~> 0.3.0)
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', 'Filter tasks by field and value'
16
- def filter(filter_string)
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
- key, value = filter_string.split(':')
19
- filtered_tasks = manager.filter_tasks({ key => value })
20
- print_tasks(filtered_tasks)
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'
@@ -78,7 +78,9 @@ module TasksCLI
78
78
 
79
79
  def filter_tasks(criteria)
80
80
  @tasks.select do |task|
81
- criteria.all? { |field, value| task.matches?(field, value) }
81
+ criteria.all? do |field, values|
82
+ values.any? { |value| task.matches?(field, value) }
83
+ end
82
84
  end
83
85
  end
84
86
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TasksCLI
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
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
+ ![TasksCLI Example](example.png)
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.2
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-16 00:00:00.000000000 Z
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
- - tasks
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