tasks_cli 0.1.1 → 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: 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