na 1.1.12 → 1.1.14

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: 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