na 1.1.22 → 1.1.23
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/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -2
- data/bin/na +45 -5
- data/lib/na/action.rb +5 -2
- data/lib/na/next_action.rb +92 -15
- data/lib/na/string.rb +1 -1
- data/lib/na/version.rb +1 -1
- data/src/README.md +1 -1
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c8bee2cada1590cdc80fe9943947c719649d441ed15a6c51228fa0bebe4e94eb
         | 
| 4 | 
            +
              data.tar.gz: 3dfc5a53bb88952243388b86e853af744b76938cbc69ef3b6f6ac8e58c03a9fc
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0603c3187363e3c5895f9e49af6deab11779cbf47442f4f76b7b5ad2608e4676d0cb337368e2a0d6a5f9bad7f5aff957d63e986cefe1997c671a6b6b5193e3e4
         | 
| 7 | 
            +
              data.tar.gz: e570fe4a32d7e45b4bd2171c73bee68adb6017ae22901c92486ccbe1bdb2461a50dac988d8ed862aa85237c070ebb305d3b6c2c61afc920826568c628d630faf
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,15 @@ | |
| 1 | 
            +
            ### 1.1.23
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            2022-10-07 10:02
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #### NEW
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            - Saved searches. Add `--save TITLE` to `tagged` or `find` commands to save the parameters for use with `na saved TITLE`
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            #### FIXED
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            - Restore wildcard capability of tag searches
         | 
| 12 | 
            +
             | 
| 1 13 | 
             
            ### 1.1.22
         | 
| 2 14 |  | 
| 3 15 | 
             
            2022-10-07 05:58
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -9,7 +9,7 @@ | |
| 9 9 | 
             
            _If you're one of the rare people like me who find this useful, feel free to
         | 
| 10 10 | 
             
            [buy me some coffee][donate]._
         | 
| 11 11 |  | 
| 12 | 
            -
            The current version of `na` is 1.1. | 
| 12 | 
            +
            The current version of `na` is 1.1.23
         | 
| 13 13 | 
             
            .
         | 
| 14 14 |  | 
| 15 15 | 
             
            `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. 
         | 
| @@ -59,7 +59,7 @@ SYNOPSIS | |
| 59 59 | 
             
                na [global options] command [command options] [arguments...]
         | 
| 60 60 |  | 
| 61 61 | 
             
            VERSION
         | 
| 62 | 
            -
                1.1. | 
| 62 | 
            +
                1.1.23
         | 
| 63 63 |  | 
| 64 64 | 
             
            GLOBAL OPTIONS
         | 
| 65 65 | 
             
                -a, --[no-]add          - Add a next action (deprecated, for backwards compatibility)
         | 
| @@ -82,6 +82,7 @@ COMMANDS | |
| 82 82 | 
             
                initconfig   - Initialize the config file using current global options
         | 
| 83 83 | 
             
                next, show   - Show next actions
         | 
| 84 84 | 
             
                prompt       - Show or install prompt hooks for the current shell
         | 
| 85 | 
            +
                saved        - Execute a saved search
         | 
| 85 86 | 
             
                tagged       - Find actions matching a tag
         | 
| 86 87 | 
             
                todos        - Show list of known todo files
         | 
| 87 88 | 
             
            ```
         | 
| @@ -174,6 +175,7 @@ COMMAND OPTIONS | |
| 174 175 | 
             
                --in=TODO_PATH                         - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
         | 
| 175 176 | 
             
                -o, --or                               - Combine search tokens with OR, displaying actions matching ANY of the terms
         | 
| 176 177 | 
             
                --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
         | 
| 178 | 
            +
                --save=TITLE                           - Save this search for future use (default: none)
         | 
| 177 179 | 
             
                -v, --invert                           - Show actions not matching search pattern
         | 
| 178 180 | 
             
                -x, --exact                            - Match pattern exactly
         | 
| 179 181 |  | 
    
        data/bin/na
    CHANGED
    
    | @@ -271,7 +271,16 @@ class App | |
| 271 271 | 
             
                c.desc 'Show actions not matching search pattern'
         | 
| 272 272 | 
             
                c.switch %i[v invert], negatable: false
         | 
| 273 273 |  | 
| 274 | 
            +
                c.desc 'Save this search for future use'
         | 
| 275 | 
            +
                c.arg_name 'TITLE'
         | 
| 276 | 
            +
                c.flag %i[save]
         | 
| 277 | 
            +
             | 
| 274 278 | 
             
                c.action do |global_options, options, args|
         | 
| 279 | 
            +
                  if options[:save]
         | 
| 280 | 
            +
                    title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
         | 
| 281 | 
            +
                    NA.save_search(title, "find #{NA.command_line.map { |c| "\"#{c}\"" }.join(' ')}")
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
             | 
