na 1.2.94 → 1.2.95

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.
data/bin/commands/next.rb CHANGED
@@ -136,6 +136,39 @@ class App
136
136
  options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
137
137
  end
138
138
 
139
+ # Detect TaskPaper-style @search() syntax in QUERY arguments and delegate
140
+ # directly to the TaskPaper search runner (supports item paths and
141
+ # advanced TaskPaper predicates).
142
+ joined_args = args.join(' ')
143
+ if joined_args =~ /@search\((.+)\)/
144
+ inner = Regexp.last_match(1)
145
+ expr = "@search(#{inner})"
146
+
147
+ file_path = options[:file] ? File.expand_path(options[:file]) : nil
148
+ NA::Pager.paginate = false if options[:omnifocus]
149
+
150
+ NA.run_taskpaper_search(
151
+ expr,
152
+ file: file_path,
153
+ options: {
154
+ depth: depth,
155
+ notes: options[:notes],
156
+ nest: options[:nest],
157
+ omnifocus: options[:omnifocus],
158
+ no_file: options[:no_file],
159
+ times: options[:times],
160
+ human: options[:human],
161
+ search_notes: options[:search_notes],
162
+ invert: false,
163
+ regex: options[:regex],
164
+ project: options[:project],
165
+ done: options[:done],
166
+ require_na: true
167
+ }
168
+ )
169
+ next
170
+ end
171
+
139
172
  if options[:exact] || options[:regex]
140
173
  search = options[:search].join(" ")
141
174
  else
@@ -263,6 +296,13 @@ class App
263
296
 
264
297
  file_path = options[:file] ? File.expand_path(options[:file]) : nil
265
298
 
299
+ # Support TaskPaper-style item paths in --project when value starts with '/'
300
+ project_filter_paths = nil
301
+ if options[:project]&.start_with?('/')
302
+ project_filter_paths = NA.resolve_item_path(path: options[:project], file: file_path, depth: depth)
303
+ options[:project] = nil
304
+ end
305
+
266
306
  todo = NA::Todo.new({ depth: depth,
267
307
  hidden: options[:hidden],
268
308
  done: options[:done],
@@ -279,6 +319,17 @@ class App
279
319
  end
280
320
  NA::Pager.paginate = false if options[:omnifocus]
281
321
 
322
+ # Apply item-path project filters, if any
323
+ if project_filter_paths && project_filter_paths.any?
324
+ todo.actions.delete_if do |a|
325
+ parents = Array(a.parent)
326
+ path = parents.join(':')
327
+ project_filter_paths.none? do |p|
328
+ path =~ /\A#{Regexp.escape(p)}(?::|\z)/i
329
+ end
330
+ end
331
+ end
332
+
282
333
  # If a plugin is specified, transform actions in memory for display only
283
334
  if options[:plugin]
284
335
  NA::Plugins.ensure_plugins_home
@@ -27,29 +27,64 @@ class App
27
27
  NA.edit_searches if options[:edit]
28
28
 
29
29
  if args.empty? && !options[:select]
30
- searches = NA.load_searches
30
+ yaml_searches = NA.load_searches
31
+ taskpaper_searches = NA.load_taskpaper_searches(depth: 1)
31
32
  NA.notify("#{NA.theme[:success]}Saved searches stored in #{NA.database_path(file: 'saved_searches.yml').highlight_filename}")
32
- NA.notify(searches.map { |k, v| "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v}" }.join("\n"))
33
+ lines = yaml_searches.map do |k, v|
34
+ "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v}"
35
+ end
36
+ unless taskpaper_searches.empty?
37
+ lines << "#{NA.theme[:prompt]}TaskPaper saved searches:"
38
+ lines.concat(
39
+ taskpaper_searches.map do |k, v|
40
+ "#{NA.theme[:filename]}#{k}: #{NA.theme[:values]}#{v[:expr]} #{NA.theme[:note]}(#{File.basename(v[:file])})"
41
+ end
42
+ )
43
+ end
44
+ NA.notify(lines.join("\n"))
33
45
  else
34
46
  NA.delete_search(args.join(',').split(/[ ,]/)) if options[:delete]
35
47
 
36
48
  if options[:select]
37
- searches = NA.load_searches
38
- res = NA.choose_from(searches.map { |k, v| "#{NA.theme[:filename]}#{k} #{NA.theme[:value]}(#{v})" }, multiple: true)
49
+ yaml_searches = NA.load_searches
50
+ taskpaper_searches = NA.load_taskpaper_searches(depth: 1)
51
+ combined = {}
52
+ yaml_searches.each { |k, v| combined[k] = { source: :yaml, value: v } }
53
+ taskpaper_searches.each { |k, v| combined[k] ||= { source: :taskpaper, value: v } }
54
+
55
+ res = NA.choose_from(
56
+ combined.map do |k, info|
57
+ val = info[:source] == :yaml ? info[:value] : info[:value][:expr]
58
+ "#{NA.theme[:filename]}#{k} #{NA.theme[:value]}(#{val})"
59
+ end,
60
+ multiple: true
61
+ )
39
62
  NA.notify("#{NA.theme[:error]}Nothing selected", exit_code: 0) if res&.empty?
40
63
  args = res.map { |r| r.match(/(\S+)(?= \()/)[1] }
41
64
  end
42
65
 
43
66
  args.each do |arg|
44
- searches = NA.load_searches
67
+ yaml_searches = NA.load_searches
68
+ taskpaper_searches = NA.load_taskpaper_searches(depth: 1)
69
+ all_keys = (yaml_searches.keys + taskpaper_searches.keys).uniq
45
70
 
46
- keys = searches.keys.delete_if { |k| k !~ /#{arg.wildcard_to_rx}/ }
71
+ keys = all_keys.delete_if { |k| k !~ /#{arg.wildcard_to_rx}/ }
47
72
  NA.notify("#{NA.theme[:error]}Search #{arg} not found", exit_code: 1) if keys.empty?
48
73
 
49
74
  keys.each do |key|
50
75
  NA.notify("#{NA.theme[:prompt]}Saved search #{NA.theme[:filename]}#{key}#{NA.theme[:warning]}:")
51
- cmd = Shellwords.shellsplit(searches[key])
52
- run(cmd)
76
+ if yaml_searches.key?(key)
77
+ value = yaml_searches[key]
78
+ if value.to_s.strip =~ /\A@search\(.+\)\s*\z/
79
+ NA.run_taskpaper_search(value)
80
+ else
81
+ cmd = Shellwords.shellsplit(value)
82
+ run(cmd)
83
+ end
84
+ elsif taskpaper_searches.key?(key)
85
+ info = taskpaper_searches[key]
86
+ NA.run_taskpaper_search(info[:expr], file: info[:file])
87
+ end
53
88
  end
54
89
  end
55
90
  end
data/lib/na/action.rb CHANGED
@@ -422,7 +422,9 @@ module NA
422
422
  return false if keys.empty?
423
423
 
424
424
  key = keys[0]
425
- return true if tag[:comp].nil?
425
+ # If no comparator is provided, treat this as an "existence" check:
426
+ # any value for this tag name is acceptable.
427
+ return true if tag[:comp].nil? || tag[:comp].to_s.empty?
426
428
 
427
429
  tag_val = @tags[key]
428
430
  val = tag[:value]