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/lib/na/pager.rb
    CHANGED
    
    | 
         @@ -7,22 +7,21 @@ module NA 
     | 
|
| 
       7 
7 
     | 
    
         
             
              module Pager
         
     | 
| 
       8 
8 
     | 
    
         
             
                class << self
         
     | 
| 
       9 
9 
     | 
    
         
             
                  # Boolean determines whether output is paginated
         
     | 
| 
      
 10 
     | 
    
         
            +
                  #
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # @return [Boolean] true if paginated
         
     | 
| 
       10 
12 
     | 
    
         
             
                  def paginate
         
     | 
| 
       11 
13 
     | 
    
         
             
                    @paginate ||= false
         
     | 
| 
       12 
14 
     | 
    
         
             
                  end
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
                  # Enable/disable pagination
         
     | 
| 
       15 
17 
     | 
    
         
             
                  #
         
     | 
| 
       16 
     | 
    
         
            -
                  # @ 
     | 
| 
       17 
     | 
    
         
            -
                   
     | 
| 
       18 
     | 
    
         
            -
                    @paginate = should_paginate
         
     | 
| 
       19 
     | 
    
         
            -
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  attr_writer :paginate
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                  # Page output. If @paginate is false, just dump to
         
     | 
| 
       22 
     | 
    
         
            -
                  # STDOUT
         
     | 
| 
       23 
     | 
    
         
            -
                  #
         
     | 
| 
       24 
     | 
    
         
            -
                  # @param      text  [String] text to paginate
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # Page output. If @paginate is false, just dump to STDOUT
         
     | 
| 
       25 
22 
     | 
    
         
             
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # @param text [String] text to paginate
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # @return [Boolean, nil] true if paged, false if not, nil if no pager
         
     | 
| 
       26 
25 
     | 
    
         
             
                  def page(text)
         
     | 
| 
       27 
26 
     | 
    
         
             
                    unless @paginate
         
     | 
| 
       28 
27 
     | 
    
         
             
                      puts text
         
     | 
| 
         @@ -53,31 +52,53 @@ module NA 
     | 
|
| 
       53 
52 
     | 
    
         
             
                      # Wait for pager to complete
         
     | 
| 
       54 
53 
     | 
    
         
             
                      _, status = Process.waitpid2(pid)
         
     | 
| 
       55 
54 
     | 
    
         
             
                      status.success?
         
     | 
| 
       56 
     | 
    
         
            -
                    rescue SystemCallError 
     | 
| 
      
 55 
     | 
    
         
            +
                    rescue SystemCallError
         
     | 
| 
       57 
56 
     | 
    
         
             
                      # Clean up on error
         
     | 
| 
       58 
     | 
    
         
            -
                       
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
                       
     | 
| 
      
 57 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 58 
     | 
    
         
            +
                        write_io.close
         
     | 
| 
      
 59 
     | 
    
         
            +
                      rescue StandardError
         
     | 
| 
      
 60 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                      end
         
     | 
| 
      
 62 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 63 
     | 
    
         
            +
                        Process.kill('TERM', pid)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      rescue StandardError
         
     | 
| 
      
 65 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                      end
         
     | 
| 
      
 67 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 68 
     | 
    
         
            +
                        Process.waitpid(pid)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      rescue StandardError
         
     | 
| 
      
 70 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 71 
     | 
    
         
            +
                      end
         
     | 
| 
       61 
72 
     | 
    
         
             
                      false
         
     | 
| 
       62 
73 
     | 
    
         
             
                    end
         
     | 
| 
       63 
74 
     | 
    
         
             
                  end
         
     | 
| 
       64 
75 
     | 
    
         | 
| 
       65 
76 
     | 
    
         
             
                  private
         
     | 
| 
       66 
77 
     | 
    
         | 
| 
      
 78 
     | 
    
         
            +
                  # Get the git pager command if available
         
     | 
| 
      
 79 
     | 
    
         
            +
                  #
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # @return [String, nil] git pager command
         
     | 
| 
       67 
81 
     | 
    
         
             
                  def git_pager
         
     | 
| 
       68 
82 
     | 
    
         
             
                    TTY::Which.exist?('git') ? `#{TTY::Which.which('git')} config --get-all core.pager` : nil
         
     | 
| 
       69 
83 
     | 
    
         
             
                  end
         
     | 
| 
       70 
84 
     | 
    
         | 
| 
      
 85 
     | 
    
         
            +
                  # List of possible pager commands
         
     | 
| 
      
 86 
     | 
    
         
            +
                  #
         
     | 
| 
      
 87 
     | 
    
         
            +
                  # @return [Array<String>] pager commands
         
     | 
| 
       71 
88 
     | 
    
         
             
                  def pagers
         
     | 
| 
       72 
89 
     | 
    
         
             
                    [
         
     | 
| 
       73 
     | 
    
         
            -
                      ENV 
     | 
| 
      
 90 
     | 
    
         
            +
                      ENV.fetch('PAGER', nil),
         
     | 
| 
       74 
91 
     | 
    
         
             
                      'less -FXr',
         
     | 
| 
       75 
     | 
    
         
            -
                      ENV 
     | 
| 
      
 92 
     | 
    
         
            +
                      ENV.fetch('GIT_PAGER', nil),
         
     | 
| 
       76 
93 
     | 
    
         
             
                      git_pager,
         
     | 
| 
       77 
94 
     | 
    
         
             
                      'more -r'
         
     | 
| 
       78 
95 
     | 
    
         
             
                    ].remove_bad
         
     | 
| 
       79 
96 
     | 
    
         
             
                  end
         
     | 
| 
       80 
97 
     | 
    
         | 
| 
      
 98 
     | 
    
         
            +
                  # Find the first available executable pager command
         
     | 
| 
      
 99 
     | 
    
         
            +
                  #
         
     | 
| 
      
 100 
     | 
    
         
            +
                  # @param commands [Array<String>] commands to check
         
     | 
| 
      
 101 
     | 
    
         
            +
                  # @return [String, nil] first available command
         
     | 
| 
       81 
102 
     | 
    
         
             
                  def find_executable(*commands)
         
     | 