| 275 284 | 
             
                  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
         | 
| 276 285 | 
             
                            3
         | 
| 277 286 | 
             
                          else
         | 
| @@ -359,7 +368,16 @@ class App | |
| 359 368 | 
             
                c.desc 'Show actions not matching tags'
         | 
| 360 369 | 
             
                c.switch %i[v invert], negatable: false
         | 
| 361 370 |  | 
| 371 | 
            +
                c.desc 'Save this search for future use'
         | 
| 372 | 
            +
                c.arg_name 'TITLE'
         | 
| 373 | 
            +
                c.flag %i[save]
         | 
| 374 | 
            +
             | 
| 362 375 | 
             
                c.action do |global_options, options, args|
         | 
| 376 | 
            +
                  if options[:save]
         | 
| 377 | 
            +
                    title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
         | 
| 378 | 
            +
                    NA.save_search(title, "tagged #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
         | 
| 379 | 
            +
                  end
         | 
| 380 | 
            +
             | 
| 363 381 | 
             
                  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
         | 
| 364 382 | 
             
                            3
         | 
| 365 383 | 
             
                          else
         | 
| @@ -371,9 +389,10 @@ class App | |
| 371 389 | 
             
                  all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
         | 
| 372 390 | 
             
                  args.join(',').split(/ *, */).each do |arg|
         | 
| 373 391 | 
             
                    # TODO: <> comparisons do nothing right now
         | 
| 374 | 
            -
                    m = arg.match(/^(?<req>[+\-!])?(?<tag>[^  | 
| 392 | 
            +
                    m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
         | 
| 393 | 
            +
             | 
| 375 394 | 
             
                    tags.push({
         | 
| 376 | 
            -
                                tag: m['tag'],
         | 
| 395 | 
            +
                                tag: m['tag'].wildcard_to_rx,
         | 
| 377 396 | 
             
                                comp: m['op'],
         | 
| 378 397 | 
             
                                value: m['val'],
         | 
| 379 398 | 
             
                                required: all_req || (!m['req'].nil? && m['req'] == '+'),
         | 
| @@ -387,9 +406,9 @@ class App | |
| 387 406 | 
             
                    options[:in].split(/ *, */).each do |a|
         | 
| 388 407 | 
             
                      m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
         | 
| 389 408 | 
             
                      todo.push({
         | 
| 390 | 
            -
             | 
| 391 | 
            -
             | 
| 392 | 
            -
             | 
| 409 | 
            +
                                  token: m['tok'],
         | 
| 410 | 
            +
                                  required: !m['req'].nil?
         | 
| 411 | 
            +
                                })
         | 
| 393 412 | 
             
                    end
         | 
| 394 413 | 
             
                  end
         | 
| 395 414 |  | 
| @@ -545,6 +564,26 @@ class App | |
| 545 564 | 
             
                end
         | 
| 546 565 | 
             
              end
         | 
| 547 566 |  | 
| 567 | 
            +
              desc 'Execute a saved search'
         | 
| 568 | 
            +
              long_desc 'Run without argument to list saved searches'
         | 
| 569 | 
            +
              arg_name 'SEARCH_TITLE', optional: true
         | 
| 570 | 
            +
              command %i[saved] do |c|
         | 
| 571 | 
            +
                c.action do |_global_options, _options, args|
         | 
| 572 | 
            +
                  searches = NA.load_searches
         | 
| 573 | 
            +
                  if args.empty?
         | 
| 574 | 
            +
                    NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
         | 
| 575 | 
            +
                    NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
         | 
| 576 | 
            +
                  else
         | 
| 577 | 
            +
                    keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
         | 
| 578 | 
            +
                    NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
         | 
| 579 | 
            +
             | 
| 580 | 
            +
                    key = keys[0]
         | 
| 581 | 
            +
                    cmd = Shellwords.shellsplit(searches[key])
         | 
| 582 | 
            +
                    exit run(cmd)
         | 
| 583 | 
            +
                  end
         | 
| 584 | 
            +
                end
         | 
| 585 | 
            +
              end
         | 
| 586 | 
            +
             | 
| 548 587 | 
             
              pre do |global, _command, _options, _args|
         | 
| 549 588 | 
             
                NA.verbose = global[:debug]
         | 
| 550 589 | 
             
                NA.extension = global[:ext]
         | 
| @@ -574,6 +613,7 @@ class App | |
| 574 613 | 
             
              end
         | 
| 575 614 | 
             
            end
         | 
| 576 615 |  | 
| 616 | 
            +
            NA.command_line = ARGV
         | 
| 577 617 | 
             
            @command = ARGV[0]
         | 
| 578 618 | 
             
            $first_arg = ARGV[1]
         | 
| 579 619 |  | 
    
        data/lib/na/action.rb
    CHANGED
    
    | @@ -127,11 +127,14 @@ module NA | |
| 127 127 | 
             
                end
         | 
| 128 128 |  | 
| 129 129 | 
             
                def compare_tag(tag)
         | 
| 130 | 
            -
                   | 
| 130 | 
            +
                  keys = @tags.keys.delete_if { |k| k !~ Regexp.new(tag[:tag], Regexp::IGNORECASE) }
         | 
| 131 | 
            +
                  return false if keys.empty?
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  key = keys[0]
         | 
| 131 134 |  | 
| 132 135 | 
             
                  return true if tag[:comp].nil?
         | 
| 133 136 |  | 
| 134 | 
            -
                  tag_val = @tags[ | 
| 137 | 
            +
                  tag_val = @tags[key]
         | 
| 135 138 | 
             
                  val = tag[:value]
         | 
| 136 139 |  | 
| 137 140 | 
             
                  return false if tag_val.nil?
         | 
    
        data/lib/na/next_action.rb
    CHANGED
    
    | @@ -3,7 +3,7 @@ | |
| 3 3 | 
             
            # Next Action methods
         | 
| 4 4 | 
             
            module NA
         | 
| 5 5 | 
             
              class << self
         | 
| 6 | 
            -
                attr_accessor :verbose, :extension, :na_tag
         | 
| 6 | 
            +
                attr_accessor :verbose, :extension, :na_tag, :command_line
         | 
| 7 7 |  | 
| 8 8 | 
             
                ##
         | 
| 9 9 | 
             
                ## Output to STDERR
         | 
| @@ -15,11 +15,57 @@ module NA | |
| 15 15 | 
             
                  return if debug && !@verbose
         | 
| 16 16 |  | 
| 17 17 | 
             
                  $stderr.puts NA::Color.template("{x}#{msg}{x}")
         | 
| 18 | 
            -
                  if exit_code | 
| 18 | 
            +
                  if exit_code
         | 
| 19 19 | 
             
                    Process.exit exit_code
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
| 22 22 |  | 
| 23 | 
            +
                ##
         | 
| 24 | 
            +
                ## Display and read a Yes/No prompt
         | 
| 25 | 
            +
                ##
         | 
| 26 | 
            +
                ## @param      prompt   [String] The prompt string
         | 
| 27 | 
            +
                ## @param      default  [Boolean] default value if
         | 
| 28 | 
            +
                ##                      return is pressed or prompt is
         | 
| 29 | 
            +
                ##                      skipped
         | 
| 30 | 
            +
                ##
         | 
| 31 | 
            +
                ## @return     [Boolean] result
         | 
| 32 | 
            +
                ##
         | 
| 33 | 
            +
                def yn(prompt, default: true)
         | 
| 34 | 
            +
                  return default unless $stdout.isatty
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  tty_state = `stty -g`
         | 
| 37 | 
            +
                  system 'stty raw -echo cbreak isig'
         | 
| 38 | 
            +
                  yn = color_single_options(default ? %w[Y n] : %w[y N])
         | 
| 39 | 
            +
                  $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
         | 
| 40 | 
            +
                  res = $stdin.sysread 1
         | 
| 41 | 
            +
                  res.chomp!
         | 
| 42 | 
            +
                  puts
         | 
| 43 | 
            +
                  system 'stty cooked'
         | 
| 44 | 
            +
                  system "stty #{tty_state}"
         | 
| 45 | 
            +
                  res.empty? ? default : res =~ /y/i
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                ##
         | 
| 49 | 
            +
                ## Helper function to colorize the Y/N prompt
         | 
| 50 | 
            +
                ##
         | 
| 51 | 
            +
                ## @param      choices  [Array] The choices with
         | 
| 52 | 
            +
                ##                      default capitalized
         | 
| 53 | 
            +
                ##
         | 
| 54 | 
            +
                ## @return     [String] colorized string
         | 
| 55 | 
            +
                ##
         | 
| 56 | 
            +
                def color_single_options(choices = %w[y n])
         | 
| 57 | 
            +
                  out = []
         | 
| 58 | 
            +
                  choices.each do |choice|
         | 
| 59 | 
            +
                    case choice
         | 
| 60 | 
            +
                    when /[A-Z]/
         | 
| 61 | 
            +
                      out.push(NA::Color.template("{bw}#{choice}{x}"))
         | 
| 62 | 
            +
                    else
         | 
| 63 | 
            +
                      out.push(NA::Color.template("{dw}#{choice}{xg}"))
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                  NA::Color.template("{xg}[#{out.join('/')}{xg}]{x}")
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 23 69 | 
             
                ##
         | 
| 24 70 | 
             
                ## Create a new todo file
         | 
| 25 71 | 
             
                ##
         | 
| @@ -308,6 +354,50 @@ module NA | |
| 308 354 | 
             
                  puts NA::Color.template(dirs.join("\n"))
         | 
| 309 355 | 
             
                end
         | 
| 310 356 |  | 
| 357 | 
            +
                def save_search(title, search)
         | 
| 358 | 
            +
                  file = database_path(file: 'saved_searches.yml')
         | 
| 359 | 
            +
                  searches = load_searches
         | 
| 360 | 
            +
                  title = title.gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
         | 
| 361 | 
            +
             | 
| 362 | 
            +
                  if searches.key?(title)
         | 
| 363 | 
            +
                    res = yn('Overwrite existing definition?', default: true)
         | 
| 364 | 
            +
                    notify('{r}Cancelled', exit_code: 0) unless res
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                  end
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                  searches[title] = search
         | 
| 369 | 
            +
                  File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
         | 
| 370 | 
            +
                  NA.notify("{y}Search #{title} saved", exit_code: 0)
         | 
| 371 | 
            +
                end
         | 
| 372 | 
            +
             | 
| 373 | 
            +
                def load_searches
         | 
| 374 | 
            +
                  file = database_path(file: 'saved_searches.yml')
         | 
| 375 | 
            +
                  if File.exist?(file)
         | 
| 376 | 
            +
                    searches = YAML.safe_load(IO.read(file))
         | 
| 377 | 
            +
                  else
         | 
| 378 | 
            +
                    searches = {
         | 
| 379 | 
            +
                      'soon' => 'tagged "due<in 2 days,due>yesterday"',
         | 
| 380 | 
            +
                      'overdue' => 'tagged "due<now"',
         | 
| 381 | 
            +
                      'high' => 'tagged "prio>3"',
         | 
| 382 | 
            +
                      'maybe' => 'tagged "maybe"'
         | 
| 383 | 
            +
                    }
         | 
| 384 | 
            +
                    File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
         | 
| 385 | 
            +
                  end
         | 
| 386 | 
            +
                  searches
         | 
| 387 | 
            +
                end
         | 
| 388 | 
            +
             | 
| 389 | 
            +
                ##
         | 
| 390 | 
            +
                ## Get path to database of known todo files
         | 
| 391 | 
            +
                ##
         | 
| 392 | 
            +
                ## @return     [String] File path
         | 
| 393 | 
            +
                ##
         | 
| 394 | 
            +
                def database_path(file: 'tdlist.txt')
         | 
| 395 | 
            +
                  db_dir = File.expand_path('~/.local/share/na')
         | 
| 396 | 
            +
                  # Create directory if needed
         | 
| 397 | 
            +
                  FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
         | 
| 398 | 
            +
                  File.join(db_dir, file)
         | 
| 399 | 
            +
                end
         | 
| 400 | 
            +
             | 
| 311 401 | 
             
                private
         | 
| 312 402 |  | 
| 313 403 | 
             
                ##
         | 
| @@ -356,19 +446,6 @@ module NA | |
| 356 446 | 
             
                  [optional, required, negated]
         | 
| 357 447 | 
             
                end
         | 
| 358 448 |  | 
| 359 | 
            -
                ##
         | 
| 360 | 
            -
                ## Get path to database of known todo files
         | 
| 361 | 
            -
                ##
         | 
| 362 | 
            -
                ## @return     [String] File path
         | 
| 363 | 
            -
                ##
         | 
| 364 | 
            -
                def database_path
         | 
| 365 | 
            -
                  db_dir = File.expand_path('~/.local/share/na')
         | 
| 366 | 
            -
                  # Create directory if needed
         | 
| 367 | 
            -
                  FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
         | 
| 368 | 
            -
                  db_file = 'tdlist.txt'
         | 
| 369 | 
            -
                  File.join(db_dir, db_file)
         | 
| 370 | 
            -
                end
         | 
| 371 | 
            -
             | 
| 372 449 | 
             
                ##
         | 
| 373 450 | 
             
                ## Find a matching path using semi-fuzzy matching.
         | 
| 374 451 | 
             
                ## Search tokens can include ! and + to negate or make
         | 
    
        data/lib/na/string.rb
    CHANGED
    
    
    
        data/lib/na/version.rb
    CHANGED
    
    
    
        data/src/README.md
    CHANGED
    
    | @@ -9,7 +9,7 @@ | |
| 9 9 | 
             
            _If you're one of the rare people like me who find this useful, feel free to
         | 
| 10 10 | 
             
            [buy me some coffee][donate]._
         | 
| 11 11 |  | 
| 12 | 
            -
            The current version of `na` is <!--VER-->1.1. | 
| 12 | 
            +
            The current version of `na` is <!--VER-->1.1.22<!--END VER-->.
         | 
| 13 13 |  | 
| 14 14 | 
             
            `na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder. 
         | 
| 15 15 |  |