na 1.2.80 → 1.2.82
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 +27 -0
 - data/Gemfile +2 -0
 - data/Gemfile.lock +27 -10
 - data/README.md +66 -21
 - data/Rakefile +6 -0
 - data/bin/commands/next.rb +4 -0
 - data/bin/commands/scan.rb +84 -0
 - data/bin/commands/update.rb +291 -14
 - data/bin/na +7 -7
 - data/lib/na/action.rb +103 -38
 - 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 +347 -305
 - 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 +94 -133
 - data/lib/na/theme.rb +37 -31
 - data/lib/na/todo.rb +153 -132
 - 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 +17 -5
 - data/test_performance.rb +5 -5
 - metadata +53 -24
 
    
        data/lib/na/theme.rb
    CHANGED
    
    | 
         @@ -3,6 +3,8 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module NA
         
     | 
| 
       4 
4 
     | 
    
         
             
              module Theme
         
     | 
| 
       5 
5 
     | 
    
         
             
                class << self
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Returns a help string describing available color placeholders for themes.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # @return [String] Help text for theme placeholders
         
     | 
| 
       6 
8 
     | 
    
         
             
                  def template_help
         
     | 
| 
       7 
9 
     | 
    
         
             
                    <<~EOHELP
         
     | 
| 
       8 
10 
     | 
    
         
             
                      Use {X} placeholders to apply colors. Available colors are:
         
     | 
| 
         @@ -22,43 +24,47 @@ module NA 
     | 
|
| 
       22 
24 
     | 
    
         
             
                    EOHELP
         
     | 
| 
       23 
25 
     | 
    
         
             
                  end
         
     | 
| 
       24 
26 
     | 
    
         | 
| 
      
 27 
     | 
    
         
            +
                  # Loads the theme configuration, merging defaults with any custom theme file and the provided template.
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # Writes the help text and theme YAML to the theme file.
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @param template [Hash] Additional theme settings to merge
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @return [Hash] The merged theme configuration
         
     | 
| 
       25 
31 
     | 
    
         
             
                  def load_theme(template: {})
         
     | 
| 
       26 
32 
     | 
    
         
             
                    NA::Benchmark.measure('Theme.load_theme') do
         
     | 
| 
       27 
33 
     | 
    
         
             
                      # Default colorization, can be overridden with full or partial template variable
         
     | 
| 
       28 
34 
     | 
    
         
             
                      default_template = {
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
      
 35 
     | 
    
         
            +
                        parent: '{c}',
         
     | 
| 
      
 36 
     | 
    
         
            +
                        bracket: '{dc}',
         
     | 
| 
      
 37 
     | 
    
         
            +
                        parent_divider: '{xw}/',
         
     | 
| 
      
 38 
     | 
    
         
            +
                        action: '{bg}',
         
     | 
| 
      
 39 
     | 
    
         
            +
                        project: '{xbk}',
         
     | 
| 
      
 40 
     | 
    
         
            +
                        tags: '{m}',
         
     | 
| 
      
 41 
     | 
    
         
            +
                        value_parens: '{m}',
         
     | 
| 
      
 42 
     | 
    
         
            +
                        values: '{c}',
         
     | 
| 
      
 43 
     | 
    
         
            +
                        search_highlight: '{y}',
         
     | 
| 
      
 44 
     | 
    
         
            +
                        note: '{dw}',
         
     | 
| 
      
 45 
     | 
    
         
            +
                        dirname: '{xdw}',
         
     | 
| 
      
 46 
     | 
    
         
            +
                        filename: '{xb}{#eccc87}',
         
     | 
| 
      
 47 
     | 
    
         
            +
                        prompt: '{m}',
         
     | 
| 
      
 48 
     | 
    
         
            +
                        success: '{bg}',
         
     | 
| 
      
 49 
     | 
    
         
            +
                        error: '{b}{#b61d2a}',
         
     | 
| 
      
 50 
     | 
    
         
            +
                        warning: '{by}',
         
     | 
| 
      
 51 
     | 
    
         
            +
                        debug: '{dw}',
         
     | 
| 
      
 52 
     | 
    
         
            +
                        templates: {
         
     | 
| 
      
 53 
     | 
    
         
            +
                          output: '%filename%parents| %action',
         
     | 
| 
      
 54 
     | 
    
         
            +
                          default: '%parent%action',
         
     | 
| 
      
 55 
     | 
    
         
            +
                          single_file: '%parent%action',
         
     | 
| 
      
 56 
     | 
    
         
            +
                          multi_file: '%filename%parent%action',
         
     | 
| 
      
 57 
     | 
    
         
            +
                          no_file: '%parent%action'
         
     | 
| 
      
 58 
     | 
    
         
            +
                        }
         
     | 
| 
       52 
59 
     | 
    
         
             
                      }
         
     | 
| 
       53 
     | 
    
         
            -
                    }
         
     | 
| 
       54 
60 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
      
 61 
     | 
    
         
            +
                      # Load custom theme
         
     | 
