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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dbcffd59a0b64250d1b095288ffc74f7efd1e4a739ad8ff9a3c5d8ec6e2d1de
4
- data.tar.gz: 9120eefbab9aebb8946e1d4ff326509f31aaeec490f0369c8e9fd38e182dfbc7
3
+ metadata.gz: d9abb35ca00132f0c3ce44c5cb8832566a771a28fc63723cfd347d080143bef2
4
+ data.tar.gz: b05037d00557b7549ba68b2ec754958e29c35b331effe8d874d3013bdc6a3fbf
5
5
  SHA512:
6
- metadata.gz: 1a7981c6a1ad57bcd674e16ab4fd013bf8837bb0c8122c3a879ff20947ebca9e80499fe50cd7c026c99ffd8b4e70c26ffc5693997f16d4f43dafe8dd298e0355
7
- data.tar.gz: a46bcec9b31f07ae9f01825b137974beb567bcb879a33a92e3d095de55d43c65002cbc0427e5c8cd120f79cdf767add69c32c2e68676ff05224daa9df930f695
6
+ metadata.gz: be862cfdacbeb81a62dc19ee0d1d771eb87fd2a2dc8cbe227b1ab384cf49aac18b11dfe7400e3e5bd7b4f4ba14a2f985a7e3e80f5d1473da051f0450c1cc92d9
7
+ data.tar.gz: 94d326c2ef477d1d5fb36104ef4a8fe573841e9cc60d81cb7b24010e37e24a4e7ad3f106fd87e3753fc133c2de9b4cb710369f1fd439589f9f5f59ab41c5d6cb
data/exe/tasku CHANGED
@@ -3,4 +3,8 @@
3
3
 
4
4
  require "tasku"
5
5
 
6
- Tasku::CLI::App.start(ARGV)
6
+ if ARGV.empty?
7
+ Tasku::TUI::App.new.run
8
+ else
9
+ Tasku::CLI::App.start(ARGV)
10
+ end
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
- pastel = Pastel.new
19
- $stdout.puts ""
20
- $stdout.puts pastel.bright_cyan(LOGO)
21
- $stdout.puts pastel.bright_cyan(" #{TAGLINE}")
22
- $stdout.puts ""
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 :clear, type: :string, desc: "Clear a field: description, start, due, model, tags, hours"
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
@@ -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.1"
5
5
  end
data/lib/tasku.rb CHANGED
@@ -10,3 +10,4 @@ Tasku::Database.connect
10
10
  require_relative "tasku/task"
11
11
  require_relative "tasku/output/terminal"
12
12
  require_relative "tasku/cli"
13
+ require_relative "tasku/tui/app"
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.1
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-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://nerimasoft.co.uk
96
+ homepage: https://github.com/tomdringer/tasku
97
97
  licenses:
98
98
  - MIT
99
99
  metadata:
100
- homepage_uri: https://nerimasoft.co.uk
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