| 
       82 
103 
     | 
    
         
             
                    execs = commands.empty? ? pagers : commands
         
     | 
| 
       83 
104 
     | 
    
         
             
                    execs
         
     | 
| 
         @@ -85,6 +106,9 @@ module NA 
     | 
|
| 
       85 
106 
     | 
    
         
             
                      .find { |cmd| TTY::Which.exist?(cmd.split.first) }
         
     | 
| 
       86 
107 
     | 
    
         
             
                  end
         
     | 
| 
       87 
108 
     | 
    
         | 
| 
      
 109 
     | 
    
         
            +
                  # Determine which pager to use
         
     | 
| 
      
 110 
     | 
    
         
            +
                  #
         
     | 
| 
      
 111 
     | 
    
         
            +
                  # @return [String, nil] pager command
         
     | 
| 
       88 
112 
     | 
    
         
             
                  def which_pager
         
     | 
| 
       89 
113 
     | 
    
         
             
                    @which_pager ||= find_executable(*pagers)
         
     | 
| 
       90 
114 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/na/project.rb
    CHANGED
    
    | 
         @@ -4,6 +4,13 @@ module NA 
     | 
|
| 
       4 
4 
     | 
    
         
             
              class Project < Hash
         
     | 
| 
       5 
5 
     | 
    
         
             
                attr_accessor :project, :indent, :line, :last_line
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
                # Initialize a Project object
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param project [String] Project name
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param indent [Integer] Indentation level
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @param line [Integer] Starting line number
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @param last_line [Integer] Ending line number
         
     | 
| 
      
 13 
     | 
    
         
            +
                # @return [void]
         
     | 
| 
       7 
14 
     | 
    
         
             
                def initialize(project, indent = 0, line = 0, last_line = 0)
         
     | 
| 
       8 
15 
     | 
    
         
             
                  super()
         
     | 
| 
       9 
16 
     | 
    
         
             
                  @project = project
         
     | 
| 
         @@ -12,17 +19,23 @@ module NA 
     | 
|
| 
       12 
19 
     | 
    
         
             
                  @last_line = last_line
         
     | 
| 
       13 
20 
     | 
    
         
             
                end
         
     | 
| 
       14 
21 
     | 
    
         | 
| 
      
 22 
     | 
    
         
            +
                # String representation of the project
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       15 
25 
     | 
    
         
             
                def to_s
         
     | 
| 
       16 
26 
     | 
    
         
             
                  { project: @project, indent: @indent, line: @line, last_line: @last_line }.to_s
         
     | 
| 
       17 
27 
     | 
    
         
             
                end
         
     | 
| 
       18 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                # Inspect the project object
         
     | 
| 
      
 30 
     | 
    
         
            +
                #
         
     | 
| 
      
 31 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
       19 
32 
     | 
    
         
             
                def inspect
         
     | 
| 
       20 
33 
     | 
    
         
             
                  [
         
     | 
| 
       21 
34 
     | 
    
         
             
                    "@project: #{@project}",
         
     | 
| 
       22 
35 
     | 
    
         
             
                    "@indent: #{@indent}",
         
     | 
| 
       23 
36 
     | 
    
         
             
                    "@line: #{@line}",
         
     | 
| 
       24 
37 
     | 
    
         
             
                    "@last_line: #{@last_line}"
         
     | 
| 
       25 
     | 
    
         
            -
                  ].join( 
     | 
| 
      
 38 
     | 
    
         
            +
                  ].join(' ')
         
     | 
| 
       26 
39 
     | 
    
         
             
                end
         
     | 
| 
       27 
40 
     | 
    
         
             
              end
         
     | 
| 
       28 
41 
     | 
    
         
             
            end
         
     | 
    
        data/lib/na/prompt.rb
    CHANGED
    
    | 
         @@ -4,6 +4,10 @@ module NA 
     | 
|
| 
       4 
4 
     | 
    
         
             
              # Prompt Hooks
         
     | 
| 
       5 
5 
     | 
    
         
             
              module Prompt
         
     | 
| 
       6 
6 
     | 
    
         
             
                class << self
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # Generate the shell prompt hook script for na
         
     | 
| 
      
 8 
     | 
    
         
            +
                  #
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # @param shell [Symbol] Shell type (:zsh, :fish, :bash)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # @return [String] Shell script for prompt hook
         
     | 
| 
       7 
11 
     | 
    
         
             
                  def prompt_hook(shell)
         
     | 
| 
       8 
12 
     | 
    
         
             
                    case shell
         
     | 
| 
       9 
13 
     | 
    
         
             
                    when :zsh
         
     | 
| 
         @@ -14,7 +18,9 @@ module NA 
     | 
|
| 
       14 
18 
     | 
    
         
             
                              when :tag
         
     | 
| 
       15 
19 
     | 
    
         
             
                                'na tagged $(basename "$PWD")'
         
     | 
| 
       16 
20 
     | 
    
         
             
                              else
         
     | 
