intent 0.6.0 → 0.7.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: 86c9e5d7f4f81b1db6abe7c6fbd2ec7daebbfef06abaa05e3129697743648e63
4
- data.tar.gz: ca5a9c91a8ee077bc018c0b5a2251d6e84dfbc4ab2ce371a1fff96dfc65596bc
3
+ metadata.gz: a80e9d9082642b1f2325b40629ff95ffb096cdba644d0f2d5aa2af25541c2d2b
4
+ data.tar.gz: 90ed06f5afbf84636fbeb70e6fab66a4868dc0d485b751178b2ff31568315237
5
5
  SHA512:
6
- metadata.gz: de2d74dc9068848a13f5b85d6fd58aefb0381d908e9ff7e2f0d2d81c688afd715dc7bc3d50b584650c3acbc5596cb4ebcd010a5c3b37de4a4ea52f296a3a2ed1
7
- data.tar.gz: f8b7273cb674c0cd9336a1d3acac3cc88a96e31d2a1cf9736ddf03f9d1c56bd5b8aa2ab7112c62655283b78a40d99f2ae70679cb101e3e02f509a66741c05834
6
+ metadata.gz: a4a23b8d4882af953e2d330c26388c91bb86513c1881192952998f6f74b5f49b682f4b609566ea18b0b6f0ad7517020ac15ccc22ce8756edc001ac5fc25c455b
7
+ data.tar.gz: cfc14324613fc505a6ec0a591e73f667dd3360b4ec1d8ee7f05d86813fcbaf5e645f12b917579adc6b046223dbc251f665f65b0cd70087dac39d59cc7adfdccf
data/.gitignore CHANGED
@@ -7,4 +7,6 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
10
11
  NOTES.md
12
+ .DS_Store
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Mark Rickerby
3
+ Copyright (c) 2016-2024 Mark Rickerby <https://maetl.net>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Intent
2
+
3
+ Automation engine with todo.txt
4
+
5
+ This tool is mired deep in the obscurity and pedantry of the text-based computing culture that originated in the 1970s and still continues today. You probably don’t want to use this.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ gem install intent
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```
16
+ intent help
17
+ ```
18
+
19
+ ## Copyright & License
20
+
21
+ Copyright 2024 Mark Rickerby <https://maetl.net>.
22
+
23
+ All documentation and modelling concepts are **CC BY-NC**. Source code and command line tools are MIT.
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  path = __FILE__
4
3
  while File.symlink?(path)
5
4
  path = File.expand_path(File.readlink(path), File.dirname(path))
@@ -8,4 +7,4 @@ $:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
8
7
 
9
8
  require 'intent'
10
9
 
11
- Intent::Review::Manager.run(ARGV.dup)
10
+ Intent::Dispatcher.exec_command(:intent, ARGV.dup)
data/bin/inventory ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ path = __FILE__
3
+
4
+ while File.symlink?(path)
5
+ path = File.expand_path(File.readlink(path), File.dirname(path))
6
+ end
7
+
8
+ $:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
9
+
10
+ require 'intent'
11
+
12
+ Intent::Dispatcher.exec_command(:inventory, ARGV.dup)
data/bin/projects CHANGED
@@ -8,4 +8,4 @@ $:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
8
8
 
9
9
  require 'intent'
10
10
 
11
- Intent::Projects::Manager.run(ARGV.dup)
11
+ Intent::Dispatcher.exec_command(:projects, ARGV.dup)
data/bin/todo CHANGED
@@ -8,4 +8,4 @@ $:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
8
8
 
9
9
  require 'intent'
10
10
 
11
- Intent::Todo::Manager.run(ARGV.dup)
11
+ Intent::Dispatcher.exec_command(:todo, ARGV.dup)
data/intent.gemspec CHANGED
@@ -20,9 +20,18 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency "todo-txt", "~> 0.12"
22
22
  spec.add_runtime_dependency "pastel", "~> 0.8"
23
- spec.add_runtime_dependency "git", "~> 1.9.1"
23
+ spec.add_runtime_dependency "git", "~> 1.19.1"
24
24
  spec.add_runtime_dependency "terminal-notifier", "~> 2.0"
25
25
  spec.add_runtime_dependency "ghost", "~> 1.0.0"
26
+ spec.add_runtime_dependency "unicode_plot", "0.0.5"
27
+ spec.add_runtime_dependency "kdl", "1.0.3"
28
+ spec.add_runtime_dependency "sorted_set", "1.0.3"
29
+ spec.add_runtime_dependency "strings-ansi", "0.2.0"
30
+ spec.add_runtime_dependency "bibtex-ruby", "6.1.0"
31
+ spec.add_runtime_dependency "tty-prompt", "~> 0.23.1"
32
+ spec.add_runtime_dependency "tty-table", "~> 0.12.0"
33
+ spec.add_runtime_dependency "tty-tree", "~> 0.4.0"
34
+ spec.add_runtime_dependency "nanoid", "~> 2.0.0"
26
35
 
27
36
  spec.add_development_dependency "bundler"
28
37
  spec.add_development_dependency "rake"
@@ -10,13 +10,37 @@ class Todo::Task
10
10
  # Completed tasks are rendered with a strikethrough
11
11
  pastel.strikethrough(to_s)
12
12
  else
13
- # Open tasks delegate to the custom formatting function
14
- print_open_task(pastel)
13
+ [
14
+ pastel.red(print_priority),
15
+ pastel.yellow(created_on.to_s),
16
+ text,
17
+ pastel.bold.magenta(print_contexts),
18
+ pastel.bold.blue(print_projects),
19
+ pastel.bold.cyan(print_tags)
20
+ ].reject { |item| !item || item.nil? || item.empty? }.join(' ')
15
21
  end
16
22
  end
17
23
 
24
+ def highlight_as_project
25
+ return to_s unless STDOUT.tty?
26
+
27
+ pastel = Pastel.new
28
+ [
29
+ pastel.bold.cyan(print_projects),
30
+ pastel.red(print_project_context)
31
+ ].reject { |item| !item || item.nil? || item.empty? }.join(' ')
32
+ end
33
+
34
+ def text=(label)
35
+ @text = label
36
+ end
37
+
18
38
  private
19
39
 
40
+ def print_project_context
41
+ 'active' if contexts.include?('@active')
42
+ end
43
+
20
44
  def print_open_task(pastel)
