intent 0.6.0 → 0.7.0

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