| 
      
 62 
     | 
    
         
            +
                      theme_file = NA.database_path(file: 'theme.yaml')
         
     | 
| 
      
 63 
     | 
    
         
            +
                      theme = if File.exist?(theme_file)
         
     | 
| 
      
 64 
     | 
    
         
            +
                                YAML.load(File.read(theme_file)) || {}
         
     | 
| 
      
 65 
     | 
    
         
            +
                              else
         
     | 
| 
      
 66 
     | 
    
         
            +
                                {}
         
     | 
| 
      
 67 
     | 
    
         
            +
                              end
         
     | 
| 
       62 
68 
     | 
    
         
             
                      theme = default_template.deep_merge(theme)
         
     | 
| 
       63 
69 
     | 
    
         | 
| 
       64 
70 
     | 
    
         
             
                      File.open(theme_file, 'w') do |f|
         
     | 
    
        data/lib/na/todo.rb
    CHANGED
    
    | 
         @@ -4,28 +4,28 @@ module NA 
     | 
|
| 
       4 
4 
     | 
    
         
             
              class Todo
         
     | 
| 
       5 
5 
     | 
    
         
             
                attr_accessor :actions, :projects, :files
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
                # Initialize a Todo object and parse actions/projects/files
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param options [Hash] Options for parsing todo files
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
       7 
11 
     | 
    
         
             
                def initialize(options = {})
         
     | 
| 
       8 
12 
     | 
    
         
             
                  @files, @actions, @projects = parse(options)
         
     | 
| 
       9 
13 
     | 
    
         
             
                end
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
       12 
     | 
    
         
            -
                 
     | 
| 
       13 
     | 
    
         
            -
                 
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
       17 
     | 
    
         
            -
                 
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
                 
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
                 
     | 
| 
       22 
     | 
    
         
            -
                 
     | 
| 
       23 
     | 
    
         
            -
                 
     | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
                ## @option      project     [String] The project
         
     | 
| 
       26 
     | 
    
         
            -
                ## @option      require_na  [Boolean] Require @na tag
         
     | 
| 
       27 
     | 
    
         
            -
                ## @option      file_path   [String] file path to parse
         
     | 
| 
       28 
     | 
    
         
            -
                ##
         
     | 
| 
      
 15 
     | 
    
         
            +
                # Read a todo file and create a list of actions
         
     | 
| 
      
 16 
     | 
    
         
            +
                #
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @param options [Hash] The options
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @option options [Number] :depth The directory depth to search for files
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @option options [Boolean] :done include @done actions
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @option options [Hash] :query The todo file query
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @option options [Array] :tag Tags to search for
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @option options [String] :search A search string
         
     | 
| 
      
 23 
     | 
    
         
            +
                # @option options [Boolean] :negate Invert results
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @option options [Boolean] :regex Interpret as regular expression
         
     | 
| 
      
 25 
     | 
    
         
            +
                # @option options [String] :project The project
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @option options [Boolean] :require_na Require @na tag
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @option options [String] :file_path file path to parse
         
     | 
| 
      
 28 
     | 
    
         
            +
                # @return [Array] files, actions, projects
         
     | 
| 
       29 
29 
     | 
    
         
             
                def parse(options)
         
     | 
| 
       30 
30 
     | 
    
         
             
                  NA::Benchmark.measure('Todo.parse') do
         
     | 
| 
       31 
31 
     | 
    
         
             
                    defaults = {
         
     | 
| 
         @@ -33,6 +33,7 @@ module NA 
     | 
|
| 
       33 
33 
     | 
    
         
             
                      done: false,
         
     | 
| 
       34 
34 
     | 
    
         
             
                      file_path: nil,
         
     | 
| 
       35 
35 
     | 
    
         
             
                      negate: false,
         
     | 
| 
      
 36 
     | 
    
         
            +
                      hidden: false,
         
     | 
| 
       36 
37 
     | 
    
         
             
                      project: nil,
         
     | 
| 
       37 
38 
     | 
    
         
             
                      query: nil,
         
     | 
| 
       38 
39 
     | 
    
         
             
                      regex: false,
         
     | 
| 
         @@ -43,72 +44,89 @@ module NA 
     | 
|
| 
       43 
44 
     | 
    
         
             
                    }
         
     | 
| 
       44 
45 
     | 
    
         | 
| 
       45 
46 
     | 
    
         
             
                    settings = defaults.merge(options)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    # Coerce settings[:search] to a string or nil if it's an integer
         
     | 
| 
      
 48 
     | 
    
         
            +
                    if settings[:search].is_a?(Integer)
         
     | 
| 
      
 49 
     | 
    
         
            +
                      settings[:search] = settings[:search] <= 0 ? nil : settings[:search].to_s
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                    # Ensure tag is always an Array
         
     | 
| 
      
 52 
     | 
    
         
            +
                    if settings[:tag].nil?
         
     | 
