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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dbcffd59a0b64250d1b095288ffc74f7efd1e4a739ad8ff9a3c5d8ec6e2d1de
4
- data.tar.gz: 9120eefbab9aebb8946e1d4ff326509f31aaeec490f0369c8e9fd38e182dfbc7
3
+ metadata.gz: 79a2feab10832832c415f3c6f7faab5ce67b8bcc6463480847f4bf4c759b8e83
4
+ data.tar.gz: 1822f38341c169aad7327edbf6c7d45beed60143f17407198896941d6bec698a
5
5
  SHA512:
6
- metadata.gz: 1a7981c6a1ad57bcd674e16ab4fd013bf8837bb0c8122c3a879ff20947ebca9e80499fe50cd7c026c99ffd8b4e70c26ffc5693997f16d4f43dafe8dd298e0355
7
- data.tar.gz: a46bcec9b31f07ae9f01825b137974beb567bcb879a33a92e3d095de55d43c65002cbc0427e5c8cd120f79cdf767add69c32c2e68676ff05224daa9df930f695
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 :clear, type: :string, desc: "Clear a field: description, start, due, model, tags, hours"
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
@@ -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
- id_str = @pastel.dim(task.id.to_s.rjust(2))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tasku
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.1
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-14 00:00:00.000000000 Z
10
+ date: 2026-06-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor