intent 0.6.0 → 0.7.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/.gitignore +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +23 -0
- data/bin/{review → intent} +1 -2
- data/bin/inventory +12 -0
- data/bin/projects +1 -1
- data/bin/todo +1 -1
- data/intent.gemspec +11 -1
- data/lib/gem_ext/todo-txt.rb +26 -2
- data/lib/intent/commands/base.rb +40 -0
- data/lib/intent/commands/errors.rb +7 -0
- data/lib/intent/commands/intent.rb +9 -0
- data/lib/intent/commands/inventory.rb +210 -0
- data/lib/intent/commands/projects.rb +34 -0
- data/lib/intent/commands/todo.rb +51 -0
- data/lib/intent/commands.rb +9 -0
- data/lib/intent/core.rb +185 -0
- data/lib/intent/desktop.rb +3 -0
- data/lib/intent/projects.rb +1 -1
- data/lib/intent/review.rb +10 -1
- data/lib/intent/text/inventory.help.txt +6 -0
- data/lib/intent/text/project.help.txt +7 -0
- data/lib/intent/text/projects.help.txt +9 -0
- data/lib/intent/text/todo.help.txt +15 -0
- data/lib/intent/todo.rb +3 -1
- data/lib/intent/ui/term_color.rb +80 -0
- data/lib/intent/verbs/add.rb +21 -0
- data/lib/intent/verbs/cite.rb +14 -0
- data/lib/intent/verbs/install.rb +20 -0
- data/lib/intent/verbs/review.rb +131 -0
- data/lib/intent/{todo/manager.rb → verbs/todo.rb} +3 -16
- data/lib/intent/version.rb +1 -1
- data/lib/intent.rb +22 -3
- metadata +170 -12
- data/.travis.yml +0 -4
- data/lib/intent/projects/manager.rb +0 -20
- data/lib/intent/review/manager.rb +0 -17
- /data/lib/intent/{projects → verbs}/status.rb +0 -0
data/lib/intent/core.rb
ADDED
@@ -0,0 +1,185 @@
|
|
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, box=nil)
|
109
|
+
description << " in:#{box}" unless box.nil?
|
110
|
+
record = Record.new("#{Date.today} #{description} id:#{id} is:#{type} sku:#{sku}")
|
111
|
+
@list.append(record)
|
112
|
+
@list.save!
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_folder!(description, id, sku, box=nil)
|
116
|
+
add_item!(description, id, :folder, sku, box)
|
117
|
+
end
|
118
|
+
|
119
|
+
def add_box!(description, id, sku)
|
120
|
+
add_item!(description, id, :box, sku)
|
121
|
+
end
|
122
|
+
|
123
|
+
def save!
|
124
|
+
@list.save!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Inbox
|
129
|
+
attr_reader :list
|
130
|
+
|
131
|
+
def initialize(db_path)
|
132
|
+
@list = List.new(db_path)
|
133
|
+
end
|
134
|
+
|
135
|
+
def all
|
136
|
+
@list.by_not_done
|
137
|
+
end
|
138
|
+
|
139
|
+
def focused
|
140
|
+
@list.by_context('@focus').by_not_done
|
141
|
+
end
|
142
|
+
|
143
|
+
def focused_projects
|
144
|
+
focused.map { |t| t.projects }.flatten.uniq
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_line!(line)
|
148
|
+
record = Record.new("#{Date.today} #{line}")
|
149
|
+
@list.prepend(record)
|
150
|
+
@list.save!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Documents
|
155
|
+
attr_reader :projects
|
156
|
+
attr_reader :inventory
|
157
|
+
attr_reader :inbox
|
158
|
+
|
159
|
+
def initialize
|
160
|
+
@projects = Projects.new("#{Intent::Env.documents_dir}/projects.txt")
|
161
|
+
@inventory = Inventory.new("#{Intent::Env.documents_dir}/inventory.txt")
|
162
|
+
@inbox = Inbox.new("#{Intent::Env.documents_dir}/todo.txt")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Dispatcher
|
168
|
+
def self.exec_command(command, args, output=STDOUT)
|
169
|
+
command = init_command(command).new
|
170
|
+
command.run(args, output)
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.init_command(command)
|
174
|
+
case command
|
175
|
+
when :intent then return Commands::Intent
|
176
|
+
when :inventory then return Commands::Inventory
|
177
|
+
when :projects then return Commands::Projects
|
178
|
+
when :project then return Commands::Project
|
179
|
+
when :todo then return Commands::Todo
|
180
|
+
else
|
181
|
+
raise Commands::Errors::COMMAND_NOT_FOUND
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
data/lib/intent/projects.rb
CHANGED
data/lib/intent/review.rb
CHANGED
@@ -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
|
-
|
10
|
+
|
11
|
+
require 'intent/todo/commands'
|
10
12
|
|
11
13
|
Todo.customize do |options|
|
12
14
|
options.require_completed_on = false
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Intent
|
2
|
+
module UI
|
3
|
+
# Shim to convert between Pastel to Paint gems
|
4
|
+
# without needing to edit existing call sites.
|
5
|
+
#
|
6
|
+
# It might be helpful to normalize this convention with an API
|
7
|
+
# anyway as we generally want to use the 8/16 colour defaults
|
8
|
+
# as they pick up user terminal customisation properly, whereas
|
9
|
+
# going full 256 or 24 bit colour means generic RGB values are likely
|
10
|
+
# to look shit on customised terminal backgrounds.
|
11
|
+
#
|
12
|
+
# This way we get the benefits of *mostly* sticking to the terminal
|
13
|
+
# defaults, while extending the range of colours with a few carefully
|
14
|
+
# chosen values.
|
15
|
+
class TermColor
|
16
|
+
def initialize
|
17
|
+
@decoration_scope = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def decorate(text, *args)
|
21
|
+
decoration_scope.push(*args)
|
22
|
+
return_decorator(text)
|
23
|
+
end
|
24
|
+
|
25
|
+
def bold(text=nil)
|
26
|
+
decoration_scope.push(:bold)
|
27
|
+
return_decorator(text)
|
28
|
+
end
|
29
|
+
|
30
|
+
def red(text=nil)
|
31
|
+
decoration_scope.push(:red)
|
32
|
+
return_decorator(text)
|
33
|
+
end
|
34
|
+
|
35
|
+
def green(text)
|
36
|
+
decoration_scope.push(:green)
|
37
|
+
return_decorator(text)
|
38
|
+
end
|
39
|
+
|
40
|
+
def blue(text)
|
41
|
+
decoration_scope.push(:blue)
|
42
|
+
return_decorator(text)
|
43
|
+
end
|
44
|
+
|
45
|
+
def yellow(text)
|
46
|
+
decoration_scope.push(:yellow)
|
47
|
+
return_decorator(text)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cyan(text)
|
51
|
+
decoration_scope.push(:cyan)
|
52
|
+
return_decorator(text)
|
53
|
+
end
|
54
|
+
|
55
|
+
def orange(text)
|
56
|
+
decoration_scope.push('orange')
|
57
|
+
return_decorator(text)
|
58
|
+
end
|
59
|
+
|
60
|
+
def brown(text)
|
61
|
+
decoration_scope.push('tan')
|
62
|
+
return_decorator(text)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :decoration_scope
|
68
|
+
|
69
|
+
def return_decorator(text)
|
70
|
+
if text.nil?
|
71
|
+
self
|
72
|
+
else
|
73
|
+
decorated = Paint[text, *decoration_scope]
|
74
|
+
decoration_scope.clear
|
75
|
+
decorated
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -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,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
|
7
|
+
class Commands < Intent::CommandDispatcher
|
8
8
|
include Syntax
|
9
9
|
|
10
|
-
def
|
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
|
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
|
data/lib/intent/version.rb
CHANGED
data/lib/intent.rb
CHANGED
@@ -1,4 +1,23 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
#require 'random'
|
2
|
+
require 'todo-txt'
|
3
|
+
require 'pastel'
|
4
|
+
require 'paint'
|
5
|
+
require 'tty-prompt'
|
6
|
+
require 'tty-table'
|
7
|
+
require 'tty-tree'
|
8
|
+
require 'nanoid'
|
9
|
+
require 'gem_ext/todo-txt'
|
10
|
+
|
11
|
+
Todo.customize do |options|
|
12
|
+
options.require_completed_on = false
|
13
|
+
end
|
14
|
+
|
4
15
|
require 'intent/version'
|
16
|
+
require 'intent/core'
|
17
|
+
require 'intent/commands'
|
18
|
+
require 'intent/ui/term_color'
|
19
|
+
# require 'intent/todo'
|
20
|
+
# require 'intent/review'
|
21
|
+
# require 'intent/projects'
|
22
|
+
# require 'intent/desktop'
|
23
|
+
|