tasks_cli 0.1.1 → 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: 1b2feeb892f58ae854a0761cd6e5c0faff6b794815628921c151b90fbc343f1a
4
- data.tar.gz: 0da27041da8f50808ac78f46f2bf5ddfe9557bda9defd57802120ad5da268c5a
3
+ metadata.gz: 23f5574360b1def72fa621127d325779afb0ae22b260755686ff3dd87cf8bef0
4
+ data.tar.gz: dd4090622a552cde4d2bf01b774f1e2ae53980fae66b767491f7dcc36d9643c2
5
5
  SHA512:
6
- metadata.gz: f8f1127576598a441dc39de0775f84f9d716bf48d5278af9d7759c9881511495b40d737d562d6c196aa047a8b6ba6217a5a9e064f558df9bae3dc23612eeacc2
7
- data.tar.gz: 12493dcc828745b56a824a536c6bf9094ba6b3ed0e0636bbe3740c6d6e1393389a1893a8981c4ecfcaca675f4413d4cd9f117c820d6b4be3810377f347ba23d1
6
+ metadata.gz: 1dcf2eef4863b35bb212cec9dbf4e090670d3c84ff734fb8e231eaed9151fd1780dc4ef7a95d640d8f7eeb377779f7fe89d3ea64a3cfadb47f69dbf154d5860c
7
+ data.tar.gz: f0d031c7ede706093e0ef8f5980ac7c64973a1b230e16a2e7557540267b1a1a3a38773ef1f9464d94f1e817076dcb934561bd1de1b150de6b9f496778728c70a
data/Gemfile CHANGED
@@ -1,7 +1,9 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in tasks_cli.gemspec
4
6
  gemspec
5
7
 
6
- gem "rake", "~> 12.0"
7
- gem "rspec", "~> 3.0"
8
+ gem 'rake', '~> 12.0'
9
+ gem 'rspec', '~> 3.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tasks_cli (0.1.3)
5
+ csv (~> 3.0)
6
+ logger (~> 1.5)
7
+ ostruct (~> 0.3.0)
8
+ rainbow (~> 3.0)
9
+ thor (~> 1.0)
10
+ tty-table (~> 0.12.0)
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ csv (3.2.8)
16
+ diff-lcs (1.5.1)
17
+ logger (1.6.1)
18
+ ostruct (0.3.3)
19
+ pastel (0.8.0)
20
+ tty-color (~> 0.5)
21
+ rainbow (3.1.1)
22
+ rake (12.3.3)
23
+ rspec (3.13.0)
24
+ rspec-core (~> 3.13.0)
25
+ rspec-expectations (~> 3.13.0)
26
+ rspec-mocks (~> 3.13.0)
27
+ rspec-core (3.13.1)
28
+ rspec-support (~> 3.13.0)
29
+ rspec-expectations (3.13.3)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.13.0)
32
+ rspec-mocks (3.13.1)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.13.0)
35
+ rspec-support (3.13.1)
36
+ strings (0.2.1)
37
+ strings-ansi (~> 0.2)
38
+ unicode-display_width (>= 1.5, < 3.0)
39
+ unicode_utils (~> 1.4)
40
+ strings-ansi (0.2.0)
41
+ thor (1.3.2)
42
+ tty-color (0.6.0)
43
+ tty-screen (0.8.2)
44
+ tty-table (0.12.0)
45
+ pastel (~> 0.8)
46
+ strings (~> 0.2.0)
47
+ tty-screen (~> 0.8)
48
+ unicode-display_width (2.6.0)
49
+ unicode_utils (1.4.0)
50
+
51
+ PLATFORMS
52
+ arm64-darwin-23
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ rake (~> 12.0)
57
+ rspec (~> 3.0)
58
+ tasks_cli!
59
+
60
+ BUNDLED WITH
61
+ 2.5.16
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/bin/tasks CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "tasks_cli"
4
+ require 'bundler/setup'
5
+ require 'tasks_cli'
5
6
 
