na 1.2.80 → 1.2.81
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.yml +8 -2
 - data/.rubocop_todo.yml +33 -538
 - data/CHANGELOG.md +19 -0
 - data/Gemfile +2 -0
 - data/Gemfile.lock +27 -10
 - data/README.md +46 -5
 - data/Rakefile +6 -0
 - data/bin/commands/next.rb +4 -0
 - data/bin/commands/scan.rb +84 -0
 - data/bin/commands/update.rb +1 -1
 - data/bin/na +7 -7
 - data/lib/na/action.rb +101 -35
 - data/lib/na/actions.rb +79 -77
 - data/lib/na/array.rb +11 -7
 - data/lib/na/benchmark.rb +21 -9
 - data/lib/na/colors.rb +84 -86
 - data/lib/na/editor.rb +22 -22
 - data/lib/na/hash.rb +32 -9
 - data/lib/na/help_monkey_patch.rb +9 -1
 - data/lib/na/next_action.rb +314 -245
 - data/lib/na/pager.rb +38 -14
 - data/lib/na/project.rb +14 -1
 - data/lib/na/prompt.rb +25 -3
 - data/lib/na/string.rb +90 -130
 - data/lib/na/theme.rb +37 -31
 - data/lib/na/todo.rb +149 -131
 - data/lib/na/version.rb +3 -1
 - data/lib/na.rb +1 -0
 - data/na.gemspec +4 -2
 - data/scripts/generate-fish-completions.rb +18 -21
 - data/src/_README.md +14 -4
 - data/test_performance.rb +5 -5
 - metadata +53 -24
 
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                na (1.2. 
     | 
| 
      
 4 
     | 
    
         
            +
                na (1.2.81)
         
     | 
| 
       5 
5 
     | 
    
         
             
                  chronic (~> 0.10, >= 0.10.2)
         
     | 
| 
       6 
6 
     | 
    
         
             
                  git (~> 3.0.0)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  gli (~> 2.21.0)
         
     | 
| 
         @@ -14,14 +14,14 @@ PATH 
     | 
|
| 
       14 
14 
     | 
    
         
             
            GEM
         
     | 
| 
       15 
15 
     | 
    
         
             
              remote: https://rubygems.org/
         
     | 
| 
       16 
16 
     | 
    
         
             
              specs:
         
     | 
| 
       17 
     | 
    
         
            -
                activesupport (8.0 
     | 
| 
      
 17 
     | 
    
         
            +
                activesupport (8.1.0)
         
     | 
| 
       18 
18 
     | 
    
         
             
                  base64
         
     | 
| 
       19 
     | 
    
         
            -
                  benchmark (>= 0.3)
         
     | 
| 
       20 
19 
     | 
    
         
             
                  bigdecimal
         
     | 
| 
       21 
20 
     | 
    
         
             
                  concurrent-ruby (~> 1.0, >= 1.3.1)
         
     | 
| 
       22 
21 
     | 
    
         
             
                  connection_pool (>= 2.2.5)
         
     | 
| 
       23 
22 
     | 
    
         
             
                  drb
         
     | 
| 
       24 
23 
     | 
    
         
             
                  i18n (>= 1.6, < 2)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  json
         
     | 
| 
       25 
25 
     | 
    
         
             
                  logger (>= 1.4.2)
         
     | 
| 
       26 
26 
     | 
    
         
             
                  minitest (>= 5.1)
         
     | 
| 
       27 
27 
     | 
    
         
             
                  securerandom (>= 0.3)
         
     | 
| 
         @@ -30,11 +30,12 @@ GEM 
     | 
|
| 
       30 
30 
     | 
    
         
             
                addressable (2.8.7)
         
     | 
| 
       31 
31 
     | 
    
         
             
                  public_suffix (>= 2.0.2, < 7.0)
         
     | 
| 
       32 
32 
     | 
    
         
             
                base64 (0.3.0)
         
     | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
      
 33 
     | 
    
         
            +
                bigdecimal (3.3.1)
         
     | 
| 
      
 34 
     | 
    
         
            +
                bump (0.6.1)
         
     | 
| 
       35 
35 
     | 
    
         
             
                chronic (0.10.2)
         
     | 
| 
       36 
36 
     | 
    
         
             
                concurrent-ruby (1.3.5)
         
     | 
| 
       37 
     | 
    
         
            -
                connection_pool (2.5. 
     | 
| 
      
 37 
     | 
    
         
            +
                connection_pool (2.5.4)
         
     | 
| 
      
 38 
     | 
    
         
            +
                diff-lcs (1.6.2)
         
     | 
| 
       38 
39 
     | 
    
         
             
                drb (2.2.3)
         
     | 
| 
       39 
40 
     | 
    
         
             
                git (3.0.2)
         
     | 
| 
       40 
41 
     | 
    
         
             
                  activesupport (>= 5.0)
         
     | 
| 
         @@ -44,15 +45,29 @@ GEM 
     | 
|
| 
       44 
45 
     | 
    
         
             
                gli (2.21.5)
         
     | 
| 
       45 
46 
     | 
    
         
             
                i18n (1.14.7)
         
     | 
| 
       46 
47 
     | 
    
         
             
                  concurrent-ruby (~> 1.0)
         
     | 
| 
      
 48 
     | 
    
         
            +
                json (2.15.2)
         
     | 
| 
       47 
49 
     | 
    
         
             
                logger (1.7.0)
         
     | 
| 
       48 
50 
     | 
    
         
             
                mdless (1.0.37)
         
     | 
| 
       49 
     | 
    
         
            -
                minitest (5. 
     | 
| 
       50 
     | 
    
         
            -
                ostruct (0.6. 
     | 
| 
      
 51 
     | 
    
         
            +
                minitest (5.26.0)
         
     | 
| 
      
 52 
     | 
    
         
            +
                ostruct (0.6.3)
         
     | 
| 
       51 
53 
     | 
    
         
             
                process_executer (1.3.0)
         
     | 
| 
       52 
54 
     | 
    
         
             
                public_suffix (6.0.2)
         
     | 
| 
       53 
55 
     | 
    
         
             
                rake (13.3.0)
         
     | 
| 
       54 
     | 
    
         
            -
                rchardet (1. 
     | 
| 
      
 56 
     | 
    
         
            +
                rchardet (1.10.0)
         
     | 
| 
       55 
57 
     | 
    
         
             
                rdoc (4.3.0)
         
     | 
| 
      
 58 
     | 
    
         
            +
                rspec (3.13.0)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  rspec-core (~> 3.13.0)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  rspec-expectations (~> 3.13.0)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  rspec-mocks (~> 3.13.0)
         
     | 
| 
      
 62 
     | 
    
         
            +
                rspec-core (3.13.3)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  rspec-support (~> 3.13.0)
         
     | 
| 
      
 64 
     | 
    
         
            +
                rspec-expectations (3.13.5)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  rspec-support (~> 3.13.0)
         
     | 
| 
      
 67 
     | 
    
         
            +
                rspec-mocks (3.13.4)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  diff-lcs (>= 1.2.0, < 2.0)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  rspec-support (~> 3.13.0)
         
     | 
| 
      
 70 
     | 
    
         
            +
                rspec-support (3.13.6)
         
     | 
| 
       56 
71 
     | 
    
         
             
                securerandom (0.4.1)
         
     | 
| 
       57 
72 
     | 
    
         
             
                tty-cursor (0.7.1)
         
     | 
| 
       58 
73 
     | 
    
         
             
                tty-reader (0.9.0)
         
     | 
| 
         @@ -65,7 +80,7 @@ GEM 
     | 
|
| 
       65 
80 
     | 
    
         
             
                tty-which (0.5.0)
         
     | 
| 
       66 
81 
     | 
    
         
             
                tzinfo (2.0.6)
         
     | 
| 
       67 
82 
     | 
    
         
             
                  concurrent-ruby (~> 1.0)
         
     | 
| 
       68 
     | 
    
         
            -
                uri (1.0. 
     | 
| 
      
 83 
     | 
    
         
            +
                uri (1.0.4)
         
     | 
| 
       69 
84 
     | 
    
         
             
                wisper (2.0.1)
         
     | 
| 
       70 
85 
     | 
    
         | 
| 
       71 
86 
     | 
    
         
             
            PLATFORMS
         
     | 
| 
         @@ -82,10 +97,12 @@ PLATFORMS 
     | 
|
| 
       82 
97 
     | 
    
         
             
              x86_64-linux-musl
         
     | 
| 
       83 
98 
     | 
    
         | 
| 
       84 
99 
     | 
    
         
             
            DEPENDENCIES
         
     | 
| 
      
 100 
     | 
    
         
            +
              bump (~> 0.6.0)
         
     | 
| 
       85 
101 
     | 
    
         
             
              minitest (~> 5.14)
         
     | 
| 
       86 
102 
     | 
    
         
             
              na!
         
     | 
| 
       87 
103 
     | 
    
         
             
              rake
         
     | 
| 
       88 
104 
     | 
    
         
             
              rdoc (~> 4.3)
         
     | 
| 
      
 105 
     | 
    
         
            +
              rspec (~> 3.0)
         
     | 
| 
       89 
106 
     | 
    
         
             
              tty-spinner (~> 0.9, >= 0.9.0)
         
     | 
| 
       90 
107 
     | 
    
         | 
| 
       91 
108 
     | 
    
         
             
            BUNDLED WITH
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -9,9 +9,9 @@ 
     | 
|
| 
       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.81.
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       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. 
     | 
| 
      
 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 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
            Used with Taskpaper files, it can add new action items quickly from the command line, automatically tagging them as next actions. It can also mark actions as completed, delete them, archive them, and move them between projects.
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
         @@ -48,7 +48,7 @@ You can list next actions in files in the current directory by typing `na`. By d 
     | 
|
| 
       48 
48 
     | 
    
         | 
| 
       49 
49 
     | 
    
         
             
            #### Adding todos
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
            You can also quickly add todo items from the command line with the `add` subcommand. The script will look for a file in the current directory with a `.taskpaper` extension (configurable). 
     | 
| 
      
 51 
     | 
    
         
            +
            You can also quickly add todo items from the command line with the `add` subcommand. The script will look for a file in the current directory with a `.taskpaper` extension (configurable).
         
     | 
| 
       52 
52 
     | 
    
         | 
| 
       53 
53 
     | 
    
         
             
            If found, it will try to locate an `Inbox:` project, or create one if it doesn't exist. Any arguments after `add` will be combined to create a new task in TaskPaper format. They will automatically be assigned as next actions (tagged `@na`) and will show up when `na` lists the tasks for the project.
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
         @@ -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.81
         
     | 
| 
       80 
80 
     | 
    
         | 
| 
       81 
81 
     | 
    
         
             
            GLOBAL OPTIONS
         
     | 
| 
       82 
82 
     | 
    
         
             
                -a, --add               - Add a next action (deprecated, for backwards compatibility)
         
     | 
| 
         @@ -116,6 +116,7 @@ COMMANDS 
     | 
|
| 
       116 
116 
     | 
    
         
             
                prompt              - Show or install prompt hooks for the current shell
         
     | 
| 
       117 
117 
     | 
    
         
             
                restore, unfinish   - Find and remove @done tag from an action
         
     | 
| 
       118 
118 
     | 
    
         
             
                saved               - Execute a saved search
         
     | 
| 
      
 119 
     | 
    
         
            +
                scan                - Scan a directory tree for todo files and cache them
         
     | 
| 
       119 
120 
     | 
    
         
             
                tag                 - Add tags to matching action(s)
         
     | 
| 
       120 
121 
     | 
    
         
             
                tagged              - Find actions matching a tag
         
     | 
| 
       121 
122 
     | 
    
         
             
                todos               - Show list of known todo files
         
     | 
| 
         @@ -133,7 +134,7 @@ If you run the `add` command with no arguments, you'll be asked for input on the 
     | 
|
| 
       133 
134 
     | 
    
         | 
| 
       134 
135 
     | 
    
         
             
            ###### Adding notes
         
     | 
| 
       135 
136 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
            Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note. 
     | 
| 
      
 137 
     | 
    
         
            +
            Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
         
     | 
| 
       137 
138 
     | 
    
         | 
| 
       138 
139 
     | 
    
         
             
            Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
         
     | 
| 
       139 
140 
     | 
    
         | 
| 
         @@ -335,6 +336,7 @@ COMMAND OPTIONS 
     | 
|
| 
       335 
336 
     | 
    
         
             
                --[no-]done                            - Include @done actions
         
     | 
| 
       336 
337 
     | 
    
         
             
                --exact                                - Search query is exact text match (not tokens)
         
     | 
| 
       337 
338 
     | 
    
         
             
                --file=TODO_FILE                       - Display matches from specific todo file ([relative] path) (default: none)
         
     | 
| 
      
 339 
     | 
    
         
            +
                --hidden                               - Include hidden directories while traversing
         
     | 
| 
       338 
340 
     | 
    
         
             
                --in, --todo=TODO                      - Display matches from a known todo file anywhere in history (short name) (may be used more than once, default: none)
         
     | 
| 
       339 
341 
     | 
    
         
             
                --nest                                 - Output actions nested by file
         
     | 
| 
       340 
342 
     | 
    
         
             
                --no_file                              - No filename in output
         
     | 
| 
         @@ -426,6 +428,44 @@ EXAMPLES 
     | 
|
| 
       426 
428 
     | 
    
         
             
                na saved
         
     | 
| 
       427 
429 
     | 
    
         
             
            ```
         
     | 
| 
       428 
430 
     | 
    
         | 
| 
      
 431 
     | 
    
         
            +
            ##### scan
         
     | 
| 
      
 432 
     | 
    
         
            +
             
     | 
| 
      
 433 
     | 
    
         
            +
            Scan a directory tree for todo files and cache them in tdlist.txt. Avoids duplicates and can optionally prune non-existent entries.
         
     | 
| 
      
 434 
     | 
    
         
            +
             
     | 
| 
      
 435 
     | 
    
         
            +
            Scan reports how many files were added and, if --prune is used, how many were pruned. With --dry-run, it lists the full file paths that would be added and/or pruned.
         
     | 
| 
      
 436 
     | 
    
         
            +
             
     | 
| 
      
 437 
     | 
    
         
            +
            ```
         
     | 
| 
      
 438 
     | 
    
         
            +
            NAME
         
     | 
| 
      
 439 
     | 
    
         
            +
                scan - Scan a directory tree for todo files and cache them
         
     | 
| 
      
 440 
     | 
    
         
            +
             
     | 
| 
      
 441 
     | 
    
         
            +
            SYNOPSIS
         
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
      
 443 
     | 
    
         
            +
                na [global options] scan [command options] [PATH]
         
     | 
| 
      
 444 
     | 
    
         
            +
             
     | 
| 
      
 445 
     | 
    
         
            +
            DESCRIPTION
         
     | 
| 
      
 446 
     | 
    
         
            +
                Searches PATH (default: current directory) for files matching the current NA.extension and adds their absolute paths to the tdlist.txt cache. Avoids duplicates. Optionally prunes non-existent entries from the cache. 
         
     | 
| 
      
 447 
     | 
    
         
            +
             
     | 
| 
      
 448 
     | 
    
         
            +
            COMMAND OPTIONS
         
     | 
| 
      
 449 
     | 
    
         
            +
                -d, --depth=DEPTH - Recurse to depth (1..N or i/inf for infinite) (default: 5)
         
     | 
| 
      
 450 
     | 
    
         
            +
                --hidden          - Include hidden directories and files while scanning
         
     | 
| 
      
 451 
     | 
    
         
            +
                -n, --dry-run     - Show what would be added/pruned, but do not write tdlist.txt
         
     | 
| 
      
 452 
     | 
    
         
            +
                -p, --prune       - Prune removed files from cache after scan
         
     | 
| 
      
 453 
     | 
    
         
            +
             
     | 
| 
      
 454 
     | 
    
         
            +
            EXAMPLES
         
     | 
| 
      
 455 
     | 
    
         
            +
             
     | 
| 
      
 456 
     | 
    
         
            +
                # Scan current directory up to default depth (5)
         
     | 
| 
      
 457 
     | 
    
         
            +
                na scan
         
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
      
 459 
     | 
    
         
            +
                # Scan a specific path up to depth 3
         
     | 
| 
      
 460 
     | 
    
         
            +
                na scan -d 3 ~/Projects
         
     | 
| 
      
 461 
     | 
    
         
            +
             
     | 
| 
      
 462 
     | 
    
         
            +
                # Scan current directory recursively with no depth limit
         
     | 
| 
      
 463 
     | 
    
         
            +
                na scan -d inf
         
     | 
| 
      
 464 
     | 
    
         
            +
             
     | 
| 
      
 465 
     | 
    
         
            +
                # Prune non-existent entries from the cache (in addition to scanning)
         
     | 
| 
      
 466 
     | 
    
         
            +
                na scan --prune
         
     | 
| 
      
 467 
     | 
    
         
            +
            ```
         
     | 
| 
      
 468 
     | 
    
         
            +
             
     | 
| 
       429 
469 
     | 
    
         
             
            ##### tagged
         
     | 
| 
       430 
470 
     | 
    
         | 
| 
       431 
471 
     | 
    
         
             
            Example: `na tagged feature +maybe`.
         
     | 
| 
         @@ -453,6 +493,7 @@ COMMAND OPTIONS 
     | 
|
| 
       453 
493 
     | 
    
         
             
                --[no-]done                            - Include @done actions
         
     | 
| 
       454 
494 
     | 
    
         
             
                --exact                                - Search query is exact text match (not tokens)
         
     | 
| 
       455 
495 
     | 
    
         
             
                --file=TODO_FILE                       - Display matches from specific todo file ([relative] path) (default: none)
         
     | 
| 
      
 496 
     | 
    
         
            +
                --hidden                               - Include hidden directories while traversing
         
     | 
| 
       456 
497 
     | 
    
         
             
                --in, --todo=TODO                      - Display matches from a known todo file anywhere in history (short name) (may be used more than once, default: none)
         
     | 
| 
       457 
498 
     | 
    
         
             
                --nest                                 - Output actions nested by file
         
     | 
| 
       458 
499 
     | 
    
         
             
                --no_file                              - No filename in output
         
     | 
    
        data/Rakefile
    CHANGED
    
    
    
        data/bin/commands/next.rb
    CHANGED
    
    | 
         @@ -19,6 +19,9 @@ class App 
     | 
|
| 
       19 
19 
     | 
    
         
             
                c.arg_name "DEPTH"
         
     | 
| 
       20 
20 
     | 
    
         
             
                c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
      
 22 
     | 
    
         
            +
                c.desc "Include hidden directories while traversing"
         
     | 
| 
      
 23 
     | 
    
         
            +
                c.switch %i[hidden], negatable: false, default_value: false
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       22 
25 
     | 
    
         
             
                c.desc "Show next actions from all known todo files (in any directory)"
         
     | 
| 
       23 
26 
     | 
    
         
             
                c.switch %i[all], negatable: false, default_value: false
         
     | 
| 
       24 
27 
     | 
    
         | 
| 
         @@ -217,6 +220,7 @@ class App 
     | 
|
| 
       217 
220 
     | 
    
         
             
                  file_path = options[:file] ? File.expand_path(options[:file]) : nil
         
     | 
| 
       218 
221 
     | 
    
         | 
| 
       219 
222 
     | 
    
         
             
                  todo = NA::Todo.new({ depth: depth,
         
     | 
| 
      
 223 
     | 
    
         
            +
                                        hidden: options[:hidden],
         
     | 
| 
       220 
224 
     | 
    
         
             
                                        done: options[:done],
         
     | 
| 
       221 
225 
     | 
    
         
             
                                        file_path: file_path,
         
     | 
| 
       222 
226 
     | 
    
         
             
                                        project: options[:project],
         
     | 
| 
         @@ -0,0 +1,84 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class App
         
     | 
| 
      
 4 
     | 
    
         
            +
              extend GLI::App
         
     | 
| 
      
 5 
     | 
    
         
            +
              desc 'Scan a directory tree for todo files and cache them'
         
     | 
| 
      
 6 
     | 
    
         
            +
              long_desc 'Searches PATH (default: current directory) for files matching the current NA.extension
         
     | 
| 
      
 7 
     | 
    
         
            +
            and adds their absolute paths to the tdlist.txt cache. Avoids duplicates. Optionally prunes
         
     | 
| 
      
 8 
     | 
    
         
            +
            non-existent entries from the cache.'
         
     | 
| 
      
 9 
     | 
    
         
            +
              arg_name 'PATH', optional: true
         
     | 
| 
      
 10 
     | 
    
         
            +
              command %i[scan] do |c|
         
     | 
| 
      
 11 
     | 
    
         
            +
                c.example 'na scan', desc: 'Scan current directory up to default depth (5)'
         
     | 
| 
      
 12 
     | 
    
         
            +
                c.example 'na scan -d 3 ~/Projects', desc: 'Scan a specific path up to depth 3'
         
     | 
| 
      
 13 
     | 
    
         
            +
                c.example 'na scan -d inf', desc: 'Scan current directory recursively with no depth limit'
         
     | 
| 
      
 14 
     | 
    
         
            +
                c.example 'na scan --prune', desc: 'Prune non-existent entries from the cache (in addition to scanning)'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                c.desc 'Recurse to depth (1..N or i/inf for infinite)'
         
     | 
| 
      
 17 
     | 
    
         
            +
                c.arg_name 'DEPTH'
         
     | 
| 
      
 18 
     | 
    
         
            +
                c.default_value '5'
         
     | 
| 
      
 19 
     | 
    
         
            +
                c.flag %i[d depth], must_match: /^(\d+|i\w*)$/i
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                c.desc 'Prune removed files from cache after scan'
         
     | 
| 
      
 22 
     | 
    
         
            +
                c.switch %i[p prune], negatable: false, default_value: false
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                c.desc 'Include hidden directories and files while scanning'
         
     | 
| 
      
 25 
     | 
    
         
            +
                c.switch %i[hidden], negatable: false, default_value: false
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                c.desc 'Show what would be added/pruned, but do not write tdlist.txt'
         
     | 
| 
      
 28 
     | 
    
         
            +
                c.switch %i[n dry-run], negatable: false, default_value: false
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                c.action do |_global_options, options, args|
         
     | 
| 
      
 31 
     | 
    
         
            +
                  base = args.first || Dir.pwd
         
     | 
| 
      
 32 
     | 
    
         
            +
                  ext = NA.extension
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  # Parse depth: numeric or starts-with-i for infinite
         
     | 
| 
      
 35 
     | 
    
         
            +
                  depth_arg = (options[:depth] || '5').to_s
         
     | 
| 
      
 36 
     | 
    
         
            +
                  infinite = depth_arg =~ /^i/i ? true : false
         
     | 
| 
      
 37 
     | 
    
         
            +
                  depth = infinite ? nil : depth_arg.to_i
         
     | 
| 
      
 38 
     | 
    
         
            +
                  depth = 5 if depth.nil? && !infinite
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  # Prepare existing cache
         
     | 
| 
      
 41 
     | 
    
         
            +
                  db = NA.database_path
         
     | 
| 
      
 42 
     | 
    
         
            +
                  existing = if File.exist?(db)
         
     | 
| 
      
 43 
     | 
    
         
            +
                               File.read(db).split(/\n/).map(&:strip)
         
     | 
| 
      
 44 
     | 
    
         
            +
                             else
         
     | 
| 
      
 45 
     | 
    
         
            +
                               []
         
     | 
| 
      
 46 
     | 
    
         
            +
                             end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  found = []
         
     | 
| 
      
 49 
     | 
    
         
            +
                  Dir.chdir(base) do
         
     | 
| 
      
 50 
     | 
    
         
            +
                    patterns = if infinite
         
     | 
| 
      
 51 
     | 
    
         
            +
                                 ["*.#{ext}", "**/*.#{ext}"]
         
     | 
| 
      
 52 
     | 
    
         
            +
                               else
         
     | 
| 
      
 53 
     | 
    
         
            +
                                 (1..[depth, 1].max).map { |d| (d > 1 ? ('*/' * (d - 1)) : '') + "*.#{ext}" }
         
     | 
| 
      
 54 
     | 
    
         
            +
                               end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    pattern = patterns.length == 1 ? patterns.first : "{#{patterns.join(',')}}"
         
     | 
| 
      
 56 
     | 
    
         
            +
                    files = Dir.glob(pattern, File::FNM_DOTMATCH)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    # Exclude hidden dirs/files (any segment starting with '.') unless --hidden
         
     | 
| 
      
 58 
     | 
    
         
            +
                    files.reject! { |f| f.split('/').any? { |seg| seg.start_with?('.') && seg !~ /^\.\.?$/ } } unless options[:hidden]
         
     | 
| 
      
 59 
     | 
    
         
            +
                    found = files.map { |f| File.expand_path(f) }
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  merged = (existing + found).map(&:strip).uniq.sort
         
     | 
| 
      
 63 
     | 
    
         
            +
                  merged.select! { |f| File.exist?(f) } if options[:prune]
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  added_files = (merged - existing)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  pruned_files = options[:prune] ? (existing - merged) : []
         
     | 
| 
      
 67 
     | 
    
         
            +
                  added = added_files.count
         
     | 
| 
      
 68 
     | 
    
         
            +
                  pruned = pruned_files.count
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  if options[:dry_run]
         
     | 
| 
      
 71 
     | 
    
         
            +
                    msg = "#{NA.theme[:success]}Dry run: would add #{added} file#{added == 1 ? '' : 's'}"
         
     | 
| 
      
 72 
     | 
    
         
            +
                    msg << ", prune #{pruned} file#{pruned == 1 ? '' : 's'}" if options[:prune]
         
     | 
| 
      
 73 
     | 
    
         
            +
                    NA.notify(msg)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    NA.notify("{bw}Would add:{x}\n#{added_files.join("\n")}") if added_files.any?
         
     | 
| 
      
 75 
     | 
    
         
            +
                    NA.notify("{bw}Would prune:{x}\n#{pruned_files.join("\n")}") if options[:prune] && pruned_files.any?
         
     | 
| 
      
 76 
     | 
    
         
            +
                  else
         
     | 
| 
      
 77 
     | 
    
         
            +
                    File.open(db, 'w') { |f| f.puts merged.join("\n") }
         
     | 
| 
      
 78 
     | 
    
         
            +
                    msg = "#{NA.theme[:success]}Scan complete: #{NA.theme[:filename]}#{added}{x}#{NA.theme[:success]} added"
         
     | 
| 
      
 79 
     | 
    
         
            +
                    msg << ", #{NA.theme[:filename]}#{pruned}{x}#{NA.theme[:success]} pruned" if options[:prune]
         
     | 
| 
      
 80 
     | 
    
         
            +
                    NA.notify(msg)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
              end
         
     | 
| 
      
 84 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/commands/update.rb
    CHANGED
    
    | 
         @@ -192,7 +192,7 @@ class App 
     | 
|
| 
       192 
192 
     | 
    
         
             
                  # Require at least one actionable option to be provided
         
     | 
| 
       193 
193 
     | 
    
         
             
                  actionable = [
         
     | 
| 
       194 
194 
     | 
    
         
             
                    options[:note],
         
     | 
| 
       195 
     | 
    
         
            -
                    (options[:priority].to_i if options[:priority]).to_i 
     | 
| 
      
 195 
     | 
    
         
            +
                    (options[:priority].to_i if options[:priority]).to_i.positive?,
         
     | 
| 
       196 
196 
     | 
    
         
             
                    !options[:move].to_s.empty?,
         
     | 
| 
       197 
197 
     | 
    
         
             
                    !(options[:tag].nil? || options[:tag].empty?),
         
     | 
| 
       198 
198 
     | 
    
         
             
                    !(options[:remove].nil? || options[:remove].empty?),
         
     | 
    
        data/bin/na
    CHANGED
    
    | 
         @@ -10,11 +10,11 @@ require 'fcntl' 
     | 
|
| 
       10 
10 
     | 
    
         
             
            require 'tempfile'
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            NA::Benchmark.init
         
     | 
| 
       13 
     | 
    
         
            -
            NA::Benchmark.measure('Gem loading') { nil } 
     | 
| 
      
 13 
     | 
    
         
            +
            NA::Benchmark.measure('Gem loading') { nil } # Measures time up to this point
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            # Search for XDG compliant config first. Default to ~/.na.rc for compatibility
         
     | 
| 
       16 
16 
     | 
    
         
             
            def self.find_config_file
         
     | 
| 
       17 
     | 
    
         
            -
              home =  
     | 
| 
      
 17 
     | 
    
         
            +
              home = Dir.home
         
     | 
| 
       18 
18 
     | 
    
         
             
              xdg_config_home = ENV['XDG_CONFIG_HOME'] || File.join(home, '.config')
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
              rc_paths = [
         
     | 
| 
         @@ -30,7 +30,6 @@ def self.find_config_file 
     | 
|
| 
       30 
30 
     | 
    
         
             
              existing_path || File.join(xdg_config_home, 'na', 'na.rc')
         
     | 
| 
       31 
31 
     | 
    
         
             
            end
         
     | 
| 
       32 
32 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
33 
     | 
    
         
             
            # Main application
         
     | 
| 
       35 
34 
     | 
    
         
             
            class App
         
     | 
| 
       36 
35 
     | 
    
         
             
              extend GLI::App
         
     | 
| 
         @@ -84,7 +83,7 @@ class App 
     | 
|
| 
       84 
83 
     | 
    
         
             
              switch %i[repo-top], default_value: false
         
     | 
| 
       85 
84 
     | 
    
         | 
| 
       86 
85 
     | 
    
         
             
              desc 'Provide a template for new/blank todo files, use initconfig to make permanent'
         
     | 
| 
       87 
     | 
    
         
            -
              flag % 
     | 
| 
      
 86 
     | 
    
         
            +
              flag %(template)
         
     | 
| 
       88 
87 
     | 
    
         | 
| 
       89 
88 
     | 
    
         
             
              desc 'Use current working directory as [p]roject, [t]ag, or [n]one'
         
     | 
| 
       90 
89 
     | 
    
         
             
              arg_name 'TYPE'
         
     | 
| 
         @@ -121,7 +120,7 @@ class App 
     | 
|
| 
       121 
120 
     | 
    
         
             
                NA.include_ext = global[:include_ext]
         
     | 
| 
       122 
121 
     | 
    
         
             
                NA.na_tag = global[:na_tag]
         
     | 
| 
       123 
122 
     | 
    
         
             
                NA.global_file = global[:file]
         
     | 
| 
       124 
     | 
    
         
            -
                NA.cwd = File.basename(ENV 
     | 
| 
      
 123 
     | 
    
         
            +
                NA.cwd = File.basename(ENV.fetch('PWD', nil))
         
     | 
| 
       125 
124 
     | 
    
         
             
                NA.cwd_is = if global[:cwd_as] =~ /^n/
         
     | 
| 
       126 
125 
     | 
    
         
             
                              :none
         
     | 
| 
       127 
126 
     | 
    
         
             
                            else
         
     | 
| 
         @@ -146,7 +145,8 @@ class App 
     | 
|
| 
       146 
145 
     | 
    
         
             
                      NA.global_file = taskpaper_file
         
     | 
| 
       147 
146 
     | 
    
         
             
                      # Add this block to create the file if it doesn't exist
         
     | 
| 
       148 
147 
     | 
    
         
             
                      unless File.exist?(taskpaper_file)
         
     | 
| 
       149 
     | 
    
         
            -
                        res = NA.yn(NA::Color.template("#{NA.theme[:warning]}Repository file not found, create #{taskpaper_file}"), 
     | 
| 
      
 148 
     | 
    
         
            +
                        res = NA.yn(NA::Color.template("#{NA.theme[:warning]}Repository file not found, create #{taskpaper_file}"),
         
     | 
| 
      
 149 
     | 
    
         
            +
                                    default: true)
         
     | 
| 
       150 
150 
     | 
    
         
             
                        if res
         
     | 
| 
       151 
151 
     | 
    
         
             
                          NA.create_todo(taskpaper_file, repo_name, template: global[:template])
         
     | 
| 
       152 
152 
     | 
    
         
             
                        else
         
     | 
| 
         @@ -198,7 +198,7 @@ class App 
     | 
|
| 
       198 
198 
     | 
    
         
             
            end
         
     | 
| 
       199 
199 
     | 
    
         | 
| 
       200 
200 
     | 
    
         
             
            NA.stdin = $stdin.read.strip if $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
         
     | 
| 
       201 
     | 
    
         
            -
            NA.stdin = nil unless NA.stdin 
     | 
| 
      
 201 
     | 
    
         
            +
            NA.stdin = nil unless NA.stdin&.length&.positive?
         
     | 
| 
       202 
202 
     | 
    
         | 
| 
       203 
203 
     | 
    
         
             
            NA.globals = []
         
     | 
| 
       204 
204 
     | 
    
         
             
            NA.command_line = []
         
     | 
    
        data/lib/na/action.rb
    CHANGED
    
    | 
         @@ -12,12 +12,20 @@ module NA 
     | 
|
| 
       12 
12 
     | 
    
         
             
                  @file = file
         
     | 
| 
       13 
13 
     | 
    
         
             
                  @project = project
         
     | 
| 
       14 
14 
     | 
    
         
             
                  @parent = parent
         
     | 
| 
       15 
     | 
    
         
            -
                  @action = action.gsub( 
     | 
| 
      
 15 
     | 
    
         
            +
                  @action = action.gsub('{', '\\{')
         
     | 
| 
       16 
16 
     | 
    
         
             
                  @tags = scan_tags
         
     | 
| 
       17 
17 
     | 
    
         
             
                  @line = idx
         
     | 
| 
       18 
18 
     | 
    
         
             
                  @note = note
         
     | 
| 
       19 
19 
     | 
    
         
             
                end
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
      
 21 
     | 
    
         
            +
                # Update the action string and note with priority, tags, and completion status
         
     | 
| 
      
 22 
     | 
    
         
            +
                #
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @param priority [Integer] Priority value to set
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @param finish [Boolean] Mark as finished
         
     | 
| 
      
 25 
     | 
    
         
            +
                # @param add_tag [Array<String>] Tags to add
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @param remove_tag [Array<String>] Tags to remove
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @param note [Array<String>] Notes to set
         
     | 
| 
      
 28 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
       21 
29 
     | 
    
         
             
                def process(priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
         
     | 
| 
       22 
30 
     | 
    
         
             
                  string = @action.dup
         
     | 
| 
       23 
31 
     | 
    
         | 
| 
         @@ -44,6 +52,9 @@ module NA 
     | 
|
| 
       44 
52 
     | 
    
         
             
                  @note = note unless note.empty?
         
     | 
| 
       45 
53 
     | 
    
         
             
                end
         
     | 
| 
       46 
54 
     | 
    
         | 
| 
      
 55 
     | 
    
         
            +
                # String representation of the action
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       47 
58 
     | 
    
         
             
                def to_s
         
     | 
| 
       48 
59 
     | 
    
         
             
                  note = if @note.count.positive?
         
     | 
| 
       49 
60 
     | 
    
         
             
                           "\n#{@note.join("\n")}"
         
     | 
| 
         @@ -53,36 +64,41 @@ module NA 
     | 
|
| 
       53 
64 
     | 
    
         
             
                  "(#{@file}:#{@line}) #{@project}:#{@parent.join('>')} | #{@action}#{note}"
         
     | 
| 
       54 
65 
     | 
    
         
             
                end
         
     | 
| 
       55 
66 
     | 
    
         | 
| 
      
 67 
     | 
    
         
            +
                # Pretty string representation of the action with color formatting
         
     | 
| 
      
 68 
     | 
    
         
            +
                #
         
     | 
| 
      
 69 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       56 
70 
     | 
    
         
             
                def to_s_pretty
         
     | 
| 
       57 
71 
     | 
    
         
             
                  note = if @note.count.positive?
         
     | 
| 
       58 
72 
     | 
    
         
             
                           "\n#{@note.join("\n")}"
         
     | 
| 
       59 
73 
     | 
    
         
             
                         else
         
     | 
| 
       60 
74 
     | 
    
         
             
                           ''
         
     | 
| 
       61 
75 
     | 
    
         
             
                         end
         
     | 
| 
       62 
     | 
    
         
            -
                  "#{NA.theme[:filename]}#{File.basename(@file)}:#{@line}#{NA.theme[:bracket]}[#{NA.theme[:project]}#{@project}:#{@parent.join( 
     | 
| 
      
 76 
     | 
    
         
            +
                  "{x}#{NA.theme[:filename]}#{File.basename(@file)}:#{@line}{x}#{NA.theme[:bracket]}[{x}#{NA.theme[:project]}#{@project}:#{@parent.join('>')}{x}#{NA.theme[:bracket]}]{x} | #{NA.theme[:action]}#{@action}{x}#{NA.theme[:note]}#{note}"
         
     | 
| 
       63 
77 
     | 
    
         
             
                end
         
     | 
| 
       64 
78 
     | 
    
         | 
| 
      
 79 
     | 
    
         
            +
                # Inspect the action object
         
     | 
| 
      
 80 
     | 
    
         
            +
                #
         
     | 
| 
      
 81 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       65 
82 
     | 
    
         
             
                def inspect
         
     | 
| 
       66 
83 
     | 
    
         
             
                  <<~EOINSPECT
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
      
 84 
     | 
    
         
            +
                    @file: #{@file}
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @project: #{@project}
         
     | 
| 
      
 86 
     | 
    
         
            +
                    @parent: #{@parent.join('>')}
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @action: #{@action}
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @tags: #{@tags}
         
     | 
| 
      
 89 
     | 
    
         
            +
                    @note: #{@note}
         
     | 
| 
       73 
90 
     | 
    
         
             
                  EOINSPECT
         
     | 
| 
       74 
91 
     | 
    
         
             
                end
         
     | 
| 
       75 
92 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
                 
     | 
| 
       77 
     | 
    
         
            -
                 
     | 
| 
       78 
     | 
    
         
            -
                 
     | 
| 
       79 
     | 
    
         
            -
                 
     | 
| 
       80 
     | 
    
         
            -
                 
     | 
| 
       81 
     | 
    
         
            -
                 
     | 
| 
       82 
     | 
    
         
            -
                 
     | 
| 
       83 
     | 
    
         
            -
                 
     | 
| 
       84 
     | 
    
         
            -
                 
     | 
| 
       85 
     | 
    
         
            -
                ##
         
     | 
| 
      
 93 
     | 
    
         
            +
                #
         
     | 
| 
      
 94 
     | 
    
         
            +
                # Pretty print an action with color and template formatting
         
     | 
| 
      
 95 
     | 
    
         
            +
                #
         
     | 
| 
      
 96 
     | 
    
         
            +
                # @param extension [String] File extension
         
     | 
| 
      
 97 
     | 
    
         
            +
                # @param template [Hash] Color template
         
     | 
| 
      
 98 
     | 
    
         
            +
                # @param regexes [Array] Regexes to highlight
         
     | 
| 
      
 99 
     | 
    
         
            +
                # @param notes [Boolean] Include notes
         
     | 
| 
      
 100 
     | 
    
         
            +
                # @param detect_width [Boolean] Detect terminal width
         
     | 
| 
      
 101 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       86 
102 
     | 
    
         
             
                def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false, detect_width: true)
         
     | 
| 
       87 
103 
     | 
    
         
             
                  NA::Benchmark.measure('Action.pretty') do
         
     | 
| 
       88 
104 
     | 
    
         
             
                    # Use cached theme instead of loading every time
         
     | 
| 
         @@ -98,24 +114,29 @@ module NA 
     | 
|
| 
       98 
114 
     | 
    
         
             
                    # Create the hierarchical parent string (optimized)
         
     | 
| 
       99 
115 
     | 
    
         
             
                    parents = if needs_parents && @parent.any?
         
     | 
| 
       100 
116 
     | 
    
         
             
                                parent_parts = @parent.map { |par| "#{template[:parent]}#{par}" }.join(template[:parent_divider])
         
     | 
| 
       101 
     | 
    
         
            -
                                NA::Color.template("{x}#{template[:bracket]}[#{template[:error]}#{parent_parts}#{template[:bracket]}]{x} ")
         
     | 
| 
      
 117 
     | 
    
         
            +
                                NA::Color.template("{x}#{template[:bracket]}[#{template[:error]}#{parent_parts}{x}#{template[:bracket]}]{x} ")
         
     | 
| 
       102 
118 
     | 
    
         
             
                              else
         
     | 
| 
       103 
119 
     | 
    
         
             
                                ''
         
     | 
| 
       104 
120 
     | 
    
         
             
                              end
         
     | 
| 
       105 
121 
     | 
    
         | 
| 
       106 
122 
     | 
    
         
             
                    # Create the project string (optimized)
         
     | 
| 
       107 
123 
     | 
    
         
             
                    project = if needs_project && !@project.empty?
         
     | 
| 
       108 
     | 
    
         
            -
                                NA::Color.template("#{template[:project]}#{@project}{x} ")
         
     | 
| 
      
 124 
     | 
    
         
            +
                                NA::Color.template("{x}#{template[:project]}#{@project}{x} ")
         
     | 
| 
       109 
125 
     | 
    
         
             
                              else
         
     | 
| 
       110 
126 
     | 
    
         
             
                                ''
         
     | 
| 
       111 
127 
     | 
    
         
             
                              end
         
     | 
| 
       112 
128 
     | 
    
         | 
| 
       113 
129 
     | 
    
         
             
                    # Create the source filename string (optimized)
         
     | 
| 
       114 
130 
     | 
    
         
             
                    filename = if needs_filename
         
     | 
| 
       115 
     | 
    
         
            -
                                  
     | 
| 
       116 
     | 
    
         
            -
                                  
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
      
 131 
     | 
    
         
            +
                                 path = @file.sub(%r{^\./}, '').sub(/#{Dir.home}/, '~')
         
     | 
| 
      
 132 
     | 
    
         
            +
                                 if File.dirname(path) == '.'
         
     | 
| 
      
 133 
     | 
    
         
            +
                                   fname = NA.include_ext ? File.basename(path) : File.basename(path, ".#{extension}")
         
     | 
| 
      
 134 
     | 
    
         
            +
                                   fname = "./#{fname}" if NA.show_cwd_indicator
         
     | 
| 
      
 135 
     | 
    
         
            +
                                   NA::Color.template("#{template[:filename]}#{fname} {x}")
         
     | 
| 
      
 136 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 137 
     | 
    
         
            +
                                   colored = (NA.include_ext ? path : path.sub(/\.#{extension}$/, '')).highlight_filename
         
     | 
| 
      
 138 
     | 
    
         
            +
                                   NA::Color.template("#{template[:filename]}#{colored} {x}")
         
     | 
| 
      
 139 
     | 
    
         
            +
                                 end
         
     | 
| 
       119 
140 
     | 
    
         
             
                               else
         
     | 
| 
       120 
141 
     | 
    
         
             
                                 ''
         
     | 
| 
       121 
142 
     | 
    
         
             
                               end
         
     | 
| 
         @@ -138,44 +159,58 @@ module NA 
     | 
|
| 
       138 
159 
     | 
    
         
             
                          # Cache width calculation
         
     | 
| 
       139 
160 
     | 
    
         
             
                          width = @cached_width ||= TTY::Screen.columns
         
     | 
| 
       140 
161 
     | 
    
         
             
                          # Calculate indent more efficiently - avoid repeated template processing
         
     | 
| 
       141 
     | 
    
         
            -
                          base_template = output_template.gsub( 
     | 
| 
       142 
     | 
    
         
            -
                          base_output = base_template.gsub( 
     | 
| 
      
 162 
     | 
    
         
            +
                          base_template = output_template.gsub('%action', '').gsub('%note', '')
         
     | 
| 
      
 163 
     | 
    
         
            +
                          base_output = base_template.gsub('%filename', filename).gsub('%project', project).gsub(/%parents?/,
         
     | 
| 
      
 164 
     | 
    
         
            +
                                                                                                                 parents)
         
     | 
| 
       143 
165 
     | 
    
         
             
                          indent = NA::Color.uncolor(NA::Color.template(base_output)).length
         
     | 
| 
       144 
166 
     | 
    
         
             
                          note = NA::Color.template(@note.wrap(width, indent, template[:note]))
         
     | 
| 
       145 
167 
     | 
    
         
             
                        else
         
     | 
| 
       146 
     | 
    
         
            -
                          note = NA::Color.template("\n#{@note.map { |l| "  #{template[:note]}• #{l}{x}" }.join("\n")}")
         
     | 
| 
      
 168 
     | 
    
         
            +
                          note = NA::Color.template("\n#{@note.map { |l| "  {x}#{template[:note]}• #{l}{x}" }.join("\n")}")
         
     | 
| 
       147 
169 
     | 
    
         
             
                        end
         
     | 
| 
       148 
170 
     | 
    
         
             
                      else
         
     | 
| 
       149 
     | 
    
         
            -
                        action += "#{template[:note]}*"
         
     | 
| 
      
 171 
     | 
    
         
            +
                        action += "{x}#{template[:note]}*"
         
     | 
| 
       150 
172 
     | 
    
         
             
                      end
         
     | 
| 
       151 
173 
     | 
    
         
             
                    end
         
     | 
| 
       152 
174 
     | 
    
         | 
| 
       153 
175 
     | 
    
         
             
                    # Wrap action if needed (optimized)
         
     | 
| 
       154 
176 
     | 
    
         
             
                    if detect_width && !action.empty?
         
     | 
| 
       155 
177 
     | 
    
         
             
                      width = @cached_width ||= TTY::Screen.columns
         
     | 
| 
       156 
     | 
    
         
            -
                      base_template = output_template.gsub( 
     | 
| 
       157 
     | 
    
         
            -
                      base_output = base_template.gsub( 
     | 
| 
      
 178 
     | 
    
         
            +
                      base_template = output_template.gsub('%action', '').gsub('%note', '')
         
     | 
| 
      
 179 
     | 
    
         
            +
                      base_output = base_template.gsub('%filename', filename).gsub('%project', project).gsub(/%parents?/, parents)
         
     | 
| 
       158 
180 
     | 
    
         
             
                      indent = NA::Color.uncolor(NA::Color.template(base_output)).length
         
     | 
| 
       159 
181 
     | 
    
         
             
                      action = action.wrap(width, indent)
         
     | 
| 
       160 
182 
     | 
    
         
             
                    end
         
     | 
| 
       161 
183 
     | 
    
         | 
| 
       162 
184 
     | 
    
         
             
                    # Replace variables in template string and output colorized (optimized)
         
     | 
| 
       163 
185 
     | 
    
         
             
                    final_output = output_template.dup
         
     | 
| 
       164 
     | 
    
         
            -
                    final_output.gsub!( 
     | 
| 
       165 
     | 
    
         
            -
                    final_output.gsub!( 
     | 
| 
      
 186 
     | 
    
         
            +
                    final_output.gsub!('%filename', filename)
         
     | 
| 
      
 187 
     | 
    
         
            +
                    final_output.gsub!('%project', project)
         
     | 
| 
       166 
188 
     | 
    
         
             
                    final_output.gsub!(/%parents?/, parents)
         
     | 
| 
       167 
     | 
    
         
            -
                    final_output.gsub!( 
     | 
| 
       168 
     | 
    
         
            -
                    final_output.gsub!( 
     | 
| 
       169 
     | 
    
         
            -
                    final_output.gsub!( 
     | 
| 
      
 189 
     | 
    
         
            +
                    final_output.gsub!('%action', action.highlight_search(regexes))
         
     | 
| 
      
 190 
     | 
    
         
            +
                    final_output.gsub!('%note', note)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    final_output.gsub!('\\{', '{')
         
     | 
| 
       170 
192 
     | 
    
         | 
| 
       171 
193 
     | 
    
         
             
                    NA::Color.template(final_output)
         
     | 
| 
       172 
194 
     | 
    
         
             
                  end
         
     | 
| 
       173 
195 
     | 
    
         
             
                end
         
     | 
| 
       174 
196 
     | 
    
         | 
| 
      
 197 
     | 
    
         
            +
                # Check if action tags match any, all, and none criteria
         
     | 
| 
      
 198 
     | 
    
         
            +
                #
         
     | 
| 
      
 199 
     | 
    
         
            +
                # @param any [Array] Tags to match any
         
     | 
| 
      
 200 
     | 
    
         
            +
                # @param all [Array] Tags to match all
         
     | 
| 
      
 201 
     | 
    
         
            +
                # @param none [Array] Tags to match none
         
     | 
| 
      
 202 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       175 
203 
     | 
    
         
             
                def tags_match?(any: [], all: [], none: [])
         
     | 
| 
       176 
204 
     | 
    
         
             
                  tag_matches_any(any) && tag_matches_all(all) && tag_matches_none(none)
         
     | 
| 
       177 
205 
     | 
    
         
             
                end
         
     | 
| 
       178 
206 
     | 
    
         | 
| 
      
 207 
     | 
    
         
            +
                # Check if action or note matches any, all, and none search criteria
         
     | 
| 
      
 208 
     | 
    
         
            +
                #
         
     | 
| 
      
 209 
     | 
    
         
            +
                # @param any [Array] Regexes to match any
         
     | 
| 
      
 210 
     | 
    
         
            +
                # @param all [Array] Regexes to match all
         
     | 
| 
      
 211 
     | 
    
         
            +
                # @param none [Array] Regexes to match none
         
     | 
| 
      
 212 
     | 
    
         
            +
                # @param include_note [Boolean] Include note in search
         
     | 
| 
      
 213 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       179 
214 
     | 
    
         
             
                def search_match?(any: [], all: [], none: [], include_note: true)
         
     | 
| 
       180 
215 
     | 
    
         
             
                  search_matches_any(any, include_note: include_note) &&
         
     | 
| 
       181 
216 
     | 
    
         
             
                    search_matches_all(all, include_note: include_note) &&
         
     | 
| 
         @@ -184,6 +219,11 @@ module NA 
     | 
|
| 
       184 
219 
     | 
    
         | 
| 
       185 
220 
     | 
    
         
             
                private
         
     | 
| 
       186 
221 
     | 
    
         | 
| 
      
 222 
     | 
    
         
            +
                # Check if action and note do not match any regexes
         
     | 
| 
      
 223 
     | 
    
         
            +
                #
         
     | 
| 
      
 224 
     | 
    
         
            +
                # @param regexes [Array] Regexes to check
         
     | 
| 
      
 225 
     | 
    
         
            +
                # @param include_note [Boolean] Include note in search
         
     | 
| 
      
 226 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       187 
227 
     | 
    
         
             
                def search_matches_none(regexes, include_note: true)
         
     | 
| 
       188 
228 
     | 
    
         
             
                  regexes.each do |rx|
         
     | 
| 
       189 
229 
     | 
    
         
             
                    regex = rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE)
         
     | 
| 
         @@ -193,6 +233,11 @@ module NA 
     | 
|
| 
       193 
233 
     | 
    
         
             
                  true
         
     | 
| 
       194 
234 
     | 
    
         
             
                end
         
     | 
| 
       195 
235 
     | 
    
         | 
| 
      
 236 
     | 
    
         
            +
                # Check if action or note matches any regexes
         
     | 
| 
      
 237 
     | 
    
         
            +
                #
         
     | 
| 
      
 238 
     | 
    
         
            +
                # @param regexes [Array] Regexes to check
         
     | 
| 
      
 239 
     | 
    
         
            +
                # @param include_note [Boolean] Include note in search
         
     | 
| 
      
 240 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       196 
241 
     | 
    
         
             
                def search_matches_any(regexes, include_note: true)
         
     | 
| 
       197 
242 
     | 
    
         
             
                  return true if regexes.empty?
         
     | 
| 
       198 
243 
     | 
    
         | 
| 
         @@ -204,6 +249,11 @@ module NA 
     | 
|
| 
       204 
249 
     | 
    
         
             
                  false
         
     | 
| 
       205 
250 
     | 
    
         
             
                end
         
     | 
| 
       206 
251 
     | 
    
         | 
| 
      
 252 
     | 
    
         
            +
                # Check if action or note matches all regexes
         
     | 
| 
      
 253 
     | 
    
         
            +
                #
         
     | 
| 
      
 254 
     | 
    
         
            +
                # @param regexes [Array] Regexes to check
         
     | 
| 
      
 255 
     | 
    
         
            +
                # @param include_note [Boolean] Include note in search
         
     | 
| 
      
 256 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       207 
257 
     | 
    
         
             
                def search_matches_all(regexes, include_note: true)
         
     | 
| 
       208 
258 
     | 
    
         
             
                  regexes.each do |rx|
         
     | 
| 
       209 
259 
     | 
    
         
             
                    regex = rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE)
         
     | 
| 
         @@ -213,6 +263,10 @@ module NA 
     | 
|
| 
       213 
263 
     | 
    
         
             
                  true
         
     | 
| 
       214 
264 
     | 
    
         
             
                end
         
     | 
| 
       215 
265 
     | 
    
         | 
| 
      
 266 
     | 
    
         
            +
                # Check if none of the tags match
         
     | 
| 
      
 267 
     | 
    
         
            +
                #
         
     | 
| 
      
 268 
     | 
    
         
            +
                # @param tags [Array] Tags to check
         
     | 
| 
      
 269 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       216 
270 
     | 
    
         
             
                def tag_matches_none(tags)
         
     | 
| 
       217 
271 
     | 
    
         
             
                  tags.each do |tag|
         
     | 
| 
       218 
272 
     | 
    
         
             
                    return false if compare_tag(tag)
         
     | 
| 
         @@ -220,6 +274,10 @@ module NA 
     | 
|
| 
       220 
274 
     | 
    
         
             
                  true
         
     | 
| 
       221 
275 
     | 
    
         
             
                end
         
     | 
| 
       222 
276 
     | 
    
         | 
| 
      
 277 
     | 
    
         
            +
                # Check if any of the tags match
         
     | 
| 
      
 278 
     | 
    
         
            +
                #
         
     | 
| 
      
 279 
     | 
    
         
            +
                # @param tags [Array] Tags to check
         
     | 
| 
      
 280 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       223 
281 
     | 
    
         
             
                def tag_matches_any(tags)
         
     | 
| 
       224 
282 
     | 
    
         
             
                  return true if tags.empty?
         
     | 
| 
       225 
283 
     | 
    
         | 
| 
         @@ -229,6 +287,10 @@ module NA 
     | 
|
| 
       229 
287 
     | 
    
         
             
                  false
         
     | 
| 
       230 
288 
     | 
    
         
             
                end
         
     | 
| 
       231 
289 
     | 
    
         | 
| 
      
 290 
     | 
    
         
            +
                # Check if all of the tags match
         
     | 
| 
      
 291 
     | 
    
         
            +
                #
         
     | 
| 
      
 292 
     | 
    
         
            +
                # @param tags [Array] Tags to check
         
     | 
| 
      
 293 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       232 
294 
     | 
    
         
             
                def tag_matches_all(tags)
         
     | 
| 
       233 
295 
     | 
    
         
             
                  tags.each do |tag|
         
     | 
| 
       234 
296 
     | 
    
         
             
                    return false unless compare_tag(tag)
         
     | 
| 
         @@ -236,6 +298,10 @@ module NA 
     | 
|
| 
       236 
298 
     | 
    
         
             
                  true
         
     | 
| 
       237 
299 
     | 
    
         
             
                end
         
     | 
| 
       238 
300 
     | 
    
         | 
| 
      
 301 
     | 
    
         
            +
                # Compare a tag against the action's tags with optional value comparison
         
     | 
| 
      
 302 
     | 
    
         
            +
                #
         
     | 
| 
      
 303 
     | 
    
         
            +
                # @param tag [Hash] Tag criteria
         
     | 
| 
      
 304 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
       239 
305 
     | 
    
         
             
                def compare_tag(tag)
         
     | 
| 
       240 
306 
     | 
    
         
             
                  tag_regex = tag[:tag].is_a?(Regexp) ? tag[:tag] : Regexp.new(tag[:tag], Regexp::IGNORECASE)
         
     | 
| 
       241 
307 
     | 
    
         
             
                  keys = @tags.keys.delete_if { |k| k !~ tag_regex }
         
     |