| 
      
 53 
     | 
    
         
            +
                      settings[:tag] = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                    elsif !settings[:tag].is_a?(Array)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      settings[:tag] = [settings[:tag]]
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
       46 
57 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                      if  
     | 
| 
       62 
     | 
    
         
            -
                         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
      
 58 
     | 
    
         
            +
                    actions = NA::Actions.new
         
     | 
| 
      
 59 
     | 
    
         
            +
                    required = []
         
     | 
| 
      
 60 
     | 
    
         
            +
                    optional = []
         
     | 
| 
      
 61 
     | 
    
         
            +
                    negated = []
         
     | 
| 
      
 62 
     | 
    
         
            +
                    required_tag = []
         
     | 
| 
      
 63 
     | 
    
         
            +
                    optional_tag = []
         
     | 
| 
      
 64 
     | 
    
         
            +
                    negated_tag = []
         
     | 
| 
      
 65 
     | 
    
         
            +
                    projects = []
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                    NA.notify("Tags: #{settings[:tag]}", debug: true)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    NA.notify("Search: #{settings[:search]}", debug: true)
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    settings[:tag]&.each do |t|
         
     | 
| 
      
 71 
     | 
    
         
            +
                      # If t is a Hash, use its keys; if String, treat as a tag string
         
     | 
| 
      
 72 
     | 
    
         
            +
                      if t.is_a?(Hash)
         
     | 
| 
      
 73 
     | 
    
         
            +
                        unless t[:tag].nil?
         
     | 
| 
      
 74 
     | 
    
         
            +
                          if settings[:negate]
         
     | 
| 
      
 75 
     | 
    
         
            +
                            optional_tag.push(t) if t[:negate]
         
     | 
| 
      
 76 
     | 
    
         
            +
                            required_tag.push(t) if t[:required] && t[:negate]
         
     | 
| 
      
 77 
     | 
    
         
            +
                            negated_tag.push(t) unless t[:negate]
         
     | 
| 
      
 78 
     | 
    
         
            +
                          else
         
     | 
| 
      
 79 
     | 
    
         
            +
                            optional_tag.push(t) unless t[:negate] || t[:required]
         
     | 
| 
      
 80 
     | 
    
         
            +
                            required_tag.push(t) if t[:required] && !t[:negate]
         
     | 
| 
      
 81 
     | 
    
         
            +
                            negated_tag.push(t) if t[:negate]
         
     | 
| 
      
 82 
     | 
    
         
            +
                          end
         
     | 
| 
      
 83 
     | 
    
         
            +
                        end
         
     | 
| 
      
 84 
     | 
    
         
            +
                      elsif t.is_a?(String)
         
     | 
| 
      
 85 
     | 
    
         
            +
                        # Treat string as a simple tag
         
     | 
| 
      
 86 
     | 
    
         
            +
                        optional_tag.push({ tag: t })
         
     | 
| 
       69 
87 
     | 
    
         
             
                      end
         
     | 
| 
       70 
88 
     | 
    
         
             
                    end
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 89 
     | 
    
         
            +
                    unless settings[:search].nil? || (settings[:search].respond_to?(:empty?) && settings[:search].empty?)
         
     | 
| 
      
 90 
     | 
    
         
            +
                      if settings[:regex] || settings[:search].is_a?(String)
         
     | 
| 
      
 91 
     | 
    
         
            +
                        if settings[:negate]
         
     | 
| 
      
 92 
     | 
    
         
            +
                          negated.push(settings[:search])
         
     | 
| 
      
 93 
     | 
    
         
            +
                        else
         
     | 
| 
      
 94 
     | 
    
         
            +
                          optional.push(settings[:search])
         
     | 
| 
      
 95 
     | 
    
         
            +
                          required.push(settings[:search])
         
     | 
| 
      
 96 
     | 
    
         
            +
                        end
         
     | 
| 
       77 
97 
     | 
    
         
             
                      else
         
     | 
| 
       78 
     | 
    
         
            -
                         
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
                         
     | 
| 
       84 
     | 
    
         
            -
                        optional.concat(opt)
         
     | 
| 
       85 
     | 
    
         
            -
                        required.concat(req)
         
     | 
| 
       86 
     | 
    
         
            -
                        negated.concat(neg)
         
     | 
| 
      
 98 
     | 
    
         
            +
                        settings[:search].each do |t|
         
     | 
| 
      
 99 
     | 
    
         
            +
                          opt, req, neg = parse_search(t, settings[:negate])
         
     | 
| 
      
 100 
     | 
    
         
            +
                          optional.concat(opt)
         
     | 
| 
      
 101 
     | 
    
         
            +
                          required.concat(req)
         
     | 
| 
      
 102 
     | 
    
         
            +
                          negated.concat(neg)
         
     | 
| 
      
 103 
     | 
    
         
            +
                        end
         
     | 
| 
       87 
104 
     | 
    
         
             
                      end
         
     | 
| 
       88 
