na 1.1.12 → 1.1.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da1e1113960a99932feda46ee18cd7a8b9e8d309f479c9e3a6ec323d97db686a
4
- data.tar.gz: 871fcae47831f04efcff94b7034abb70d32cb47b2f504247b94705ad015c191f
3
+ metadata.gz: 2324b729bcc5e20eab3e063427fa5c6480274ea844fe72117e7f25c7e9801764
4
+ data.tar.gz: 68d00ee26f320e14afd0e6a72a072b2cf30c82beaaf7818962b90e7fa0884914
5
5
  SHA512:
6
- metadata.gz: c8e8c92c9d3a3ffa4a00fe8fcdd8ace94681e19f9ac53a3d8b988bcf4bd69d3b032765b4a870e3d9f660cd58b18783e97e8333cdb252164cb60f0062495210b5
7
- data.tar.gz: de5596e85883b893afbe2ca43833fe60f82fd96ab12e1f2506d84d13a90644d5d403b748b6598c9fb4c4d4db3998bdd0e560a1c071f4f7c2946b5f75dd45c728
6
+ metadata.gz: b6b7fd0066ba41b9fa184ab3231cb8d7b68e68526bc380f21f2b12048de1c4a4eae24d2da8e47abe95c2389536e90ee32fa35ebdf786570745b8153c72d65e4a
7
+ data.tar.gz: 320264ba4f636f8a68804f7767bd09a4c2105adf684b603514d933003f06f72e2cebb978476a659c2a7ee716fbdaadf7f9fd507c3985ec2dc870162da2751414
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ### 1.1.14
2
+
3
+ 2022-10-06 12:30
4
+
5
+ #### IMPROVED
6
+
7
+ - Code cleanup
8
+ - Highlight search terms in results
9
+
10
+ #### FIXED
11
+
12
+ - Multiple search terms overriding each other
13
+
14
+ ### 1.1.13
15
+
16
+ 2022-10-06 06:28
17
+
18
+ #### IMPROVED
19
+
20
+ - When specifying arguments to `next`, allow paths separated by / to do more exact matching
21
+
1
22
  ### 1.1.12
2
23
 
3
24
  2022-10-06 05:42
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.1.12)
4
+ na (1.1.14)
5
5
  gli (~> 2.21.0)
6
6
  tty-reader (~> 0.9, >= 0.9.0)
7
7
  tty-screen (~> 0.8, >= 0.8.1)
data/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 1.1.12
12
+ The current version of `na` is 1.1.14
13
13
  .
14
14
 
15
15
  `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.
@@ -37,7 +37,7 @@ You can list next actions in files in the current directory by typing `na`. By d
37
37
 
38
38
  #### Easy matching
39
39
 
40
- `na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev mark`. It will always look for the shortest match.
40
+ `na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev/mark`. Creat paths by separating with / or :, separate multiple queries with spaces. na will always look for the shortest match for a path.
41
41
 
42
42
  #### Recursion
43
43
 
@@ -152,6 +152,8 @@ EXAMPLES
152
152
 
153
153
  Example: `na find cool feature idea`
154
154
 
155
+ Unless `--exact` is specified, search is tokenized and combined with OR, so `na find cool feature idea` translates to `cool OR feature OR idea`, matching any string that contains any of the words. To make a token required, add a `+` before it (e.g. `cool +feature idea` is `(cool OR idea) AND feature`). Wildcards allowed (`*` and `?`), use `--regex` to interpret the search as a regular expression. Use `-v` to invert the results (display non-matching actions only).
156
+
155
157
  ```
156
158
  NAME
157
159
  find - Find actions matching a search pattern
@@ -229,7 +231,9 @@ EXAMPLES
229
231
 
230
232
  ##### tagged
231
233
 
232
- Example: `na tagged feature +maybe`
234
+ Example: `na tagged feature +maybe`.
235
+
236
+ Separate multiple tags with spaces or commas. By default tags are combined with OR, so actions matching any of the tags listed will be displayed. Use `+` to make a tag required and `!` to negate a tag (only display if the action does _not_ contain the tag). Use `-v` to invert the search and display all actions that _don't_ match.
233
237
 
