na 1.2.35 → 1.2.38

Sign up to get free protection for your applications and to get access to all the features.
data/lib/na/pager.rb CHANGED
@@ -44,7 +44,7 @@ module NA
44
44
  IO.select [input]
45
45
 
46
46
  begin
47
- NA.notify("{dw}Pager #{pager}", debug: true)
47
+ NA.notify("#{NA.theme[:debug]}Pager #{pager}", debug: true)
48
48
  exec(pager)
49
49
  rescue SystemCallError => e
50
50
  raise Errors::DoingStandardError, "Pager error, #{e}"
data/lib/na/prompt.rb CHANGED
@@ -14,7 +14,7 @@ module NA
14
14
  when :tag
15
15
  'na tagged $(basename "$PWD")'
16
16
  else
17
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
17
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
18
18
  end
19
19
  else
20
20
  'na next'
@@ -31,7 +31,7 @@ module NA
31
31
  when :tag
32
32
  'na tagged (basename "$PWD")'
33
33
  else
34
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
34
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
35
35
  end
36
36
  else
37
37
  'na next'
@@ -50,7 +50,7 @@ module NA
50
50
  when :tag
51
51
  'na tagged $(basename "$PWD")'
52
52
  else
53
- NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
53
+ NA.notify("#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1)
54
54
  end
55
55
  else
56
56
  'na next'
@@ -83,7 +83,7 @@ module NA
83
83
  def show_prompt_hook(shell)
84
84
  file = prompt_file(shell)
85
85
 
86
- NA.notify("{bw}# Add this to {y}#{file}{x}")
86
+ NA.notify("#{NA.theme[:warning]}# Add this to #{NA.theme[:filename]}#{file}")
87
87
  puts prompt_hook(shell)
88
88
  end
89
89
 
@@ -91,8 +91,8 @@ module NA
91
91
  file = prompt_file(shell)
92
92
 
93
93
  File.open(File.expand_path(file), 'a') { |f| f.puts prompt_hook(shell) }
94
- NA.notify("{y}Added {bw}#{shell}{xy} prompt hook to {bw}#{file}{xy}.{x}")
95
- NA.notify("{y}You may need to close the current terminal and open a new one to enable the script.{x}")
94
+ NA.notify("#{NA.theme[:success]}Added #{NA.theme[:filename]}#{shell}{x}#{NA.theme[:success]} prompt hook to #{NA.theme[:filename]}#{file}#{NA.theme[:success]}.")
95
+ NA.notify("#{NA.theme[:warning]}You may need to close the current terminal and open a new one to enable the script.")
96
96
  end
97
97
  end
98
98
  end
data/lib/na/string.rb CHANGED
@@ -6,6 +6,15 @@ REGEX_TIME = /^#{REGEX_CLOCK}$/i.freeze
6
6
 
7
7
  # String helpers
8
8
  class ::String
9
+ ##
10
+ ## Insert a comment character at the start of every line
11
+ ##
12
+ ## @param char [String] The character to insert (default #)
13
+ ##
14
+ def comment(char = "#")
15
+ split(/\n/).map { |l| "# #{l}" }.join("\n")
16
+ end
17
+
9
18
  ##
10
19
  ## Tests if object is nil or empty
11
20
  ##
@@ -75,6 +84,17 @@ class ::String
75
84
  self =~ /@#{NA.na_tag}\b/
76
85
  end
77
86
 
87
+ ##
88
+ ## Colorize the dirname and filename of a path
89
+ ##
90
+ ## @return Colorized string
91
+ ##
92
+ def highlight_filename
93
+ dir = File.dirname(self).shorten_path
94
+ file = File.basename(self, ".#{NA.extension}")
95
+ "#{NA.theme[:dirname]}#{dir}/#{NA.theme[:filename]}#{file}{x}"
96
+ end
97
+
78
98
  ##
79
99
  ## Colorize @tags with ANSI escapes
80
100
  ##
@@ -88,7 +108,7 @@ class ::String
88
108
  ##
89
109
  ## @return [String] string with @tags highlighted
90
110
  ##
91
- def highlight_tags(color: '{m}', value: '{y}', parens: '{m}', last_color: '{xg}')
111
+ def highlight_tags(color: NA.theme[:tags], value: NA.theme[:value], parens: NA.theme[:value_parens], last_color: NA.theme[:action])
92
112
  tag_color = NA::Color.template(color)
93
113
  paren_color = NA::Color.template(parens)
94
114
  value_color = NA::Color.template(value)
@@ -106,9 +126,9 @@ class ::String
106
126
  ## @param last_color [String] Color to restore after
107
127
  ## highlight
108
128
  ##
109
- def highlight_search(regexes, color: '{y}', last_color: '{xg}')
129
+ def highlight_search(regexes, color: NA.theme[:search_highlight], last_color: NA.theme[:action])
110
130
  string = dup
111
- color = NA::Color.template(color)
131
+ color = NA::Color.template(color.dup)
112
132
  regexes.each do |rx|
113
133
  next if rx.nil?
114
134
 
data/lib/na/theme.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NA
4
+ module Theme
5
+ class << self
6
+ def template_help
7
+ <<~EOHELP
8
+ Use {X} placeholders to apply colors. Available colors are:
9
+
10
+ w: white, k: black, g: green, l: blue,
11
+ y: yellow, c: cyan, m: magenta, r: red,
12
+ W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
13
+ Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
14
+ d: dark, b: bold, u: underline, i: italic, x: reset
15
+
16
+ Multiple placeholders can be combined in a single {} pair.
17
+
18
+ You can also use {#RGB} and {#RRGGBB} to specify hex colors.
19
+ Add a b before the # to make the hex a background color ({b#fa0}).
20
+
21
+
22
+ EOHELP
23
+ end
24
+
25
+ def load_theme(template: {})
26
+ # Default colorization, can be overridden with full or partial template variable
27
+ default_template = {
28
+ parent: '{c}',
29
+ bracket: '{dc}',
30
+ parent_divider: '{xw}/',
31
+ action: '{bg}',
32
+ project: '{xbk}',
33
+ tags: '{m}',
34
+ value_parens: '{m}',
35
+ values: '{c}',
36
+ search_highlight: '{y}',
37
+ note: '{dw}',
38
+ dirname: '{dw}',
39
+ filename: '{xb}{#eccc87}',
40
+ prompt: '{m}',
41
+ success: '{bg}',
42
+ error: '{b}{#b61d2a}',
43
+ warning: '{by}',
44
+ debug: '{dw}',
45
+ templates: {
46
+ output: '%filename%parents| %action',
47
+ default: '%parent%action',
48
+ single_file: '%parent%action',
49
+ multi_file: '%filename%parent%action'
50
+ }
51
+ }
52
+
53
+ # Load custom theme
54
+ theme_file = NA.database_path(file: 'theme.yaml')
55
+ theme = if File.exist?(theme_file)
56
+ YAML.load(IO.read(theme_file)) || {}
57
+ else
58
+ {}
59
+ end
60
+ theme = default_template.merge(theme)
61
+
62
+ File.open(theme_file, 'w') do |f|
63
+ f.puts template_help.comment
64
+ f.puts YAML.dump(theme)
65
+ end
66
+
67
+ theme.merge(template)
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/na/todo.rb ADDED
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NA
4
+ class Todo
5
+ attr_accessor :actions, :projects, :files
6
+
7
+ def initialize(options = {})
8
+ @files, @actions, @projects = parse(options)
9
+ end
10
+
11
+ ##
12
+ ## Read a todo file and create a list of actions
13
+ ##
14
+ ## @param options The options
15
+ ##
16
+ ## @option depth [Number] The directory depth to
17
+ ## search for files
18
+ ## @option done [Boolean] include @done actions
19
+ ## @option query [Hash] The todo file query
20
+ ## @option tag [Array] Tags to search for
21
+ ## @option search [String] A search string
22
+ ## @option negate [Boolean] Invert results
23
+ ## @option regex [Boolean] Interpret as regular
24
+ ## expression
25
+ ## @option project [String] The project
26
+ ## @option require_na [Boolean] Require @na tag
27
+ ## @option file_path [String] file path to parse
28
+ ##
29
+ def parse(options)
30
+ defaults = {
31
+ depth: 1,
32
+ done: false,
33
+ file_path: nil,
34
+ negate: false,
35
+ project: nil,
36
+ query: nil,
37
+ regex: false,
38
+ require_na: true,
39
+ search: nil,
40
+ tag: nil
41
+ }
42
+
43
+ settings = defaults.merge(options)
44
+
45
+ actions = NA::Actions.new
46
+ required = []
47
+ optional = []
48
+ negated = []
49
+ required_tag = []
50
+ optional_tag = []
51
+ negated_tag = []
52
+ projects = []
53
+
54
+ NA.notify("Tags: #{settings[:tag]}", debug:true)
55
+ NA.notify("Search: #{settings[:search]}", debug:true)
56
+
57
+ settings[:tag]&.each do |t|
58
+ unless t[:tag].nil?
59
+ if settings[:negate]
60
+ optional_tag.push(t) if t[:negate]
61
+ required_tag.push(t) if t[:required] && t[:negate]
62
+ negated_tag.push(t) unless t[:negate]
63
+ else
64
+ optional_tag.push(t) unless t[:negate]
65
+ required_tag.push(t) if t[:required] && !t[:negate]
66
+ negated_tag.push(t) if t[:negate]
67
+ end
68
+ end
69
+ end
70
+
71
+ unless settings[:search].nil? || settings[:search].empty?
72
+ if settings[:regex] || settings[:search].is_a?(String)
73
+ if settings[:negate]
74
+ negated.push(settings[:search])
75
+ else
76
+ optional.push(settings[:search])
77
+ required.push(settings[:search])
78
+ end
79
+ else
80
+ settings[:search].each do |t|
81
+ opt, req, neg = parse_search(t, settings[:negate])
82
+ optional.concat(opt)
83
+ required.concat(req)
84
+ negated.concat(neg)
85
+ end
86
+ end
87
+ end
88
+
89
+ files = if !settings[:file_path].nil?
90
+ [settings[:file_path]]
91
+ elsif settings[:query].nil?
92
+ NA.find_files(depth: settings[:depth])
93
+ else
94
+ NA.match_working_dir(settings[:query])
95
+ end
96
+
97
+ files.each do |file|
98
+ NA.save_working_dir(File.expand_path(file))
99
+ content = file.read_file
100
+ indent_level = 0
101
+ parent = []
102
+ in_action = false
103
+ content.split(/\n/).each.with_index do |line, idx|
104
+ if line.project?
105
+ in_action = false
106
+ proj = line.project
107
+ indent = line.indent_level
108
+
109
+ if indent.zero? # top level project
110
+ parent = [proj]
111
+ elsif indent <= indent_level # if indent level is same or less, split parent before indent level and append
112
+ parent.slice!(indent, parent.count - indent)
113
+ parent.push(proj)
114
+ else # if indent level is greater, append project to parent
115
+ parent.push(proj)
116
+ end
117
+
118
+ projects.push(NA::Project.new(parent.join(':'), indent, idx, idx))
119
+
120
+ indent_level = indent
121
+ elsif line.blank?
122
+ in_action = false
123
+ elsif line.action?
124
+ in_action = false
125
+
126
+ action = line.action
127
+ new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action, idx)
128
+
129
+ projects[-1].last_line = idx if projects.count.positive?
130
+
131
+ next if line.done? && !settings[:done]
132
+
133
+ next if settings[:require_na] && !line.na?
134
+
135
+ has_search = !optional.empty? || !required.empty? || !negated.empty?
136
+
137
+ next if has_search && !new_action.search_match?(any: optional,
138
+ all: required,
139
+ none: negated)
140
+
141
+ if settings[:project]
142
+ rx = settings[:project].split(%r{[/:]}).join('.*?/')
143
+ next unless parent.join('/') =~ Regexp.new("#{rx}.*?", Regexp::IGNORECASE)
144
+ end
145
+
146
+ has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
147
+ next if has_tag && !new_action.tags_match?(any: optional_tag,
148
+ all: required_tag,
149
+ none: negated_tag)
150
+
151
+ actions.push(new_action)
152
+ in_action = true
153
+ elsif in_action
154
+ actions[-1].note.push(line.strip) if actions.count.positive?
155
+ projects[-1].last_line = idx if projects.count.positive?
156
+ end
157
+ end
158
+ projects = projects.dup
159
+ end
160
+
161
+ [files, actions, projects]
162
+ end
163
+
164
+ def parse_search(tag, negate)
165
+ required = []
166
+ optional = []
167
+ negated = []
168
+ new_rx = tag[:token].to_s.wildcard_to_rx
169
+
170
+ if negate
171
+ optional.push(new_rx) if tag[:negate]
172
+ required.push(new_rx) if tag[:required] && tag[:negate]
173
+ negated.push(new_rx) unless tag[:negate]
174
+ else
175
+ optional.push(new_rx) unless tag[:negate]
176
+ required.push(new_rx) if tag[:required] && !tag[:negate]
177
+ negated.push(new_rx) if tag[:negate]
178
+ end
179
+
180
+ [optional, required, negated]
181
+ end
182
+ end
183
+ end
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.2.35'
2
+ VERSION = '1.2.38'
3
3
  end
data/lib/na.rb CHANGED
@@ -13,7 +13,11 @@ require 'na/hash'
13
13
  require 'na/colors'
14
14
  require 'na/string'
15
15
  require 'na/array'
16
+ require 'na/theme'
17
+ require 'na/todo'
18
+ require 'na/actions'
16
19
  require 'na/project'
17
20
  require 'na/action'
21
+ require 'na/editor'
18
22
  require 'na/next_action'
19
23
  require 'na/prompt'
data/src/_README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  _If you're one of the rare people like me who find this useful, feel free to
10
10
  [buy me some coffee][donate]._
11
11
 
12
- The current version of `na` is <!--VER-->1.2.34<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.2.37<!--END VER-->.
13
13
 
14
14
  `na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder.
15
15
 
@@ -209,6 +209,50 @@ See the help output for a list of all available actions.
209
209
  @cli(bundle exec bin/na help update)
210
210
  ```
211
211
 
212
+ ##### changelog
213
+
214
+ View recent changes with `na changelog` or `na changes`.
215
+
216
+ ```
217
+ @cli(bundle exec bin/na help changelog)
218
+ ```
219
+
220
+ ##### complete
221
+
222
+ Mark an action as complete, shortcut for `na update --finish`.
223
+
224
+ ```
225
+ @cli(bundle exec bin/na help complete)
226
+ ```
227
+
228
+ ##### archive
229
+
230
+ Mark an action as complete and move to archive, shortcut for `na update --archive`.
231
+
232
+ ```
233
+ @cli(bundle exec bin/na help archive)
234
+ ```
235
+
236
+ ##### tag
237
+
238
+ Add, remove, or modify tags.
239
+
240
+ Use `na tag TAGNAME --[search|tagged] SEARCH_STRING` to add a tag to matching action (use `--all` to apply to all matching actions). If you use `!TAGNAME` it will remove that tag (regardless of value). To change the value of an existing tag (or add it if it doesn't exist), use `~TAGNAME(NEW VALUE)`.
241
+
242
+ ```
243
+ @cli(bundle exec bin/na help tag)
244
+ ```
245
+
246
+ ##### undo
247
+
248
+ Undoes the last file change resulting from an add or update command. If no argument is given, it undoes whatever the last change in history was. If an argument is provided, it's used to match against the change history, finding a specific file to restore from backup.
249
+
250
+ Only the most recent change can be undone.
251
+
252
+ ```
253
+ @cli(bundle exec bin/na help undo)
254
+ ```
255
+
212
256
  ### Configuration
213
257
 
214
258
  Global options such as todo extension and default next action tag can be stored permanently by using the `na initconfig` command. Run na with the global options you'd like to set, and add `initconfig` at the end of the command. A file will be written to `~/.na.rc`. You can edit this manually, or just update it using the `initconfig --force` command to overwrite it with new settings.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: na
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.35
4
+ version: 1.2.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-30 00:00:00.000000000 Z
11
+ date: 2023-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -222,12 +222,15 @@ files:
222
222
  - bin/commands/saved.rb
223
223
  - bin/commands/tagged.rb
224
224
  - bin/commands/todos.rb
225
+ - bin/commands/undo.rb
225
226
  - bin/commands/update.rb
226
227
  - bin/na
227
228
  - lib/na.rb
228
229
  - lib/na/action.rb
230
+ - lib/na/actions.rb
229
231
  - lib/na/array.rb
230
232
  - lib/na/colors.rb
233
+ - lib/na/editor.rb
231
234
  - lib/na/hash.rb
232
235
  - lib/na/help_monkey_patch.rb
233
236
  - lib/na/next_action.rb
@@ -235,6 +238,8 @@ files:
235
238
  - lib/na/project.rb
236
239
  - lib/na/prompt.rb
237
240
  - lib/na/string.rb
241
+ - lib/na/theme.rb
242
+ - lib/na/todo.rb
238
243
  - lib/na/version.rb
239
244
  - na.gemspec
240
245
  - na.rdoc