105 
     | 
    
         
             
                    end
         
     | 
| 
       89 
     | 
    
         
            -
                  end
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
                  # Pre-compile regexes for better performance
         
     | 
| 
       92 
     | 
    
         
            -
                  optional = optional.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
       93 
     | 
    
         
            -
                  required = required.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
       94 
     | 
    
         
            -
                  negated = negated.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
       95 
106 
     | 
    
         | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
             
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
      
 107 
     | 
    
         
            +
                    # Pre-compile regexes for better performance
         
     | 
| 
      
 108 
     | 
    
         
            +
                    optional = optional.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
      
 109 
     | 
    
         
            +
                    required = required.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
      
 110 
     | 
    
         
            +
                    negated = negated.map { |rx| rx.is_a?(Regexp) ? rx : Regexp.new(rx, Regexp::IGNORECASE) }
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    files = if !settings[:file_path].nil?
         
     | 
| 
      
 113 
     | 
    
         
            +
                              [settings[:file_path]]
         
     | 
| 
      
 114 
     | 
    
         
            +
                            elsif settings[:query].nil?
         
     | 
| 
      
 115 
     | 
    
         
            +
                              NA.find_files(depth: settings[:depth], include_hidden: settings[:hidden])
         
     | 
| 
      
 116 
     | 
    
         
            +
                            else
         
     | 
| 
      
 117 
     | 
    
         
            +
                              NA.match_working_dir(settings[:query])
         
     | 
| 
      
 118 
     | 
    
         
            +
                            end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    NA.notify("Files: #{files.join(', ')}", debug: true)
         
     | 
| 
      
 121 
     | 
    
         
            +
                    # Cache project regex compilation outside the line loop for better performance
         
     | 
| 
      
 122 
     | 
    
         
            +
                    project_regex = if settings[:project]
         
     | 
| 
      
 123 
     | 
    
         
            +
                                      rx = settings[:project].split(%r{[/:]}).join('.*?/')
         
     | 
| 
      
 124 
     | 
    
         
            +
                                      Regexp.new("#{rx}.*?", Regexp::IGNORECASE)
         
     | 
| 
      
 125 
     | 
    
         
            +
                                    end
         
     | 
| 
       110 
126 
     | 
    
         | 
| 
       111 
127 
     | 
    
         
             
                    files.each do |file|
         
     | 
| 
      
 128 
     | 
    
         
            +
                      next if File.directory?(file)
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
       112 
130 
     | 
    
         
             
                      NA::Benchmark.measure("Parse file: #{File.basename(file)}") do
         
     | 
| 
       113 
131 
     | 
    
         
             
                        NA.save_working_dir(File.expand_path(file))
         
     | 
| 
       114 
132 
     | 
    
         
             
                        content = file.read_file
         
     | 
| 
         @@ -116,60 +134,58 @@ module NA 
     | 
|
| 
       116 
134 
     | 
    
         
             
                        parent = []
         
     | 
| 
       117 
135 
     | 
    
         
             
                        in_yaml = false
         
     | 
| 
       118 
136 
     | 
    
         
             
                        in_action = false
         
     | 