| 
       17 
     | 
    
         
            -
                                NA.notify( 
     | 
| 
      
 21 
     | 
    
         
            +
                                NA.notify(
         
     | 
| 
      
 22 
     | 
    
         
            +
                                  "#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1
         
     | 
| 
      
 23 
     | 
    
         
            +
                                )
         
     | 
| 
       18 
24 
     | 
    
         
             
                              end
         
     | 
| 
       19 
25 
     | 
    
         
             
                            else
         
     | 
| 
       20 
26 
     | 
    
         
             
                              'na next'
         
     | 
| 
         @@ -31,7 +37,9 @@ module NA 
     | 
|
| 
       31 
37 
     | 
    
         
             
                              when :tag
         
     | 
| 
       32 
38 
     | 
    
         
             
                                'na tagged (basename "$PWD")'
         
     | 
| 
       33 
39 
     | 
    
         
             
                              else
         
     | 
| 
       34 
     | 
    
         
            -
                                NA.notify( 
     | 
| 
      
 40 
     | 
    
         
            +
                                NA.notify(
         
     | 
| 
      
 41 
     | 
    
         
            +
                                  "#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1
         
     | 
| 
      
 42 
     | 
    
         
            +
                                )
         
     | 
| 
       35 
43 
     | 
    
         
             
                              end
         
     | 
| 
       36 
44 
     | 
    
         
             
                            else
         
     | 
| 
       37 
45 
     | 
    
         
             
                              'na next'
         
     | 
| 
         @@ -50,7 +58,9 @@ module NA 
     | 
|
| 
       50 
58 
     | 
    
         
             
                              when :tag
         
     | 
| 
       51 
59 
     | 
    
         
             
                                'na tagged $(basename "$PWD")'
         
     | 
| 
       52 
60 
     | 
    
         
             
                              else
         
     | 
| 
       53 
     | 
    
         
            -
                                NA.notify( 
     | 
| 
      
 61 
     | 
    
         
            +
                                NA.notify(
         
     | 
| 
      
 62 
     | 
    
         
            +
                                  "#{NA.theme[:error]}When using a global file, a prompt hook requires `--cwd_as [tag|project]`", exit_code: 1
         
     | 
| 
      
 63 
     | 
    
         
            +
                                )
         
     | 
| 
       54 
64 
     | 
    
         
             
                              end
         
     | 
| 
       55 
65 
     | 
    
         
             
                            else
         
     | 
| 
       56 
66 
     | 
    
         
             
                              'na next'
         
     | 
| 
         @@ -70,6 +80,10 @@ module NA 
     | 
|
| 
       70 
80 
     | 
    
         
             
                    end
         
     | 
| 
       71 
81 
     | 
    
         
             
                  end
         
     | 
| 
       72 
82 
     | 
    
         | 
| 
      
 83 
     | 
    
         
            +
                  # Get the configuration file path for the given shell
         
     | 
| 
      
 84 
     | 
    
         
            +
                  #
         
     | 
| 
      
 85 
     | 
    
         
            +
                  # @param shell [Symbol] Shell type
         
     | 
| 
      
 86 
     | 
    
         
            +
                  # @return [String] Path to shell config file
         
     | 
| 
       73 
87 
     | 
    
         
             
                  def prompt_file(shell)
         
     | 
| 
       74 
88 
     | 
    
         
             
                    files = {
         
     | 
| 
       75 
89 
     | 
    
         
             
                      zsh: '~/.zshrc',
         
     | 
| 
         @@ -80,6 +94,10 @@ module NA 
     | 
|
| 
       80 
94 
     | 
    
         
             
                    files[shell]
         
     | 
| 
       81 
95 
     | 
    
         
             
                  end
         
     | 
| 
       82 
96 
     | 
    
         | 
| 
      
 97 
     | 
    
         
            +
                  # Display the prompt hook script and notify user of config file
         
     | 
| 
      
 98 
     | 
    
         
            +
                  #
         
     | 
| 
      
 99 
     | 
    
         
            +
                  # @param shell [Symbol] Shell type
         
     | 
| 
      
 100 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
       83 
101 
     | 
    
         
             
                  def show_prompt_hook(shell)
         
     | 
| 
       84 
102 
     | 
    
         
             
                    file = prompt_file(shell)
         
     | 
| 
       85 
103 
     | 
    
         | 
| 
         @@ -87,6 +105,10 @@ module NA 
     | 
|
| 
       87 
105 
     | 
    
         
             
                    puts prompt_hook(shell)
         
     | 
| 
       88 
106 
     | 
    
         
             
                  end
         
     | 
| 
       89 
107 
     | 
    
         | 
| 
      
 108 
     | 
    
         
            +
                  # Install the prompt hook script into the shell config file
         
     | 
| 
      
 109 
     | 
    
         
            +
                  #
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # @param shell [Symbol] Shell type
         
     | 
| 
      
 111 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
       90 
112 
     | 
    
         
             
                  def install_prompt_hook(shell)
         
     | 
| 
       91 
113 
     | 
    
         
             
                    file = prompt_file(shell)
         
     | 
| 
       92 
114 
     | 
    
         | 
    
        data/lib/na/string.rb
    CHANGED
    
    | 
         @@ -6,30 +6,20 @@ REGEX_TIME = /^#{REGEX_CLOCK}$/i.freeze 
     | 
|
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            # String helpers
         
     | 
| 
       8 
8 
     | 
    
         
             
            class ::String
         
     | 
| 
       9 
     | 
    
         
            -
               
     | 
| 
       10 
     | 
    
         
            -
               
     | 
| 
       11 
     | 
    
         
            -
               
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
              ##
         
     | 
| 
       14 
     | 
    
         
            -
              def comment(char = "#")
         
     | 
| 
       15 
     | 
    
         
            -
                split(/\n/).map { |l| "# #{l}" }.join("\n")
         
     | 
| 
      
 9 
     | 
    
         
            +
              # Insert a comment character at the start of every line
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @param char [String] The character to insert (default #)
         
     | 
| 
      
 11 
     | 
    
         
            +
              def comment(_char = '#')
         
     | 
| 
      
 12 
     | 
    
         
            +
                split("\n").map { |l| "# #{l}" }.join("\n")
         
     | 
| 
       16 
13 
     | 
    
         
             
              end
         
     | 
| 
       17 
14 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
               
     | 
| 
       19 
     | 
    
         
            -
               
     | 
| 
       20 
     | 
    
         
            -
              ##
         
     | 
| 
       21 
     | 
    
         
            -
              ## @return     [Boolean] true if object is defined and
         
     | 
| 
       22 
     | 
    
         
            -
              ##             has content
         
     | 
| 
       23 
     | 
    
         
            -
              ##
         
     | 
| 
      
 15 
     | 
    
         
            +
              # Tests if object is nil or empty
         
     | 
| 
      
 16 
     | 
    
         
            +
              # @return [Boolean] true if object is defined and has content
         
     | 
| 
       24 
17 
     | 
    
         
             
              def good?
         
     | 
| 
       25 
18 
     | 
    
         
             
                !strip.empty?
         
     | 
| 
       26 
19 
     | 
    
         
             
              end
         
     | 
| 
       27 
20 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
               
     | 
| 
       29 
     | 
    
         
            -
               
     | 
| 
       30 
     | 
    
         
            -
              ##
         
     | 
| 
       31 
     | 
    
         
            -
              ## @return     [Boolean] line is empty or comment
         
     | 
| 
       32 
     | 
    
         
            -
              ##
         
     | 
| 
      
 21 
     | 
    
         
            +
              # Test if line should be ignored
         
     | 
| 
      
 22 
     | 
    
         
            +
              # @return [Boolean] line is empty or comment
         
     | 
| 
       33 
23 
     | 
    
         
             
              def ignore?
         
     | 
| 
       34 
24 
     | 
    
         
             
                line = self
         
     | 
| 
       35 
25 
     | 
    
         
             
                line =~ /^#/ || line.strip.empty?
         
     | 
| 
         @@ -50,19 +40,16 @@ class ::String 
     | 
|
| 
       50 
40 
     | 
    
         
             
                end
         
     | 
| 
       51 
41 
     | 
    
         | 
| 
       52 
42 
     | 
    
         
             
                # IO.read(file).force_encoding('ASCII-8BIT').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
         
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
      
 43 
     | 
    
         
            +
                File.read(file).force_encoding('utf-8')
         
     | 
| 
       54 
44 
     | 
    
         
             
              end
         
     | 
| 
       55 
45 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
               
     | 
| 
       57 
     | 
    
         
            -
               
     | 
| 
       58 
     | 
    
         
            -
              ##
         
     | 
| 
       59 
     | 
    
         
            -
              ## @return     [Number] number of indents detected
         
     | 
| 
       60 
     | 
    
         
            -
              ##
         
     | 
| 
      
 46 
     | 
    
         
            +
              # Determine indentation level of line
         
     | 
| 
      
 47 
     | 
    
         
            +
              # @return [Number] number of indents detected
         
     | 
| 
       61 
48 
     | 
    
         
             
              def indent_level
         
     | 
| 
       62 
49 
     | 
    
         
             
                prefix = match(/(^[ \t]+)/)
         
     | 
| 
       63 
50 
     | 
    
         
             
                return 0 if prefix.nil?
         
     | 
| 
       64 
51 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                prefix[1].gsub( 
     | 
| 
      
 52 
     | 
    
         
            +
                prefix[1].gsub('    ', "\t").scan("\t").count
         
     | 
| 
       66 
53 
     | 
    
         
             
              end
         
     | 
| 
       67 
54 
     | 
    
         | 
| 
       68 
55 
     | 
    
         
             
              def action?
         
     | 
| 
         @@ -94,31 +81,22 @@ class ::String 
     | 
|
| 
       94 
81 
     | 
    
         
             
                self =~ /@#{NA.na_tag}\b/
         
     | 
| 
       95 
82 
     | 
    
         
             
              end
         
     | 
| 
       96 
83 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
               
     | 
| 
       98 
     | 
    
         
            -
               
     | 
| 
       99 
     | 
    
         
            -
              ##
         
     | 
| 
       100 
     | 
    
         
            -
              ## @return     Colorized string
         
     | 
| 
       101 
     | 
    
         
            -
              ##
         
     | 
| 
      
 84 
     | 
    
         
            +
              # Colorize the dirname and filename of a path
         
     | 
| 
      
 85 
     | 
    
         
            +
              # @return [String] Colorized string
         
     | 
| 
       102 
86 
     | 
    
         
             
              def highlight_filename
         
     | 
| 
       103 
87 
     | 
    
         
             
                dir = File.dirname(self).shorten_path.trunc_middle(TTY::Screen.columns / 3)
         
     | 
| 
       104 
88 
     | 
    
         
             
                file = NA.include_ext ? File.basename(self) : File.basename(self, ".#{NA.extension}")
         
     | 
| 
       105 
89 
     | 
    
         
             
                "#{NA.theme[:dirname]}#{dir}/#{NA.theme[:filename]}#{file}{x}"
         
     | 
| 
       106 
90 
     | 
    
         
             
              end
         
     | 
| 
       107 
91 
     | 
    
         | 
| 
       108 
     | 
    
         
            -
               
     | 
| 
       109 
     | 
    
         
            -
               
     | 
| 
       110 
     | 
    
         
            -
               
     | 
| 
       111 
     | 
    
         
            -
               
     | 
| 
       112 
     | 
    
         
            -
               
     | 
| 
       113 
     | 
    
         
            -
               
     | 
| 
       114 
     | 
    
         
            -
               
     | 
| 
       115 
     | 
    
         
            -
             
     | 
| 
       116 
     | 
    
         
            -
              ## @param      last_color  [String] Color to restore after
         
     | 
| 
       117 
     | 
    
         
            -
              ##                         tag highlight
         
     | 
| 
       118 
     | 
    
         
            -
              ##
         
     | 
| 
       119 
     | 
    
         
            -
              ## @return     [String] string with @tags highlighted
         
     | 
| 
       120 
     | 
    
         
            -
              ##
         
     | 
| 
       121 
     | 
    
         
            -
              def highlight_tags(color: NA.theme[:tags], value: NA.theme[:value], parens: NA.theme[:value_parens], last_color: NA.theme[:action])
         
     | 
| 
      
 92 
     | 
    
         
            +
              # Colorize @tags with ANSI escapes
         
     | 
| 
      
 93 
     | 
    
         
            +
              # @param color [String] color (see #Color)
         
     | 
| 
      
 94 
     | 
    
         
            +
              # @param value [String] The value color template
         
     | 
| 
      
 95 
     | 
    
         
            +
              # @param parens [String] The parens color template
         
     | 
| 
      
 96 
     | 
    
         
            +
              # @param last_color [String] Color to restore after tag highlight
         
     | 
| 
      
 97 
     | 
    
         
            +
              # @return [String] string with @tags highlighted
         
     | 
| 
      
 98 
     | 
    
         
            +
              def highlight_tags(color: NA.theme[:tags], value: NA.theme[:value], parens: NA.theme[:value_parens],
         
     | 
| 
      
 99 
     | 
    
         
            +
                                 last_color: NA.theme[:action])
         
     | 
| 
       122 
100 
     | 
    
         
             
                tag_color = NA::Color.template(color)
         
     | 
| 
       123 
101 
     | 
    
         
             
                paren_color = NA::Color.template(parens)
         
     | 
| 
       124 
102 
     | 
    
         
             
                value_color = NA::Color.template(value)
         
     | 
| 
         @@ -132,21 +110,16 @@ class ::String 
     | 
|
| 
       132 
110 
     | 
    
         
             
                end
         
     | 
| 
       133 
111 
     | 
    
         
             
              end
         
     | 
| 
       134 
112 
     | 
    
         | 
| 
       135 
     | 
    
         
            -
               
     | 
| 
       136 
     | 
    
         
            -
               
     | 
| 
       137 
     | 
    
         
            -
               
     | 
| 
       138 
     | 
    
         
            -
               
     | 
| 
       139 
     | 
    
         
            -
              ##                         search
         
     | 
| 
       140 
     | 
    
         
            -
              ## @param      color       [String] The highlight color
         
     | 
| 
       141 
     | 
    
         
            -
              ##                         template
         
     | 
| 
       142 
     | 
    
         
            -
              ## @param      last_color  [String] Color to restore after
         
     | 
| 
       143 
     | 
    
         
            -
              ##                         highlight
         
     | 
| 
       144 
     | 
    
         
            -
              ##
         
     | 
| 
      
 113 
     | 
    
         
            +
              # Highlight search results
         
     | 
| 
      
 114 
     | 
    
         
            +
              # @param regexes [Array] The regexes for the search
         
     | 
| 
      
 115 
     | 
    
         
            +
              # @param color [String] The highlight color template
         
     | 
| 
      
 116 
     | 
    
         
            +
              # @param last_color [String] Color to restore after highlight
         
     | 
| 
       145 
117 
     | 
    
         
             
              def highlight_search(regexes, color: NA.theme[:search_highlight], last_color: NA.theme[:action])
         
     | 
| 
       146 
118 
     | 
    
         
             
                string = dup
         
     | 
| 
       147 
119 
     | 
    
         
             
                color = NA::Color.template(color.dup)
         
     | 
| 
       148 
120 
     | 
    
         
             
                regexes.each do |rx|
         
     | 
| 
       149 
121 
     | 
    
         
             
                  next if rx.nil?
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
       150 
123 
     | 
    
         
             
                  rx = Regexp.new(rx, Regexp::IGNORECASE) if rx.is_a?(String)
         
     | 
| 
       151 
124 
     | 
    
         | 
| 
       152 
125 
     | 
    
         
             
                  string.gsub!(rx) do
         
     | 
| 
         @@ -158,16 +131,23 @@ class ::String 
     | 
|
| 
       158 
131 
     | 
    
         
             
                string
         
     | 
| 
       159 
132 
     | 
    
         
             
              end
         
     | 
| 
       160 
133 
     | 
    
         | 
| 
      
 134 
     | 
    
         
            +
              # Truncate the string in the middle, replacing the removed section with '[...]'.
         
     | 
| 
      
 135 
     | 
    
         
            +
              # @param max [Integer] Maximum allowed length of the string
         
     | 
| 
      
 136 
     | 
    
         
            +
              # @return [String] Truncated string with middle replaced if necessary
         
     | 
| 
       161 
137 
     | 
    
         
             
              def trunc_middle(max)
         
     | 
| 
       162 
138 
     | 
    
         
             
                return self unless length > max
         
     | 
| 
       163 
139 
     | 
    
         | 
| 
       164 
140 
     | 
    
         
             
                half = (max / 2).floor - 3
         
     | 
| 
       165 
     | 
    
         
            -
                chars =  
     | 
| 
      
 141 
     | 
    
         
            +
                chars = chars
         
     | 
| 
       166 
142 
     | 
    
         
             
                pre = chars.slice(0, half)
         
     | 
| 
       167 
143 
     | 
    
         
             
                post = chars.reverse.slice(0, half).reverse
         
     | 
| 
       168 
     | 
    
         
            -
                "#{pre.join 
     | 
| 
      
 144 
     | 
    
         
            +
                "#{pre.join}[...]#{post.join}"
         
     | 
| 
       169 
145 
     | 
    
         
             
              end
         
     | 
| 
       170 
146 
     | 
    
         | 
| 
      
 147 
     | 
    
         
            +
              # Wrap the string to a given width, indenting each line and preserving tag formatting.
         
     | 
| 
      
 148 
     | 
    
         
            +
              # @param width [Integer] The maximum line width
         
     | 
| 
      
 149 
     | 
    
         
            +
              # @param indent [Integer] Number of spaces to indent each line
         
     | 
| 
      
 150 
     | 
    
         
            +
              # @return [String] Wrapped string
         
     | 
| 
       171 
151 
     | 
    
         
             
              def wrap(width, indent)
         
     | 
| 
       172 
152 
     | 
    
         
             
                return "\n#{self}" if width <= 80
         
     | 
| 
       173 
153 
     | 
    
         | 
| 
         @@ -188,40 +168,33 @@ class ::String 
     | 
|
| 
       188 
168 
     | 
    
         
             
                  end
         
     | 
| 
       189 
169 
     | 
    
         
             
                end
         
     | 
| 
       190 
170 
     | 
    
         
             
                output << line.join(' ')
         
     | 
| 
       191 
     | 
    
         
            -
                output.join("\n 
     | 
| 
      
 171 
     | 
    
         
            +
                output.join("\n#{' ' * (indent + 2)}").gsub(/†/, ' ')
         
     | 
| 
       192 
172 
     | 
    
         
             
              end
         
     | 
| 
       193 
173 
     | 
    
         | 
| 
       194 
174 
     | 
    
         
             
              # Returns the last escape sequence from a string.
         
     | 
| 
       195 
     | 
    
         
            -
              #
         
     | 
| 
       196 
     | 
    
         
            -
              # @ 
     | 
| 
       197 
     | 
    
         
            -
              #             assumption that the result of inserting them
         
     | 
| 
       198 
     | 
    
         
            -
              #             will generate the same color as was set at
         
     | 
| 
       199 
     | 
    
         
            -
              #             the end of the string. Because you can send
         
     | 
| 
       200 
     | 
    
         
            -
              #             modifiers like dark and bold separate from
         
     | 
| 
       201 
     | 
    
         
            -
              #             color codes, only using the last code may
         
     | 
| 
       202 
     | 
    
         
            -
              #             not render the same style.
         
     | 
| 
       203 
     | 
    
         
            -
              #
         
     | 
| 
       204 
     | 
    
         
            -
              # @return     [String]  All escape codes in string
         
     | 
| 
       205 
     | 
    
         
            -
              #
         
     | 
| 
      
 175 
     | 
    
         
            +
              # @note Actually returns all escape codes, with the assumption that the result of inserting them will generate the same color as was set at end of the string. Because you can send modifiers like dark and bold separate from color codes, only using the last code may not render the same style.
         
     | 
| 
      
 176 
     | 
    
         
            +
              # @return [String] All escape codes in string
         
     | 
| 
       206 
177 
     | 
    
         
             
              def last_color
         
     | 
| 
       207 
     | 
    
         
            -
                scan(/\e\[[\d;]+m/).join 
     | 
| 
      
 178 
     | 
    
         
            +
                scan(/\e\[[\d;]+m/).join.gsub("\e[0m", '')
         
     | 
| 
       208 
179 
     | 
    
         
             
              end
         
     | 
| 
       209 
180 
     | 
    
         | 
| 
       210 
     | 
    
         
            -
               
     | 
| 
       211 
     | 
    
         
            -
               
     | 
| 
       212 
     | 
    
         
            -
               
     | 
| 
       213 
     | 
    
         
            -
               
     | 
| 
       214 
     | 
    
         
            -
              ##             between characters, joins segments with
         
     | 
| 
       215 
     | 
    
         
            -
              ##             slashes and requires that last segment
         
     | 
| 
       216 
     | 
    
         
            -
              ##             match last segment of target path
         
     | 
| 
       217 
     | 
    
         
            -
              ##
         
     | 
| 
       218 
     | 
    
         
            -
              ## @param      distance      The distance allowed between characters
         
     | 
| 
       219 
     | 
    
         
            -
              ## @param      require_last  Require match to be last element in path
         
     | 
| 
       220 
     | 
    
         
            -
              ##
         
     | 
| 
      
 181 
     | 
    
         
            +
              # Convert a directory path to a regular expression
         
     | 
| 
      
 182 
     | 
    
         
            +
              # @note Splits at / or :, adds variable distance between characters, joins segments with slashes and requires that last segment match last segment of target path
         
     | 
| 
      
 183 
     | 
    
         
            +
              # @param distance [Integer] The distance allowed between characters
         
     | 
| 
      
 184 
     | 
    
         
            +
              # @param require_last [Boolean] Require match to be last element in path
         
     | 
| 
       221 
185 
     | 
    
         
             
              def dir_to_rx(distance: 1, require_last: true)
         
     | 
| 
       222 
     | 
    
         
            -
                "#{split(%r{[/:]}).map  
     | 
| 
      
 186 
     | 
    
         
            +
                "#{split(%r{[/:]}).map do |comp|
         
     | 
| 
      
 187 
     | 
    
         
            +
                  comp.chars.join(".{0,#{distance}}").gsub('*', '[^ ]*?')
         
     | 
| 
      
 188 
     | 
    
         
            +
                end.join('.*?/.*?')}#{require_last ? '[^/]*?$' : ''}"
         
     | 
| 
       223 
189 
     | 
    
         
             
              end
         
     | 
| 
       224 
190 
     | 
    
         | 
| 
      
 191 
     | 
    
         
            +
              # Check if the string matches directory patterns using any, all, and none criteria.
         
     | 
| 
      
 192 
     | 
    
         
            +
              # @param any [Array] Patterns where any match is sufficient
         
     | 
| 
      
 193 
     | 
    
         
            +
              # @param all [Array] Patterns where all must match
         
     | 
| 
      
 194 
     | 
    
         
            +
              # @param none [Array] Patterns where none must match
         
     | 
| 
      
 195 
     | 
    
         
            +
              # @param require_last [Boolean] Require last segment match
         
     | 
| 
      
 196 
     | 
    
         
            +
              # @param distance [Integer] Allowed character distance in regex
         
     | 
| 
      
 197 
     | 
    
         
            +
              # @return [Boolean] True if matches criteria
         
     | 
| 
       225 
198 
     | 
    
         
             
              def dir_matches(any: [], all: [], none: [], require_last: true, distance: 1)
         
     | 
| 
       226 
199 
     | 
    
         
             
                any_rx = any.map { |q| q.dir_to_rx(distance: distance, require_last: require_last) }
         
     | 
| 
       227 
200 
     | 
    
         
             
                all_rx = all.map { |q| q.dir_to_rx(distance: distance, require_last: require_last) }
         
     | 
| 
         @@ -229,29 +202,29 @@ class ::String 
     | 
|
| 
       229 
202 
     | 
    
         
             
                matches_any(any_rx) && matches_all(all_rx) && matches_none(none_rx)
         
     | 
| 
       230 
203 
     | 
    
         
             
              end
         
     | 
| 
       231 
204 
     | 
    
         | 
| 
      
 205 
     | 
    
         
            +
              # Check if the string matches any, all, and none regex patterns.
         
     | 
| 
      
 206 
     | 
    
         
            +
              # @param any [Array] Patterns where any match is sufficient
         
     | 
| 
      
 207 
     | 
    
         
            +
              # @param all [Array] Patterns where all must match
         
     | 
| 
      
 208 
     | 
    
         
            +
              # @param none [Array] Patterns where none must match
         
     | 
| 
      
 209 
     | 
    
         
            +
              # @return [Boolean] True if matches criteria
         
     | 
| 
       232 
210 
     | 
    
         
             
              def matches(any: [], all: [], none: [])
         
     | 
| 
       233 
211 
     | 
    
         
             
                matches_any(any) && matches_all(all) && matches_none(none)
         
     | 
| 
       234 
212 
     | 
    
         
             
              end
         
     | 
| 
       235 
213 
     | 
    
         | 
| 
       236 
     | 
    
         
            -
               
     | 
| 
       237 
     | 
    
         
            -
               
     | 
| 
       238 
     | 
    
         
            -
              ##
         
     | 
| 
       239 
     | 
    
         
            -
              ## @return     [String] Regex string
         
     | 
| 
       240 
     | 
    
         
            -
              ##
         
     | 
| 
      
 214 
     | 
    
         
            +
              # Convert wildcard characters to regular expressions
         
     | 
| 
      
 215 
     | 
    
         
            +
              # @return [String] Regex string
         
     | 
| 
       241 
216 
     | 
    
         
             
              def wildcard_to_rx
         
     | 
| 
       242 
     | 
    
         
            -
                gsub( 
     | 
| 
      
 217 
     | 
    
         
            +
                gsub('.', '\\.').gsub('?', '.').gsub('*', '[^ ]*?')
         
     | 
| 
       243 
218 
     | 
    
         
             
              end
         
     | 
| 
       244 
219 
     | 
    
         | 
| 
      
 220 
     | 
    
         
            +
              # Capitalize the first character of the string in place.
         
     | 
| 
      
 221 
     | 
    
         
            +
              # @return [String] The modified string
         
     | 
| 
       245 
222 
     | 
    
         
             
              def cap_first!
         
     | 
| 
       246 
223 
     | 
    
         
             
                replace cap_first
         
     | 
| 
       247 
224 
     | 
    
         
             
              end
         
     | 
| 
       248 
225 
     | 
    
         | 
| 
       249 
     | 
    
         
            -
               
     | 
| 
       250 
     | 
    
         
            -
               
     | 
| 
       251 
     | 
    
         
            -
              ## capitalization in place
         
     | 
| 
       252 
     | 
    
         
            -
              ##
         
     | 
| 
       253 
     | 
    
         
            -
              ## @return     [String] capitalized string
         
     | 
| 
       254 
     | 
    
         
            -
              ##
         
     | 
| 
      
 226 
     | 
    
         
            +
              # Capitalize first character, leaving other capitalization in place
         
     | 
| 
      
 227 
     | 
    
         
            +
              # @return [String] capitalized string
         
     | 
| 
       255 
228 
     | 
    
         
             
              def cap_first
         
     | 
| 
       256 
229 
     | 
    
         
             
                sub(/^([a-z])(.*)$/) do
         
     | 
| 
       257 
230 
     | 
    
         
             
                  m = Regexp.last_match
         
     | 
| 
         @@ -259,24 +232,14 @@ class ::String 
     | 
|
| 
       259 
232 
     | 
    
         
             
                end
         
     | 
| 
       260 
233 
     | 
    
         
             
              end
         
     | 
| 
       261 
234 
     | 
    
         | 
| 
       262 
     | 
    
         
            -
               
     | 
| 
       263 
     | 
    
         
            -
               
     | 
| 
       264 
     | 
    
         
            -
              ##
         
     | 
| 
       265 
     | 
    
         
            -
              ## @return     [String] shortened path
         
     | 
| 
       266 
     | 
    
         
            -
              ##
         
     | 
| 
      
 235 
     | 
    
         
            +
              # Replace home directory with tilde
         
     | 
| 
      
 236 
     | 
    
         
            +
              # @return [String] shortened path
         
     | 
| 
       267 
237 
     | 
    
         
             
              def shorten_path
         
     | 
| 
       268 
     | 
    
         
            -
                sub(/^#{ 
     | 
| 
      
 238 
     | 
    
         
            +
                sub(/^#{Dir.home}/, '~')
         
     | 
| 
       269 
239 
     | 
    
         
             
              end
         
     | 
| 
       270 
240 
     | 
    
         | 
| 
       271 
     | 
    
         
            -
               
     | 
| 
       272 
     | 
    
         
            -
               
     | 
| 
       273 
     | 
    
         
            -
              ## within configured date tags (tags whose value is
         
     | 
| 
       274 
     | 
    
         
            -
              ## expected to be a date). Modifies string in place.
         
     | 
| 
       275 
     | 
    
         
            -
              ##
         
     | 
| 
       276 
     | 
    
         
            -
              ## @param      additional_tags  [Array] An array of
         
     | 
| 
       277 
     | 
    
         
            -
              ##                              additional tags to
         
     | 
| 
       278 
     | 
    
         
            -
              ##                              consider date_tags
         
     | 
| 
       279 
     | 
    
         
            -
              ##
         
     | 
| 
      
 241 
     | 
    
         
            +
              # Convert (chronify) natural language dates within configured date tags (tags whose value is expected to be a date). Modifies string in place.
         
     | 
| 
      
 242 
     | 
    
         
            +
              # @param additional_tags [Array] An array of additional tags to consider date_tags
         
     | 
| 
       280 
243 
     | 
    
         
             
              def expand_date_tags(additional_tags = nil)
         
     | 
| 
       281 
244 
     | 
    
         
             
                iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
         
     | 
| 
       282 
245 
     | 
    
         | 
| 
         @@ -312,27 +275,14 @@ class ::String 
     | 
|
| 
       312 
275 
     | 
    
         
             
                end
         
     | 
| 
       313 
276 
     | 
    
         
             
              end
         
     | 
| 
       314 
277 
     | 
    
         | 
| 
       315 
     | 
    
         
            -
               
     | 
| 
       316 
     | 
    
         
            -
               
     | 
| 
       317 
     | 
    
         
            -
               
     | 
| 
       318 
     | 
    
         
            -
               
     | 
| 
       319 
     | 
    
         
            -
               
     | 
| 
       320 
     | 
    
         
            -
               
     | 
| 
       321 
     | 
    
         
            -
               
     | 
| 
       322 
     | 
    
         
            -
               
     | 
| 
       323 
     | 
    
         
            -
              ##               PDT'
         
     | 
| 
       324 
     | 
    
         
            -
              ##
         
     | 
| 
       325 
     | 
    
         
            -
              ## @param      options  Additional options
         
     | 
| 
       326 
     | 
    
         
            -
              ##
         
     | 
| 
       327 
     | 
    
         
            -
              ## @option options :future [Boolean] assume future date
         
     | 
| 
       328 
     | 
    
         
            -
              ##                                   (default: false)
         
     | 
| 
       329 
     | 
    
         
            -
              ##
         
     | 
| 
       330 
     | 
    
         
            -
              ## @option options :guess  [Symbol] :begin or :end to
         
     | 
| 
       331 
     | 
    
         
            -
              ##                                   assume beginning or end of
         
     | 
| 
       332 
     | 
    
         
            -
              ##                                   arbitrary time range
         
     | 
| 
       333 
     | 
    
         
            -
              ##
         
     | 
| 
       334 
     | 
    
         
            -
              ## @return     [DateTime] result
         
     | 
| 
       335 
     | 
    
         
            -
              ##
         
     | 
| 
      
 278 
     | 
    
         
            +
              # Converts input string into a Time object when input takes on the following formats:
         
     | 
| 
      
 279 
     | 
    
         
            +
              #   - interval format e.g. '1d2h30m', '45m' etc.
         
     | 
| 
      
 280 
     | 
    
         
            +
              #   - a semantic phrase e.g. 'yesterday 5:30pm'
         
     | 
| 
      
 281 
     | 
    
         
            +
              #   - a strftime e.g. '2016-03-15 15:32:04 PDT'
         
     | 
| 
      
 282 
     | 
    
         
            +
              # @param options [Hash] Additional options
         
     | 
| 
      
 283 
     | 
    
         
            +
              # @option options :future [Boolean] assume future date (default: false)
         
     | 
| 
      
 284 
     | 
    
         
            +
              # @option options :guess [Symbol] :begin or :end to assume beginning or end of arbitrary time range
         
     | 
| 
      
 285 
     | 
    
         
            +
              # @return [DateTime] result
         
     | 
| 
       336 
286 
     | 
    
         
             
              def chronify(**options)
         
     | 
| 
       337 
287 
     | 
    
         
             
                now = Time.now
         
     | 
| 
       338 
288 
     | 
    
         
             
                raise StandardError, "Invalid time expression #{inspect}" if to_s.strip == ''
         
     | 
| 
         @@ -353,7 +303,7 @@ class ::String 
     | 
|
| 
       353 
303 
     | 
    
         
             
                else
         
     | 
| 
       354 
304 
     | 
    
         
             
                  date_string = dup
         
     | 
| 
       355 
305 
     | 
    
         
             
                  date_string = 'today' if date_string.match(REGEX_DAY) && now.strftime('%a') =~ /^#{Regexp.last_match(1)}/i
         
     | 
| 
       356 
     | 
    
         
            -
                  date_string = "#{options[:context] 
     | 
| 
      
 306 
     | 
    
         
            +
                  date_string = "#{options[:context]} #{date_string}" if date_string =~ REGEX_TIME && options[:context]
         
     | 
| 
       357 
307 
     | 
    
         | 
| 
       358 
308 
     | 
    
         
             
                  require 'chronic' unless defined?(Chronic)
         
     | 
| 
       359 
309 
     | 
    
         
             
                  res = Chronic.parse(date_string, {
         
     | 
| 
         @@ -368,8 +318,12 @@ class ::String 
     | 
|
| 
       368 
318 
     | 
    
         
             
                res
         
     | 
| 
       369 
319 
     | 
    
         
             
              end
         
     | 
| 
       370 
320 
     | 
    
         | 
| 
      
 321 
     | 
    
         
            +
              # Private helper methods for pattern matching
         
     | 
| 
       371 
322 
     | 
    
         
             
              private
         
     | 
| 
       372 
323 
     | 
    
         | 
| 
      
 324 
     | 
    
         
            +
              # Returns true if none of the regexes match the string.
         
     | 
| 
      
 325 
     | 
    
         
            +
              # @param regexes [Array] Array of regex patterns
         
     | 
| 
      
 326 
     | 
    
         
            +
              # @return [Boolean] True if none match
         
     | 
| 
       373 
327 
     | 
    
         
             
              def matches_none(regexes)
         
     | 
| 
       374 
328 
     | 
    
         
             
                regexes.each do |rx|
         
     | 
| 
       375 
329 
     | 
    
         
             
                  return false if match(Regexp.new(rx, Regexp::IGNORECASE))
         
     | 
| 
         @@ -377,6 +331,9 @@ class ::String 
     | 
|
| 
       377 
331 
     | 
    
         
             
                true
         
     | 
| 
       378 
332 
     | 
    
         
             
              end
         
     | 
| 
       379 
333 
     | 
    
         | 
| 
      
 334 
     | 
    
         
            +
              # Returns true if any of the regexes match the string.
         
     | 
| 
      
 335 
     | 
    
         
            +
              # @param regexes [Array] Array of regex patterns
         
     | 
| 
      
 336 
     | 
    
         
            +
              # @return [Boolean] True if any match
         
     | 
| 
       380 
337 
     | 
    
         
             
              def matches_any(regexes)
         
     | 
| 
       381 
338 
     | 
    
         
             
                regexes.each do |rx|
         
     | 
| 
       382 
339 
     | 
    
         
             
                  return true if match(Regexp.new(rx, Regexp::IGNORECASE))
         
     | 
| 
         @@ -384,6 +341,9 @@ class ::String 
     | 
|
| 
       384 
341 
     | 
    
         
             
                false
         
     | 
| 
       385 
342 
     | 
    
         
             
              end
         
     | 
| 
       386 
343 
     | 
    
         | 
| 
      
 344 
     | 
    
         
            +
              # Returns true if all of the regexes match the string.
         
     | 
| 
      
 345 
     | 
    
         
            +
              # @param regexes [Array] Array of regex patterns
         
     | 
| 
      
 346 
     | 
    
         
            +
              # @return [Boolean] True if all match
         
     | 
| 
       387 
347 
     | 
    
         
             
              def matches_all(regexes)
         
     | 
| 
       388 
348 
     | 
    
         
             
                regexes.each do |rx|
         
     | 
| 
       389 
349 
     | 
    
         
             
                  return false unless match(Regexp.new(rx, Regexp::IGNORECASE))
         
     | 
    
        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|
         
     |