intent 0.5.5 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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