| 
       119 
     | 
    
         
            -
                        content.split( 
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
             
     | 
| 
       125 
     | 
    
         
            -
             
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
       127 
     | 
    
         
            -
             
     | 
| 
       128 
     | 
    
         
            -
             
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
       131 
     | 
    
         
            -
             
     | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
             
     | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
       137 
     | 
    
         
            -
             
     | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
             
     | 
| 
       143 
     | 
    
         
            -
             
     | 
| 
       144 
     | 
    
         
            -
             
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
             
     | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
       150 
     | 
    
         
            -
             
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
             
     | 
| 
       154 
     | 
    
         
            -
             
     | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
             
     | 
| 
       170 
     | 
    
         
            -
             
     | 
| 
       171 
     | 
    
         
            -
                        projects[-1].last_line = idx if projects.count.positive?
         
     | 
| 
       172 
     | 
    
         
            -
                      end
         
     | 
| 
      
 137 
     | 
    
         
            +
                        content.split("\n").each.with_index do |line, idx|
         
     | 
| 
      
 138 
     | 
    
         
            +
                          if in_yaml && line !~ /^(---|~~~)\s*$/
         
     | 
| 
      
 139 
     | 
    
         
            +
                            NA.notify("YAML: #{line}", debug: true)
         
     | 
| 
      
 140 
     | 
    
         
            +
                          elsif line =~ /^(---|~~~)\s*$/
         
     | 
| 
      
 141 
     | 
    
         
            +
                            in_yaml = !in_yaml
         
     | 
| 
      
 142 
     | 
    
         
            +
                          elsif line.project? && !in_yaml
         
     | 
| 
      
 143 
     | 
    
         
            +
                            in_action = false
         
     | 
| 
      
 144 
     | 
    
         
            +
                            proj = line.project
         
     | 
| 
      
 145 
     | 
    
         
            +
                            indent = line.indent_level
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                            if indent.zero? # top level project
         
     | 
| 
      
 148 
     | 
    
         
            +
                              parent = [proj]
         
     | 
| 
      
 149 
     | 
    
         
            +
                            elsif indent <= indent_level # if indent level is same or less, split parent before indent level and append
         
     | 
| 
      
 150 
     | 
    
         
            +
                              parent.slice!(indent, parent.count - indent)
         
     | 
| 
      
 151 
     | 
    
         
            +
                              parent.push(proj)
         
     | 
| 
      
 152 
     | 
    
         
            +
                            else # if indent level is greater, append project to parent
         
     | 
| 
      
 153 
     | 
    
         
            +
                              parent.push(proj)
         
     | 
| 
      
 154 
     | 
    
         
            +
                            end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                            projects.push(NA::Project.new(parent.join(':'), indent, idx, idx))
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                            indent_level = indent
         
     | 
| 
      
 159 
     | 
    
         
            +
                          elsif line.blank?
         
     | 
| 
      
 160 
     | 
    
         
            +
                            in_action = false # Comment out to allow line breaks in of notes, which isn't TaskPaper-compatible
         
     | 
| 
      
 161 
     | 
    
         
            +
                          elsif line.action?
         
     | 
| 
      
 162 
     | 
    
         
            +
                            in_action = false
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                            # Early exits before creating Action object
         
     | 
| 
      
 165 
     | 
    
         
            +
                            next if line.done? && !settings[:done]
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                            next if settings[:require_na] && !line.na?
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                            next if project_regex && parent.join('/') !~ project_regex
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                            # Only create Action if we passed basic filters
         
     | 
| 
      
 172 
     | 
    
         
            +
                            action = line.action
         
     | 
| 
      
 173 
     | 
    
         
            +
                            new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action, idx)
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                            projects[-1].last_line = idx if projects.count.positive?
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                            # Tag matching
         
     | 
| 
      
 178 
     | 
    
         
            +
                            has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
         
     | 
| 
      
 179 
     | 
    
         
            +
                            next if has_tag && !new_action.tags_match?(any: optional_tag,
         
     | 
| 
      
 180 
     | 
    
         
            +
                                                                       all: required_tag,
         
     | 
| 
      
 181 
     | 
    
         
            +
                                                                       none: negated_tag)
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                            actions.push(new_action)
         
     | 
| 
      
 184 
     | 
    
         
            +
                            in_action = true
         
     | 
| 
      
 185 
     | 
    
         
            +
                          elsif in_action
         
     | 
| 
      
 186 
     | 
    
         
            +
                            actions[-1].note.push(line.strip) if actions.count.positive?
         
     | 
| 
      
 187 
     | 
    
         
            +
                            projects[-1].last_line = idx if projects.count.positive?
         
     | 
| 
      
 188 
     | 
    
         
            +
                          end
         
     | 
| 
       173 
189 
     | 
    
         
             
                        end
         
     | 
| 
       174 
190 
     | 
    
         
             
                        projects = projects.dup
         
     | 
| 
       175 
191 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -179,9 +195,9 @@ module NA 
     | 
|
| 
       179 
195 
     | 
    
         
             
                      actions.delete_if do |new_action|
         
     | 
| 
       180 
196 
     | 
    
         
             
                        has_search = !optional.empty? || !required.empty? || !negated.empty?
         
     | 
| 
       181 
197 
     | 
    
         
             
                        has_search && !new_action.search_match?(any: optional,
         
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
       184 
     | 
    
         
            -
             
     | 
| 
      
 198 
     | 
    
         
            +
                                                                all: required,
         
     | 
| 
      
 199 
     | 
    
         
            +
                                                                none: negated,
         
     | 
| 
      
 200 
     | 
    
         
            +
                                                                include_note: settings[:search_note])
         
     | 
| 
       185 
201 
     | 
    
         
             
                      end
         
     | 
| 
       186 
202 
     | 
    
         
             
                    end
         
     | 
| 
       187 
203 
     | 
    
         | 
| 
         @@ -189,6 +205,11 @@ module NA 
     | 
|
| 
       189 
205 
     | 
    
         
             
                  end
         
     | 
| 
       190 
206 
     | 
    
         
             
                end
         
     | 
| 
       191 
207 
     | 
    
         | 
| 
      
 208 
     | 
    
         
            +
                # Parse a search tag and categorize as optional, required, or negated
         
     | 
| 
      
 209 
     | 
    
         
            +
                #
         
     | 
| 
      
 210 
     | 
    
         
            +
                # @param tag [Hash] Search tag with :token, :negate, :required
         
     | 
| 
      
 211 
     | 
    
         
            +
                # @param negate [Boolean] Invert results
         
     | 
| 
      
 212 
     | 
    
         
            +
                # @return [Array<Array>] Arrays of optional, required, and negated regexes
         
     | 
| 
       192 
213 
     | 
    
         
             
                def parse_search(tag, negate)
         
     | 