21
45
  [
22
46
  pastel.red(print_priority),
@@ -0,0 +1,40 @@
1
+ module Intent
2
+ module Commands
3
+ class Base
4
+ attr_reader :identity
5
+ attr_reader :documents
6
+
7
+ def initialize
8
+ @identity = strip_classname
9
+ @documents = ::Intent::Core::Documents.new
10
+ end
11
+
12
+ def print_help(output)
13
+ output.puts(File.read(help_txt_path))
14
+ end
15
+
16
+ def generate_id
17
+ Nanoid.generate(size: 8, alphabet: ID_ALPHABET)
18
+ end
19
+
20
+ private
21
+
22
+ T_CLASS_PREFIX = 'Intent::Commands::'
23
+
24
+ ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
25
+
26
+ def strip_classname
27
+ self.class.to_s.sub(T_CLASS_PREFIX, '').downcase
28
+ end
29
+
30
+ def help_txt_path
31
+ # TODO: does this work on Windows?
32
+ "#{__dir__}/../text/#{identity}.help.txt"
33
+ end
34
+
35
+ def create_noun(type, label, tags)
36
+ Intent::Core::Noun.new(type, label, tags)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ module Intent
2
+ module Commands
3
+ module Errors
4
+ COMMAND_NOT_FOUND = "Command not found"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Intent
2
+ module Commands
3
+ class Intent
4
+ def run(args, output)
5
+ output.puts args
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,174 @@
1
+ require 'tty-reader'
2
+
3
+ module Intent
4
+ module Commands
5
+ class Inventory < Base
6
+ def run(args, output)
7
+ if args.empty?
8
+ print_help(output)
9
+ else
10
+ case args.first.to_sym
11
+ when :help
12
+ print_help(output)
13
+ when :list
14
+ tree = TTY::Tree.new(inventory_tree)
15
+ output.puts(tree.render)
16
+ when :add
17
+ noun = args[1].to_sym
18
+ case noun
19
+ when :folder then add_folder(args, output)
20
+ when :box then add_box(args, output)
21
+ when :stock then add_stock(args, output)
22
+ else
23
+ raise "Noun not found"
24
+ end
25
+ when :assign
26
+ noun = args[1].to_sym
27
+ case noun
28
+ when :folder then assign_folder(args, output)
29
+ when :box then assign_box(args, output)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def inventory_tree
38
+ pastel = Pastel.new
39
+ root = {}
40
+ documents.inventory.boxes.each do |box|
41
+ color_code = case box.tags[:id][0].downcase.to_sym
42
+ when :g then :green
43
+ when :y then :yellow
44
+ when :b then :blue
45
+ when :r then :red
46
+ else
47
+ :white
48
+ end
49
+ box_key = "#{pastel.decorate(box.tags[:id], :bold, color_code)} #{box.text}"
50
+ child_items = documents.inventory.items_in(box.tags[:id]).map do |item|
51
+ "#{pastel.decorate(item.tags[:id], :bold, color_code)} #{item.text}"
52
+ end
53
+ root[box_key] = child_items
54
+ end
55
+ root
56
+ end
57
+
58
+ def inventory_units_of(type)
59
+ documents.inventory.units_of(type).map do |unit|
60
+ [unit.text, unit.tags[:sku]]
61
+ end.to_h
62
+ end
63
+
64
+ def inventory_unassigned_folders
65
+ folder_types = documents.inventory.units_of(:folder)
66
+ documents.inventory.unassigned_folders.map do |folder|
67
+ unit_label = folder_types.find { |f| f.tags[:sku] == folder.tags[:sku] }
68
+ ["#{folder.text} #{folder.tags[:id]} (#{unit_label.text})", folder.tags[:id]]
69
+ end.to_h
70
+ end
71
+
72
+ def inventory_unassigned_boxes
73
+ box_types = documents.inventory.units_of(:box)
74
+ documents.inventory.unassigned_boxes.map do |box|
75
+ unit_label = box_types.find { |f| f.tags[:sku] == box.tags[:sku] }
76
+ ["#{box.text} #{box.tags[:id]} (#{unit_label.text})", box.tags[:id]]
77
+ end.to_h
78
+ end
79
+
80
+ def add_stock(args, output)
81
+ prompt = TTY::Prompt.new
82
+ type = prompt.select('type of stock:', [:folder, :box])
83
+ ref = self
84
+
85
+ unit = prompt.collect do
86
+ key(:sku).ask('sku:', default: ref.generate_id)
87
+ key(:label).ask('label:', default: "[Undocumented #{type.capitalize}]")
88
+ key(:qty).ask('quantity:', default: 1, convert: :int)
89
+ end
90
+
91
+ documents.inventory.add_unit!(unit[:label], type, unit[:sku])
92
+
93
+ unit[:qty].times do
94
+ label = "[Unlabelled #{type.capitalize}]"
95
+ documents.inventory.add_item!(label, ref.generate_id, type, unit[:sku])
96
+ end
97
+ end
98
+
99
+ def add_folder(args, output)
100
+ skus = inventory_units_of(:folder)
101
+ prompt = TTY::Prompt.new
102
+ ref = self
103
+
104
+ item = prompt.collect do
105
+ key(:sku).select('sku:', skus, filter: true)
106
+ key(:id).ask('id:', default: ref.generate_id)
107
+ key(:label).ask('label:', default: '[Unlabelled Folder]')
108
+ key(:active).yes?('is active:')
109
+ end
110
+
111
+ label = item[:active] ? "#{item[:label]} @active" : item[:label]
112
+
113
+ documents.inventory.add_folder!(label, item[:id], item[:sku])
114
+ end
115
+
116
+ def add_box(args, output)
117
+ skus = inventory_units_of(:box)
118
+ prompt = TTY::Prompt.new
119
+ ref = self
120
+
121
+ item = prompt.collect do
122
+ key(:sku).select('sku:', skus, filter: true)
123
+ key(:id).ask('id:', default: ref.generate_id)
124
+ key(:label).ask('label:', default: '[Unlabelled Box]')
125
+ end
126
+
127
+ # Repository write pattern
128
+ documents.inventory.add_box!(label, item[:id], item[:sku])
129
+
130
+ # Alternative design
131
+ # noun = create_noun(:box, label, tags)
132
+ # Add.invoke(:append, documents.inventory, noun)
133
+ end
134
+
135
+ def assign_folder(args, output)
136
+ projects = documents.projects.all_tokens
137
+ folders = inventory_unassigned_folders
138
+ prompt = TTY::Prompt.new
139
+
140
+ folder_id = prompt.select('folder:', folders)
141
+ folder = documents.inventory.folder_by_id(folder_id)
142
+
143
+ details = prompt.collect do
144
+ key(:projects).multi_select('projects:', projects)
145
+ key(:label).ask('label:', default: folder.text)
146
+ # TODO: is active
147
+ end
148
+
149
+ folder.text = details[:label]
150
+ folder.projects.concat(details[:projects])
151
+ documents.inventory.save!
152
+ end
153
+
154
+ def assign_box(args, output)
155
+ projects = documents.projects.all_tokens
156
+ boxes = inventory_unassigned_boxes
157
+ prompt = TTY::Prompt.new
158
+
159
+ box_id = prompt.select('box:', boxes)
160
+ box = documents.inventory.box_by_id(box_id)
161
+
162
+ details = prompt.collect do
163
+ key(:projects).multi_select('projects:', projects)
164
+ key(:label).ask('label:', default: box.text)
165
+ # TODO: is active
166
+ end
167
+
168
+ box.text = details[:label]
169
+ box.projects.concat(details[:projects])
170
+ documents.inventory.save!
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,34 @@
1
+ require 'readline'
2
+
3
+ module Intent
4
+ module Commands
5
+ class Projects < Base
6
+ def run(args, output)
7
+ if args.empty?
8
+ print_help(output)
9
+ else
10
+ case args.first.to_sym
11
+ when :help
12
+ print_help(output)
13
+ when :list
14
+ documents.projects.all.each do |task|
15
+ output.puts task.highlight_as_project
16
+ end
17
+ when :add
18
+ add_project(args, output)
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def add_project(args, output)
26
+ prompt = TTY::Prompt.new
27
+ name = prompt.ask('Project identifier: ')
28
+ name = name.downcase.gsub("_", "-").gsub(" ", "-").gsub(/[^0-9a-z\-]/i, '')
29
+ name = "+#{name}" unless name.start_with?('+')
30
+ output.puts name
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ module Intent
2
+ module Commands
3
+ class Todo < Base
4
+ def run(args, output)
5
+ if args.empty?
6
+ print_help(output)
7
+ else
8
+ case args.first.to_sym
9
+ when :add then add_line(args, output)
10
+ when :list then list_draw(args, output)
11
+ when :focus then focus_draw(args, output)
12
+ else
13
+ raise "Verb not found"
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def add_line(args, output)
21
+ reader = TTY::Reader.new
22
+ line = reader.read_line("task line: ")
23
+ documents.inbox.add_line!(line)
24
+ end
25
+
26
+ def focus_draw(args, output)
27
+ pastel = Pastel.new
28
+ documents.inbox.focused_projects.each do |project|
29
+ output.puts pastel.green(project)
30
+ end
31
+ end
32
+
33
+ def list_draw(args, output)
34
+ filtered_list = documents.inbox.all
35
+
36
+ unless args[1].nil?
37
+ case args[1][0]
38
+ when '@'
39
+ filtered_list = filtered_list.by_context(args[1]).by_not_done
40
+ when '+'
41
+ filtered_list = filtered_list.by_project(args[1])
42
+ end
43
+ end
44
+
45
+ filtered_list.by_not_done.each do |task|
46
+ output.puts task.to_s_highlighted
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ require 'intent/commands/base'
2
+ require 'intent/commands/errors'
3
+ require 'intent/commands/intent'
4
+ require 'intent/commands/todo'
5
+ require 'intent/commands/projects'
6
+ #require 'intent/commands/project'
7
+ #require 'intent/commands/ideas'
8
+ #require 'intent/commands/idea'
9
+ require 'intent/commands/inventory'
@@ -0,0 +1,184 @@
1
+ module Intent
2
+ module Env
3
+ def self.documents_dir
4
+ File.expand_path(ENV['INTENT_DOCUMENTS_DIR'] || "").to_s
5
+ end
6
+
7
+ def self.inbox_dir
8
+ ENV['INTENT_INBOX_DIR']
9
+ end
10
+
11
+ def self.assets_dir
12
+ ENV['INTENT_ARCHIVE_DIR']
13
+ end
14
+
15
+ def self.projects_dir
16
+ ENV['INTENT_PROJECTS_DIR']
17
+ end
18
+ end
19
+
20
+ module Core
21
+ List = ::Todo::List
22
+ Record = ::Todo::Task
23
+
24
+ class Noun
25
+ def initialize(type, label, tags)
26
+ @type = type
27
+ @label = label
28
+ @props = lex_props(tags)
29
+ @tags = tags
30
+ end
31
+
32
+ private
33
+
34
+ def lex_props(tags)
35
+ p tags
36
+ end
37
+ end
38
+
39
+ class Projects
40
+ attr_reader :list
41
+
42
+ def initialize(db_path)
43
+ @list = List.new(db_path)
44
+ end
45
+
46
+ def all
47
+ list.by_not_done
48
+ end
49
+
50
+ def all_tokens
51
+ all.map { |project| project.projects.first }
52
+ end
53
+ end
54
+
55
+ class Inventory
56
+ attr_reader :list
57
+
58
+ def initialize(db_path)
59
+ @list = List.new(db_path)
60
+ end
61
+
62
+ def all
63
+ list.by_not_done
64
+ end
65
+
66
+ def folder_by_id(id)
67
+ all.find { |i| i.tags[:is] == 'folder' && i.tags[:id] == id }
68
+ end
69
+
70
+ def folders
71
+ all.filter { |i| i.tags[:is] == 'folder' }
72
+ end
73
+
74
+ def items_in(id)
75
+ all.filter { |i| i.tags[:in] == id }
76
+ end
77
+
78
+ def unassigned_folders
79
+ all.filter { |i| i.tags[:is] == 'folder' && i.projects.empty? }
80
+ end
81
+
82
+ def assigned_folders
83
+ all.filter { |i| i.tags[:is] == 'folder' && i.projects.any? }
84
+ end
85
+
86
+ def boxes
87
+ all.filter { |i| i.tags[:is] == 'box' }
88
+ end
89
+
90
+ def unassigned_boxes
91
+ all.filter { |i| i.tags[:is] == 'box' && i.projects.empty? }
92
+ end
93
+
94
+ def assigned_boxes
95
+ all.filter { |i| i.tags[:is] == 'box' && i.projects.any? }
96
+ end
97
+
98
+ def units_of(noun)
99
+ all.filter { |i| i.tags[:is] == 'unit' && i.tags[:type] == noun.to_s }
100
+ end
101
+
102
+ def add_unit!(description, type, sku)
103
+ record = Record.new("#{Date.today} #{description} is:unit type:#{type} sku:#{sku}")
104
+ @list.append(record)
105
+ @list.save!
106
+ end
107
+
108
+ def add_item!(description, id, type, sku)
109
+ record = Record.new("#{Date.today} #{description} id:#{id} is:#{type} sku:#{sku}")
110
+ @list.append(record)
111
+ @list.save!
112
+ end
113
+
114
+ def add_folder!(description, id, sku)
115
+ add_item!(description, id, :folder, sku)
116
+ end
117
+
118
+ def add_box!(description, id, sku)
119
+ add_item!(description, id, :box, sku)
120
+ end
121
+
122
+ def save!
123
+ @list.save!
124
+ end
125
+ end
126
+
127
+ class Inbox
128
+ attr_reader :list
129
+
130
+ def initialize(db_path)
131
+ @list = List.new(db_path)
132
+ end
133
+
134
+ def all
135
+ @list.by_not_done
136
+ end
137
+
138
+ def focused
139
+ @list.by_context('@focus').by_not_done
140
+ end
141
+
142
+ def focused_projects
143
+ focused.map { |t| t.projects }.flatten.uniq
144
+ end
145
+
146
+ def add_line!(line)
147
+ record = Record.new("#{Date.today} #{line}")
148
+ @list.prepend(record)
149
+ @list.save!
150
+ end
151
+ end
152
+
153
+ class Documents
154
+ attr_reader :projects
155
+ attr_reader :inventory
156
+ attr_reader :inbox
157
+
158
+ def initialize
159
+ @projects = Projects.new("#{Intent::Env.documents_dir}/projects.txt")
160
+ @inventory = Inventory.new("#{Intent::Env.documents_dir}/inventory.txt")
161
+ @inbox = Inbox.new("#{Intent::Env.documents_dir}/todo.txt")
162
+ end
163
+ end
164
+ end
165
+
166
+ class Dispatcher
167
+ def self.exec_command(command, args, output=STDOUT)
168
+ command = init_command(command).new
169
+ command.run(args, output)
170
+ end
171
+
172
+ def self.init_command(command)
173
+ case command
174
+ when :intent then return Commands::Intent
175
+ when :inventory then return Commands::Inventory
176
+ when :projects then return Commands::Projects
177
+ when :project then return Commands::Project
178
+ when :todo then return Commands::Todo
179
+ else
180
+ raise Commands::Errors::COMMAND_NOT_FOUND
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,3 @@
1
+ require 'pathname'
2
+
3
+ require 'intent/desktop/commands'
@@ -1,4 +1,4 @@
1
1
  require 'pathname'
2
2
 
3
3
  require 'intent/projects/status'
4
- require 'intent/projects/manager'
4
+ require 'intent/projects/commands'
data/lib/intent/review.rb CHANGED
@@ -1 +1,10 @@
1
- require 'intent/review/manager'
1
+ require 'tty-box'
2
+ require 'tty-screen'
3
+ require 'tty-reader'
4
+ require 'tty-cursor'
5
+ require 'tty-pager'
6
+ require 'pastel'
7
+ require 'todo-txt'
8
+ require 'unicode_plot'
9
+
10
+ require 'intent/review/commands'
@@ -0,0 +1,6 @@
1
+ usage: inventory <verb>
2
+
3
+ Tools for managing a design archive and inventory ledger.
4
+
5
+ inventory list - list items in the inventory
6
+ inventory add <noun> - add a new item to the inventory
@@ -0,0 +1,7 @@
1
+ usage: project <task>
2
+ intent project <task>
3
+
4
+ Individual project tasks
5
+
6
+ project init - initializes a new project
7
+ project link - links an existing project
@@ -0,0 +1,9 @@
1
+ usage: projects <task>
2
+ intent projects <task>
3
+
4
+ A set of tasks for managing a network of plain text projects.
5
+
6
+ projects status - DEPRECATED parses tracked file status in local project directories
7
+ projects active - lists currently active projects
8
+ projects focus - initialise interactive focus navigator
9
+ projects sync - stores synchronised commit of current changes to projects
@@ -0,0 +1,15 @@
1
+ usage: todo <task>
2
+ intent todo <task>
3
+
4
+ A set of tasks for managing a plain text todo list.
5
+
6
+ todo list - list all items in the list
7
+ todo add - add a new task to the list
8
+ todo sample - randomly select a priority task"
9
+ todo projects - list all project tags in the list
10
+ todo contexts - list all context tags in the list
11
+ todo archive - archive completed tasks in the nearest `done.txt`
12
+ todo status - show completion status for all projects
13
+ todo sync - synchronize local changes with remote git repo
14
+ todo collect - collect open browser tabs as items for later reading
15
+ todo focus - block distractions and start focusing
data/lib/intent/todo.rb CHANGED
@@ -5,8 +5,10 @@ require 'fileutils'
5
5
  require 'logger'
6
6
  require 'git'
7
7
  require 'terminal-notifier'
8
+ require 'sorted_set'
8
9
  require 'ghost'
9
- require 'intent/todo/manager'
10
+
11
+ require 'intent/todo/commands'
10
12
 
11
13
  Todo.customize do |options|
12
14
  options.require_completed_on = false
@@ -0,0 +1,21 @@
1
+ module Intent
2
+ module Verbs
3
+ class Add
4
+ def initialize(documents)
5
+ @documents = documents
6
+ end
7
+
8
+ def invoke_prepend(scope, noun)
9
+ ledger = @documents.send(scope)
10
+ ledger.prepend(new Todo::Task(noun.todo_s))
11
+ ledger.save!
12
+ end
13
+
14
+ def invoke_append(ledger, noun)
15
+ ledger = @documents.send(scope)
16
+ ledger.append(new Todo::Task(noun.todo_s))
17
+ ledger.save!
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Intent
2
+ module Verbs
3
+ class Cite
4
+ attr_reader :documents
5
+ attr_reader :projects
6
+ attr_reader :contexts
7
+ attr_reader :bibliography
8
+
9
+ def initialize(documents)
10
+ @documents = documents
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module Intent
2
+ module Verbs
3
+ class Install
4
+ attr_reader :documents
5
+
6
+ def initialize(documents)
7
+ @documents = documents
8
+ end
9
+
10
+ def run
11
+ # Preprequisite checks
12
+ # return env_not_detected! unless @documents.env_detected?
13
+ # return already_installed! if @documents.install_detected?
14
+
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,131 @@
1
+ module Intent
2
+ module Review
3
+ class Commands < Intent::CommandDispatcher
4
+ def run(args, output=STDOUT)
5
+ if args.empty?
6
+ launch_densityplot
7
+ else
8
+ print_help(output)
9
+ end
10
+ end
11
+
12
+ def launch_densityplot
13
+ UnicodePlot.barplot(data: {'calyx': 20, 'fictive': 50}, title: "Projects").render
14
+ end
15
+
16
+ def launch_ui_loop
17
+ list = Todo::List.new(ENV['TODO_TXT'])
18
+
19
+ box = TTY::Box.frame(
20
+ width: TTY::Screen.width,
21
+ height: TTY::Screen.height-1,
22
+ align: :center,
23
+ border: :thick,
24
+ style: {
25
+ fg: :bright_yellow,
26
+ border: {
27
+ fg: :white,
28
+ }
29
+ }
30
+ )
31
+
32
+ focus_index = 0
33
+
34
+ def truncate(str, width)
35
+ return str unless str.length >= width
36
+
37
+ "#{str.slice(0, width-3)}..."
38
+ end
39
+
40
+ def draw_card(top, item, focus)
41
+
42
+ style = unless top == focus
43
+ { fg: :white, border: { fg: :white } }
44
+ else
45
+ { fg: :bright_white, border: { fg: :bright_magenta, bg: :black } }
46
+ end
47
+
48
+ title = if top == focus
49
+ {bottom_right: "[x: close][enter: launch]"}
50
+ else
51
+ {}
52
+ end
53
+
54
+ #pastel = Pastel.new
55
+ width = 90
56
+
57
+ TTY::Box.frame(width: width, height: 4, left: 4, title: title, border: :thick, style: style) do
58
+ "\s#{truncate(item[:title], 88)}\n\s#{truncate(item[:href], 88)}"
59
+ end
60
+ end
61
+
62
+ items_per_page = (TTY::Screen.height-1) / 4
63
+
64
+ items = list.by_not_done.by_context("@reading").slice(0, items_per_page).map do |item|
65
+ { title: item.text, href: item.text }
66
+ end
67
+
68
+ $cursor = TTY::Cursor
69
+ print $cursor.hide
70
+
71
+ def draw_list(items, focus)
72
+ buffer = []
73
+ items.each_with_index do |item, i|
74
+ buffer << draw_card(i, item, focus)
75
+ end
76
+ result = buffer.join("")
77
+
78
+ print $cursor.clear_screen + $cursor.move_to(0,0)
79
+ print result
80
+ end
81
+
82
+ draw_list(items, focus_index)
83
+
84
+ reader = TTY::Reader.new(interrupt: :exit)
85
+
86
+ reader.on(:keyctrl_x, :keyescape) do
87
+ print $cursor.clear_screen + $cursor.move_to(0,0)
88
+ print $cursor.show
89
+ exit
90
+ end
91
+
92
+ reader.on(:keytab, :keydown) do
93
+ if focus_index == items.count - 1
94
+ focus_index = 0
95
+ else
96
+ focus_index += 1
97
+ end
98
+ draw_list(items, focus_index)
99
+ end
100
+
101
+ reader.on(:keyup) do
102
+ if focus_index == 0
103
+ focus_index = items.count - 1
104
+ else
105
+ focus_index -= 1
106
+ end
107
+ draw_list(items, focus_index)
108
+ end
109
+
110
+ reader.on(:keypress) do |key|
111
+ if key.value == "x"
112
+ items.delete_at(focus_index)
113
+ focus_index -= 1 if focus_index >= items.count
114
+ draw_list(items, focus_index)
115
+ end
116
+ end
117
+
118
+ reader.on(:keyenter) do
119
+ `chrome-cli open #{items[focus_index][:href]}`
120
+ end
121
+
122
+ loop do
123
+ reader.read_line(echo: false)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+
131
+
@@ -4,10 +4,10 @@ module Intent
4
4
  List = ::Todo::List
5
5
  Syntax = ::Todo::Syntax
6
6
 
7
- class Manager
7
+ class Commands < Intent::CommandDispatcher
8
8
  include Syntax
9
9
 
10
- def self.run(args, output=STDOUT)
10
+ def run(args, output=STDOUT)
11
11
  if args.empty?
12
12
  print_help(output)
13
13
  else
@@ -183,20 +183,7 @@ module Intent
183
183
  end
184
184
 
185
185
  def self.print_help(output)
186
- output.puts "usage: todo"
187
- output.puts
188
- output.puts "A set of tasks for managing a plain text todo list."
189
- output.puts
190
- output.puts "todo list - list all items in the list"
191
- output.puts "todo add - add a new task to the list"
192
- output.puts "todo sample - randomly select a priority task"
193
- output.puts "todo projects - list all project tags in the list"
194
- output.puts "todo contexts - list all context tags in the list"
195
- output.puts "todo archive - archive completed tasks in the nearest `done.txt`"
196
- output.puts "todo status - show completion status for all projects"
197
- output.puts "todo sync - synchronize local changes with remote git repo"
198
- output.puts "todo collect - collect open browser tabs as items for later reading"
199
- output.puts "todo focus - block distractions and start focusing"
186
+ output.puts File.read('./todo.help.txt')
200
187
  end
201
188
  end
202
189
  end
@@ -1,3 +1,3 @@
1
1
  module Intent
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
data/lib/intent.rb CHANGED
@@ -1,4 +1,21 @@
1
- require 'intent/todo'
2
- require 'intent/review'
3
- require 'intent/projects'
1
+ #require 'random'
2
+ require 'todo-txt'
3
+ require 'pastel'
4
+ require 'tty-prompt'
5
+ require 'tty-table'
6
+ require 'tty-tree'
7
+ require 'nanoid'
8
+ require 'gem_ext/todo-txt'
9
+
10
+ Todo.customize do |options|
11
+ options.require_completed_on = false
12
+ end
13
+
4
14
  require 'intent/version'
15
+ require 'intent/core'
16
+ require 'intent/commands'
17
+ # require 'intent/todo'
18
+ # require 'intent/review'
19
+ # require 'intent/projects'
20
+ # require 'intent/desktop'
21
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-11 00:00:00.000000000 Z
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: todo-txt
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.9.1
47
+ version: 1.19.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.9.1
54
+ version: 1.19.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: terminal-notifier
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,132 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 1.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: unicode_plot
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.0.5
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.0.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: kdl
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.3
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.3
111
+ - !ruby/object:Gem::Dependency
112
+ name: sorted_set
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 1.0.3
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 1.0.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: strings-ansi
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 0.2.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 0.2.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: bibtex-ruby
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 6.1.0
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 6.1.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: tty-prompt
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.23.1
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.23.1
167
+ - !ruby/object:Gem::Dependency
168
+ name: tty-table
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.12.0
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.12.0
181
+ - !ruby/object:Gem::Dependency
182
+ name: tty-tree
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.4.0
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.4.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: nanoid
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 2.0.0
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 2.0.0
83
209
  - !ruby/object:Gem::Dependency
84
210
  name: bundler
85
211
  requirement: !ruby/object:Gem::Requirement
@@ -126,37 +252,54 @@ description:
126
252
  email:
127
253
  - me@maetl.net
128
254
  executables:
255
+ - intent
256
+ - inventory
129
257
  - project_doc
130
258
  - projects
131
259
  - projects_active
132
- - review
133
260
  - todo
134
261
  - todo_review
135
262
  extensions: []
136
263
  extra_rdoc_files: []
137
264
  files:
138
265
  - ".gitignore"
139
- - ".travis.yml"
140
266
  - Gemfile
141
267
  - LICENSE.txt
268
+ - README.md
142
269
  - Rakefile
270
+ - bin/intent
271
+ - bin/inventory
143
272
  - bin/project_doc
144
273
  - bin/projects
145
274
  - bin/projects_active
146
- - bin/review
147
275
  - bin/todo
148
276
  - bin/todo_review
149
277
  - intent.gemspec
150
278
  - lib/gem_ext/todo-txt.rb
151
279
  - lib/intent.rb
280
+ - lib/intent/commands.rb
281
+ - lib/intent/commands/base.rb
282
+ - lib/intent/commands/errors.rb
283
+ - lib/intent/commands/intent.rb
284
+ - lib/intent/commands/inventory.rb
285
+ - lib/intent/commands/projects.rb
286
+ - lib/intent/commands/todo.rb
287
+ - lib/intent/core.rb
288
+ - lib/intent/desktop.rb
152
289
  - lib/intent/projects.rb
153
- - lib/intent/projects/manager.rb
154
- - lib/intent/projects/status.rb
155
290
  - lib/intent/review.rb
156
- - lib/intent/review/manager.rb
291
+ - lib/intent/text/inventory.help.txt
292
+ - lib/intent/text/project.help.txt
293
+ - lib/intent/text/projects.help.txt
294
+ - lib/intent/text/todo.help.txt
157
295
  - lib/intent/todo.rb
158
- - lib/intent/todo/manager.rb
159
296
  - lib/intent/ui/ttyui.rb
297
+ - lib/intent/verbs/add.rb
298
+ - lib/intent/verbs/cite.rb
299
+ - lib/intent/verbs/install.rb
300
+ - lib/intent/verbs/review.rb
301
+ - lib/intent/verbs/status.rb
302
+ - lib/intent/verbs/todo.rb
160
303
  - lib/intent/version.rb
161
304
  homepage: http://maetl.net
162
305
  licenses:
@@ -177,7 +320,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
320
  - !ruby/object:Gem::Version
178
321
  version: '0'
179
322
  requirements: []
180
- rubygems_version: 3.1.2
323
+ rubygems_version: 3.3.7
181
324
  signing_key:
182
325
  specification_version: 4
183
326
  summary: Text-based project management
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.1.3
4
- before_install: gem install bundler -v 1.10.6
@@ -1,20 +0,0 @@
1
- module Intent
2
- module Projects
3
- class Manager
4
- def self.run(args, output=STDOUT)
5
- if args.empty?
6
- print_help(output)
7
- else
8
- case args.first.to_sym
9
- when :status
10
- Status.run(File.expand_path('~/Projects').to_s)
11
- end
12
- end
13
- end
14
-
15
- def self.print_help(output)
16
- output.puts "usage: review"
17
- end
18
- end
19
- end
20
- end
@@ -1,17 +0,0 @@
1
- module Intent
2
- module Review
3
- class Manager
4
- def self.run(args, output=STDOUT)
5
- if args.empty?
6
- print_help(output)
7
- else
8
- puts args
9
- end
10
- end
11
-
12
- def self.print_help(output)
13
- output.puts "usage: review"
14
- end
15
- end
16
- end
17
- end
File without changes