6
- TasksCLI::CLI.start(ARGV)
7
+ TasksCLI::CLI.start(ARGV)
data/example.png ADDED
Binary file
data/lib/tasks_cli/cli.rb CHANGED
@@ -1,34 +1,48 @@
1
- require "thor"
2
- require "rainbow"
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'rainbow'
5
+ require 'tty-table'
3
6
 
4
7
  module TasksCLI
5
8
  class CLI < Thor
6
- desc "list", "List all tasks"
9
+ desc 'list', 'List all tasks'
7
10
  def list
8
11
  manager = TaskManager.new
9
12
  print_tasks(manager.all_tasks)
10
13
  end
11
14
 
12
- desc "filter FIELD:VALUE", "Filter tasks by field and value"
13
- 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
14
24
  manager = TaskManager.new
15
- key, value = filter_string.split(':')
16
- filtered_tasks = manager.filter_tasks({ key => value })
17
- 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
18
32
  end
19
33
 
20
- desc "view NUMBER", "View details of a specific task"
34
+ desc 'view NUMBER', 'View details of a specific task'
21
35
  def view(number)
22
36
  manager = TaskManager.new
23
37
  task = manager.find_task(number)
24
38
  if task
25
39
  puts task.detailed_view
26
40
  else
27
- puts Rainbow("Task not found").red
41
+ puts Rainbow('Task not found').red
28
42
  end
29
43
  end
30
44
 
31
- desc "update NUMBER STATUS", "Update the status of a task"
45
+ desc 'update NUMBER STATUS', 'Update the status of a task'
32
46
  def update(number, status)
33
47
  manager = TaskManager.new
34
48
  manager.update_status(number, status)
@@ -38,21 +52,26 @@ module TasksCLI
38
52
 
39
53
  def print_tasks(tasks)
40
54
  if tasks.empty?
41
- puts Rainbow("No tasks found.").yellow
55
+ puts Rainbow('No tasks found.').yellow
42
56
  return
43
57
  end
44
58
 