| 
       193 
214 
     | 
    
         
             
                  required = []
         
     | 
| 
       194 
215 
     | 
    
         
             
                  optional = []
         
     | 
    
        data/lib/na/version.rb
    CHANGED
    
    
    
        data/lib/na.rb
    CHANGED
    
    
    
        data/na.gemspec
    CHANGED
    
    | 
         @@ -24,12 +24,14 @@ spec = Gem::Specification.new do |s| 
     | 
|
| 
       24 
24 
     | 
    
         
             
              s.add_development_dependency('minitest', '~> 5.14')
         
     | 
| 
       25 
25 
     | 
    
         
             
              s.add_development_dependency('rdoc', '~> 4.3')
         
     | 
| 
       26 
26 
     | 
    
         
             
              s.add_runtime_dependency('chronic', '~> 0.10', '>= 0.10.2')
         
     | 
| 
      
 27 
     | 
    
         
            +
              s.add_runtime_dependency('git', '~> 3.0.0')
         
     | 
| 
       27 
28 
     | 
    
         
             
              s.add_runtime_dependency('gli','~> 2.21.0')
         
     | 
| 
       28 
29 
     | 
    
         
             
              s.add_runtime_dependency('mdless', '~> 1.0', '>= 1.0.32')
         
     | 
| 
      
 30 
     | 
    
         
            +
              s.add_runtime_dependency('ostruct', '~> 0.6', '>= 0.6.1')
         
     | 
| 
       29 
31 
     | 
    
         
             
              s.add_runtime_dependency('tty-reader', '~> 0.9', '>= 0.9.0')
         
     | 
| 
       30 
32 
     | 
    
         
             
              s.add_runtime_dependency('tty-screen', '~> 0.8', '>= 0.8.1')
         
     | 
| 
       31 
33 
     | 
    
         
             
              s.add_runtime_dependency('tty-which', '~> 0.5', '>= 0.5.0')
         
     | 
| 
       32 
     | 
    
         
            -
            	s.add_runtime_dependency('git', '~> 3.0.0')
         
     | 
| 
       33 
     | 
    
         
            -
              s.add_runtime_dependency('ostruct', '~> 0.6', '>= 0.6.1')
         
     | 
| 
       34 
34 
     | 
    
         
             
              s.add_development_dependency('tty-spinner', '~> 0.9', '>= 0.9.0')
         
     | 
| 
      
 35 
     | 
    
         
            +
              s.add_development_dependency 'rspec', '~> 3.0'
         
     | 
| 
      
 36 
     | 
    
         
            +
              s.add_development_dependency 'bump', '~> 0.6.0'
         
     | 
| 
       35 
37 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,4 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
       2 
4 
     | 
    
         
             
            require 'tty-progressbar'
         
     | 
| 
       3 
5 
     | 
    
         
             
            require 'shellwords'
         
     | 
| 
       4 
6 
     | 
    
         | 
| 
         @@ -21,7 +23,6 @@ class ::String 
     | 
|
| 
       21 
23 
     | 
    
         
             
            end
         
     | 
| 
       22 
24 
     | 
    
         | 
| 
       23 
25 
     | 
    
         
             
            class FishCompletions
         
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
26 
     | 
    
         
             
              attr_accessor :commands, :global_options
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
              def generate_helpers
         
     | 
| 
         @@ -68,7 +69,7 @@ class FishCompletions 
     | 
|
| 
       68 
69 
     | 
    
         
             
                sections = {}
         
     | 
| 
       69 
70 
     | 
    
         
             
                scanned.each do |sect|
         
     | 
| 
       70 
71 
     | 
    
         
             
                  title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
         
     | 
