tasku 0.1.1 → 0.2.0
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/lib/tasku/cli.rb +111 -2
- data/lib/tasku/database.rb +5 -0
- data/lib/tasku/output/terminal.rb +3 -1
- data/lib/tasku/task.rb +5 -0
- data/lib/tasku/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79a2feab10832832c415f3c6f7faab5ce67b8bcc6463480847f4bf4c759b8e83
|
|
4
|
+
data.tar.gz: 1822f38341c169aad7327edbf6c7d45beed60143f17407198896941d6bec698a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81d5f5be067d85648390dc180a4319472f4a6dab03beae90fe1c0311304b6e0c82254c29cab325d7a4807fcf2b987752a88593df991fba10cf7e4ebeea687ada
|
|
7
|
+
data.tar.gz: 1232719b250229d29e533d10dfc568e7fb33048cb5c599d9c096c39b4c31d38db9226fbc0423931b5d61af8c03a01e0a40c0721f64b7bbb30044d33bb6f1f5ff
|
data/lib/tasku/cli.rb
CHANGED
|
@@ -20,11 +20,81 @@ module Tasku
|
|
|
20
20
|
$stdout.puts pastel.bright_cyan(LOGO)
|
|
21
21
|
$stdout.puts pastel.bright_cyan(" #{TAGLINE}")
|
|
22
22
|
$stdout.puts ""
|
|
23
|
+
|
|
24
|
+
sql_index = args.index("--sql")
|
|
25
|
+
if sql_index
|
|
26
|
+
args.delete_at(sql_index)
|
|
27
|
+
query = args[sql_index]
|
|
28
|
+
if query.nil? || query.empty?
|
|
29
|
+
puts pastel.red(" No query provided after --sql.")
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
args.delete_at(sql_index)
|
|
33
|
+
new.invoke(:sql, [query])
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
23
37
|
super
|
|
24
38
|
end
|
|
25
39
|
|
|
26
40
|
class_option :db, type: :string, desc: "Path to SQLite database (default: ~/.tasku/tasks.db)", hide: true
|
|
27
41
|
|
|
42
|
+
desc "sql QUERY", "Run a raw SQL query against the database"
|
|
43
|
+
def sql(query)
|
|
44
|
+
if query.strip.upcase.start_with?("SELECT")
|
|
45
|
+
dataset = Tasku::Database.db.fetch(query)
|
|
46
|
+
rows = dataset.all
|
|
47
|
+
|
|
48
|
+
if rows.empty?
|
|
49
|
+
puts pastel.yellow(" Query returned no results.")
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
raw_keys = rows.first.keys
|
|
54
|
+
has_code = raw_keys.include?(:code)
|
|
55
|
+
columns = raw_keys.reject { |c| %i[description tags model_name created_at updated_at code].include?(c.to_sym) }
|
|
56
|
+
|
|
57
|
+
display_cols = columns.map { |c| c == :id ? "CODE-ID" : c.to_s }
|
|
58
|
+
code_id_keys = columns.include?(:id) && has_code
|
|
59
|
+
|
|
60
|
+
col_widths = columns.each_with_index.map do |c, i|
|
|
61
|
+
data_vals = rows.map do |r|
|
|
62
|
+
v = if c == :id && code_id_keys && r[:code] && !r[:code].to_s.empty?
|
|
63
|
+
"#{r[:code]}-#{r[:id]}"
|
|
64
|
+
else
|
|
65
|
+
r[c].to_s
|
|
66
|
+
end
|
|
67
|
+
v.length
|
|
68
|
+
end
|
|
69
|
+
[display_cols[i].length, data_vals.max || 0].max
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
puts ""
|
|
73
|
+
header = display_cols.each_with_index.map { |c, i| pastel.bold(c.ljust(col_widths[i])) }
|
|
74
|
+
puts " #{header.join(' ')}"
|
|
75
|
+
puts " #{display_cols.each_with_index.map { |_c, i| pastel.dim("\u2500" * col_widths[i]) }.join(' ')}"
|
|
76
|
+
|
|
77
|
+
rows.each do |row|
|
|
78
|
+
cells = columns.each_with_index.map do |col, i|
|
|
79
|
+
val = if col == :id && code_id_keys && row[:code] && !row[:code].to_s.empty?
|
|
80
|
+
"#{row[:code]}-#{row[:id]}"
|
|
81
|
+
else
|
|
82
|
+
row[col].to_s
|
|
83
|
+
end
|
|
84
|
+
colour_cell(col, val.ljust(col_widths[i]), row)
|
|
85
|
+
end
|
|
86
|
+
puts " #{cells.join(' ')}"
|
|
87
|
+
puts " #{display_cols.each_with_index.map { |_c, i| pastel.dim("\u2500" * col_widths[i]) }.join(' ')}"
|
|
88
|
+
end
|
|
89
|
+
puts ""
|
|
90
|
+
else
|
|
91
|
+
affected = Tasku::Database.db.run(query)
|
|
92
|
+
puts pastel.green(" Query executed successfully.")
|
|
93
|
+
end
|
|
94
|
+
rescue Sequel::DatabaseError => e
|
|
95
|
+
abort pastel.red("SQL error: #{e.message}")
|
|
96
|
+
end
|
|
97
|
+
|
|
28
98
|
desc "add", "Create a new task"
|
|
29
99
|
option :name, type: :string, desc: "Task name", required: false
|
|
30
100
|
option :description, type: :string, desc: "Task description"
|
|
@@ -37,6 +107,7 @@ module Tasku
|
|
|
37
107
|
option :status, type: :string, desc: "Status: backlog, todo, in_progress, done, cancelled, archived"
|
|
38
108
|
option :tags, type: :string, desc: "Comma-separated tags"
|
|
39
109
|
option :hours, type: :numeric, desc: "Estimated hours"
|
|
110
|
+
option :code, type: :string, desc: "Code"
|
|
40
111
|
option :interactive, type: :boolean, aliases: "-i", desc: "Interactive mode", default: false
|
|
41
112
|
def add
|
|
42
113
|
attrs = if options[:interactive] || options.values_at(:name, :description, :project, :category).all?(&:nil?)
|
|
@@ -113,7 +184,8 @@ module Tasku
|
|
|
113
184
|
option :status, type: :string, desc: "Status: backlog, todo, in_progress, done, cancelled, archived"
|
|
114
185
|
option :tags, type: :string, desc: "Comma-separated tags"
|
|
115
186
|
option :hours, type: :numeric, desc: "Estimated hours"
|
|
116
|
-
option :
|
|
187
|
+
option :code, type: :string, desc: "Code"
|
|
188
|
+
option :clear, type: :string, desc: "Clear a field: description, start, due, model, tags, hours, code"
|
|
117
189
|
def edit(id)
|
|
118
190
|
task = find_task(id)
|
|
119
191
|
|
|
@@ -131,7 +203,8 @@ module Tasku
|
|
|
131
203
|
"due" => :due_day,
|
|
132
204
|
"model" => :model_name,
|
|
133
205
|
"tags" => :tags,
|
|
134
|
-
"hours" => :estimated_hours
|
|
206
|
+
"hours" => :estimated_hours,
|
|
207
|
+
"code" => :code
|
|
135
208
|
}
|
|
136
209
|
clear_fields.each do |f|
|
|
137
210
|
col = clear_map[f]
|
|
@@ -265,6 +338,9 @@ module Tasku
|
|
|
265
338
|
model_name = prompt.ask("Model:", default: "")
|
|
266
339
|
model_name = nil if model_name&.empty?
|
|
267
340
|
|
|
341
|
+
code = prompt.ask("Code:", default: "")
|
|
342
|
+
code = nil if code&.empty?
|
|
343
|
+
|
|
268
344
|
tags = prompt.ask("Tags (comma-separated):", default: "")
|
|
269
345
|
tags = nil if tags&.empty?
|
|
270
346
|
|
|
@@ -281,6 +357,7 @@ module Tasku
|
|
|
281
357
|
start_day: parse_date(start_day),
|
|
282
358
|
due_day: parse_date(due_day),
|
|
283
359
|
model_name: (model_name unless model_name&.empty?),
|
|
360
|
+
code: (code unless code&.empty?),
|
|
284
361
|
priority: priority,
|
|
285
362
|
status: status,
|
|
286
363
|
tags: (tags unless tags&.empty?),
|
|
@@ -300,6 +377,7 @@ module Tasku
|
|
|
300
377
|
start_day: parse_date(options[:start]),
|
|
301
378
|
due_day: parse_date(options[:due]),
|
|
302
379
|
model_name: options[:model],
|
|
380
|
+
code: options[:code],
|
|
303
381
|
priority: options[:priority] || "none",
|
|
304
382
|
status: options[:status] || "todo",
|
|
305
383
|
tags: options[:tags],
|
|
@@ -319,6 +397,7 @@ module Tasku
|
|
|
319
397
|
start_day: parse_date(options[:start]),
|
|
320
398
|
due_day: parse_date(options[:due]),
|
|
321
399
|
model_name: options[:model],
|
|
400
|
+
code: options[:code],
|
|
322
401
|
priority: options[:priority],
|
|
323
402
|
status: options[:status],
|
|
324
403
|
tags: options[:tags],
|
|
@@ -337,6 +416,36 @@ module Tasku
|
|
|
337
416
|
|
|
338
417
|
abort pastel.red("Invalid status '#{value}'. Valid: #{Task::VALID_STATUSES.join(', ')}")
|
|
339
418
|
end
|
|
419
|
+
|
|
420
|
+
PRIORITY_COLOURS = {
|
|
421
|
+
"none" => :dim,
|
|
422
|
+
"low" => :cyan,
|
|
423
|
+
"medium" => :yellow,
|
|
424
|
+
"high" => :red,
|
|
425
|
+
"urgent" => :bright_magenta
|
|
426
|
+
}.freeze
|
|
427
|
+
|
|
428
|
+
STATUS_COLOURS = {
|
|
429
|
+
"backlog" => :dim,
|
|
430
|
+
"todo" => :blue,
|
|
431
|
+
"in_progress" => :yellow,
|
|
432
|
+
"done" => :green,
|
|
433
|
+
"cancelled" => :red,
|
|
434
|
+
"archived" => :dim
|
|
435
|
+
}.freeze
|
|
436
|
+
|
|
437
|
+
def colour_cell(col, padded_val, row)
|
|
438
|
+
case col.to_s
|
|
439
|
+
when "priority"
|
|
440
|
+
colour = PRIORITY_COLOURS[row[col].to_s] || :dim
|
|
441
|
+
pastel.send(colour, padded_val)
|
|
442
|
+
when "status"
|
|
443
|
+
colour = STATUS_COLOURS[row[col].to_s] || :dim
|
|
444
|
+
pastel.send(colour, padded_val)
|
|
445
|
+
else
|
|
446
|
+
padded_val
|
|
447
|
+
end
|
|
448
|
+
end
|
|
340
449
|
end
|
|
341
450
|
end
|
|
342
451
|
end
|
data/lib/tasku/database.rb
CHANGED
|
@@ -30,6 +30,7 @@ module Tasku
|
|
|
30
30
|
Date :start_day
|
|
31
31
|
Date :due_day
|
|
32
32
|
String :model_name
|
|
33
|
+
String :code
|
|
33
34
|
String :priority, default: "none"
|
|
34
35
|
String :status, default: "todo"
|
|
35
36
|
String :tags
|
|
@@ -37,6 +38,10 @@ module Tasku
|
|
|
37
38
|
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
38
39
|
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
|
|
39
40
|
end
|
|
41
|
+
|
|
42
|
+
if db.table_exists?(:tasks) && !db.schema(:tasks).map(&:first).include?(:code)
|
|
43
|
+
db.alter_table(:tasks) { add_column :code, String }
|
|
44
|
+
end
|
|
40
45
|
end
|
|
41
46
|
end
|
|
42
47
|
end
|
|
@@ -63,6 +63,7 @@ module Tasku
|
|
|
63
63
|
["Status", status_tag(task.status)],
|
|
64
64
|
["Start Day", task.start_day ? task.start_day.to_s : @pastel.dim("—")],
|
|
65
65
|
["Due Day", due_cell(task)],
|
|
66
|
+
["Code", task.code || @pastel.dim("—")],
|
|
66
67
|
["Model", task.model_name || @pastel.dim("—")],
|
|
67
68
|
["Tags", task.tag_list.empty? ? @pastel.dim("—") : task.tag_list.join(", ")],
|
|
68
69
|
["Est. Hours", task.estimated_hours ? task.estimated_hours.to_s : @pastel.dim("—")],
|
|
@@ -127,7 +128,8 @@ module Tasku
|
|
|
127
128
|
private
|
|
128
129
|
|
|
129
130
|
def build_columns(task)
|
|
130
|
-
|
|
131
|
+
id_val = task.code && !task.code.empty? ? "#{task.code}-#{task.id}" : task.id.to_s
|
|
132
|
+
id_str = @pastel.dim(id_val)
|
|
131
133
|
name_str = @pastel.bold(task.name)
|
|
132
134
|
proj_str = task.project || @pastel.dim("—")
|
|
133
135
|
prio_str = priority_tag(task.priority)
|
data/lib/tasku/task.rb
CHANGED
|
@@ -9,6 +9,11 @@ module Tasku
|
|
|
9
9
|
VALID_PRIORITIES = %w[none low medium high urgent].freeze
|
|
10
10
|
VALID_STATUSES = %w[backlog todo in_progress done cancelled archived].freeze
|
|
11
11
|
|
|
12
|
+
def before_validation
|
|
13
|
+
super
|
|
14
|
+
self.code = code.to_s.upcase[0, 3] if code
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
def validate
|
|
13
18
|
super
|
|
14
19
|
errors.add(:name, "cannot be empty") if name.nil? || name.strip.empty?
|
data/lib/tasku/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tasku
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom Dringer
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-06-
|
|
10
|
+
date: 2026-06-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: thor
|