45
- puts Rainbow("\n%-10s %-30s %-15s %-10s %-25s" % ["Number", "Name", "Status", "Priority", "Last Updated"]).underline
46
- tasks.each do |task|
47
- puts "%-10s %-30s %-15s %-10s %-25s" % [
59
+ header = ['Number', 'Epic', 'Name', 'Status', 'Priority', 'Last Updated']
60
+ rows = tasks.map do |task|
61
+ [
48
62
  Rainbow(task.number).cyan,
49
- Rainbow(task.name.to_s.truncate(30)).magenta,
50
- task.status_colored,
63
+ Rainbow(task.epic).yellow,
64
+ Rainbow(task.name).magenta,
65
+ task.status_color,
51
66
  task.priority_colored,
52
- Rainbow(task.updated_at).blue
67
+ Rainbow(task.updated_at.to_s).blue
53
68
  ]
54
69
  end
70
+
71
+ table = TTY::Table.new(header: header, rows: rows)
72
+ puts table.render(:unicode, padding: [0, 1], alignment: [:left])
73
+
55
74
  puts "\nTotal tasks: #{Rainbow(tasks.count).green}"
56
75
  end
57
76
  end
58
- end
77
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rainbow'
2
4
 
3
5
  module TasksCLI
4
6
  class Task
5
7
  attr_reader :epic, :number, :name, :description, :priority, :status, :relates_to, :blocked_by, :updated_at
6
-
8
+
7
9
  def initialize(row)
8
10
  @epic = extract_value(row[0])
9
11
  @number = extract_value(row[1])
@@ -17,25 +19,27 @@ module TasksCLI
17
19
  end
18
20
 
19
21
  def extract_value(field)
22
+ return '' if field.nil?
20
23
  return field if field.is_a?(String)
24
+
21
25
  field.is_a?(Array) ? field[1].to_s.strip : field.to_s.strip
22
26
  end
23
27
 
24
28
  def to_s
25
- "#{Rainbow(@number).cyan}: #{Rainbow(@name).bright} (#{status_colored}) - Priority: #{priority_colored} - Updated: #{@updated_at}"
29
+ "#{Rainbow(@number).cyan}: #{Rainbow(@name).bright} (#{status_color}) - Priority: #{priority_colored} - Updated: #{@updated_at}"
26
30
  end
27
31
 
28
32
  def detailed_view
29
33
  <<~TASK
30
- #{Rainbow("Epic:").bright} #{@epic}
31
- #{Rainbow("Number:").bright} #{Rainbow(@number).cyan}
32
- #{Rainbow("Name:").bright} #{Rainbow(@name).bright}
33
- #{Rainbow("Description:").bright} #{@description}
34
- #{Rainbow("Priority:").bright} #{priority_colored}
35
- #{Rainbow("Status:").bright} #{status_colored}
36
- #{Rainbow("Relates To:").bright} #{@relates_to}
37
- #{Rainbow("Blocked By:").bright} #{@blocked_by}
38
- #{Rainbow("Last Updated:").bright} #{@updated_at}
34
+ #{Rainbow('Epic:').bright} #{@epic}
35
+ #{Rainbow('Number:').bright} #{Rainbow(@number).cyan}
36
+ #{Rainbow('Name:').bright} #{Rainbow(@name).bright}
37
+ #{Rainbow('Description:').bright} #{@description}
38
+ #{Rainbow('Priority:').bright} #{priority_colored}
39
+ #{Rainbow('Status:').bright} #{status_color}
40
+ #{Rainbow('Relates To:').bright} #{@relates_to}
41
+ #{Rainbow('Blocked By:').bright} #{@blocked_by}
42
+ #{Rainbow('Last Updated:').bright} #{@updated_at}
39
43
  TASK
40
44
  end
41
45
 
@@ -53,7 +57,7 @@ module TasksCLI
53
57
  @updated_at = Time.now.iso8601
54
58
  end
55
59
 
56
- def status_colored
60
+ def status_color
57
61
  case @status.downcase
58
62
  when 'to do' then Rainbow(@status).red
59
63
  when 'in progress' then Rainbow(@status).yellow
@@ -71,4 +75,4 @@ module TasksCLI
71
75
  end
72
76
  end
73
77
  end
74
- end
78
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'csv'
2
4
  require 'json'
3
5
  require 'rainbow'
@@ -12,11 +14,13 @@ module TasksCLI
12
14
  def load_tasks
13
15
  @tasks = []
14
16
  CSV.foreach(@file_path, headers: false) do |row|
15
- next if row == ["Epic Name", "Ticket Number", "Ticket Name", "Ticket Description", "Priority", "Status", "Relates To", "Blocked By"]
17
+ next if row == ['Epic Name', 'Ticket Number', 'Ticket Name', 'Ticket Description', 'Priority', 'Status',
18
+ 'Relates To', 'Blocked By']
19
+
16
20
  begin
17
21
  parsed_row = row.map { |field| parse_field(field) }
18
22
  @tasks << Task.new(parsed_row)
19
- rescue => e
23
+ rescue StandardError => e
20
24
  puts "Error processing row: #{row.inspect}"
21
25
  puts "Error message: #{e.message}"
22
26
  end
@@ -24,13 +28,14 @@ module TasksCLI
24
28
  rescue CSV::MalformedCSVError => e
25
29
  puts Rainbow("Error reading CSV: #{e.message}. Trying to parse with alternative method...").red
26
30
  parse_csv_manually
27
- rescue => e
31
+ rescue StandardError => e
28
32
  puts Rainbow("Unexpected error while loading tasks: #{e.message}").red
29
33
  puts e.backtrace
30
34
  end
31
35
 
32
36
  def parse_field(field)
33
- return field unless field.start_with?('[') && field.end_with?(']')
37
+ return field if field.nil? || !(field.start_with?('[') && field.end_with?(']'))
38
+
34
39
  parsed = JSON.parse(field.gsub('=>', ':'))
35
40
  [parsed[0], parsed[1]]
36
41
  rescue JSON::ParserError
@@ -40,11 +45,11 @@ module TasksCLI
40
45
  def parse_csv_manually
41
46
  @tasks = []
42
47
  lines = File.readlines(@file_path)
43
- lines[1..-1].each do |line|
48
+ lines[1..].each do |line|
44
49
  fields = line.strip.split(',')
45
50
  parsed_fields = fields.map { |field| parse_field(field) }
46
51
  @tasks << Task.new(parsed_fields)
47
- rescue => e
52
+ rescue StandardError => e
48
53
  puts "Error processing line: #{line}"
49
54
  puts "Error message: #{e.message}"
50
55
  end
@@ -54,7 +59,7 @@ module TasksCLI
54
59
  lines = File.readlines(@file_path)
55
60
  File.open(@file_path, 'w') do |file|
56
61
  file.puts lines[0].chomp # Write header as is
57
- lines[1..-1].each do |line|
62
+ lines[1..].each do |line|
58
63
  fields = line.strip.split(',').map { |f| parse_field(f) }
59
64
  if fields[1][1] == task_number
60
65
  task = find_task(task_number)
@@ -68,12 +73,14 @@ module TasksCLI
68
73
  end
69
74
 
70
75
  def all_tasks
71
- @tasks
76
+ @tasks[1..]
72
77
  end
73
78
 
74
79
  def filter_tasks(criteria)
75
80
  @tasks.select do |task|
76
- criteria.all? { |field, value| task.matches?(field, value) }
81
+ criteria.all? do |field, values|
82
+ values.any? { |value| task.matches?(field, value) }
83
+ end
77
84
  end
78
85
  end
79
86
 
@@ -93,8 +100,8 @@ module TasksCLI
93
100
  puts Rainbow("Last updated: #{TasksCLI.format_date(task.updated_at)}").cyan
94
101
  else
95
102
  TasksCLI.logger.warn("Task not found: #{number}")
96
- puts Rainbow("Task not found").red
103
+ puts Rainbow('Task not found').red
97
104
  end
98
105
  end
99
106
  end
100
- end
107
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TasksCLI
2
- VERSION = "0.1.1"
3
- end
4
+ VERSION = '0.1.3'
5
+ end
data/lib/tasks_cli.rb CHANGED
@@ -1,8 +1,10 @@
1
- require "tasks_cli/version"
2
- require "tasks_cli/cli"
3
- require "tasks_cli/task"
4
- require "tasks_cli/task_manager"
5
- require "logger"
1
+ # frozen_string_literal: true
2
+
3
+ require 'tasks_cli/version'
4
+ require 'tasks_cli/cli'
5
+ require 'tasks_cli/task'
6
+ require 'tasks_cli/task_manager'
7
+ require 'logger'
6
8
 
7
9
  module TasksCLI
8
10
  class Error < StandardError; end
@@ -37,9 +39,8 @@ module TasksCLI
37
39
 
38
40
  # Logging
39
41
  def self.logger
40
- @logger ||= Logger.new(STDOUT).tap do |log|
42
+ @logger ||= Logger.new($stdout).tap do |log|
41
43
  log.progname = 'TasksCLI'
42
44
  end
43
45
  end
44
46
  end
45
- ```
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
+
data/tasks_cli.gemspec CHANGED
@@ -1,35 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'lib/tasks_cli/version'
2
4
 
3
5
  Gem::Specification.new do |spec|
4
- spec.name = "tasks_cli"
6
+ spec.name = 'tasks_cli'
5
7
  spec.version = TasksCLI::VERSION
6
- spec.authors = ["adilw3nomad"]
7
- spec.email = ["adil@w3nomad.com"]
8
+ spec.authors = ['adilw3nomad']
9
+ spec.email = ['adil@w3nomad.com']
8
10
 
9
- spec.summary = %q{A simple CLI for managing tasks}
10
- spec.description = %q{A command-line interface for managing tasks stored in a CSV file}
11
- spec.homepage = "https://github.com/adilw3nomad/tasks_cli"
12
- spec.license = "MIT"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
11
+ spec.summary = 'A simple CLI for managing tasks'
12
+ spec.description = 'A command-line interface for managing tasks stored in a CSV file'
13
+ spec.homepage = 'https://github.com/adilw3nomad/tasks_cli'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
16
 
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/adilw3nomad/tasks_cli"
17
- spec.metadata["changelog_uri"] = "https://github.com/adilw3nomad/tasks_cli/blob/master/CHANGELOG.md"
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/adilw3nomad/tasks_cli'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/adilw3nomad/tasks_cli/blob/master/CHANGELOG.md'
18
20
 
19
21
  # Specify which files should be added to the gem when it is released.
20
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
24
  `git ls-files -z`.split("\x0").reject do |f|
23
25
  f.match(%r{^(test|spec|features)/}) ||
24
- f.match(/^tasks_cli-.*\.gem$/) ||
25
- f.match(/^\.git/) ||
26
- f.match(/^\.github/)
26
+ f.match(/^tasks_cli-.*\.gem$/) ||
27
+ f.match(/^\.git/) ||
28
+ f.match(/^\.github/)
27
29
  end
28
30
  end
29
- spec.bindir = "exe"
30
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
31
+ spec.bindir = 'bin'
32
+ spec.executables = ['tasks']
33
+ spec.require_paths = ['lib']
32
34
 
33
- spec.add_dependency "thor", "~> 1.0"
34
- spec.add_dependency "rainbow", "~> 3.0"
35
- end
35
+ spec.add_dependency 'csv', '~> 3.0'
36
+ spec.add_dependency 'logger', '~> 1.5'
37
+ spec.add_dependency 'ostruct', '~> 0.3.0'
38
+ spec.add_dependency 'rainbow', '~> 3.0'
39
+ spec.add_dependency 'thor', '~> 1.0'
40
+ spec.add_dependency 'tty-table', '~> 0.12.0'
41
+ end
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tasks_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - adilw3nomad
8
8
  autorequire:
9
- bindir: exe
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
- name: thor
14
+ name: csv
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.0'
19
+ version: '3.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.0'
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ostruct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rainbow
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -38,21 +66,53 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-table
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.12.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.12.0
41
97
  description: A command-line interface for managing tasks stored in a CSV file
42
98
  email:
43
99
  - adil@w3nomad.com
44
- executables: []
100
+ executables:
101
+ - tasks
45
102
  extensions: []
46
103
  extra_rdoc_files: []
47
104
  files:
48
105
  - Gemfile
106
+ - Gemfile.lock
107
+ - LICENSE
49
108
  - bin/tasks
109
+ - example.png
50
110
  - lib/tasks_cli.rb
51
111
  - lib/tasks_cli/cli.rb
52
112
  - lib/tasks_cli/task.rb
53
113
  - lib/tasks_cli/task_manager.rb
54
114
  - lib/tasks_cli/version.rb
55
- - tasks
115
+ - readme.md
56
116
  - tasks_cli.gemspec
57
117
  homepage: https://github.com/adilw3nomad/tasks_cli
58
118
  licenses:
data/tasks DELETED
@@ -1,248 +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 extract_value(field)
27
- return field if field.is_a?(String)
28
- field.is_a?(Array) ? field[1].to_s.strip : field.to_s.strip
29
- end
30
-
31
- def to_s
32
- "#{Rainbow(@number).cyan}: #{Rainbow(@name).bright} (#{status_colored}) - Priority: #{priority_colored} - Updated: #{@updated_at}"
33
- end
34
-
35
- def detailed_view
36
- <<~TASK
37
- #{Rainbow("Epic:").bright} #{@epic}
38
- #{Rainbow("Number:").bright} #{Rainbow(@number).cyan}
39
- #{Rainbow("Name:").bright} #{Rainbow(@name).bright}
40
- #{Rainbow("Description:").bright} #{@description}
41
- #{Rainbow("Priority:").bright} #{priority_colored}
42
- #{Rainbow("Status:").bright} #{status_colored}
43
- #{Rainbow("Relates To:").bright} #{@relates_to}
44
- #{Rainbow("Blocked By:").bright} #{@blocked_by}
45
- #{Rainbow("Last Updated:").bright} #{@updated_at}
46
- TASK
47
- end
48
-
49
- def to_csv
50
- [@epic, @number, @name, @description, @priority, @status, @relates_to, @blocked_by, @updated_at]
51
- end
52
-
53
- def matches?(field, value)
54
- send(field.to_sym).to_s.downcase.include?(value.to_s.downcase)
55
- rescue NoMethodError
56
- false
57
- end
58
-
59
- def update_timestamp
60
- @updated_at = Time.now.iso8601
61
- end
62
-
63
- def status_colored
64
- case @status.downcase
65
- when 'to do' then Rainbow(@status).red
66
- when 'in progress' then Rainbow(@status).yellow
67
- when 'done' then Rainbow(@status).green
68
- else Rainbow(@status).white
69
- end
70
- end
71
-
72
- def priority_colored
73
- case @priority.downcase
74
- when 'high' then Rainbow(@priority).red
75
- when 'medium' then Rainbow(@priority).yellow
76
- when 'low' then Rainbow(@priority).green
77
- else Rainbow(@priority).white
78
- end
79
- end
80
- end
81
-
82
- class TaskManager
83
- def initialize(file_path)
84
- @file_path = file_path
85
- load_tasks
86
- end
87
-
88
- def load_tasks
89
- @tasks = []
90
- CSV.foreach(@file_path, headers: false) do |row|
91
- next if row == ["Epic Name", "Ticket Number", "Ticket Name", "Ticket Description", "Priority", "Status", "Relates To", "Blocked By"]
92
- begin
93
- parsed_row = row.map do |field|
94
- parse_field(field)
95
- end
96
- @tasks << Task.new(parsed_row)
97
- rescue => e
98
- puts "Error processing row: #{row.inspect}"
99
- puts "Error message: #{e.message}"
100
- end
101
- end
102
- rescue CSV::MalformedCSVError => e
103
- puts Rainbow("Error reading CSV: #{e.message}. Trying to parse with alternative method...").red
104
- parse_csv_manually
105
- rescue => e
106
- puts Rainbow("Unexpected error while loading tasks: #{e.message}").red
107
- puts e.backtrace
108
- end
109
-
110
- def parse_field(field)
111
- return field unless field.start_with?('[') && field.end_with?(']')
112
- parsed = JSON.parse(field.gsub('=>', ':'))
113
- [parsed[0], parsed[1]]
114
- rescue JSON::ParserError
115
- [nil, field]
116
- end
117
-
118
- def parse_csv_manually
119
- @tasks = []
120
- lines = File.readlines(@file_path)
121
- lines[1..-1].each do |line|
122
- fields = line.strip.split(',')
123
- parsed_fields = fields.map { |field| parse_field(field) }
124
- @tasks << Task.new(parsed_fields)
125
- rescue => e
126
- puts "Error processing line: #{line}"
127
- puts "Error message: #{e.message}"
128
- end
129
- end
130
-
131
- def save_tasks(task_number)
132
- lines = File.readlines(@file_path)
133
- File.open(@file_path, 'w') do |file|
134
- file.puts lines[0].chomp # Write header as is
135
- lines[1..-1].each do |line|
136
- fields = line.strip.split(',').map { |f| parse_field(f) }
137
- if fields[1][1] == task_number
138
- task = find_task(task_number)
139
- updated_line = task.to_csv.map { |value| [fields[_1][0], value].to_s }.join(',')
140
- file.puts updated_line
141
- else
142
- file.puts line.chomp # Write unchanged lines as is
143
- end
144
- end
145
- end
146
- end
147
-
148
- def all_tasks
149
- @tasks
150
- end
151
-
152
- def filter_tasks(criteria)
153
- @tasks.select do |task|
154
- criteria.all? { |field, value| task.matches?(field, value) }
155
- end
156
- end
157
-
158
- def find_task(number)
159
- @tasks.find { |task| task.number == number }
160
- end
161
-
162
- def update_status(number, new_status)
163
- task = find_task(number)
164
- if task
165
- old_status = task.status
166
- task.instance_variable_set(:@status, new_status)
167
- task.update_timestamp
168
- save_tasks(number)
169
- puts Rainbow("Updated status of task #{number} from #{old_status} to #{new_status}").green
170
- puts Rainbow("Last updated: #{task.updated_at}").cyan
171
- else
172
- puts Rainbow("Task not found").red
173
- end
174
- end
175
- end
176
-
177
- def print_tasks(tasks)
178
- if tasks.empty?
179
- puts Rainbow("No tasks found.").yellow
180
- return
181
- end
182
-
183
- puts Rainbow("\n%-10s %-30s %-15s %-10s %-25s" % ["Number", "Name", "Status", "Priority", "Last Updated"]).underline
184
- tasks.each do |task|
185
- puts "%-10s %-30s %-15s %-10s %-25s" % [
186
- Rainbow(task.number).cyan,
187
- Rainbow(task.name.to_s.truncate(30)).magenta,
188
- task.status_colored,
189
- task.priority_colored,
190
- Rainbow(task.updated_at).blue
191
- ]
192
- end
193
- puts "\nTotal tasks: #{Rainbow(tasks.count).green}"
194
- end
195
-
196
- # Main program
197
- if __FILE__ == $0
198
- begin
199
- file_path = File.expand_path('~/tasks.csv') # Store the CSV in the user's home directory
200
- manager = TaskManager.new(file_path)
201
-
202
- options = {}
203
- OptionParser.new do |opts|
204
- opts.banner = "Usage: tasks [options]"
205
-
206
- opts.on("-a", "--all", "View all tasks") do
207
- options[:all] = true
208
- end
209
-
210
- opts.on("-f FIELD:VALUE", "--filter FIELD:VALUE", "Filter tasks by field and value (can be used multiple times)") do |f|
211
- options[:filter] ||= {}
212
- key, value = f.split(':')
213
- options[:filter][key] = value
214
- end
215
-
216
- opts.on("-v NUMBER", "--view NUMBER", "View a single task") do |v|
217
- options[:view] = v
218
- end
219
-
220
- opts.on("-u NUMBER,STATUS", "--update NUMBER,STATUS", Array, "Update a task's status") do |u|
221
- options[:update] = u
222
- end
223
- end.parse!
224
-
225
- if options[:all]
226
- print_tasks(manager.all_tasks)
227
- elsif options[:filter]
228
- print_tasks(manager.filter_tasks(options[:filter]))
229
- elsif options[:view]
230
- task = manager.find_task(options[:view])
231
- if task
232
- puts task.detailed_view
233
- else
234
- puts Rainbow("Task not found").red
235
- end
236
- elsif options[:update]
237
- number, status = options[:update]
238
- manager.update_status(number, status)
239
- else
240
- puts Rainbow("No valid option provided. Use -h for help.").yellow
241
- end
242
- rescue Errno::ENOENT
243
- puts Rainbow("Error: CSV file '#{file_path}' not found. Please ensure the file exists.").red
244
- rescue => e
245
- puts Rainbow("An unexpected error occurred: #{e.message}").red
246
- puts e.backtrace
247
- end
248
- end