| 
       71 
     | 
    
         
            -
                  content = sect[1].split( 
     | 
| 
      
 72 
     | 
    
         
            +
                  content = sect[1].split("\n").map(&:strip).delete_if(&:empty?)
         
     | 
| 
       72 
73 
     | 
    
         
             
                  sections[title] = content
         
     | 
| 
       73 
74 
     | 
    
         
             
                end
         
     | 
| 
       74 
75 
     | 
    
         
             
                sections
         
     | 
| 
         @@ -77,6 +78,7 @@ class FishCompletions 
     | 
|
| 
       77 
78 
     | 
    
         
             
              def parse_option(option)
         
     | 
| 
       78 
79 
     | 
    
         
             
                res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
         
     | 
| 
       79 
80 
     | 
    
         
             
                return nil unless res
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
       80 
82 
     | 
    
         
             
                {
         
     | 
| 
       81 
83 
     | 
    
         
             
                  short: res['short'],
         
     | 
| 
       82 
84 
     | 
    
         
             
                  long: res['long'],
         
     | 
| 
         @@ -92,7 +94,7 @@ class FishCompletions 
     | 
|
| 
       92 
94 
     | 
    
         
             
              def parse_command(command)
         
     | 
| 
       93 
95 
     | 
    
         
             
                res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
         
     | 
| 
       94 
96 
     | 
    
         
             
                commands = [res['cmd']]
         
     | 
| 
       95 
     | 
    
         
            -
                commands.concat(res['alias'].split( 
     | 
| 
      
 97 
     | 
    
         
            +
                commands.concat(res['alias'].split(', ').delete_if(&:empty?)) if res['alias']
         
     | 
| 
       96 
98 
     | 
    
         | 
| 
       97 
99 
     | 
    
         
             
                {
         
     | 
| 
       98 
100 
     | 
    
         
             
                  commands: commands,
         
     | 
| 
         @@ -106,7 +108,7 @@ class FishCompletions 
     | 
|
| 
       106 
108 
     | 
    
         | 
| 
       107 
109 
     | 
    
         
             
              def generate_subcommand_completions
         
     | 
| 
       108 
110 
     | 
    
         
             
                out = []
         
     | 
| 
       109 
     | 
    
         
            -
                @commands.each_with_index do |cmd,  
     | 
| 
      
 111 
     | 
    
         
            +
                @commands.each_with_index do |cmd, _i|
         
     | 
| 
       110 
112 
     | 
    
         
             
                  out << "complete -xc na -n '__fish_na_needs_command' -a '#{cmd[:commands].join(' ')}' -d #{Shellwords.escape(cmd[:description])}"
         
     | 
| 
       111 
113 
     | 
    
         
             
                end
         
     | 
| 
       112 
114 
     | 
    
         | 
| 
         @@ -114,35 +116,30 @@ class FishCompletions 
     | 
|
| 
       114 
116 
     | 
    
         
             
              end
         
     | 
| 
       115 
117 
     | 
    
         | 
| 
       116 
118 
     | 
    
         
             
              def generate_subcommand_option_completions
         
     | 
| 
       117 
     | 
    
         
            -
             
     | 
| 
       118 
119 
     | 
    
         
             
                out = []
         
     | 
| 
       119 
120 
     | 
    
         
             
                need_export = []
         
     | 
| 
       120 
121 
     | 
    
         | 
| 
       121 
     | 
    
         
            -
                @commands.each_with_index do |cmd,  
     | 
| 
      
 122 
     | 
    
         
            +
                @commands.each_with_index do |cmd, _i|
         
     | 
| 
       122 
123 
     | 
    
         
             
                  @bar.advance
         
     | 
| 
       123 
124 
     | 
    
         
             
                  data = get_help_sections(cmd[:commands].first)
         
     | 
| 
       124 
125 
     | 
    
         | 
| 
       125 
     | 
    
         
            -
                  if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
         
     | 
| 
       126 
     | 
    
         
            -
                    out << "complete -c na -F -n '__fish_na_using_command #{cmd[:commands].join(" ")}'"
         
     | 
| 
       127 
     | 
    
         
            -
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
                  out << "complete -c na -F -n '__fish_na_using_command #{cmd[:commands].join(' ')}'" if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
         
     | 
| 
       128 
127 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
                   
     | 
| 
       130 
     | 
    
         
            -
                    parse_options(data[:command_options]).each do |option|
         
     | 
| 
       131 
     | 
    
         
            -
                      next if option.nil?
         
     | 
| 
      
 128 
     | 
    
         
            +
                  next unless data[:command_options]
         
     | 
| 
       132 
129 
     | 
    
         | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
     | 
    
         
            -
                      long = option[:long] ? "-l #{option[:long]}" : ''
         
     | 
| 
       136 
     | 
    
         
            -
                      out << "complete -c na #{long} #{short} -f #{arg} -n '__fish_na_using_command #{cmd[:commands].join(' ')}' -d #{Shellwords.escape(option[:description])}"
         
     | 
| 
      
 130 
     | 
    
         
            +
                  parse_options(data[:command_options]).each do |option|
         
     | 
| 
      
 131 
     | 
    
         
            +
                    next if option.nil?
         
     | 
| 
       137 
132 
     | 
    
         | 
| 
       138 
     | 
    
         
            -
             
     | 
| 
       139 
     | 
    
         
            -
                     
     | 
| 
      
 133 
     | 
    
         
            +
                    arg = option[:arg] ? '-r' : ''
         
     | 
| 
      
 134 
     | 
    
         
            +
                    short = option[:short] ? "-s #{option[:short]}" : ''
         
     | 
| 
      
 135 
     | 
    
         
            +
                    long = option[:long] ? "-l #{option[:long]}" : ''
         
     | 
| 
      
 136 
     | 
    
         
            +
                    out << "complete -c na #{long} #{short} -f #{arg} -n '__fish_na_using_command #{cmd[:commands].join(' ')}' -d #{Shellwords.escape(option[:description])}"
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                    need_export.concat(cmd[:commands]) if option[:long] == 'output'
         
     | 
| 
       140 
139 
     | 
    
         
             
                  end
         
     | 
| 
       141 
140 
     | 
    
         
             
                end
         
     | 
| 
       142 
141 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
                unless need_export.empty?
         
     | 
| 
       144 
     | 
    
         
            -
                  out << "complete -f -c na -s o -l output -x -n '__fish_na_using_command #{need_export.join(' ')}' -a '(__fish_na_export_plugins)'"
         
     | 
| 
       145 
     | 
    
         
            -
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
                out << "complete -f -c na -s o -l output -x -n '__fish_na_using_command #{need_export.join(' ')}' -a '(__fish_na_export_plugins)'" unless need_export.empty?
         
     | 
| 
       146 
143 
     | 
    
         | 
| 
       147 
144 
     | 
    
         
             
                # clear
         
     | 
| 
       148 
145 
     | 
    
         
             
                out.join("\n")
         
     | 
    
        data/src/_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 <!--VER-->1.2. 
     | 
| 
      
 12 
     | 
    
         
            +
            The current version of `na` is <!--VER-->1.2.81<!--END VER-->.
         
     | 
| 
       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 
     | 
    
         | 
| 
         @@ -82,7 +82,7 @@ If you run the `add` command with no arguments, you'll be asked for input on the 
     | 
|
| 
       82 
82 
     | 
    
         | 
| 
       83 
83 
     | 
    
         
             
            ###### Adding notes
         
     | 
| 
       84 
84 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
            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. 
     | 
| 
      
 85 
     | 
    
         
            +
            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.
         
     | 
| 
       86 
86 
     | 
    
         | 
| 
       87 
87 
     | 
    
         
             
            Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
         
     | 
| 
       88 
88 
     | 
    
         | 
| 
         @@ -163,6 +163,16 @@ Run `na saved` without an argument to list your saved searches. 
     | 
|
| 
       163 
163 
     | 
    
         
             
            @cli(bundle exec bin/na help saved)
         
     | 
| 
       164 
164 
     | 
    
         
             
            ```
         
     | 
| 
       165 
165 
     | 
    
         | 
| 
      
 166 
     | 
    
         
            +
            ##### scan
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
            Scan a directory tree for todo files and cache them in tdlist.txt. Avoids duplicates and can optionally prune non-existent entries.
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
            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.
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            ```
         
     | 
| 
      
 173 
     | 
    
         
            +
            @cli(bundle exec bin/na help scan)
         
     | 
| 
      
 174 
     | 
    
         
            +
            ```
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
       166 
176 
     | 
    
         
             
            ##### tagged
         
     | 
| 
       167 
177 
     | 
    
         | 
| 
       168 
178 
     | 
    
         
             
            Example: `na tagged feature +maybe`.
         
     | 
| 
         @@ -174,7 +184,7 @@ You can also perform value comparisons on tags. A value in a TaskPaper tag is ad 
     | 
|
| 
       174 
184 
     | 
    
         
             
            To perform a string comparison, you can use `*=` (contains), `^=` (starts with), `$=` (ends with), or `=` (matches). E.g. `na tagged "note*=video"`.
         
     | 
| 
       175 
185 
     | 
    
         | 
| 
       176 
186 
     | 
    
         
             
            ```
         
     | 
| 
       177 
     | 
    
         
            -
            @cli(bundle exec bin/na help  
     | 
| 
      
 187 
     | 
    
         
            +
            @cli(bundle exec bin/na help tagged)
         
     | 
| 
       178 
188 
     | 
    
         
             
            ```
         
     | 
| 
       179 
189 
     | 
    
         | 
| 
       180 
190 
     | 
    
         
             
            ##### todos
         
     | 
| 
         @@ -199,6 +209,8 @@ If more than one file is matched, a menu will be presented, multiple selections 
     | 
|
| 
       199 
209 
     | 
    
         | 
| 
       200 
210 
     | 
    
         
             
            Any time an update action is carried out, a backup of the file before modification will be made in the same directory with a `.` prepended and `.bak` appended (e.g. `marked.taskpaper` is copied to `.marked.taskpaper.bak`). Only one undo step is available, but if something goes wrong (and this feature is still experimental, so be wary), you can just copy the ".bak" file back to the original.
         
     | 
| 
       201 
211 
     | 
    
         | 
| 
      
 212 
     | 
    
         
            +
            > **Note:** When using the `update` command, if you have [fzf](https://github.com/junegunn/fzf) installed, menus for selecting files or actions will support multi-select (tab to mark multiple, return to confirm). If [gum](https://github.com/charmbracelet/gum) is installed, multi-select is also supported (use j/k/x to navigate and mark). If neither is available, a simple prompt is used. This makes it easy to apply updates to multiple actions at once.
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
       202 
214 
     | 
    
         
             
            ###### Marking a task as complete
         
     | 
| 
       203 
215 
     | 
    
         | 
| 
       204 
216 
     | 
    
         
             
            You can mark an action complete using `--finish`, which will add a dated @done tag to the action. You can also mark it @done and immediately move it to the Archive project using `--archive`.
         
     |