tasku 0.1.1 → 0.2.1
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/exe/tasku +5 -1
- data/lib/tasku/cli.rb +110 -7
- 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
- data/lib/tasku.rb +1 -0
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9abb35ca00132f0c3ce44c5cb8832566a771a28fc63723cfd347d080143bef2
|
|
4
|
+
data.tar.gz: b05037d00557b7549ba68b2ec754958e29c35b331effe8d874d3013bdc6a3fbf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be862cfdacbeb81a62dc19ee0d1d771eb87fd2a2dc8cbe227b1ab384cf49aac18b11dfe7400e3e5bd7b4f4ba14a2f985a7e3e80f5d1473da051f0450c1cc92d9
|
|
7
|
+
data.tar.gz: 94d326c2ef477d1d5fb36104ef4a8fe573841e9cc60d81cb7b24010e37e24a4e7ad3f106fd87e3753fc133c2de9b4cb710369f1fd439589f9f5f59ab41c5d6cb
|
data/exe/tasku
CHANGED
data/lib/tasku/cli.rb
CHANGED
|
@@ -15,16 +15,80 @@ module Tasku
|
|
|
15
15
|
|
|
16
16
|
class App < Thor
|
|
17
17
|
def self.start(args = ARGV, **opts)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
sql_index = args.index("--sql")
|
|
19
|
+
if sql_index
|
|
20
|
+
args.delete_at(sql_index)
|
|
21
|
+
query = args[sql_index]
|
|
22
|
+
if query.nil? || query.empty?
|
|
23
|
+
puts pastel.red(" No query provided after --sql.")
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
args.delete_at(sql_index)
|
|
27
|
+
new.invoke(:sql, [query])
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
23
31
|
super
|
|
24
32
|
end
|
|
25
33
|
|
|
26
34
|
class_option :db, type: :string, desc: "Path to SQLite database (default: ~/.tasku/tasks.db)", hide: true
|
|
27
35
|
|
|
36
|
+
desc "sql QUERY", "Run a raw SQL query against the database"
|
|
37
|
+
def sql(query)
|
|
38
|
+
if query.strip.upcase.match?(/\A(SELECT|PRAGMA|EXPLAIN)/)
|
|
39
|
+
dataset = Tasku::Database.db.fetch(query)
|
|
40
|
+
rows = dataset.all
|
|
41
|
+
|
|
42
|
+
if rows.empty?
|
|
43
|
+
puts pastel.yellow(" Query returned no results.")
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
raw_keys = rows.first.keys
|
|
48
|
+
has_code = raw_keys.include?(:code)
|
|
49
|
+
columns = raw_keys.reject { |c| %i[model_name created_at updated_at code].include?(c.to_sym) }
|
|
50
|
+
|
|
51
|
+
display_cols = columns.map { |c| c == :id ? "CODE-ID" : c.to_s }
|
|
52
|
+
code_id_keys = columns.include?(:id) && has_code
|
|
53
|
+
|
|
54
|
+
col_widths = columns.each_with_index.map do |c, i|
|
|
55
|
+
data_vals = rows.map do |r|
|
|
56
|
+
v = if c == :id && code_id_keys && r[:code] && !r[:code].to_s.empty?
|
|
57
|
+
"#{r[:code]}-#{r[:id]}"
|
|
58
|
+
else
|
|
59
|
+
r[c].to_s
|
|
60
|
+
end
|
|
61
|
+
v.length
|
|
62
|
+
end
|
|
63
|
+
[display_cols[i].length, data_vals.max || 0].max
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
puts ""
|
|
67
|
+
header = display_cols.each_with_index.map { |c, i| pastel.bold(c.ljust(col_widths[i])) }
|
|
68
|
+
puts " #{header.join(' ')}"
|
|
69
|
+
puts " #{display_cols.each_with_index.map { |_c, i| pastel.dim("\u2500" * col_widths[i]) }.join(' ')}"
|
|
70
|
+
|
|
71
|
+
rows.each do |row|
|
|
72
|
+
cells = columns.each_with_index.map do |col, i|
|
|
73
|
+
val = if col == :id && code_id_keys && row[:code] && !row[:code].to_s.empty?
|
|
74
|
+
"#{row[:code]}-#{row[:id]}"
|
|
75
|
+
else
|
|
76
|
+
row[col].to_s
|
|
77
|
+
end
|
|
78
|
+
colour_cell(col, val.ljust(col_widths[i]), row)
|
|
79
|
+
end
|
|
80
|
+
puts " #{cells.join(' ')}"
|
|
81
|
+
puts " #{display_cols.each_with_index.map { |_c, i| pastel.dim("\u2500" * col_widths[i]) }.join(' ')}"
|
|
82
|
+
end
|
|
83
|
+
puts ""
|
|
84
|
+
else
|
|
85
|
+
affected = Tasku::Database.db.run(query)
|
|
86
|
+
puts pastel.green(" Query executed successfully.")
|
|
87
|
+
end
|
|
88
|
+
rescue Sequel::DatabaseError => e
|
|
89
|
+
abort pastel.red("SQL error: #{e.message}")
|
|
90
|
+
end
|
|
91
|
+
|
|
28
92
|
desc "add", "Create a new task"
|
|
29
93
|
option :name, type: :string, desc: "Task name", required: false
|
|
30
94
|
option :description, type: :string, desc: "Task description"
|
|
@@ -37,6 +101,7 @@ module Tasku
|
|
|
37
101
|
option :status, type: :string, desc: "Status: backlog, todo, in_progress, done, cancelled, archived"
|
|
38
102
|
option :tags, type: :string, desc: "Comma-separated tags"
|
|
39
103
|
option :hours, type: :numeric, desc: "Estimated hours"
|
|
104
|
+
option :code, type: :string, desc: "Code"
|
|
40
105
|
option :interactive, type: :boolean, aliases: "-i", desc: "Interactive mode", default: false
|
|
41
106
|
def add
|
|
42
107
|
attrs = if options[:interactive] || options.values_at(:name, :description, :project, :category).all?(&:nil?)
|
|
@@ -113,7 +178,8 @@ module Tasku
|
|
|
113
178
|
option :status, type: :string, desc: "Status: backlog, todo, in_progress, done, cancelled, archived"
|
|
114
179
|
option :tags, type: :string, desc: "Comma-separated tags"
|
|
115
180
|
option :hours, type: :numeric, desc: "Estimated hours"
|
|
116
|
-
option :
|
|
181
|
+
option :code, type: :string, desc: "Code"
|
|
182
|
+
option :clear, type: :string, desc: "Clear a field: description, start, due, model, tags, hours, code"
|
|
117
183
|
def edit(id)
|
|
118
184
|
task = find_task(id)
|
|
119
185
|
|
|
@@ -131,7 +197,8 @@ module Tasku
|
|
|
131
197
|
"due" => :due_day,
|
|
132
198
|
"model" => :model_name,
|
|
133
199
|
"tags" => :tags,
|
|
134
|
-
"hours" => :estimated_hours
|
|
200
|
+
"hours" => :estimated_hours,
|
|
201
|
+
"code" => :code
|
|
135
202
|
}
|
|
136
203
|
clear_fields.each do |f|
|
|
137
204
|
col = clear_map[f]
|
|
@@ -265,6 +332,9 @@ module Tasku
|
|
|
265
332
|
model_name = prompt.ask("Model:", default: "")
|
|
266
333
|
model_name = nil if model_name&.empty?
|
|
267
334
|
|
|
335
|
+
code = prompt.ask("Code:", default: "")
|
|
336
|
+
code = nil if code&.empty?
|
|
337
|
+
|
|
268
338
|
tags = prompt.ask("Tags (comma-separated):", default: "")
|
|
269
339
|
tags = nil if tags&.empty?
|
|
270
340
|
|
|
@@ -281,6 +351,7 @@ module Tasku
|
|
|
281
351
|
start_day: parse_date(start_day),
|
|
282
352
|
due_day: parse_date(due_day),
|
|
283
353
|
model_name: (model_name unless model_name&.empty?),
|
|
354
|
+
code: (code unless code&.empty?),
|
|
284
355
|
priority: priority,
|
|
285
356
|
status: status,
|
|
286
357
|
tags: (tags unless tags&.empty?),
|
|
@@ -300,6 +371,7 @@ module Tasku
|
|
|
300
371
|
start_day: parse_date(options[:start]),
|
|
301
372
|
due_day: parse_date(options[:due]),
|
|
302
373
|
model_name: options[:model],
|
|
374
|
+
code: options[:code],
|
|
303
375
|
priority: options[:priority] || "none",
|
|
304
376
|
status: options[:status] || "todo",
|
|
305
377
|
tags: options[:tags],
|
|
@@ -319,6 +391,7 @@ module Tasku
|
|
|
319
391
|
start_day: parse_date(options[:start]),
|
|
320
392
|
due_day: parse_date(options[:due]),
|
|
321
393
|
model_name: options[:model],
|
|
394
|
+
code: options[:code],
|
|
322
395
|
priority: options[:priority],
|
|
323
396
|
status: options[:status],
|
|
324
397
|
tags: options[:tags],
|
|
@@ -337,6 +410,36 @@ module Tasku
|
|
|
337
410
|
|
|
338
411
|
abort pastel.red("Invalid status '#{value}'. Valid: #{Task::VALID_STATUSES.join(', ')}")
|
|
339
412
|
end
|
|
413
|
+
|
|
414
|
+
PRIORITY_COLOURS = {
|
|
415
|
+
"none" => :dim,
|
|
416
|
+
"low" => :cyan,
|
|
417
|
+
"medium" => :yellow,
|
|
418
|
+
"high" => :red,
|
|
419
|
+
"urgent" => :bright_magenta
|
|
420
|
+
}.freeze
|
|
421
|
+
|
|
422
|
+
STATUS_COLOURS = {
|
|
423
|
+
"backlog" => :dim,
|
|
424
|
+
"todo" => :blue,
|
|
425
|
+
"in_progress" => :yellow,
|
|
426
|
+
"done" => :green,
|
|
427
|
+
"cancelled" => :red,
|
|
428
|
+
"archived" => :dim
|
|
429
|
+
}.freeze
|
|
430
|
+
|
|
431
|
+
def colour_cell(col, padded_val, row)
|
|
432
|
+
case col.to_s
|
|
433
|
+
when "priority"
|
|
434
|
+
colour = PRIORITY_COLOURS[row[col].to_s] || :dim
|
|
435
|
+
pastel.send(colour, padded_val)
|
|
436
|
+
when "status"
|
|
437
|
+
colour = STATUS_COLOURS[row[col].to_s] || :dim
|
|
438
|
+
pastel.send(colour, padded_val)
|
|
439
|
+
else
|
|
440
|
+
padded_val
|
|
441
|
+
end
|
|
442
|
+
end
|
|
340
443
|
end
|
|
341
444
|
end
|
|
342
445
|
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
data/lib/tasku.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.1
|
|
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-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: thor
|
|
@@ -93,11 +93,13 @@ files:
|
|
|
93
93
|
- lib/tasku/output/terminal.rb
|
|
94
94
|
- lib/tasku/task.rb
|
|
95
95
|
- lib/tasku/version.rb
|
|
96
|
-
homepage: https://
|
|
96
|
+
homepage: https://github.com/tomdringer/tasku
|
|
97
97
|
licenses:
|
|
98
98
|
- MIT
|
|
99
99
|
metadata:
|
|
100
|
-
homepage_uri: https://
|
|
100
|
+
homepage_uri: https://github.com/tomdringer/tasku
|
|
101
|
+
source_code_uri: https://github.com/tomdringer/tasku
|
|
102
|
+
changelog_uri: https://github.com/tomdringer/tasku/releases
|
|
101
103
|
rdoc_options: []
|
|
102
104
|
require_paths:
|
|
103
105
|
- lib
|