234
238
  ```
235
239
  NAME
data/bin/na CHANGED
@@ -269,7 +269,7 @@ class App
269
269
  tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
270
270
  else
271
271
  tokens = []
272
- args.each do |arg|
272
+ args.join(' ').split(/ /).each do |arg|
273
273
  m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
274
274
  tokens.push({
275
275
  token: m['tok'],
@@ -285,7 +285,8 @@ class App
285
285
  regex: options[:regex],
286
286
  project: options[:project],
287
287
  require_na: false)
288
- NA.output_actions(actions, depth, files: files)
288
+ regexes = tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
289
+ NA.output_actions(actions, depth, files: files, regexes: regexes)
289
290
  end
290
291
  end
291
292
 
@@ -336,7 +337,8 @@ class App
336
337
  negate: options[:invert],
337
338
  project: options[:project],
338
339
  require_na: false)
339
- NA.output_actions(actions, depth, files: files)
340
+ regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
341
+ NA.output_actions(actions, depth, files: files, regexes: regexes)
340
342
  end
341
343
  end
342
344
 
data/lib/na/action.rb CHANGED
@@ -27,7 +27,7 @@ module NA
27
27
  EOINSPECT
28
28
  end
29
29
 
30
- def pretty(extension: 'taskpaper', template: {})
30
+ def pretty(extension: 'taskpaper', template: {}, regexes: [])
31
31
  default_template = {
32
32
  file: '{xbk}',
33
33
  parent: '{c}',
@@ -67,7 +67,7 @@ module NA
67
67
  NA::Color.template(template[:output].gsub(/%filename/, filename)
68
68
  .gsub(/%project/, project)
69
69
  .gsub(/%parents?/, parents)
70
- .gsub(/%action/, action))
70
+ .gsub(/%action/, action.highlight_search(regexes)))
71
71
  end
72
72
 
73
73
  def tags_match?(any: [], all: [], none: [])
@@ -80,8 +80,10 @@ module NA
80
80
  notify("{by}Task added to {bw}#{file}")
81
81
  end
82
82
 
83
- def output_actions(actions, depth, files: nil)
84
- template = if files&.count.positive?
83
+ def output_actions(actions, depth, files: nil, regexes: [])
84
+ return if files.nil?
85
+
86
+ template = if files.count.positive?
85
87
  if files.count == 1
86
88
  '%parent%action'
87
89
  else
@@ -96,11 +98,10 @@ module NA
96
98
  else
97
99
  '%parent%action'
98
100
  end
99
- if files && @verbose
100
- files.map { |f| notify("{dw}#{f}") }
101
- end
102
101
 
103
- puts actions.map { |action| action.pretty(template: { output: template }) }
102
+ files.map { |f| notify("{dw}#{f}") } if files && @verbose
103
+
104
+ puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes) })
104
105
  end
105
106
 
106
107
  def parse_actions(depth: 1, query: nil, tag: nil, search: nil, negate: false, regex: false, project: nil, require_na: true)
@@ -136,28 +137,19 @@ module NA
136
137
  end
137
138
  else
138
139
  search.each do |t|
139
- new_rx = t[:token].to_s.wildcard_to_rx
140
-
141
- if negate
142
- optional.push(new_rx) if t[:negate]
143
- required.push(new_rx) if t[:required] && t[:negate]
144
- negated.push(new_rx) unless t[:negate]
145
- else
146
- optional.push(new_rx) unless t[:negate]
147
- required.push(new_rx) if t[:required] && !t[:negate]
148
- negated.push(new_rx) if t[:negate]
149
- end
140
+ opt, req, neg = parse_search(t, negate)
141
+ optional.concat(opt)
142
+ required.concat(req)
143
+ negated.concat(neg)
150
144
  end
151
145
  end
152
146
  end
153
147
 
154
- na_tag = "@#{NA.na_tag.sub(/^@/, '')}"
155
-
156
- if query.nil?
157
- files = find_files(depth: depth)
158
- else
159
- files = match_working_dir(query)
160
- end
148
+ files = if query.nil?
149
+ find_files(depth: depth)
150
+ else
151
+ match_working_dir(query)
152
+ end
161
153
 
162
154
  files.each do |file|
163
155
  save_working_dir(File.expand_path(file))
@@ -207,6 +199,40 @@ module NA
207
199
  [files, actions]
208
200
  end
209
201
 
202
+ def edit_file(file: nil, app: nil)
203
+ os_open(file, app: app) if file && File.exist?(file)
204
+ end
205
+
206
+ ##
207
+ ## Platform-agnostic open command
208
+ ##
209
+ ## @param file [String] The file to open
210
+ ##
211
+ def os_open(file, app: nil)
212
+ os = RbConfig::CONFIG['target_os']
213
+ case os
214
+ when /darwin.*/i
215
+ darwin_open(file, app: app)
216
+ when /mingw|mswin/i
217
+ win_open(file)
218
+ else
219
+ linux_open(file)
220
+ end
221
+ end
222
+
223
+ def weed_cache_file
224
+ db_dir = File.expand_path('~/.local/share/na')
225
+ db_file = 'tdlist.txt'
226
+ file = File.join(db_dir, db_file)
227
+ if File.exist?(file)
228
+ dirs = IO.read(file).split("\n")
229
+ dirs.delete_if { |f| !File.exist?(f) }
230
+ File.open(file, 'w') { |f| f.puts dirs.join("\n") }
231
+ end
232
+ end
233
+
234
+ private
235
+
210
236
  ##
211
237
  ## Generate a menu of options and allow user selection
212
238
  ##
@@ -234,6 +260,25 @@ module NA
234
260
  res
235
261
  end
236
262
 
263
+ def parse_search(tag, negate)
264
+ required = []
265
+ optional = []
266
+ negated = []
267
+ new_rx = tag[:token].to_s.wildcard_to_rx
268
+
269
+ if negate
270
+ optional.push(new_rx) if tag[:negate]
271
+ required.push(new_rx) if tag[:required] && tag[:negate]
272
+ negated.push(new_rx) unless tag[:negate]
273
+ else
274
+ optional.push(new_rx) unless tag[:negate]
275
+ required.push(new_rx) if tag[:required] && !tag[:negate]
276
+ negated.push(new_rx) if tag[:negate]
277
+ end
278
+
279
+ [optional, required, negated]
280
+ end
281
+
237
282
  ##
238
283
  ## Get path to database of known todo files
239
284
  ##
@@ -276,7 +321,7 @@ module NA
276
321
  notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
277
322
 
278
323
  dirs = IO.read(file).split("\n")
279
- dirs.delete_if { |d| !d.matches(any: optional, all: required) }
324
+ dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
280
325
  dirs.sort.uniq
281
326
  end
282
327
 
@@ -289,21 +334,6 @@ module NA
289
334
  File.open(file, 'w') { |f| f.puts dirs.join("\n") }
290
335
  end
291
336
 
292
- def weed_cache_file
293
- db_dir = File.expand_path('~/.local/share/na')
294
- db_file = 'tdlist.txt'
295
- file = File.join(db_dir, db_file)
296
- if File.exist?(file)
297
- dirs = IO.read(file).split("\n")
298
- dirs.delete_if { |f| !File.exist?(f) }
299
- File.open(file, 'w') { |f| f.puts dirs.join("\n") }
300
- end
301
- end
302
-
303
- def edit_file(file: nil, app: nil)
304
- os_open(file, app: app) if file && File.exist?(file)
305
- end
306
-
307
337
  def darwin_open(file, app: nil)
308
338
  if app
309
339
  `open -a "#{app}" #{Shellwords.escape(file)}`
