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 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