na 1.2.84 → 1.2.85
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 +4 -4
- data/.rubocop_todo.yml +6 -11
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/lib/na/action.rb +11 -1
- data/lib/na/array.rb +9 -0
- data/lib/na/benchmark.rb +8 -0
- data/lib/na/colors.rb +11 -4
- data/lib/na/editor.rb +9 -0
- data/lib/na/hash.rb +9 -0
- data/lib/na/help_monkey_patch.rb +10 -1
- data/lib/na/next_action.rb +45 -2
- data/lib/na/project.rb +8 -0
- data/lib/na/string.rb +45 -10
- data/lib/na/theme.rb +8 -0
- data/lib/na/todo.rb +8 -0
- data/lib/na/version.rb +5 -1
- data/src/_README.md +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da5eb39ed44c356c713172455a60fe197de3d1d1f00dc2eedadeb24fe40c9dde
|
|
4
|
+
data.tar.gz: 5e58a4e4c17fac35a091b3cc49e7dca7aca06289b1fbc897be752bb4cdb93817
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 053e5636721b5c9544e22099840ef279462cbba96bd8f561c6a37c3f5b5453d7872bf549da8e8f78009f2c0a417805711ee3c7eebe2dfdf05538bbad84f7aafc
|
|
7
|
+
data.tar.gz: 4626f2c6808056d9de394bd1eec109c0c56a8622015157ecc95bdf555523729254735550a775ae4542b5c20023d7bfc152152db0cf042661e07e5ec6ef1bc9ef
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2025-10-
|
|
3
|
+
# on 2025-10-26 12:18:27 UTC using RuboCop version 1.75.7.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -13,12 +13,7 @@ Lint/DuplicateBranch:
|
|
|
13
13
|
- 'lib/na/action.rb'
|
|
14
14
|
- 'lib/na/colors.rb'
|
|
15
15
|
|
|
16
|
-
# Offense count:
|
|
17
|
-
Lint/SelfAssignment:
|
|
18
|
-
Exclude:
|
|
19
|
-
- 'lib/na/string.rb'
|
|
20
|
-
|
|
21
|
-
# Offense count: 1
|
|
16
|
+
# Offense count: 2
|
|
22
17
|
# This cop supports safe autocorrection (--autocorrect).
|
|
23
18
|
# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
|
|
24
19
|
# NotImplementedExceptions: NotImplementedError
|
|
@@ -40,9 +35,9 @@ Metrics/BlockLength:
|
|
|
40
35
|
# Offense count: 4
|
|
41
36
|
# Configuration parameters: CountComments, CountAsOne.
|
|
42
37
|
Metrics/ClassLength:
|
|
43
|
-
Max:
|
|
38
|
+
Max: 763
|
|
44
39
|
|
|
45
|
-
# Offense count:
|
|
40
|
+
# Offense count: 23
|
|
46
41
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
47
42
|
Metrics/CyclomaticComplexity:
|
|
48
43
|
Max: 66
|
|
@@ -55,14 +50,14 @@ Metrics/MethodLength:
|
|
|
55
50
|
# Offense count: 2
|
|
56
51
|
# Configuration parameters: CountComments, CountAsOne.
|
|
57
52
|
Metrics/ModuleLength:
|
|
58
|
-
Max:
|
|
53
|
+
Max: 765
|
|
59
54
|
|
|
60
55
|
# Offense count: 4
|
|
61
56
|
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
62
57
|
Metrics/ParameterLists:
|
|
63
58
|
Max: 19
|
|
64
59
|
|
|
65
|
-
# Offense count:
|
|
60
|
+
# Offense count: 22
|
|
66
61
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
67
62
|
Metrics/PerceivedComplexity:
|
|
68
63
|
Max: 76
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
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.2.
|
|
12
|
+
The current version of `na` is 1.2.85.
|
|
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
|
|
|
@@ -76,7 +76,7 @@ SYNOPSIS
|
|
|
76
76
|
na [global options] command [command options] [arguments...]
|
|
77
77
|
|
|
78
78
|
VERSION
|
|
79
|
-
1.2.
|
|
79
|
+
1.2.85
|
|
80
80
|
|
|
81
81
|
GLOBAL OPTIONS
|
|
82
82
|
-a, --add - Add a next action (deprecated, for backwards compatibility)
|
data/lib/na/action.rb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NA
|
|
4
|
+
# Represents a single actionable item in a todo file, with tags, notes, and project context.
|
|
5
|
+
#
|
|
6
|
+
# @example Create a new action
|
|
7
|
+
# action = NA::Action.new('todo.txt', 'Inbox', [], '- Buy milk', 1)
|
|
4
8
|
class Action < Hash
|
|
5
9
|
attr_reader :file, :project, :tags, :line
|
|
6
10
|
attr_accessor :parent, :action, :note
|
|
7
11
|
|
|
12
|
+
# @example
|
|
13
|
+
# action = NA::Action.new('todo.txt', 'Inbox', [], '- Buy milk', 1)
|
|
8
14
|
def initialize(file, project, parent, action, idx, note = [])
|
|
9
15
|
super()
|
|
10
16
|
|
|
@@ -25,6 +31,8 @@ module NA
|
|
|
25
31
|
# @param remove_tag [Array<String>] Tags to remove
|
|
26
32
|
# @param note [Array<String>] Notes to set
|
|
27
33
|
# @return [void]
|
|
34
|
+
# @example
|
|
35
|
+
# action.process(priority: 5, finish: true, add_tag: ['urgent'], remove_tag: ['waiting'], note: ['Call Bob'])
|
|
28
36
|
def process(priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
|
|
29
37
|
string = @action.dup
|
|
30
38
|
|
|
@@ -54,6 +62,8 @@ module NA
|
|
|
54
62
|
# String representation of the action
|
|
55
63
|
#
|
|
56
64
|
# @return [String]
|
|
65
|
+
# @example
|
|
66
|
+
# action.to_s #=> "{ project: 'Inbox', ... }"
|
|
57
67
|
def to_s
|
|
58
68
|
note = if @note.count.positive?
|
|
59
69
|
"\n#{@note.join("\n")}"
|
|
@@ -127,7 +137,7 @@ module NA
|
|
|
127
137
|
|
|
128
138
|
# Create the source filename string (optimized)
|
|
129
139
|
filename = if needs_filename
|
|
130
|
-
path = @file.sub(%r{^\./}, '').sub(/#{Dir.home}/, '~')
|
|
140
|
+
path = @file ? @file.sub(%r{^\./}, '').sub(/#{Dir.home}/, '~') : ''
|
|
131
141
|
if File.dirname(path) == '.'
|
|
132
142
|
fname = NA.include_ext ? File.basename(path) : File.basename(path, ".#{extension}")
|
|
133
143
|
fname = "./#{fname}" if NA.show_cwd_indicator
|
data/lib/na/array.rb
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
##
|
|
4
|
+
# Extensions to Ruby's Array class for todo management and formatting.
|
|
5
|
+
#
|
|
6
|
+
# @example Remove bad elements from an array
|
|
7
|
+
# ['foo', '', nil, 0, false, 'bar'].remove_bad #=> ['foo', 'bar']
|
|
3
8
|
class ::Array
|
|
4
9
|
# Like Array#compact -- removes nil items, but also
|
|
5
10
|
# removes empty strings, zero or negative numbers and FalseClass items
|
|
6
11
|
#
|
|
7
12
|
# @return [Array] Array without "bad" elements
|
|
13
|
+
# @example
|
|
14
|
+
# ['foo', '', nil, 0, false, 'bar'].remove_bad #=> ['foo', 'bar']
|
|
8
15
|
def remove_bad
|
|
9
16
|
compact.map { |x| x.is_a?(String) ? x.strip : x }.select(&:good?)
|
|
10
17
|
end
|
|
@@ -15,6 +22,8 @@ class ::Array
|
|
|
15
22
|
# @param indent [Integer] Indentation spaces
|
|
16
23
|
# @param color [String] Color code to apply
|
|
17
24
|
# @return [Array, String] Wrapped and colorized lines
|
|
25
|
+
# @example
|
|
26
|
+
# ['foo', 'bar'].wrap(80, 2, '{g}') #=> "\n{g} • foo{x}\n{g} • bar{x}"
|
|
18
27
|
def wrap(width, indent, color)
|
|
19
28
|
return map { |l| "#{color} #{l.wrap(width, 2)}" } if width < 60
|
|
20
29
|
|
data/lib/na/benchmark.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NA
|
|
4
|
+
# Provides benchmarking utilities for measuring code execution time.
|
|
5
|
+
#
|
|
6
|
+
# @example Measure a block of code
|
|
7
|
+
# NA::Benchmark.measure('sleep') { sleep(1) }
|
|
4
8
|
module Benchmark
|
|
5
9
|
class << self
|
|
6
10
|
attr_accessor :enabled, :timings
|
|
@@ -18,6 +22,8 @@ module NA
|
|
|
18
22
|
#
|
|
19
23
|
# @param label [String] Label for the measurement
|
|
20
24
|
# @return [Object] Result of the block
|
|
25
|
+
# @example
|
|
26
|
+
# NA::Benchmark.measure('sleep') { sleep(1) }
|
|
21
27
|
def measure(label)
|
|
22
28
|
return yield unless @enabled
|
|
23
29
|
|
|
@@ -31,6 +37,8 @@ module NA
|
|
|
31
37
|
# Output a performance report to STDERR
|
|
32
38
|
#
|
|
33
39
|
# @return [void]
|
|
40
|
+
# @example
|
|
41
|
+
# NA::Benchmark.report
|
|
34
42
|
def report
|
|
35
43
|
return unless @enabled
|
|
36
44
|
|
data/lib/na/colors.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
# Cribbed from <https://github.com/flori/term-ansicolor>
|
|
4
4
|
module NA
|
|
5
5
|
# Terminal output color functions.
|
|
6
|
+
#
|
|
7
|
+
# @example Clear the color template cache
|
|
8
|
+
# NA::Color.clear_template_cache
|
|
6
9
|
module Color
|
|
7
10
|
# Regexp to match excape sequences
|
|
8
11
|
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
|
|
@@ -199,6 +202,10 @@ module NA
|
|
|
199
202
|
@template_cache ||= {}
|
|
200
203
|
end
|
|
201
204
|
|
|
205
|
+
# Clears the cached compiled color templates.
|
|
206
|
+
# @return [void]
|
|
207
|
+
# @example
|
|
208
|
+
# NA::Color.clear_template_cache
|
|
202
209
|
def clear_template_cache
|
|
203
210
|
@template_cache = {}
|
|
204
211
|
end
|
|
@@ -231,15 +238,15 @@ module NA
|
|
|
231
238
|
# i: italic, x: reset (remove background, color,
|
|
232
239
|
# emphasis)
|
|
233
240
|
#
|
|
234
|
-
# Also accepts {#RGB} and {#RRGGBB} strings. Put a b
|
|
241
|
+
# Also accepts `{#RGB}` and `{#RRGGBB}` strings. Put a b
|
|
235
242
|
# before the hash to make it a background color
|
|
236
243
|
#
|
|
237
244
|
# @example Convert a templated string
|
|
238
|
-
# Color.template('{Rwb}Warning
|
|
239
|
-
# little {g}ill{x}')
|
|
245
|
+
# Color.template('\\{Rwb\\}Warning:\\{x\\} \\{w\}\you look a
|
|
246
|
+
# little \\{g\\}ill\\{x\\}')
|
|
240
247
|
#
|
|
241
248
|
# @example Convert using RGB colors
|
|
242
|
-
# Color.template('{#f0a}This is an RGB color')
|
|
249
|
+
# Color.template('\\{#f0a\\}This is an RGB color')
|
|
243
250
|
#
|
|
244
251
|
# @param input [String, Array] The template
|
|
245
252
|
# string. If this is an array, the
|
data/lib/na/editor.rb
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
require 'English'
|
|
4
4
|
|
|
5
5
|
module NA
|
|
6
|
+
# Provides editor selection and argument helpers for launching text editors.
|
|
6
7
|
module Editor
|
|
7
8
|
class << self
|
|
9
|
+
# Returns the default editor command, checking environment variables and available editors.
|
|
10
|
+
# @param prefer_git_editor [Boolean] Prefer GIT_EDITOR over EDITOR
|
|
11
|
+
# @return [String, nil] Editor command or nil if not found
|
|
8
12
|
def default_editor(prefer_git_editor: true)
|
|
9
13
|
editor ||= if prefer_git_editor
|
|
10
14
|
ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV.fetch('EDITOR', nil)
|
|
@@ -29,10 +33,15 @@ module NA
|
|
|
29
33
|
nil
|
|
30
34
|
end
|
|
31
35
|
|
|
36
|
+
# Returns the default editor command with its arguments.
|
|
37
|
+
# @return [String] Editor command with arguments
|
|
32
38
|
def editor_with_args
|
|
33
39
|
args_for_editor(default_editor)
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
# Returns the editor command with appropriate arguments for file opening.
|
|
43
|
+
# @param editor [String] Editor command
|
|
44
|
+
# @return [String] Editor command with arguments
|
|
36
45
|
def args_for_editor(editor)
|
|
37
46
|
return editor if editor =~ /-\S/
|
|
38
47
|
|
data/lib/na/hash.rb
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
##
|
|
4
|
+
# Extensions to Ruby's Hash class for symbolizing keys and deep freezing values.
|
|
5
|
+
#
|
|
6
|
+
# @example Symbolize all keys in a hash
|
|
7
|
+
# { 'foo' => 1, 'bar' => { 'baz' => 2 } }.symbolize_keys #=> { :foo => 1, :bar => { :baz => 2 } }
|
|
3
8
|
class ::Hash
|
|
4
9
|
# Convert all keys in the hash to symbols recursively
|
|
5
10
|
#
|
|
6
11
|
# @return [Hash] Hash with symbolized keys
|
|
12
|
+
# @example
|
|
13
|
+
# { 'foo' => 1, 'bar' => { 'baz' => 2 } }.symbolize_keys #=> { :foo => 1, :bar => { :baz => 2 } }
|
|
7
14
|
def symbolize_keys
|
|
8
15
|
each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
|
|
9
16
|
end
|
|
@@ -12,6 +19,8 @@ class ::Hash
|
|
|
12
19
|
# Freeze all values in a hash
|
|
13
20
|
#
|
|
14
21
|
# @return Hash with all values frozen
|
|
22
|
+
# @example
|
|
23
|
+
# { foo: { bar: 'baz' } }.deep_freeze
|
|
15
24
|
def deep_freeze
|
|
16
25
|
chilled = {}
|
|
17
26
|
each do |k, v|
|
data/lib/na/help_monkey_patch.rb
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
##
|
|
4
|
+
# Monkeypatches for GLI CLI framework to support paginated help output.
|
|
5
|
+
#
|
|
6
|
+
# @example Show help for a command
|
|
7
|
+
# GLI::Commands::Help.new.show_help({}, {}, [], $stdout, $stderr)
|
|
3
8
|
module GLI
|
|
9
|
+
##
|
|
10
|
+
# Command extensions for GLI CLI framework.
|
|
4
11
|
module Commands
|
|
5
12
|
# Help Command Monkeypatch for paginated output
|
|
6
13
|
class Help < Command
|
|
7
14
|
# Show help output for GLI commands with paginated output
|
|
8
15
|
#
|
|
9
|
-
# @param
|
|
16
|
+
# @param _global_options [Hash] Global CLI options
|
|
10
17
|
# @param options [Hash] Command-specific options
|
|
11
18
|
# @param arguments [Array] Command arguments
|
|
12
19
|
# @param out [IO] Output stream
|
|
13
20
|
# @param error [IO] Error stream
|
|
14
21
|
# @return [void]
|
|
22
|
+
# @example
|
|
23
|
+
# GLI::Commands::Help.new.show_help({}, {}, [], $stdout, $stderr)
|
|
15
24
|
def show_help(_global_options, options, arguments, out, error)
|
|
16
25
|
NA::Pager.paginate = true
|
|
17
26
|
|
data/lib/na/next_action.rb
CHANGED
|
@@ -8,10 +8,17 @@ module NA
|
|
|
8
8
|
attr_accessor :verbose, :extension, :include_ext, :na_tag, :command_line, :command, :globals, :global_file,
|
|
9
9
|
:cwd_is, :cwd, :stdin, :show_cwd_indicator
|
|
10
10
|
|
|
11
|
+
# Returns the current theme hash for color and style settings.
|
|
12
|
+
# @return [Hash] The theme settings
|
|
11
13
|
def theme
|
|
12
14
|
@theme ||= NA::Theme.load_theme
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
# Print a message to stderr, optionally exit or debug.
|
|
18
|
+
# @param msg [String] The message to print
|
|
19
|
+
# @param exit_code [Integer, Boolean] Exit code or false for no exit
|
|
20
|
+
# @param debug [Boolean] Only print if verbose
|
|
21
|
+
# @return [void]
|
|
15
22
|
def notify(msg, exit_code: false, debug: false)
|
|
16
23
|
return if debug && !NA.verbose
|
|
17
24
|
|
|
@@ -23,6 +30,8 @@ module NA
|
|
|
23
30
|
Process.exit exit_code if exit_code
|
|
24
31
|
end
|
|
25
32
|
|
|
33
|
+
# Returns a map of priority levels to numeric values.
|
|
34
|
+
# @return [Hash{String=>Integer}] Priority mapping
|
|
26
35
|
def priority_map
|
|
27
36
|
{
|
|
28
37
|
'h' => 5,
|
|
@@ -128,6 +137,11 @@ module NA
|
|
|
128
137
|
end
|
|
129
138
|
end
|
|
130
139
|
|
|
140
|
+
# Shift project indices after a given index by a length.
|
|
141
|
+
# @param projects [Array<NA::Project>] Projects to shift
|
|
142
|
+
# @param idx [Integer] Index after which to shift
|
|
143
|
+
# @param length [Integer] Amount to shift
|
|
144
|
+
# @return [Array<NA::Project>] Shifted projects
|
|
131
145
|
def shift_index_after(projects, idx, length = 1)
|
|
132
146
|
projects.map do |proj|
|
|
133
147
|
proj.line = proj.line - length if proj.line > idx
|
|
@@ -194,9 +208,8 @@ module NA
|
|
|
194
208
|
#
|
|
195
209
|
# @param target [String] Path to the todo file
|
|
196
210
|
# @param project [String] Project name
|
|
197
|
-
# @param projects [Array<NA::Project>] Existing projects
|
|
198
211
|
# @return [NA::Project] The new project
|
|
199
|
-
def insert_project(target, project
|
|
212
|
+
def insert_project(target, project)
|
|
200
213
|
path = project.split(%r{[:/]})
|
|
201
214
|
todo = NA::Todo.new(file_path: target)
|
|
202
215
|
built = []
|
|
@@ -602,6 +615,19 @@ module NA
|
|
|
602
615
|
end
|
|
603
616
|
end
|
|
604
617
|
|
|
618
|
+
# Find files matching criteria and containing actions.
|
|
619
|
+
# @param options [Hash] Options for file search
|
|
620
|
+
# @option options [Integer] :depth Search depth
|
|
621
|
+
# @option options [Boolean] :done Include done actions
|
|
622
|
+
# @option options [String] :file_path File path
|
|
623
|
+
# @option options [Boolean] :negate Negate search
|
|
624
|
+
# @option options [Boolean] :hidden Include hidden files
|
|
625
|
+
# @option options [String] :project Project name
|
|
626
|
+
# @option options [String] :query Query string
|
|
627
|
+
# @option options [Boolean] :regex Use regex
|
|
628
|
+
# @option options [String] :search Search string
|
|
629
|
+
# @option options [String] :tag Tag to filter
|
|
630
|
+
# @return [Array<String>] Matching files
|
|
605
631
|
def find_files_matching(options = {})
|
|
606
632
|
defaults = {
|
|
607
633
|
depth: 1,
|
|
@@ -690,6 +716,10 @@ module NA
|
|
|
690
716
|
end
|
|
691
717
|
end
|
|
692
718
|
|
|
719
|
+
# Find a directory with an exact match from a list.
|
|
720
|
+
# @param dirs [Array<String>] Directories to search
|
|
721
|
+
# @param search [Array<Hash>] Search tokens
|
|
722
|
+
# @return [Array<String>] Matching directories
|
|
693
723
|
def find_exact_dir(dirs, search)
|
|
694
724
|
terms = search.filter { |s| !s[:negate] }.map { |t| t[:token] }.join(' ')
|
|
695
725
|
out = dirs
|
|
@@ -878,6 +908,12 @@ module NA
|
|
|
878
908
|
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
|
879
909
|
end
|
|
880
910
|
|
|
911
|
+
# List projects in a todo file or matching query.
|
|
912
|
+
# @param query [Array] Query tokens
|
|
913
|
+
# @param file_path [String, nil] File path
|
|
914
|
+
# @param depth [Integer] Search depth
|
|
915
|
+
# @param paths [Boolean] Show full paths
|
|
916
|
+
# @return [void]
|
|
881
917
|
def list_projects(query: [], file_path: nil, depth: 1, paths: true)
|
|
882
918
|
files = if NA.global_file
|
|
883
919
|
[NA.global_file]
|
|
@@ -906,6 +942,9 @@ module NA
|
|
|
906
942
|
end
|
|
907
943
|
end
|
|
908
944
|
|
|
945
|
+
# List todo files matching a query.
|
|
946
|
+
# @param query [Array] Query tokens
|
|
947
|
+
# @return [void]
|
|
909
948
|
def list_todos(query: [])
|
|
910
949
|
dirs = if query
|
|
911
950
|
match_working_dir(query, distance: 2, require_last: false)
|
|
@@ -922,6 +961,10 @@ module NA
|
|
|
922
961
|
puts NA::Color.template(dirs.join("\n"))
|
|
923
962
|
end
|
|
924
963
|
|
|
964
|
+
# Save a search definition to the database.
|
|
965
|
+
# @param title [String] The search title
|
|
966
|
+
# @param search [String] The search string
|
|
967
|
+
# @return [void]
|
|
925
968
|
def save_search(title, search)
|
|
926
969
|
file = database_path(file: 'saved_searches.yml')
|
|
927
970
|
searches = load_searches
|
data/lib/na/project.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NA
|
|
4
|
+
# Represents a project section in a todo file, with indentation and line tracking.
|
|
5
|
+
#
|
|
6
|
+
# @example Create a new project
|
|
7
|
+
# project = NA::Project.new('Inbox', 0, 1, 5)
|
|
4
8
|
class Project < Hash
|
|
5
9
|
attr_accessor :project, :indent, :line, :last_line
|
|
6
10
|
|
|
@@ -11,6 +15,8 @@ module NA
|
|
|
11
15
|
# @param line [Integer] Starting line number
|
|
12
16
|
# @param last_line [Integer] Ending line number
|
|
13
17
|
# @return [void]
|
|
18
|
+
# @example
|
|
19
|
+
# project = NA::Project.new('Inbox', 0, 1, 5)
|
|
14
20
|
def initialize(project, indent = 0, line = 0, last_line = 0)
|
|
15
21
|
super()
|
|
16
22
|
@project = project
|
|
@@ -22,6 +28,8 @@ module NA
|
|
|
22
28
|
# String representation of the project
|
|
23
29
|
#
|
|
24
30
|
# @return [String]
|
|
31
|
+
# @example
|
|
32
|
+
# project.to_s #=> "{ project: 'Inbox', ... }"
|
|
25
33
|
def to_s
|
|
26
34
|
{ project: @project, indent: @indent, line: @line, last_line: @last_line }.to_s
|
|
27
35
|
end
|
data/lib/na/string.rb
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Special handling for nil strings
|
|
4
|
+
class ::NilClass
|
|
5
|
+
# Always returns an empty string for nil.
|
|
6
|
+
# @return [String]
|
|
7
|
+
def highlight_filename
|
|
8
|
+
''
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Always returns an empty string for nil.
|
|
12
|
+
# @param max [Integer] Maximum allowed length
|
|
13
|
+
# @return [String]
|
|
14
|
+
def trunc_middle(max)
|
|
15
|
+
''
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Matches day names (e.g., mon, tue, wednesday)
|
|
20
|
+
# @return [Regexp]
|
|
3
21
|
REGEX_DAY = /^(mon|tue|wed|thur?|fri|sat|sun)(\w+(day)?)?$/i.freeze
|
|
22
|
+
|
|
23
|
+
# Matches clock times (e.g., 12:30 pm, midnight)
|
|
24
|
+
# @return [String]
|
|
4
25
|
REGEX_CLOCK = '(?:\d{1,2}+(?::\d{1,2}+)?(?: *(?:am|pm))?|midnight|noon)'
|
|
26
|
+
|
|
27
|
+
# Matches time strings using REGEX_CLOCK
|
|
28
|
+
# @return [Regexp]
|
|
5
29
|
REGEX_TIME = /^#{REGEX_CLOCK}$/i.freeze
|
|
6
30
|
|
|
7
31
|
# String helpers
|
|
8
32
|
class ::String
|
|
9
33
|
# Insert a comment character at the start of every line
|
|
10
34
|
# @param char [String] The character to insert (default #)
|
|
11
|
-
def comment(
|
|
12
|
-
split("\n").map { |l| "# #{l}" }.join("\n")
|
|
35
|
+
def comment(char = '#')
|
|
36
|
+
split("\n").map { |l| "#{char} #{l}" }.join("\n")
|
|
13
37
|
end
|
|
14
38
|
|
|
15
39
|
# Tests if object is nil or empty
|
|
@@ -25,6 +49,10 @@ class ::String
|
|
|
25
49
|
line =~ /^#/ || line.strip.empty?
|
|
26
50
|
end
|
|
27
51
|
|
|
52
|
+
# Returns the contents of the file, or raises if missing.
|
|
53
|
+
# Handles directories and NA extension.
|
|
54
|
+
# @return [String] Contents of the file
|
|
55
|
+
# @raise [RuntimeError] if the file does not exist
|
|
28
56
|
def read_file
|
|
29
57
|
file = File.expand_path(self)
|
|
30
58
|
raise "Missing file #{file}" unless File.exist?(file)
|
|
@@ -64,11 +92,15 @@ class ::String
|
|
|
64
92
|
!action? && self =~ /:( +@\S+(\([^)]*\))?)*$/
|
|
65
93
|
end
|
|
66
94
|
|
|
95
|
+
# Returns the project name if matched, otherwise nil.
|
|
96
|
+
# @return [String, nil]
|
|
67
97
|
def project
|
|
68
98
|
m = match(/^([ \t]*)([^\-][^@:]*?): *(@\S+ *)*$/)
|
|
69
99
|
m ? m[2] : nil
|
|
70
100
|
end
|
|
71
101
|
|
|
102
|
+
# Returns the action text with leading dash and whitespace removed.
|
|
103
|
+
# @return [String]
|
|
72
104
|
def action
|
|
73
105
|
sub(/^[ \t]*- /, '')
|
|
74
106
|
end
|
|
@@ -84,6 +116,8 @@ class ::String
|
|
|
84
116
|
# Colorize the dirname and filename of a path
|
|
85
117
|
# @return [String] Colorized string
|
|
86
118
|
def highlight_filename
|
|
119
|
+
return '' if nil?
|
|
120
|
+
|
|
87
121
|
dir = File.dirname(self).shorten_path.trunc_middle(TTY::Screen.columns / 3)
|
|
88
122
|
file = NA.include_ext ? File.basename(self) : File.basename(self, ".#{NA.extension}")
|
|
89
123
|
"#{NA.theme[:dirname]}#{dir}/#{NA.theme[:filename]}#{file}{x}"
|
|
@@ -135,14 +169,15 @@ class ::String
|
|
|
135
169
|
# @param max [Integer] Maximum allowed length of the string
|
|
136
170
|
# @return [String] Truncated string with middle replaced if necessary
|
|
137
171
|
def trunc_middle(max)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
172
|
+
return '' if nil?
|
|
173
|
+
|
|
174
|
+
return self unless length > max
|
|
175
|
+
|
|
176
|
+
half = (max / 2).floor - 3
|
|
177
|
+
cs = chars
|
|
178
|
+
pre = cs.slice(0, half)
|
|
179
|
+
post = cs.reverse.slice(0, half).reverse
|
|
180
|
+
"#{pre.join}[...]#{post.join}"
|
|
146
181
|
end
|
|
147
182
|
|
|
148
183
|
# Wrap the string to a given width, indenting each line and preserving tag formatting.
|
data/lib/na/theme.rb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NA
|
|
4
|
+
# Provides theme and color template helpers for todo CLI output.
|
|
5
|
+
#
|
|
6
|
+
# @example Get theme help text
|
|
7
|
+
# NA::Theme.template_help
|
|
4
8
|
module Theme
|
|
5
9
|
class << self
|
|
6
10
|
# Returns a help string describing available color placeholders for themes.
|
|
7
11
|
# @return [String] Help text for theme placeholders
|
|
12
|
+
# @example
|
|
13
|
+
# NA::Theme.template_help
|
|
8
14
|
def template_help
|
|
9
15
|
<<~EOHELP
|
|
10
16
|
Use {X} placeholders to apply colors. Available colors are:
|
|
@@ -28,6 +34,8 @@ module NA
|
|
|
28
34
|
# Writes the help text and theme YAML to the theme file.
|
|
29
35
|
# @param template [Hash] Additional theme settings to merge
|
|
30
36
|
# @return [Hash] The merged theme configuration
|
|
37
|
+
# @example
|
|
38
|
+
# NA::Theme.load_theme(template: { action: '{r}' })
|
|
31
39
|
def load_theme(template: {})
|
|
32
40
|
NA::Benchmark.measure('Theme.load_theme') do
|
|
33
41
|
# Default colorization, can be overridden with full or partial template variable
|
data/lib/na/todo.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NA
|
|
4
|
+
# Represents a parsed todo file, including actions, projects, and file management.
|
|
5
|
+
#
|
|
6
|
+
# @example Parse a todo file
|
|
7
|
+
# todo = NA::Todo.new(file_path: 'todo.txt')
|
|
4
8
|
class Todo
|
|
5
9
|
attr_accessor :actions, :projects, :files
|
|
6
10
|
|
|
@@ -8,6 +12,8 @@ module NA
|
|
|
8
12
|
#
|
|
9
13
|
# @param options [Hash] Options for parsing todo files
|
|
10
14
|
# @return [void]
|
|
15
|
+
# @example
|
|
16
|
+
# todo = NA::Todo.new(file_path: 'todo.txt')
|
|
11
17
|
def initialize(options = {})
|
|
12
18
|
@files, @actions, @projects = parse(options)
|
|
13
19
|
end
|
|
@@ -26,6 +32,8 @@ module NA
|
|
|
26
32
|
# @option options [Boolean] :require_na Require @na tag
|
|
27
33
|
# @option options [String] :file_path file path to parse
|
|
28
34
|
# @return [Array] files, actions, projects
|
|
35
|
+
# @example
|
|
36
|
+
# files, actions, projects = todo.parse(file_path: 'todo.txt')
|
|
29
37
|
def parse(options)
|
|
30
38
|
NA::Benchmark.measure('Todo.parse') do
|
|
31
39
|
defaults = {
|
data/lib/na/version.rb
CHANGED
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.
|
|
12
|
+
The current version of `na` is <!--VER-->1.2.84<!--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
|
|