@@ -323,22 +353,5 @@ module NA
323
353
  notify('{r}Unable to determine executable for `xdg-open`.')
324
354
  end
325
355
  end
326
-
327
- ##
328
- ## Platform-agnostic open command
329
- ##
330
- ## @param file [String] The file to open
331
- ##
332
- def os_open(file, app: nil)
333
- os = RbConfig::CONFIG['target_os']
334
- case os
335
- when /darwin.*/i
336
- darwin_open(file, app: app)
337
- when /mingw|mswin/i
338
- win_open(file)
339
- else
340
- linux_open(file)
341
- end
342
- end
343
356
  end
344
357
  end
data/lib/na/string.rb CHANGED
@@ -5,9 +5,7 @@ class ::String
5
5
  prefix = match(/(^[ \t]+)/)
6
6
  return 0 if prefix.nil?
7
7
 
8
- tabs = prefix[1].gsub(/ /, "\t").scan(/\t/).count
9
-
10
- tabs
8
+ prefix[1].gsub(/ /, "\t").scan(/\t/).count
11
9
  end
12
10
 
13
11
  ##
@@ -21,13 +19,65 @@ class ::String
21
19
  tag_color = NA::Color.template(color)
22
20
  paren_color = NA::Color.template(parens)
23
21
  value_color = NA::Color.template(value)
