intent 0.5.5 → 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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +10 -28
  5. data/bin/{review → intent} +1 -2
  6. data/bin/inventory +12 -0
  7. data/bin/project_doc +30 -0
  8. data/bin/projects +1 -1
  9. data/bin/projects_active +123 -0
  10. data/bin/todo +1 -1
  11. data/bin/todo_review +123 -0
  12. data/intent.gemspec +15 -7
  13. data/lib/gem_ext/todo-txt.rb +26 -2
  14. data/lib/intent/commands/base.rb +40 -0
  15. data/lib/intent/commands/errors.rb +7 -0
  16. data/lib/intent/commands/intent.rb +9 -0
  17. data/lib/intent/commands/inventory.rb +174 -0
  18. data/lib/intent/commands/projects.rb +34 -0
  19. data/lib/intent/commands/todo.rb +51 -0
  20. data/lib/intent/commands.rb +9 -0
  21. data/lib/intent/core.rb +184 -0
  22. data/lib/intent/desktop.rb +3 -0
  23. data/lib/intent/projects.rb +1 -1
  24. data/lib/intent/review.rb +10 -1
  25. data/lib/intent/text/inventory.help.txt +6 -0
  26. data/lib/intent/text/project.help.txt +7 -0
  27. data/lib/intent/text/projects.help.txt +9 -0
  28. data/lib/intent/text/todo.help.txt +15 -0
  29. data/lib/intent/todo.rb +3 -1
  30. data/lib/intent/ui/ttyui.rb +90 -0
  31. data/lib/intent/verbs/add.rb +21 -0
  32. data/lib/intent/verbs/cite.rb +14 -0
  33. data/lib/intent/verbs/install.rb +20 -0
  34. data/lib/intent/verbs/review.rb +131 -0
  35. data/lib/intent/{todo/manager.rb → verbs/todo.rb} +3 -16
  36. data/lib/intent/version.rb +1 -1
  37. data/lib/intent.rb +20 -3
  38. metadata +179 -32
  39. data/.travis.yml +0 -4
  40. data/CODE_OF_CONDUCT.md +0 -13
  41. data/lib/intent/projects/manager.rb +0 -20
  42. data/lib/intent/review/manager.rb +0 -17
  43. /data/lib/intent/{projects → verbs}/status.rb +0 -0
@@ -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,90 @@
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
+
9
+ module Intent
10
+ module UI
11
+ class Panel
12
+ def initialize(x, y, width, height)
13
+ @x = x
14
+ @y = y
15
+ @width = width
16
+ @height = height
17
+ end
18
+
19
+ def draw
20
+ TTY::Box.frame(
21
+ left: @x,
22
+ top: @y,
23
+ width: @width,
24
+ height: @height,
25
+ align: :center,
26
+ border: :light,
27
+ style: {
28
+ fg: :white,
29
+ border: {
30
+ fg: :white,
31
+ }
32
+ }
33
+ )
34
+ end
35
+ end
36
+
37
+ class Window
38
+ def initialize
39
+ @width = TTY::Screen.width
40
+ @height = TTY::Screen.height-1
41
+ @panels = [Panel.new(0, 0, @width, @height)]
42
+ @cursor = TTY::Cursor
43
+ end
44
+
45
+ def split_vertical(view1=nil, view2=nil)
46
+ width = (@width / 2) - 1
47
+ height = @height - 2
48
+
49
+ @panels << Panel.new(1, 1, width, height)
50
+ @panels << Panel.new(width + 1, 1, width, height)
51
+ end
52
+
53
+ def split_horizontal(view1=nil, view2=nil)
54
+ width = @width - 2
55
+ height = (@height / 2) - 2
56
+ end
57
+
58
+ def box_frame
59
+ TTY::Box.frame(
60
+ width: @width,
61
+ height: @height,
62
+ align: :center,
63
+ border: :thick,
64
+ style: {
65
+ fg: :blue,
66
+ border: {
67
+ fg: :white,
68
+ }
69
+ }
70
+ )
71
+ end
72
+
73
+ def draw
74
+ @cursor.clear_screen
75
+
76
+ print box_frame
77
+
78
+ @panels.each do |panel|
79
+ @cursor.move_to
80
+
81
+ print panel.draw
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ win = Intent::UI::Window.new
89
+ win.split_vertical
90
+ win.draw
@@ -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