24
- gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/, "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
22
+ gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/,
23
+ "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
24
+ end
25
+
26
+ def highlight_search(regexes, color: '{y}')
27
+ string = dup
28
+ color = NA::Color.template(color)
29
+ regexes.each do |rx|
30
+ next if rx.nil?
31
+
32
+ string.gsub!(/(#{rx.wildcard_to_rx})/i, "#{color}\\1#{NA::Color.template('{xg}')}")
33
+ end
34
+ string
35
+ end
36
+
37
+ # Returns the last escape sequence from a string.
38
+ #
39
+ # Actually returns all escape codes, with the assumption
40
+ # that the result of inserting them will generate the
41
+ # same color as was set at the end of the string.
42
+ # Because you can send modifiers like dark and bold
43
+ # separate from color codes, only using the last code
44
+ # may not render the same style.
45
+ #
46
+ # @return [String] All escape codes in string
47
+ #
48
+ def last_color
49
+ scan(/\e\[[\d;]+m/).join('').gsub(/\e\[0m/, '')
50
+ end
51
+
52
+ def dir_to_rx
53
+ "#{split(%r{[/:]}).join('.*?/.*?')}[^/]+$"
54
+ end
55
+
56
+ def dir_matches(any: [], all: [])
57
+ matches_any(any.map(&:dir_to_rx)) && matches_all(all.map(&:dir_to_rx))
25
58
  end
26
59
 
27
60
  def matches(any: [], all: [], none: [])
28
61
  matches_any(any) && matches_all(all) && matches_none(none)
29
62
  end
30
63
 
64
+ def wildcard_to_rx
65
+ gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
66
+ end
67
+
68
+ def cap_first!
69
+ replace cap_first
70
+ end
71
+
72
+ def cap_first
73
+ sub(/^([a-z])(.*)$/) do
74
+ m = Regexp.last_match
75
+ m[1].upcase << m[2]
76
+ end
77
+ end
78
+
79
+ private
80
+
31
81
  def matches_none(regexes)
32
82
  regexes.each do |rx|
33
83
  return false if match(Regexp.new(rx, Regexp::IGNORECASE))
@@ -48,19 +98,4 @@ class ::String
48
98
  end
49
99
  true
50
100
  end
51
-
52
- def wildcard_to_rx
53
- gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
54
- end
55
-
56
- def cap_first!
57
- replace cap_first
58
- end
59
-
60
- def cap_first
61
- sub(/^([a-z])(.*)$/) do
62
- m = Regexp.last_match
63
- m[1].upcase << m[2]
64
- end
65
- end
66
101
  end
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.1.12'
2
+ VERSION = '1.1.14'
3
3
  end
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.1.11<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.1.13<!--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
 
@@ -36,7 +36,7 @@ You can list next actions in files in the current directory by typing `na`. By d
36
36
 
37
37
  #### Easy matching
38
38
 
39
- `na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev mark`. It will always look for the shortest match.
39
+ `na` features intelligent project matching. Every time it locates a todo file, it adds the project to the database. Once a project is recorded, you can list its actions by using any portion of the parent directories or file names. If your project is in `~/Sites/dev/markedapp`, you could quickly list its next actions by typing `na next dev/mark`. Creat paths by separating with / or :, separate multiple queries with spaces. na will always look for the shortest match for a path.
40
40
 
41
41
  #### Recursion
42
42
 
@@ -151,6 +151,8 @@ EXAMPLES
151
151
 
152
152
  Example: `na find cool feature idea`
153
153
 
154
+ Unless `--exact` is specified, search is tokenized and combined with OR, so `na find cool feature idea` translates to `cool OR feature OR idea`, matching any string that contains any of the words. To make a token required, add a `+` before it (e.g. `cool +feature idea` is `(cool OR idea) AND feature`). Wildcards allowed (`*` and `?`), use `--regex` to interpret the search as a regular expression. Use `-v` to invert the results (display non-matching actions only).
155
+
154
156
  ```
155
157
  NAME
156
158
  find - Find actions matching a search pattern
@@ -228,7 +230,9 @@ EXAMPLES
228
230
 
229
231
  ##### tagged
230
232
 
231
- Example: `na tagged feature +maybe`
233
+ Example: `na tagged feature +maybe`.
234
+
235
+ Separate multiple tags with spaces or commas. By default tags are combined with OR, so actions matching any of the tags listed will be displayed. Use `+` to make a tag required and `!` to negate a tag (only display if the action does _not_ contain the tag). Use `-v` to invert the search and display all actions that _don't_ match.
232
236
 
233
237
  ```
234
238
  NAME
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: na
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.12
4
+ version: 1.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra